([]);\n+\n+ const { computedSelectItem, reloadCatalogueItemReviewers } = useCatalogueTree.useShallowSelector((s) => ({\n+ computedSelectItem: s.computedSelectItem,\n+ reloadCatalogueItemReviewers: s.getCatalogueItemReviewers,\n+ }));\n+\n+ const [form] = Form.useForm<{ reviewers: number[]; type: DepoCatalogueReviewEnum }>();\n+\n+ const selectedList = useMemo(() => currentReviewList.map(i => i.reviewers || []).reduce((p, c) => p.concat(c), []), [currentReviewList])\n+\n+ useEffect(() => {\n+ return () => form.resetFields();\n+ }, [form, isOpen]);\n+\n+ const onSubmit = async () => {\n+ if (loading) return;\n+ try {\n+ setLoading(true);\n+ const formValue = await form.validateFields();\n+ if (!computedSelectItem) {\n+ message.error('意料之外的错误,请刷新重试');\n+ return;\n+ }\n+ const params: UpdateCatalogueReviewersParams = {\n+ dirId: computedSelectItem.id,\n+ updateCatalogueReviewLevels: [\n+ {\n+ level: currentReviewList.length + 1,\n+ type: formValue.type,\n+ reviewers: userList\n+ .filter((i) => formValue.reviewers.includes(i.id))\n+ .map((i) => ({ id: i.id, username: i.username, nickname: i.nickname })),\n+ },\n+ ],\n+ };\n+ const response = await updateCatalogueReviewers(params);\n+ if (response.success) {\n+ await reloadCatalogueItemReviewers();\n+ setIsOpen(false);\n+ }\n+ } finally {\n+ setLoading(false);\n+ }\n+ };\n+\n+ return (\n+ <>\n+ \n+ setIsOpen(false)}\n+ >\n+ \n+ setUserList(list)}\n+ disableItems={selectedList}\n+ filterOption={(input, option) => !!option?.label?.toString().toLowerCase().includes(input?.toLowerCase())}\n+ />\n+ \n+ \n+ \n+ 会签\n+ 或签\n+ \n+ \n+ \n+ \n+ >\n+ );\n+};\n",
+ ],
+};
+
+export const b = {
+ newFile: {
+ fileName: "a/packages/myreact-reactivity/src/reactive/feature.ts",
+ content:
+ 'import { Component, createElement, useState, useCallback, useMemo } from "@my-react/react";\n\nimport { proxyRefs, ReactiveEffect } from "../api";\n\nimport type { UnwrapRef } from "../api";\nimport type { LikeReactNode } from "@my-react/react";\n\ntype LifeCycle = {\n onBeforeMount: Array<() => void>;\n\n onMounted: Array<() => void>;\n\n onBeforeUpdate: Array<() => void>;\n\n onUpdated: Array<() => void>;\n\n onBeforeUnmount: Array<() => void>;\n\n onUnmounted: Array<() => void>;\n\n hasHookInstalled: boolean;\n\n canUpdateComponent: boolean;\n};\n\n/**\n * @internal\n */\nexport let globalInstance: LifeCycle | null = null;\n\nexport function createReactive, S extends Record>(props?: {\n setup: () => S;\n render?: (props: UnwrapRef & P) => LikeReactNode;\n}) {\n const setup = typeof props === "function" ? props : props.setup;\n\n const render = typeof props === "function" ? null : props.render;\n\n class ForBeforeUnmount extends Component<{ ["$$__instance__$$"]: LifeCycle; children: LikeReactNode }> {\n componentWillUnmount(): void {\n this.props.$$__instance__$$.onBeforeUnmount.forEach((f) => f());\n }\n\n render() {\n return this.props.children;\n }\n }\n\n class ForBeforeMount extends Component<{ ["$$__instance__$$"]: LifeCycle; children: LikeReactNode }> {\n componentDidMount(): void {\n this.props.$$__instance__$$.onBeforeMount.forEach((f) => f());\n }\n\n render() {\n return this.props.children;\n }\n }\n\n class RenderWithLifeCycle extends Component<\n {\n ["$$__trigger__$$"]: () => void;\n ["$$__instance__$$"]: LifeCycle;\n ["$$__reactiveState__$$"]: UnwrapRef;\n children?: (props: UnwrapRef & P) => LikeReactNode;\n } & P\n > {\n componentDidMount(): void {\n this.props.$$__instance__$$.onMounted.forEach((f) => f());\n }\n\n componentDidUpdate(): void {\n this.props.$$__instance__$$.onUpdated.forEach((f) => f());\n }\n\n componentWillUnmount(): void {\n this.props.$$__instance__$$.onUnmounted.forEach((f) => f());\n this.reactiveEffect.stop();\n }\n\n shouldComponentUpdate(): boolean {\n this.props.$$__instance__$$.canUpdateComponent = false;\n this.props.$$__instance__$$.onBeforeUpdate.forEach((f) => f());\n this.props.$$__instance__$$.canUpdateComponent = true;\n return true;\n }\n\n reactiveEffect = new ReactiveEffect(() => {\n const { children, $$__trigger__$$, $$__reactiveState__$$, $$__instance__$$, ...last } = this.props;\n const targetRender = (render || children) as (props: UnwrapRef & P) => LikeReactNode;\n const element = targetRender?.({ ...last, ...$$__reactiveState__$$ } as UnwrapRef & P) || null;\n return element;\n }, this.props.$$__trigger__$$);\n\n render() {\n return createElement(ForBeforeMount, { ["$$__instance__$$"]: this.props.$$__instance__$$, children: this.reactiveEffect.run() });\n }\n }\n\n class Render extends Component<\n {\n ["$$__trigger__$$"]: () => void;\n ["$$__reactiveState__$$"]: UnwrapRef;\n children?: (props: UnwrapRef & P) => LikeReactNode;\n } & P\n > {\n componentWillUnmount(): void {\n this.reactiveEffect.stop();\n }\n\n reactiveEffect = new ReactiveEffect(() => {\n const { children, $$__trigger__$$, $$__reactiveState__$$, $$__instance__$$, ...last } = this.props;\n const targetRender = (render || children) as (props: UnwrapRef & P) => LikeReactNode;\n const element = targetRender?.({ ...last, ...$$__reactiveState__$$ } as UnwrapRef & P) || null;\n return element;\n }, this.props.$$__trigger__$$);\n\n render() {\n return this.reactiveEffect.run();\n }\n }\n\n const MyReactReactiveComponent = (props: P & { children?: (props: UnwrapRef & P) => LikeReactNode }) => {\n const [instance] = useState(() => ({\n onBeforeMount: [],\n onBeforeUpdate: [],\n onBeforeUnmount: [],\n onMounted: [],\n onUpdated: [],\n onUnmounted: [],\n hasHookInstalled: false,\n canUpdateComponent: true,\n }));\n\n const state = useMemo(() => {\n globalInstance = instance;\n\n const state = proxyRefs(setup());\n\n globalInstance = null;\n\n return state;\n }, []);\n\n if (__DEV__) {\n for (const key in props) {\n if (key in state) {\n console.warn(`duplicate key ${key} in Component props and reactive state, please fix this usage`);\n }\n }\n if (props["children"] && typeof props["children"] !== "function") {\n throw new Error("the component which return from createReactive() expect a function children");\n }\n }\n\n const [, setState] = useState(() => 0);\n\n const updateCallback = useCallback(() => {\n if (instance.canUpdateComponent) {\n setState((i) => i + 1);\n }\n }, []);\n\n if (instance.hasHookInstalled) {\n return createElement(ForBeforeUnmount, {\n ["$$__instance__$$"]: instance,\n children: createElement(RenderWithLifeCycle, {\n ...props,\n ["$$__trigger__$$"]: updateCallback,\n ["$$__reactiveState__$$"]: state,\n ["$$__instance__$$"]: instance,\n }),\n }) as LikeReactNode;\n } else {\n return createElement(Render, { ...props, ["$$__trigger__$$"]: updateCallback, ["$$__reactiveState__$$"]: state }) as LikeReactNode;\n }\n };\n\n return MyReactReactiveComponent;\n}\n',
+ },
+ hunks: [
+ 'diff --git a/packages/myreact-reactivity/src/reactive/feature.ts b/packages/myreact-reactivity/src/reactive/feature.ts\nindex 5b301628..15aac42f 100644\n--- a/packages/myreact-reactivity/src/reactive/feature.ts\n+++ b/packages/myreact-reactivity/src/reactive/feature.ts\n@@ -74,7 +74,7 @@ export function createReactive, S extends Reco\n \n componentWillUnmount(): void {\n this.props.$$__instance__$$.onUnmounted.forEach((f) => f());\n- this.effect.stop();\n+ this.reactiveEffect.stop();\n }\n \n shouldComponentUpdate(): boolean {\n@@ -84,7 +84,7 @@ export function createReactive
, S extends Reco\n return true;\n }\n \n- effect = new ReactiveEffect(() => {\n+ reactiveEffect = new ReactiveEffect(() => {\n const { children, $$__trigger__$$, $$__reactiveState__$$, $$__instance__$$, ...last } = this.props;\n const targetRender = (render || children) as (props: UnwrapRef & P) => LikeReactNode;\n const element = targetRender?.({ ...last, ...$$__reactiveState__$$ } as UnwrapRef & P) || null;\n@@ -92,7 +92,7 @@ export function createReactive
, S extends Reco\n }, this.props.$$__trigger__$$);\n \n render() {\n- return createElement(ForBeforeMount, { ["$$__instance__$$"]: this.props.$$__instance__$$, children: this.effect.run() });\n+ return createElement(ForBeforeMount, { ["$$__instance__$$"]: this.props.$$__instance__$$, children: this.reactiveEffect.run() });\n }\n }\n \n@@ -104,10 +104,10 @@ export function createReactive
, S extends Reco\n } & P\n > {\n componentWillUnmount(): void {\n- this.effect.stop();\n+ this.reactiveEffect.stop();\n }\n \n- effect = new ReactiveEffect(() => {\n+ reactiveEffect = new ReactiveEffect(() => {\n const { children, $$__trigger__$$, $$__reactiveState__$$, $$__instance__$$, ...last } = this.props;\n const targetRender = (render || children) as (props: UnwrapRef & P) => LikeReactNode;\n const element = targetRender?.({ ...last, ...$$__reactiveState__$$ } as UnwrapRef & P) || null;\n@@ -115,7 +115,7 @@ export function createReactive
, S extends Reco\n }, this.props.$$__trigger__$$);\n \n render() {\n- return this.effect.run();\n+ return this.reactiveEffect.run();\n }\n }\n',
+ ],
+};
+
+export const c = {
+ newFile: {
+ fileName: "a/packages/myreact-dom/src/client/tools/highlight.ts",
+ content:
+ 'import { STATE_TYPE, include } from "@my-react/react-shared";\n\nimport { enableHighlight } from "@my-react-dom-shared";\n\nimport type { MyReactFiberNode } from "@my-react/react-reconciler";\nimport type { ClientDomDispatch } from "@my-react-dom-client/renderDispatch";\n\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport const debounce = (callback: T, time?: number): T => {\n let id = null;\n return ((...args) => {\n clearTimeout(id);\n id = setTimeout(() => {\n callback.call(null, ...args);\n }, time || 40);\n }) as unknown as T;\n};\n\n/**\n * @internal\n */\nexport class HighLight {\n /**\n * @type HighLight\n */\n static instance: HighLight | undefined = undefined;\n\n /**\n *\n * @returns HighLight\n */\n static getHighLightInstance = () => {\n HighLight.instance = HighLight.instance || new HighLight();\n\n return HighLight.instance;\n };\n\n mask: HTMLCanvasElement | null = null;\n\n range = document.createRange();\n\n running = false;\n\n __pendingUpdate__: Set = new Set();\n\n __pendingAppend__: Set = new Set();\n\n __pendingSetRef__: Set = new Set();\n\n __pendingWarn__: Set = new Set();\n\n width = 0;\n\n height = 0;\n\n constructor() {\n this.mask = document.createElement("canvas");\n this.mask.setAttribute("data-highlight", "@my-react");\n this.mask.style.cssText = `\n position: fixed;\n z-index: 99999999;\n left: 0;\n top: 0;\n pointer-events: none;\n `;\n document.documentElement.prepend(this.mask);\n this.setSize();\n window.addEventListener("resize", this.setSize);\n }\n\n setSize = debounce(() => {\n this.width = window.innerWidth || document.documentElement.clientWidth;\n\n this.height = window.innerHeight || document.documentElement.clientHeight;\n\n this.mask.width = this.width;\n\n this.mask.height = this.height;\n });\n\n highLight = (fiber: MyReactFiberNode, type: "update" | "append" | "setRef" | "warn") => {\n if (fiber.nativeNode) {\n switch (type) {\n case "update":\n this.__pendingUpdate__.add(fiber);\n break;\n case "append":\n this.__pendingAppend__.add(fiber);\n break;\n case "setRef":\n this.__pendingSetRef__.add(fiber);\n break;\n case "warn":\n this.__pendingWarn__.add(fiber);\n }\n }\n\n if (!this.running) {\n this.running = true;\n requestAnimationFrame(this.flashPending);\n }\n };\n\n flashPending = () => {\n const context = this.mask.getContext("2d");\n\n const allPendingUpdate = new Set(this.__pendingUpdate__);\n\n this.__pendingUpdate__.clear();\n\n context.strokeStyle = "rgba(200,50,50,0.8)";\n\n allPendingUpdate.forEach((fiber) => {\n if (include(fiber.state, STATE_TYPE.__unmount__)) return;\n try {\n const node = fiber.nativeNode as HTMLElement;\n if (node.nodeType === Node.TEXT_NODE) {\n this.range.selectNodeContents(node);\n } else {\n this.range.selectNode(node);\n }\n const rect = this.range.getBoundingClientRect();\n if (\n (rect.width || rect.height) &&\n rect.top >= 0 &&\n rect.left >= 0 &&\n rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n ) {\n // do the highlight paint\n const left = rect.left - 0.5;\n const top = rect.top - 0.5;\n const width = rect.width + 1;\n const height = rect.height + 1;\n context.strokeRect(\n left < 0 ? 0 : left,\n top < 0 ? 0 : top,\n width > window.innerWidth ? window.innerWidth : width,\n height > window.innerHeight ? window.innerHeight : height\n );\n }\n } catch {\n void 0;\n }\n });\n\n const allPendingAppend = new Set(this.__pendingAppend__);\n\n this.__pendingAppend__.clear();\n\n allPendingAppend.forEach((fiber) => {\n if (include(fiber.state, STATE_TYPE.__unmount__)) return;\n try {\n const node = fiber.nativeNode as HTMLElement;\n if (node.nodeType === Node.TEXT_NODE) {\n this.range.selectNodeContents(node);\n } else {\n this.range.selectNode(node);\n }\n const rect = this.range.getBoundingClientRect();\n if (\n (rect.width || rect.height) &&\n rect.top >= 0 &&\n rect.left >= 0 &&\n rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n ) {\n // do the highlight paint\n const left = rect.left - 0.5;\n const top = rect.top - 0.5;\n const width = rect.width + 1;\n const height = rect.height + 1;\n context.strokeRect(\n left < 0 ? 0 : left,\n top < 0 ? 0 : top,\n width > window.innerWidth ? window.innerWidth : width,\n height > window.innerHeight ? window.innerHeight : height\n );\n }\n } catch {\n void 0;\n }\n });\n\n const allPendingSetRef = new Set(this.__pendingSetRef__);\n\n this.__pendingSetRef__.clear();\n\n allPendingSetRef.forEach((fiber) => {\n if (include(fiber.state, STATE_TYPE.__unmount__)) return;\n try {\n const node = fiber.nativeNode as HTMLElement;\n if (node.nodeType === Node.TEXT_NODE) {\n this.range.selectNodeContents(node);\n } else {\n this.range.selectNode(node);\n }\n const rect = this.range.getBoundingClientRect();\n if (\n (rect.width || rect.height) &&\n rect.top >= 0 &&\n rect.left >= 0 &&\n rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n ) {\n // do the highlight paint\n const left = rect.left - 0.5;\n const top = rect.top - 0.5;\n const width = rect.width + 1;\n const height = rect.height + 1;\n context.strokeRect(\n left < 0 ? 0 : left,\n top < 0 ? 0 : top,\n width > window.innerWidth ? window.innerWidth : width,\n height > window.innerHeight ? window.innerHeight : height\n );\n }\n } catch {\n void 0;\n }\n });\n\n context.strokeStyle = "rgba(230,150,40,0.8)";\n\n const allPendingWarn = new Set(this.__pendingWarn__);\n\n this.__pendingWarn__.clear();\n\n allPendingWarn.forEach((fiber) => {\n if (include(fiber.state, STATE_TYPE.__unmount__)) return;\n try {\n const node = fiber.nativeNode as HTMLElement;\n if (node.nodeType === Node.TEXT_NODE) {\n this.range.selectNodeContents(node);\n } else {\n this.range.selectNode(node);\n }\n const rect = this.range.getBoundingClientRect();\n if (\n (rect.width || rect.height) &&\n rect.top >= 0 &&\n rect.left >= 0 &&\n rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n ) {\n // do the highlight paint\n const left = rect.left - 0.5;\n const top = rect.top - 0.5;\n const width = rect.width + 1;\n const height = rect.height + 1;\n context.strokeRect(\n left < 0 ? 0 : left,\n top < 0 ? 0 : top,\n width > window.innerWidth ? window.innerWidth : width,\n height > window.innerHeight ? window.innerHeight : height\n );\n }\n } catch {\n void 0;\n }\n });\n\n setTimeout(() => {\n context.clearRect(0, 0, this.width, this.height);\n this.running = false;\n if (this.__pendingUpdate__.size || this.__pendingAppend__.size || this.__pendingSetRef__.size) {\n this.running = true;\n this.flashPending();\n }\n }, 100);\n };\n}\n\nexport const highlightUpdateFiber = function (this: ClientDomDispatch, fiber: MyReactFiberNode) {\n if (this.isAppMounted && !this.isHydrateRender && !this.isServerRender && (enableHighlight.current || window.__highlight__)) {\n HighLight.getHighLightInstance().highLight(fiber, "update");\n }\n};\n',
+ },
+ hunks: [
+ "diff --git a/packages/myreact-dom/src/client/tools/highlight.ts b/packages/myreact-dom/src/client/tools/highlight.ts\nindex 13cba7db..002becdc 100644\n--- a/packages/myreact-dom/src/client/tools/highlight.ts\n+++ b/packages/myreact-dom/src/client/tools/highlight.ts\n@@ -112,31 +112,35 @@ export class HighLight {\n \n allPendingUpdate.forEach((fiber) => {\n if (include(fiber.state, STATE_TYPE.__unmount__)) return;\n- const node = fiber.nativeNode as HTMLElement;\n- if (node.nodeType === Node.TEXT_NODE) {\n- this.range.selectNodeContents(node);\n- } else {\n- this.range.selectNode(node);\n- }\n- const rect = this.range.getBoundingClientRect();\n- if (\n- (rect.width || rect.height) &&\n- rect.top >= 0 &&\n- rect.left >= 0 &&\n- rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n- ) {\n- // do the highlight paint\n- const left = rect.left - 0.5;\n- const top = rect.top - 0.5;\n- const width = rect.width + 1;\n- const height = rect.height + 1;\n- context.strokeRect(\n- left < 0 ? 0 : left,\n- top < 0 ? 0 : top,\n- width > window.innerWidth ? window.innerWidth : width,\n- height > window.innerHeight ? window.innerHeight : height\n- );\n+ try {\n+ const node = fiber.nativeNode as HTMLElement;\n+ if (node.nodeType === Node.TEXT_NODE) {\n+ this.range.selectNodeContents(node);\n+ } else {\n+ this.range.selectNode(node);\n+ }\n+ const rect = this.range.getBoundingClientRect();\n+ if (\n+ (rect.width || rect.height) &&\n+ rect.top >= 0 &&\n+ rect.left >= 0 &&\n+ rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n+ ) {\n+ // do the highlight paint\n+ const left = rect.left - 0.5;\n+ const top = rect.top - 0.5;\n+ const width = rect.width + 1;\n+ const height = rect.height + 1;\n+ context.strokeRect(\n+ left < 0 ? 0 : left,\n+ top < 0 ? 0 : top,\n+ width > window.innerWidth ? window.innerWidth : width,\n+ height > window.innerHeight ? window.innerHeight : height\n+ );\n+ }\n+ } catch {\n+ void 0;\n }\n });\n \n@@ -146,31 +150,35 @@ export class HighLight {\n \n allPendingAppend.forEach((fiber) => {\n if (include(fiber.state, STATE_TYPE.__unmount__)) return;\n- const node = fiber.nativeNode as HTMLElement;\n- if (node.nodeType === Node.TEXT_NODE) {\n- this.range.selectNodeContents(node);\n- } else {\n- this.range.selectNode(node);\n- }\n- const rect = this.range.getBoundingClientRect();\n- if (\n- (rect.width || rect.height) &&\n- rect.top >= 0 &&\n- rect.left >= 0 &&\n- rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n- ) {\n- // do the highlight paint\n- const left = rect.left - 0.5;\n- const top = rect.top - 0.5;\n- const width = rect.width + 1;\n- const height = rect.height + 1;\n- context.strokeRect(\n- left < 0 ? 0 : left,\n- top < 0 ? 0 : top,\n- width > window.innerWidth ? window.innerWidth : width,\n- height > window.innerHeight ? window.innerHeight : height\n- );\n+ try {\n+ const node = fiber.nativeNode as HTMLElement;\n+ if (node.nodeType === Node.TEXT_NODE) {\n+ this.range.selectNodeContents(node);\n+ } else {\n+ this.range.selectNode(node);\n+ }\n+ const rect = this.range.getBoundingClientRect();\n+ if (\n+ (rect.width || rect.height) &&\n+ rect.top >= 0 &&\n+ rect.left >= 0 &&\n+ rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n+ ) {\n+ // do the highlight paint\n+ const left = rect.left - 0.5;\n+ const top = rect.top - 0.5;\n+ const width = rect.width + 1;\n+ const height = rect.height + 1;\n+ context.strokeRect(\n+ left < 0 ? 0 : left,\n+ top < 0 ? 0 : top,\n+ width > window.innerWidth ? window.innerWidth : width,\n+ height > window.innerHeight ? window.innerHeight : height\n+ );\n+ }\n+ } catch {\n+ void 0;\n }\n });\n \n@@ -180,31 +188,35 @@ export class HighLight {\n \n allPendingSetRef.forEach((fiber) => {\n if (include(fiber.state, STATE_TYPE.__unmount__)) return;\n- const node = fiber.nativeNode as HTMLElement;\n- if (node.nodeType === Node.TEXT_NODE) {\n- this.range.selectNodeContents(node);\n- } else {\n- this.range.selectNode(node);\n- }\n- const rect = this.range.getBoundingClientRect();\n- if (\n- (rect.width || rect.height) &&\n- rect.top >= 0 &&\n- rect.left >= 0 &&\n- rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n- ) {\n- // do the highlight paint\n- const left = rect.left - 0.5;\n- const top = rect.top - 0.5;\n- const width = rect.width + 1;\n- const height = rect.height + 1;\n- context.strokeRect(\n- left < 0 ? 0 : left,\n- top < 0 ? 0 : top,\n- width > window.innerWidth ? window.innerWidth : width,\n- height > window.innerHeight ? window.innerHeight : height\n- );\n+ try {\n+ const node = fiber.nativeNode as HTMLElement;\n+ if (node.nodeType === Node.TEXT_NODE) {\n+ this.range.selectNodeContents(node);\n+ } else {\n+ this.range.selectNode(node);\n+ }\n+ const rect = this.range.getBoundingClientRect();\n+ if (\n+ (rect.width || rect.height) &&\n+ rect.top >= 0 &&\n+ rect.left >= 0 &&\n+ rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n+ ) {\n+ // do the highlight paint\n+ const left = rect.left - 0.5;\n+ const top = rect.top - 0.5;\n+ const width = rect.width + 1;\n+ const height = rect.height + 1;\n+ context.strokeRect(\n+ left < 0 ? 0 : left,\n+ top < 0 ? 0 : top,\n+ width > window.innerWidth ? window.innerWidth : width,\n+ height > window.innerHeight ? window.innerHeight : height\n+ );\n+ }\n+ } catch {\n+ void 0;\n }\n });\n \n@@ -216,31 +228,35 @@ export class HighLight {\n \n allPendingWarn.forEach((fiber) => {\n if (include(fiber.state, STATE_TYPE.__unmount__)) return;\n- const node = fiber.nativeNode as HTMLElement;\n- if (node.nodeType === Node.TEXT_NODE) {\n- this.range.selectNodeContents(node);\n- } else {\n- this.range.selectNode(node);\n- }\n- const rect = this.range.getBoundingClientRect();\n- if (\n- (rect.width || rect.height) &&\n- rect.top >= 0 &&\n- rect.left >= 0 &&\n- rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n- ) {\n- // do the highlight paint\n- const left = rect.left - 0.5;\n- const top = rect.top - 0.5;\n- const width = rect.width + 1;\n- const height = rect.height + 1;\n- context.strokeRect(\n- left < 0 ? 0 : left,\n- top < 0 ? 0 : top,\n- width > window.innerWidth ? window.innerWidth : width,\n- height > window.innerHeight ? window.innerHeight : height\n- );\n+ try {\n+ const node = fiber.nativeNode as HTMLElement;\n+ if (node.nodeType === Node.TEXT_NODE) {\n+ this.range.selectNodeContents(node);\n+ } else {\n+ this.range.selectNode(node);\n+ }\n+ const rect = this.range.getBoundingClientRect();\n+ if (\n+ (rect.width || rect.height) &&\n+ rect.top >= 0 &&\n+ rect.left >= 0 &&\n+ rect.right <= (window.innerWidth || document.documentElement.clientWidth) &&\n+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)\n+ ) {\n+ // do the highlight paint\n+ const left = rect.left - 0.5;\n+ const top = rect.top - 0.5;\n+ const width = rect.width + 1;\n+ const height = rect.height + 1;\n+ context.strokeRect(\n+ left < 0 ? 0 : left,\n+ top < 0 ? 0 : top,\n+ width > window.innerWidth ? window.innerWidth : width,\n+ height > window.innerHeight ? window.innerHeight : height\n+ );\n+ }\n+ } catch {\n+ void 0;\n }\n });\n",
+ ],
+};
+
+export const d = {
+ oldFile: {
+ fileName: "src/views/test/result/singleResult/singleResultDetail/llm/components/result.vue",
+ content:
+ "\n \n
\n \n
\n
\"\n :params=\"finalParams\"\n :columns=\"columns\"\n >\n \n 分析结果 \n 平均分 \n 总分 \n 结果\n \n \n \n {{ AnalyzeResultInfo[row.analyzeResult]?.label }}\n \n \n {{ row.score }}\n \n \n \n
\n\n\n\n",
+ },
+ newFile: {
+ fileName: "src/views/test/result/singleResult/singleResultDetail/llm/components/result.vue",
+ content:
+ "\n \n
\n \n
\n
\"\n :params=\"finalParams\"\n :columns=\"columns\"\n >\n \n \n 导出数据\n \n \n \n 分析结果 \n 平均分 \n 总分 \n 结果\n \n \n \n {{ AnalyzeResultInfo[row.analyzeResult]?.label }}\n \n \n {{ row.score }}\n \n \n \n
\n\n\n\n",
+ },
+ hunks: [
+ '--- src/views/test/result/singleResult/singleResultDetail/llm/components/result.vue\n+++ src/views/test/result/singleResult/singleResultDetail/llm/components/result.vue\n@@ -6,10 +6,19 @@\n \n+ \n+ \n+ 导出数据\n+ \n+ \n \n 分析结果 \n 平均分 \n',
+ "--- src/views/test/result/singleResult/singleResultDetail/llm/components/result.vue\n+++ src/views/test/result/singleResult/singleResultDetail/llm/components/result.vue\n@@ -43,6 +52,7 @@\n queryCaseResult,\n AnalyzeResult,\n } from '@/apis/wqe-case/result/singleResult';\n+ import { exportLLMSingleResultCases } from '@/apis/wqe-testcase/result';\n import { createDrawer } from '@/components/basic/basicDrawer';\n import { BasicTableInstance, Column } from '@/components/basic/basicTable';\n import { FilterConditionDto } from '@/components/business/filters';\n",
+ '--- src/views/test/result/singleResult/singleResultDetail/llm/components/result.vue\n+++ src/views/test/result/singleResult/singleResultDetail/llm/components/result.vue\n@@ -137,5 +147,22 @@\n };\n \n defineExpose({ reset: () => tableRef.value?.reset(), setStatus });\n+\n+ /**\n+ * 导出数据\n+ */\n+ const exporting = ref(false);\n+ const selectionRows = ref([]);\n+ const handleExport = () => {\n+ exporting.value = true;\n+ exportLLMSingleResultCases({\n+ caseResultIds: selectionRows.value.map((item) => item.id),\n+ toolType: currentType,\n+ jobId: currentJobId,\n+ runSeq: props.runSeq,\n+ }).finally(() => {\n+ exporting.value = false;\n+ });\n+ };\n \n \n',
+ ],
+};
+
+export const e = {
+ oldFile: {
+ fileName: "src/apis/modules/catalog/module.ts",
+ content:
+ "export type CataLogTreeItem = {\n id: number;\n depoId: number;\n key: string;\n level: number;\n title: string;\n value: string;\n isLeaf: boolean;\n parentId: number;\n inherited: boolean;\n createTime: string;\n updateTime: string;\n reviewers: string;\n // TODO\n state: number;\n children: null | CataLogTreeItem[];\n};\n\nexport type ApplyCataLogItem = {\n applyId: number;\n catalogId: number;\n createTime: string;\n updateTime: string;\n // TODO\n deleted: number;\n\n id: number;\n remark: string | null;\n // TODO\n reviewState: number;\n\n reviewers: string;\n version: number;\n}\n",
+ },
+ newFile: {
+ fileName: null,
+ content: null,
+ },
+ hunks: [
+ "--- src/apis/modules/catalog/module.ts\n+++ /dev/null\n@@ -1,34 +0,0 @@\n-export type CataLogTreeItem = {\n- id: number;\n- depoId: number;\n- key: string;\n- level: number;\n- title: string;\n- value: string;\n- isLeaf: boolean;\n- parentId: number;\n- inherited: boolean;\n- createTime: string;\n- updateTime: string;\n- reviewers: string;\n- // TODO\n- state: number;\n- children: null | CataLogTreeItem[];\n-};\n-\n-export type ApplyCataLogItem = {\n- applyId: number;\n- catalogId: number;\n- createTime: string;\n- updateTime: string;\n- // TODO\n- deleted: number;\n-\n- id: number;\n- remark: string | null;\n- // TODO\n- reviewState: number;\n-\n- reviewers: string;\n- version: number;\n-}\n",
+ ],
+};
+
+export const f = {
+ oldFile: {
+ fileName: "src/apis/modules/catalog/index.ts",
+ content:
+ "import request from '@/apis/fetcher';\nimport type { BasicResponse } from '@/apis/fetcher/type';\nimport type { ApplyCataLogItem, CataLogTreeItem } from './module';\n\nexport const getCataLogTree = (repoId: number) => {\n return request>(`/api/catalogue/getCatalogTree/${repoId}`, {\n method: 'GET',\n description: '根据仓库ID获取目录树结构',\n });\n};\n\nexport const getApplyCataLogById = (applyId: number) => {\n return request>(`/api/applyCatalog/getCatalogById/${applyId}`, {\n method: 'GET',\n description: '根据申请ID获取目录结构',\n });\n};\n\nexport const getMrApplyCataLogById = (applyId: number) => {\n return request>(`/api/mrApplyCatalog/getCatalogById/${applyId}`, {\n method: 'GET',\n description: '根据ID获取MR目录结构',\n });\n};\n",
+ },
+ newFile: {
+ fileName: null,
+ content: null,
+ },
+ hunks: [
+ "--- src/apis/modules/catalog/index.ts\n+++ /dev/null\n@@ -1,24 +0,0 @@\n-import request from '@/apis/fetcher';\n-import type { BasicResponse } from '@/apis/fetcher/type';\n-import type { ApplyCataLogItem, CataLogTreeItem } from './module';\n-\n-export const getCataLogTree = (repoId: number) => {\n- return request>(`/api/catalogue/getCatalogTree/${repoId}`, {\n- method: 'GET',\n- description: '根据仓库ID获取目录树结构',\n- });\n-};\n-\n-export const getApplyCataLogById = (applyId: number) => {\n- return request>(`/api/applyCatalog/getCatalogById/${applyId}`, {\n- method: 'GET',\n- description: '根据申请ID获取目录结构',\n- });\n-};\n-\n-export const getMrApplyCataLogById = (applyId: number) => {\n- return request>(`/api/mrApplyCatalog/getCatalogById/${applyId}`, {\n- method: 'GET',\n- description: '根据ID获取MR目录结构',\n- });\n-};\n",
+ ],
+};
diff --git a/ui/solid-example/src/index.css b/ui/solid-example/src/index.css
index 6119ad9..b5c61c9 100644
--- a/ui/solid-example/src/index.css
+++ b/ui/solid-example/src/index.css
@@ -1,68 +1,3 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/ui/solid-example/src/index.tsx b/ui/solid-example/src/index.tsx
index 0e0597e..84840a2 100644
--- a/ui/solid-example/src/index.tsx
+++ b/ui/solid-example/src/index.tsx
@@ -1,7 +1,9 @@
+import 'solid-devtools'
/* @refresh reload */
import { render } from 'solid-js/web'
import './index.css'
+import "@git-diff-view/solid/styles/diff-view.css"
import App from './App.tsx'
const root = document.getElementById('root')
diff --git a/ui/solid-example/src/worker.ts b/ui/solid-example/src/worker.ts
new file mode 100644
index 0000000..6630e3c
--- /dev/null
+++ b/ui/solid-example/src/worker.ts
@@ -0,0 +1,48 @@
+import { DiffFile } from "@git-diff-view/react";
+
+import type { DiffViewProps } from "@git-diff-view/vue";
+
+export type MessageData = {
+ id: number;
+ data: DiffViewProps["data"];
+ theme?: "light" | "dark";
+ bundle: ReturnType;
+};
+
+const post = (d: MessageData) => postMessage(d);
+
+onmessage = (event: MessageEvent) => {
+ const _data = event.data;
+
+ const data = _data.data;
+
+ const file = new DiffFile(
+ data?.oldFile?.fileName || "",
+ data?.oldFile?.content || "",
+ data?.newFile?.fileName || "",
+ data?.newFile?.content || "",
+ data?.hunks || [],
+ data?.oldFile?.fileLang || "",
+ data?.newFile?.fileLang || ""
+ );
+
+ file.initTheme(_data.theme);
+
+ file.initRaw();
+
+ file.initSyntax();
+
+ file.buildSplitDiffLines();
+
+ file.buildUnifiedDiffLines();
+
+ const res: MessageData = {
+ id: _data.id,
+ data: _data.data,
+ bundle: file.getBundle(),
+ };
+
+ file.clear();
+
+ post(res);
+};
diff --git a/ui/solid-example/tailwind.config.js b/ui/solid-example/tailwind.config.js
new file mode 100644
index 0000000..45ce112
--- /dev/null
+++ b/ui/solid-example/tailwind.config.js
@@ -0,0 +1,8 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/ui/solid-example/vite.config.ts b/ui/solid-example/vite.config.ts
index 4a303c3..f109d28 100644
--- a/ui/solid-example/vite.config.ts
+++ b/ui/solid-example/vite.config.ts
@@ -1,6 +1,10 @@
+import devtools from "solid-devtools/vite";
import { defineConfig } from "vite";
import solid from "vite-plugin-solid";
export default defineConfig({
- plugins: [solid()],
+ plugins: [solid(), devtools({ autoname: true })],
+ worker: {
+ format: "es",
+ },
});
From acaa18d58a04ddf394f79f79191e5d2d760dc3f7 Mon Sep 17 00:00:00 2001
From: MrWangJustToDo <2711470541@qq.com>
Date: Mon, 23 Jun 2025 22:38:56 +0800
Subject: [PATCH 11/12] solid done
---
packages/react/src/components/DiffView.tsx | 14 +-
.../components/DiffSplitContentLineNormal.tsx | 8 +-
.../components/DiffSplitContentLineWrap.tsx | 10 +-
.../components/DiffSplitExtendLineNormal.tsx | 8 +-
.../components/DiffSplitExtendLineWrap.tsx | 10 +-
.../components/DiffSplitHunkLineNormal.tsx | 14 +-
.../src/components/DiffSplitHunkLineWrap.tsx | 8 +-
.../src/components/DiffSplitViewNormal.tsx | 12 +-
.../components/DiffSplitWidgetLineNormal.tsx | 2 +-
.../src/components/DiffUnifiedContentLine.tsx | 299 ++++++++++++++++++
.../src/components/DiffUnifiedExtendLine.tsx | 72 +++++
.../src/components/DiffUnifiedHunkLine.tsx | 149 +++++++++
.../solid/src/components/DiffUnifiedView.tsx | 115 ++++++-
.../src/components/DiffUnifiedWidgetLine.tsx | 81 +++++
packages/solid/src/components/DiffView.tsx | 20 +-
packages/solid/src/components/tools.ts | 21 +-
packages/solid/src/hooks/generate.ts | 4 +-
packages/vue/src/components/DiffView.tsx | 16 +-
ui/solid-example/src/App.tsx | 110 ++++++-
19 files changed, 892 insertions(+), 81 deletions(-)
create mode 100644 packages/solid/src/components/DiffUnifiedContentLine.tsx
create mode 100644 packages/solid/src/components/DiffUnifiedExtendLine.tsx
create mode 100644 packages/solid/src/components/DiffUnifiedHunkLine.tsx
create mode 100644 packages/solid/src/components/DiffUnifiedWidgetLine.tsx
diff --git a/packages/react/src/components/DiffView.tsx b/packages/react/src/components/DiffView.tsx
index 25a23be..84002dc 100644
--- a/packages/react/src/components/DiffView.tsx
+++ b/packages/react/src/components/DiffView.tsx
@@ -96,6 +96,7 @@ const InternalDiffView = (
style,
diffViewMode,
diffViewWrap,
+ diffViewTheme,
diffViewFontSize,
diffViewHighlight,
renderWidgetLine,
@@ -209,13 +210,17 @@ const InternalDiffView = (
]);
useEffect(() => {
- const cb = diffFile.subscribe(() => {
+ const init = () => {
wrapperRef.current?.setAttribute("data-theme", diffFile._getTheme() || "light");
wrapperRef.current?.setAttribute("data-highlighter", diffFile._getHighlighterName());
- });
+ }
+
+ init();
+
+ const cb = diffFile.subscribe(init);
return cb;
- }, [diffFile]);
+ }, [diffFile, diffViewTheme]);
const value = useMemo(() => ({ useDiffContext }), [useDiffContext]);
@@ -315,7 +320,7 @@ const DiffViewWithRef = (
diffFile.initSyntax({ registerHighlighter });
diffFile.notifyAll();
}
- }, [diffFile, props.diffViewHighlight, registerHighlighter, diffViewTheme]);
+ }, [diffFile, props.diffViewHighlight, registerHighlighter]);
// fix react strict mode error
useUnmount(() => (__DEV__ ? diffFile?._destroy?.() : diffFile?.clear?.()), [diffFile]);
@@ -330,6 +335,7 @@ const DiffViewWithRef = (
{...restProps}
diffFile={diffFile}
isMounted={isMounted}
+ diffViewTheme={diffViewTheme}
diffViewMode={restProps.diffViewMode || DiffModeEnum.SplitGitHub}
diffViewFontSize={restProps.diffViewFontSize || 14}
/>
diff --git a/packages/solid/src/components/DiffSplitContentLineNormal.tsx b/packages/solid/src/components/DiffSplitContentLineNormal.tsx
index 6f049ea..176b384 100644
--- a/packages/solid/src/components/DiffSplitContentLineNormal.tsx
+++ b/packages/solid/src/components/DiffSplitContentLineNormal.tsx
@@ -73,10 +73,10 @@ export const DiffSplitContentLine = (props: {
? props.diffFile.getOldPlainLine(currentLine()?.lineNumber || 0)
: props.diffFile.getNewPlainLine(currentLine()?.lineNumber || 0)
);
- setCurrentLineHasDiff(!!currentLine()?.diff);
- setCurrentLineHasChange(checkDiffLineIncludeChange(currentLine()?.diff));
- setCurrentLineHasHidden(currentLine()?.isHidden);
- setCurrentLineHasContent(currentLine()?.lineNumber);
+ setCurrentLineHasDiff(() => !!currentLine()?.diff);
+ setCurrentLineHasChange(() => checkDiffLineIncludeChange(currentLine()?.diff));
+ setCurrentLineHasHidden(() => currentLine()?.isHidden);
+ setCurrentLineHasContent(() => currentLine()?.lineNumber);
};
createEffect(() => {
diff --git a/packages/solid/src/components/DiffSplitContentLineWrap.tsx b/packages/solid/src/components/DiffSplitContentLineWrap.tsx
index e40f8a8..d736581 100644
--- a/packages/solid/src/components/DiffSplitContentLineWrap.tsx
+++ b/packages/solid/src/components/DiffSplitContentLineWrap.tsx
@@ -56,11 +56,11 @@ export const DiffSplitContentLine = (props: { index: number; diffFile: DiffFile;
setNewSyntaxLine(props.diffFile.getNewSyntaxLine(newLine()?.lineNumber || 0));
setOldPlainLine(props.diffFile.getOldPlainLine(oldLine()?.lineNumber || 0));
setNewPlainLine(props.diffFile.getNewPlainLine(newLine()?.lineNumber || 0));
- setHasDiff(!!oldLine()?.diff || !!newLine()?.diff);
- setHasChange(checkDiffLineIncludeChange(oldLine()?.diff) || checkDiffLineIncludeChange(newLine()?.diff));
- setHasHidden(oldLine()?.isHidden && newLine()?.isHidden);
- setOldLineIsDelete(oldLine()?.diff?.type === DiffLineType.Delete);
- setNewLineIsAdded(newLine()?.diff?.type === DiffLineType.Add);
+ setHasDiff(() => !!oldLine()?.diff || !!newLine()?.diff);
+ setHasChange(() => checkDiffLineIncludeChange(oldLine()?.diff) || checkDiffLineIncludeChange(newLine()?.diff));
+ setHasHidden(() => oldLine()?.isHidden && newLine()?.isHidden);
+ setOldLineIsDelete(() => oldLine()?.diff?.type === DiffLineType.Delete);
+ setNewLineIsAdded(() => newLine()?.diff?.type === DiffLineType.Add);
};
init();
diff --git a/packages/solid/src/components/DiffSplitExtendLineNormal.tsx b/packages/solid/src/components/DiffSplitExtendLineNormal.tsx
index 65290c6..ec9187a 100644
--- a/packages/solid/src/components/DiffSplitExtendLineNormal.tsx
+++ b/packages/solid/src/components/DiffSplitExtendLineNormal.tsx
@@ -43,9 +43,9 @@ export const DiffSplitExtendLine = (props: {
setOldLine(props.diffFile.getSplitLeftLine(props.index));
setNewLine(props.diffFile.getSplitRightLine(props.index));
setEnableExpand(props.diffFile.getExpandEnabled());
- setOldLineExtend(extendData()?.oldFile?.[oldLine()?.lineNumber || ""]);
- setNewLineExtend(extendData()?.newFile?.[newLine()?.lineNumber || ""]);
- setCurrentIsHidden(currentItem()?.isHidden);
+ setOldLineExtend(() => extendData()?.oldFile?.[oldLine()?.lineNumber || ""]);
+ setNewLineExtend(() => extendData()?.newFile?.[newLine()?.lineNumber || ""]);
+ setCurrentIsHidden(() => currentItem()?.isHidden);
};
init();
@@ -89,7 +89,7 @@ export const DiffSplitExtendLine = (props: {
data-side={SplitSide[props.side]}
class="diff-line diff-line-extend"
>
- {currentExtend() ? (
+ {renderExtend() && currentExtend() ? (
extendData()?.oldFile?.[oldLine()?.lineNumber || ""]);
+ setNewLineExtend(() => extendData()?.newFile?.[newLine()?.lineNumber || ""]);
+ setCurrentIsShow(() => !!checkIsShow());
};
init();
@@ -56,7 +56,7 @@ export const DiffSplitExtendLine = (props: { index: number; diffFile: DiffFile;
return (
- {renderExtend() ? (
+ {renderExtend() && oldLineExtend() ? (
{renderExtend()?.({
@@ -75,7 +75,7 @@ export const DiffSplitExtendLine = (props: { index: number; diffFile: DiffFile;
colspan={2}
/>
)}
- {renderExtend() ? (
+ {renderExtend() && newLineExtend() ? (
{
setCurrentHunk(props.diffFile.getSplitHunkLine(props.index));
setEnableExpand(props.diffFile.getExpandEnabled());
- setCouldExpand(enableExpand() && currentHunk()?.splitInfo);
- setCurrentIsShow(checkCurrentIsShow());
- setCurrentShowExpandAll(checkCurrentShowExpandAll());
+ setCouldExpand(() => enableExpand() && currentHunk()?.splitInfo);
+ setCurrentIsShow(checkCurrentIsShow);
+ setCurrentShowExpandAll(checkCurrentShowExpandAll);
};
init();
@@ -213,7 +213,7 @@ const DiffSplitHunkLineGitLab = (props: { index: number; side: SplitSide; diffFi
const currentIsPureHunk = createMemo(() => {
const hunk = currentHunk();
- return hunk && !hunk.splitInfo;
+ return hunk && props.diffFile._getIsPureDiffRender() && !hunk.splitInfo;
});
const currentIsLastLine = createMemo(() => {
@@ -229,9 +229,9 @@ const DiffSplitHunkLineGitLab = (props: { index: number; side: SplitSide; diffFi
const init = () => {
setCurrentHunk(props.diffFile.getSplitHunkLine(props.index));
setEnableExpand(props.diffFile.getExpandEnabled());
- setCouldExpand(enableExpand() && currentHunk()?.splitInfo);
- setCurrentIsShow(checkCurrentIsShow());
- setCurrentShowExpandAll(checkCurrentShowExpandAll());
+ setCouldExpand(() => enableExpand() && currentHunk()?.splitInfo);
+ setCurrentIsShow(checkCurrentIsShow);
+ setCurrentShowExpandAll(checkCurrentShowExpandAll);
};
init();
diff --git a/packages/solid/src/components/DiffSplitHunkLineWrap.tsx b/packages/solid/src/components/DiffSplitHunkLineWrap.tsx
index e966f01..53b7fe0 100644
--- a/packages/solid/src/components/DiffSplitHunkLineWrap.tsx
+++ b/packages/solid/src/components/DiffSplitHunkLineWrap.tsx
@@ -41,7 +41,7 @@ const DiffSplitHunkLineGitHub = (props: { index: number; diffFile: DiffFile; lin
const currentIsPureHunk = createMemo(() => {
const hunk = currentHunk();
- return hunk && !hunk.splitInfo;
+ return hunk && props.diffFile._getIsPureDiffRender() && !hunk.splitInfo;
});
const currentIsLastLine = createMemo(() => {
@@ -53,9 +53,9 @@ const DiffSplitHunkLineGitHub = (props: { index: number; diffFile: DiffFile; lin
const init = () => {
setCurrentHunk(props.diffFile.getSplitHunkLine(props.index));
setEnableExpand(props.diffFile.getExpandEnabled());
- setCouldExpand(enableExpand() && currentHunk()?.splitInfo);
- setCurrentShowExpandAll(checkCurrentShowExpandAll());
- setCurrentIsShow(checkCurrentIsShow());
+ setCouldExpand(() => enableExpand() && currentHunk()?.splitInfo);
+ setCurrentIsShow(checkCurrentIsShow);
+ setCurrentShowExpandAll(checkCurrentShowExpandAll);
};
init();
diff --git a/packages/solid/src/components/DiffSplitViewNormal.tsx b/packages/solid/src/components/DiffSplitViewNormal.tsx
index d3b577d..665cd4e 100644
--- a/packages/solid/src/components/DiffSplitViewNormal.tsx
+++ b/packages/solid/src/components/DiffSplitViewNormal.tsx
@@ -110,9 +110,9 @@ const DiffSplitViewTable = (props: { side: SplitSide; diffFile: DiffFile; onSele
export const DiffSplitViewNormal = (props: { diffFile: DiffFile }) => {
const isMounted = useIsMounted();
- let ref1 = null as HTMLDivElement | null;
+ const [ref1, setRef1] = createSignal(null);
- let ref2 = null as HTMLDivElement | null;
+ const [ref2, setRef2] = createSignal(null);
const [side, setSide] = createSignal(undefined);
@@ -128,8 +128,8 @@ export const DiffSplitViewNormal = (props: { diffFile: DiffFile }) => {
const initSyncScroll = () => {
if (!isMounted()) return;
- const left = ref1;
- const right = ref2;
+ const left = ref1();
+ const right = ref2();
if (!left || !right) return;
const clean = syncScroll(left, right);
onCleanup(clean);
@@ -156,7 +156,7 @@ export const DiffSplitViewNormal = (props: { diffFile: DiffFile }) => {
| |
+ }
+ >
+
+ }
+ >
+
+
+
+
+ );
+};
diff --git a/packages/solid/src/components/DiffUnifiedExtendLine.tsx b/packages/solid/src/components/DiffUnifiedExtendLine.tsx
new file mode 100644
index 0000000..100c331
--- /dev/null
+++ b/packages/solid/src/components/DiffUnifiedExtendLine.tsx
@@ -0,0 +1,72 @@
+import { SplitSide, type DiffFile } from "@git-diff-view/core";
+import { createEffect, createMemo, createSignal, onCleanup, Show } from "solid-js";
+
+import { useDomWidth, useExtendData, useRenderExtend } from "../hooks";
+
+
+export const DiffUnifiedExtendLine = (props: { index: number; diffFile: DiffFile; lineNumber: number }) => {
+ const extendData = useExtendData();
+
+ const renderExtend = useRenderExtend();
+
+ const [unifiedItem, setUnifiedItem] = createSignal(props.diffFile.getUnifiedLine(props.index));
+
+ const [oldExtend, setOldExtend] = createSignal(extendData()?.oldFile?.[unifiedItem()?.oldLineNumber || 0]);
+
+ const [newExtend, setNewExtend] = createSignal(extendData()?.newFile?.[unifiedItem()?.newLineNumber || 0]);
+
+ const [currentIsHidden, setCurrentIsHidden] = createSignal(unifiedItem()?.isHidden);
+
+ createEffect(() => {
+ const init = () => {
+ setUnifiedItem(props.diffFile.getUnifiedLine(props.index));
+ setOldExtend(() => extendData()?.oldFile?.[unifiedItem()?.oldLineNumber || 0]);
+ setNewExtend(() => extendData()?.newFile?.[unifiedItem()?.newLineNumber || 0]);
+ setCurrentIsHidden(() => unifiedItem()?.isHidden);
+ };
+
+ init();
+
+ const unsubscribe = props.diffFile.subscribe(init);
+
+ onCleanup(unsubscribe);
+ });
+
+ const currentIsShow = createMemo(() => Boolean((oldExtend() || newExtend()) && !currentIsHidden() && renderExtend()));
+
+ const lineSelector = createMemo(() => `.unified-diff-table-wrapper`);
+
+ const width = useDomWidth({
+ selector: lineSelector,
+ enable: currentIsShow,
+ });
+
+ return (
+
+
+
+
+ {width() > 0 &&
+ oldExtend() &&
+ renderExtend()?.({
+ diffFile: props.diffFile,
+ side: SplitSide.old,
+ lineNumber: unifiedItem()?.oldLineNumber || 0,
+ data: oldExtend()?.data,
+ onUpdate: props.diffFile.notifyAll,
+ })}
+ {width() > 0 &&
+ newExtend() &&
+ renderExtend()?.({
+ diffFile: props.diffFile,
+ side: SplitSide.new,
+ lineNumber: unifiedItem().newLineNumber || 0,
+ data: newExtend()?.data,
+ onUpdate: props.diffFile.notifyAll,
+ })}
+
+ |
+
+
+ );
+};
diff --git a/packages/solid/src/components/DiffUnifiedHunkLine.tsx b/packages/solid/src/components/DiffUnifiedHunkLine.tsx
new file mode 100644
index 0000000..7f9198c
--- /dev/null
+++ b/packages/solid/src/components/DiffUnifiedHunkLine.tsx
@@ -0,0 +1,149 @@
+import { composeLen, type DiffFile } from "@git-diff-view/core";
+import {
+ hunkLineNumberBGName,
+ plainLineNumberColorName,
+ diffAsideWidthName,
+ hunkContentBGName,
+ hunkContentColorName,
+} from "@git-diff-view/utils";
+import { createEffect, createMemo, createSignal, onCleanup, Show } from "solid-js";
+
+import { useEnableWrap } from "../hooks";
+
+import { ExpandUp, ExpandDown, ExpandAll } from "./DiffExpand";
+
+export const DiffUnifiedHunkLine = (props: { index: number; diffFile: DiffFile; lineNumber: number }) => {
+ const [currentHunk, setCurrentHunk] = createSignal(props.diffFile.getUnifiedHunkLine(props.index));
+
+ const [enableExpand, setEnableExpand] = createSignal(props.diffFile.getExpandEnabled());
+
+ const couldExpand = createMemo(() => enableExpand() && !!currentHunk()?.unifiedInfo);
+
+ const enableWrap = useEnableWrap();
+
+ const checkCurrentIsShow = () => {
+ const hunk = currentHunk();
+ return hunk && hunk.unifiedInfo && hunk.unifiedInfo.startHiddenIndex < hunk.unifiedInfo.endHiddenIndex;
+ };
+
+ const [currentIsShow, setCurrentIsShow] = createSignal(checkCurrentIsShow());
+
+ const checkCurrentIsEnableAll = () => {
+ const hunk = currentHunk();
+ return hunk && hunk.unifiedInfo && hunk.unifiedInfo.endHiddenIndex - hunk.unifiedInfo.startHiddenIndex < composeLen;
+ };
+
+ const [currentIsEnableAll, setCurrentIsEnableAll] = createSignal(checkCurrentIsEnableAll());
+
+ const [currentIsFirstLine, setCurrentIsFirstLine] = createSignal(currentHunk()?.isFirst);
+
+ const [currentIsLastLine, setCurrentIsLastLine] = createSignal(currentHunk()?.isLast);
+
+ const [currentIsPureHunk, setCurrentIsPureHunk] = createSignal(
+ currentHunk() && props.diffFile._getIsPureDiffRender() && !currentHunk()?.unifiedInfo
+ );
+
+ createEffect(() => {
+ const init = () => {
+ setCurrentHunk(props.diffFile.getUnifiedHunkLine(props.index));
+
+ setEnableExpand(props.diffFile.getExpandEnabled());
+
+ setCurrentIsShow(checkCurrentIsShow);
+ setCurrentIsEnableAll(checkCurrentIsEnableAll);
+ setCurrentIsFirstLine(() => currentHunk()?.isFirst);
+ setCurrentIsLastLine(() => currentHunk()?.isLast);
+ setCurrentIsPureHunk(() => currentHunk() && props.diffFile._getIsPureDiffRender() && !currentHunk()?.unifiedInfo);
+ };
+
+ init();
+
+ const unsubscribe = props.diffFile.subscribe(init);
+
+ onCleanup(unsubscribe);
+ });
+
+ return (
+
+
+
+ {couldExpand() ? (
+ currentIsFirstLine() ? (
+
+ ) : currentIsLastLine() ? (
+
+ ) : currentIsEnableAll() ? (
+
+ ) : (
+ <>
+
+
+ >
+ )
+ ) : (
+
+ )}
+ |
+
+
+ {currentHunk()?.unifiedInfo?.plainText || currentHunk()?.text}
+
+ |
+
+
+ );
+};
diff --git a/packages/solid/src/components/DiffUnifiedView.tsx b/packages/solid/src/components/DiffUnifiedView.tsx
index 8427599..313ad08 100644
--- a/packages/solid/src/components/DiffUnifiedView.tsx
+++ b/packages/solid/src/components/DiffUnifiedView.tsx
@@ -1,10 +1,117 @@
-import type { DiffFile } from "@git-diff-view/core";
+import { getUnifiedContentLine, type DiffFile } from "@git-diff-view/core";
+import { diffAsideWidthName, diffFontSizeName, removeAllSelection } from "@git-diff-view/utils";
+import { createEffect, createMemo, createSignal, onCleanup, For } from "solid-js";
+
+import { useEnableWrap, useFontSize, useTextWidth } from "../hooks";
+
+import { DiffUnifiedContentLine } from "./DiffUnifiedContentLine";
+import { DiffUnifiedExtendLine } from "./DiffUnifiedExtendLine";
+import { DiffUnifiedHunkLine } from "./DiffUnifiedHunkLine";
+import { DiffUnifiedWidgetLine } from "./DiffUnifiedWidgetLine";
export const DiffUnifiedView = (props: { diffFile: DiffFile }) => {
+ const [lines, setLines] = createSignal(getUnifiedContentLine(props.diffFile));
+
+ const [maxText, setMaxText] = createSignal(props.diffFile.unifiedLineLength.toString());
+
+ createEffect(() => {
+ const init = () => {
+ setLines(getUnifiedContentLine(props.diffFile));
+ setMaxText(props.diffFile.unifiedLineLength.toString());
+ };
+
+ init();
+
+ const unsubscribe = props.diffFile.subscribe(init);
+
+ onCleanup(unsubscribe);
+ });
+
+ const fontSize = useFontSize();
+
+ const enableWrap = useEnableWrap();
+
+ const [state, setState] = createSignal ();
+
+ const onMouseDown = (e: MouseEvent) => {
+ let ele = e.target as HTMLElement | null;
+
+ if (ele && ele?.nodeName === "BUTTON") {
+ removeAllSelection();
+ return;
+ }
+
+ while (ele && ele instanceof HTMLElement) {
+ const state = ele.getAttribute("data-state");
+ if (state) {
+ if (state === "extend" || state === "hunk" || state === "widget") {
+ setState(false);
+ removeAllSelection();
+ } else {
+ setState(true);
+ removeAllSelection();
+ }
+ return;
+ }
+ ele = ele.parentElement;
+ }
+ };
+
+ const font = createMemo(() => ({ fontSize: fontSize() + "px", fontFamily: "Menlo, Consolas, monospace" }));
+
+ const width = useTextWidth({ text: maxText, font });
+
+ const computedWidth = createMemo(() => Math.max(40, width() + 10));
+
+ const getId = () => `diff-root${props.diffFile.getId()}`;
+
return (
-
- Unified Diff View
- This is a placeholder for the unified diff view component.
+
);
};
diff --git a/packages/solid/src/components/DiffUnifiedWidgetLine.tsx b/packages/solid/src/components/DiffUnifiedWidgetLine.tsx
new file mode 100644
index 0000000..13125d5
--- /dev/null
+++ b/packages/solid/src/components/DiffUnifiedWidgetLine.tsx
@@ -0,0 +1,81 @@
+import { SplitSide, type DiffFile } from "@git-diff-view/core";
+import { createEffect, createMemo, createSignal, onCleanup, Show } from "solid-js";
+
+import { useDomWidth, useRenderWidget } from "../hooks";
+
+import { useDiffWidgetContext } from "./DiffWidgetContext";
+
+export const DiffUnifiedWidgetLine = (props: { index: number; diffFile: DiffFile; lineNumber: number }) => {
+ const renderWidget = useRenderWidget();
+
+ const [widget, setWidget] = useDiffWidgetContext() || [];
+
+ const [unifiedItem, setUnifiedItem] = createSignal(props.diffFile.getUnifiedLine(props.index));
+
+ const oldWidget = createMemo(
+ () =>
+ unifiedItem()?.oldLineNumber &&
+ widget?.()?.side === SplitSide.old &&
+ widget?.()?.lineNumber === unifiedItem()?.oldLineNumber
+ );
+
+ const newWidget = createMemo(
+ () =>
+ unifiedItem()?.newLineNumber &&
+ widget?.()?.side === SplitSide.new &&
+ widget?.()?.lineNumber === unifiedItem()?.newLineNumber
+ );
+
+ const [currentIsHidden, setCurrentIsHidden] = createSignal(unifiedItem()?.isHidden);
+
+ createEffect(() => {
+ const init = () => {
+ setUnifiedItem(props.diffFile.getUnifiedLine(props.index));
+ setCurrentIsHidden(() => unifiedItem()?.isHidden);
+ };
+
+ init();
+
+ const unsubscribe = props.diffFile.subscribe(init);
+
+ onCleanup(unsubscribe);
+ });
+
+ const currenIsShow = createMemo(() => !!(oldWidget() || newWidget()) && !currentIsHidden() && !!renderWidget());
+
+ const onCloseWidget = () => setWidget?.({});
+
+ const lineSelector = createMemo(() => `.unified-diff-table-wrapper`);
+
+ const width = useDomWidth({
+ selector: lineSelector,
+ enable: currenIsShow,
+ });
+
+ return (
+
+
+
+
+ {width() > 0 &&
+ oldWidget() &&
+ renderWidget()?.({
+ diffFile: props.diffFile,
+ side: SplitSide.old,
+ lineNumber: unifiedItem()?.oldLineNumber || 0,
+ onClose: onCloseWidget,
+ })}
+ {width() > 0 &&
+ newWidget() &&
+ renderWidget()?.({
+ diffFile: props.diffFile,
+ side: SplitSide.new,
+ lineNumber: unifiedItem().newLineNumber || 0,
+ onClose: onCloseWidget,
+ })}
+
+ |
+
+
+ );
+};
diff --git a/packages/solid/src/components/DiffView.tsx b/packages/solid/src/components/DiffView.tsx
index 565c117..ce682e5 100644
--- a/packages/solid/src/components/DiffView.tsx
+++ b/packages/solid/src/components/DiffView.tsx
@@ -99,7 +99,7 @@ const InternalDiffView = (props: DiffViewProps) => {
return diffFile;
};
- let wrapperRef: HTMLDivElement | undefined;
+ const [wrapperRef, setWrapRef] = createSignal(null);
const diffFile = createMemo(getInstance);
@@ -196,11 +196,17 @@ const InternalDiffView = (props: DiffViewProps) => {
const initAttribute = () => {
const mounted = isMounted();
const currentDiffFile = diffFile();
- if (mounted && currentDiffFile && wrapperRef) {
- const cb = currentDiffFile.subscribe(() => {
- wrapperRef?.setAttribute("data-theme", currentDiffFile._getTheme() || "light");
- wrapperRef?.setAttribute("data-highlighter", currentDiffFile._getHighlighterName());
- });
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
+ props.diffViewTheme;
+ if (mounted && currentDiffFile && wrapperRef()) {
+ const init = () => {
+ wrapperRef()?.setAttribute("data-theme", currentDiffFile._getTheme() || "light");
+ wrapperRef()?.setAttribute("data-highlighter", currentDiffFile._getHighlighterName());
+ };
+
+ init();
+
+ const cb = currentDiffFile.subscribe(init);
onCleanup(() => cb());
}
@@ -224,7 +230,7 @@ const InternalDiffView = (props: DiffViewProps) => {
data-theme={diffFile()?._getTheme?.() || "light"}
data-version={__VERSION__}
data-highlighter={diffFile()?._getHighlighterName?.()}
- ref={wrapperRef}
+ ref={setWrapRef}
>
diff --git a/packages/solid/src/components/tools.ts b/packages/solid/src/components/tools.ts
index f395387..9cf2db6 100644
--- a/packages/solid/src/components/tools.ts
+++ b/packages/solid/src/components/tools.ts
@@ -38,26 +38,7 @@ export const createDiffConfigStore = (props: DiffViewProps, diffFileId: str
});
const setExtendData = (_extendData: DiffViewProps["extendData"]) => {
- const existOldKeys = Object.keys(extendData.value.oldFile || {});
- const inComingOldKeys = Object.keys(_extendData?.oldFile || {});
- for (const key of existOldKeys) {
- if (!inComingOldKeys.includes(key)) {
- delete extendData.value.oldFile[key];
- }
- }
- for (const key of inComingOldKeys) {
- extendData.value.oldFile[key] = _extendData?.oldFile?.[key];
- }
- const existNewKeys = Object.keys(extendData.value.newFile || {});
- const inComingNewKeys = Object.keys(_extendData?.newFile || {});
- for (const key of existNewKeys) {
- if (!inComingNewKeys.includes(key)) {
- delete extendData.value.newFile[key];
- }
- }
- for (const key of inComingNewKeys) {
- extendData.value.newFile[key] = _extendData?.newFile?.[key];
- }
+ extendData.value = { oldFile: { ..._extendData?.oldFile }, newFile: { ..._extendData?.newFile } };
};
const renderWidgetLine = ref(props.renderWidgetLine);
diff --git a/packages/solid/src/hooks/generate.ts b/packages/solid/src/hooks/generate.ts
index f347c7d..5ddb13e 100644
--- a/packages/solid/src/hooks/generate.ts
+++ b/packages/solid/src/hooks/generate.ts
@@ -15,9 +15,7 @@ export const generateHook = (key:
const [state, setState] = createSignal(reactiveHook?.getReadonlyState()[key] as K);
createEffect(() => {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- const init = () => setState(reactiveHook?.getReadonlyState()[key] as K);
+ const init = () => setState(() => reactiveHook?.getReadonlyState()[key] as K);
init();
diff --git a/packages/vue/src/components/DiffView.tsx b/packages/vue/src/components/DiffView.tsx
index d03a5ad..311080e 100644
--- a/packages/vue/src/components/DiffView.tsx
+++ b/packages/vue/src/components/DiffView.tsx
@@ -155,9 +155,7 @@ export const DiffView = defineComponent<
const initSyntax = () => {
if (!isMounted.value || !enableHighlight.value || !diffFile.value) return;
- // hack to track the value change
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
- theme.value;
+
const instance = diffFile.value;
instance.initSyntax({
registerHighlighter: props.registerHighlighter,
@@ -168,10 +166,18 @@ export const DiffView = defineComponent<
const initAttribute = (onClean: (cb: () => void) => void) => {
if (!isMounted.value || !diffFile.value || !wrapperRef.value) return;
const instance = diffFile.value;
- const cb = instance.subscribe(() => {
+ // hack to track the value change
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
+ theme.value;
+ const init = () => {
wrapperRef.value?.setAttribute("data-theme", instance._getTheme() || "light");
wrapperRef.value?.setAttribute("data-highlighter", instance._getHighlighterName());
- });
+ }
+
+ init();
+
+ const cb = instance.subscribe(init);
+
onClean(() => cb());
};
diff --git a/ui/solid-example/src/App.tsx b/ui/solid-example/src/App.tsx
index 12da1b5..b4d8e82 100644
--- a/ui/solid-example/src/App.tsx
+++ b/ui/solid-example/src/App.tsx
@@ -1,4 +1,4 @@
-import { DiffFile, DiffView } from "@git-diff-view/solid";
+import { DiffFile, DiffModeEnum, DiffView, SplitSide } from "@git-diff-view/solid";
import { createEffect, createSignal, Show } from "solid-js";
import * as data from "./data";
@@ -14,6 +14,29 @@ function App() {
const [diffFile, setDiffFile] = createSignal();
+ const [highlight, setHighlight] = createSignal(true);
+
+ const [wrap, setWrap] = createSignal(true);
+
+ const [dark, setDark] = createSignal(false);
+
+ const [mode, setMode] = createSignal(DiffModeEnum.Split);
+
+ const toggleHighlight = () => setHighlight((l) => !l);
+
+ const toggleTheme = () => setDark((d) => !d);
+
+ const toggleWrap = () => setWrap((w) => !w);
+
+ const toggleMode = () =>
+ setMode((l) => {
+ return l === DiffModeEnum.Split ? DiffModeEnum.Unified : DiffModeEnum.Split;
+ });
+
+ const [v, setV] = createSignal("");
+
+ const [extend, setExtend] = createSignal({});
+
worker.addEventListener("message", (e: MessageEvent) => {
const { data, bundle } = e.data;
const instance = DiffFile.createInstance(data || {}, bundle);
@@ -29,6 +52,14 @@ function App() {
});
});
+ createEffect(() => {
+ const currentDiffFile = diffFile();
+
+ if (currentDiffFile) {
+ setExtend({ oldFile: {}, newFile: {} });
+ }
+ });
+
return (
<>
@@ -45,9 +76,84 @@ function App() {
+
+
+
+
+
+
+
+
-
+ setV("")}
+ renderWidgetLine={({ side, lineNumber, onClose }) => (
+
+ )}
+ renderExtendLine={({ data }) => (
+
+
+ {" "}
+ {">>"} {data as string}{" "}
+
+
+ )}
+ />
>
From b278a829e771dcd925a26df32d6a7d4709c5a4f4 Mon Sep 17 00:00:00 2001
From: MrWangJustToDo <2711470541@qq.com>
Date: Tue, 24 Jun 2025 14:20:31 +0800
Subject: [PATCH 12/12] wip
---
.../components/DiffSplitContentLineNormal.tsx | 6 +-
.../components/DiffSplitContentLineWrap.tsx | 6 +-
.../components/DiffSplitExtendLineNormal.tsx | 9 +-
.../components/DiffSplitWidgetLineNormal.tsx | 15 ++--
packages/solid/src/hooks/useDomWidth.ts | 2 +-
packages/solid/src/hooks/useSyncHeight.ts | 87 +++++++++++--------
.../components/DiffSplitContentLineNormal.tsx | 10 +--
.../components/DiffSplitContentLineWrap.tsx | 28 +++---
.../components/DiffSplitExtendLineNormal.tsx | 12 ++-
.../components/DiffSplitWidgetLineNormal.tsx | 16 ++--
packages/vue/src/hooks/useDomWidth.ts | 2 +-
packages/vue/src/hooks/useSyncHeight.ts | 87 +++++++++++--------
12 files changed, 162 insertions(+), 118 deletions(-)
diff --git a/packages/react/src/components/DiffSplitContentLineNormal.tsx b/packages/react/src/components/DiffSplitContentLineNormal.tsx
index 9e93008..9ef8e72 100644
--- a/packages/react/src/components/DiffSplitContentLineNormal.tsx
+++ b/packages/react/src/components/DiffSplitContentLineNormal.tsx
@@ -44,7 +44,7 @@ const InternalDiffSplitLine = ({
const hasDiff = !!currentLine?.diff;
- const hasContent = !!currentLine.lineNumber;
+ const hasContent = !!currentLine?.lineNumber;
const hasChange = checkDiffLineIncludeChange(currentLine?.diff);
@@ -109,8 +109,8 @@ const InternalDiffSplitLine = ({
oldLine()?.diff?.type === DiffLineType.Delete;
- const [newLineIsAdded, setNewLineIsAdded] = createSignal(newLine()?.diff?.type === DiffLineType.Add);
+ const newLineIsAdded = () => newLine()?.diff?.type === DiffLineType.Add;
createEffect(() => {
const init = () => {
@@ -59,8 +59,6 @@ export const DiffSplitContentLine = (props: { index: number; diffFile: DiffFile;
setHasDiff(() => !!oldLine()?.diff || !!newLine()?.diff);
setHasChange(() => checkDiffLineIncludeChange(oldLine()?.diff) || checkDiffLineIncludeChange(newLine()?.diff));
setHasHidden(() => oldLine()?.isHidden && newLine()?.isHidden);
- setOldLineIsDelete(() => oldLine()?.diff?.type === DiffLineType.Delete);
- setNewLineIsAdded(() => newLine()?.diff?.type === DiffLineType.Add);
};
init();
diff --git a/packages/solid/src/components/DiffSplitExtendLineNormal.tsx b/packages/solid/src/components/DiffSplitExtendLineNormal.tsx
index ec9187a..b4cc16a 100644
--- a/packages/solid/src/components/DiffSplitExtendLineNormal.tsx
+++ b/packages/solid/src/components/DiffSplitExtendLineNormal.tsx
@@ -14,8 +14,6 @@ export const DiffSplitExtendLine = (props: {
const renderExtend = useRenderExtend();
- const currentSide = createMemo(() => SplitSide[props.side]);
-
const lineSelector = createMemo(() => `div[data-line="${props.lineNumber}-extend-content"]`);
const lineWrapperSelector = createMemo(() => `tr[data-line="${props.lineNumber}-extend"]`);
@@ -34,7 +32,7 @@ export const DiffSplitExtendLine = (props: {
const [newLineExtend, setNewLineExtend] = createSignal(extendData()?.newFile?.[newLine()?.lineNumber || ""]);
- const currentItem = createMemo(() => (props.side === SplitSide.old ? oldLine() : newLine()));
+ const [currentItem, setCurrentItem] = createSignal(props.side === SplitSide.old ? oldLine() : newLine());
const [currentIsHidden, setCurrentIsHidden] = createSignal(currentItem()?.isHidden);
@@ -45,6 +43,7 @@ export const DiffSplitExtendLine = (props: {
setEnableExpand(props.diffFile.getExpandEnabled());
setOldLineExtend(() => extendData()?.oldFile?.[oldLine()?.lineNumber || ""]);
setNewLineExtend(() => extendData()?.newFile?.[newLine()?.lineNumber || ""]);
+ setCurrentItem(() => (props.side === SplitSide.old ? oldLine() : newLine()));
setCurrentIsHidden(() => currentItem()?.isHidden);
};
@@ -69,10 +68,12 @@ export const DiffSplitExtendLine = (props: {
() => (props.side === SplitSide.old ? !!oldLineExtend() : !!newLineExtend()) && currentIsShow()
);
+ const extendSide = createMemo(() => SplitSide[currentExtend() ? props.side : props.side === SplitSide.new ? SplitSide.old : SplitSide.new]);
+
useSyncHeight({
selector: lineSelector,
wrapper: lineWrapperSelector,
- side: currentSide,
+ side: extendSide,
enable: currentIsShow,
});
diff --git a/packages/solid/src/components/DiffSplitWidgetLineNormal.tsx b/packages/solid/src/components/DiffSplitWidgetLineNormal.tsx
index bdfb3e8..6b36796 100644
--- a/packages/solid/src/components/DiffSplitWidgetLineNormal.tsx
+++ b/packages/solid/src/components/DiffSplitWidgetLineNormal.tsx
@@ -30,7 +30,7 @@ export const DiffSplitWidgetLine = (props: {
!!newLine()?.lineNumber && widget?.()?.side === SplitSide.new && widget?.()?.lineNumber === newLine()?.lineNumber
);
- const currentLine = createMemo(() => (props.side === SplitSide.old ? oldLine() : newLine()));
+ const [currentLine, setCUrrentLine] = createSignal(props.side === SplitSide.old ? oldLine() : newLine());
const [currentIsHidden, setCurrentIsHidden] = createSignal(currentLine()?.isHidden);
@@ -38,6 +38,7 @@ export const DiffSplitWidgetLine = (props: {
const init = () => {
setOldLine(props.diffFile.getSplitLeftLine(props.index));
setNewLine(props.diffFile.getSplitRightLine(props.index));
+ setCUrrentLine(() => (props.side === SplitSide.old ? oldLine() : newLine()));
setCurrentIsHidden(() => currentLine()?.isHidden);
};
@@ -56,15 +57,17 @@ export const DiffSplitWidgetLine = (props: {
props.side === SplitSide.old ? ".old-diff-table-wrapper" : ".new-diff-table-wrapper"
);
- const observeSide = createMemo(() => SplitSide[props.side]);
+ const currentWidget = createMemo(() => (props.side === SplitSide.old ? oldLineWidget() : newLineWidget()));
+
+ const observeSide = createMemo(
+ () => SplitSide[currentWidget() ? props.side : props.side === SplitSide.old ? SplitSide.new : SplitSide.old]
+ );
const currentIsShow = createMemo(
() => (!!oldLineWidget() || !!newLineWidget()) && !currentIsHidden() && !newLine()?.isHidden && !!renderWidget
);
- const currentWidget = createMemo(
- () => (props.side === SplitSide.old ? oldLineWidget() : newLineWidget()) && !!currentIsShow()
- );
+ const currentEnable = createMemo(() => currentWidget() && !!currentIsShow());
const onCloseWidget = () => setWidget?.({});
@@ -77,7 +80,7 @@ export const DiffSplitWidgetLine = (props: {
const width = useDomWidth({
selector: wrapperSelector,
- enable: currentWidget,
+ enable: currentEnable,
});
return (
diff --git a/packages/solid/src/hooks/useDomWidth.ts b/packages/solid/src/hooks/useDomWidth.ts
index 38cd518..c7945ff 100644
--- a/packages/solid/src/hooks/useDomWidth.ts
+++ b/packages/solid/src/hooks/useDomWidth.ts
@@ -3,7 +3,7 @@ import { createEffect, createSignal, onCleanup, type Accessor } from "solid-js";
import { useId } from "./useId";
import { useIsMounted } from "./useIsMounted";
-type ObserveElement = HTMLElement & {
+export type ObserveElement = HTMLElement & {
__observeCallback?: Set<() => void>;
__observeInstance?: ResizeObserver;
};
diff --git a/packages/solid/src/hooks/useSyncHeight.ts b/packages/solid/src/hooks/useSyncHeight.ts
index 5ee23c0..38162f0 100644
--- a/packages/solid/src/hooks/useSyncHeight.ts
+++ b/packages/solid/src/hooks/useSyncHeight.ts
@@ -3,6 +3,8 @@ import { createEffect, onCleanup, type Accessor } from "solid-js";
import { useId } from "./useId";
import { useIsMounted } from "./useIsMounted";
+import type { ObserveElement } from "./useDomWidth";
+
export const useSyncHeight = ({
selector,
wrapper,
@@ -24,53 +26,70 @@ export const useSyncHeight = ({
if (enable()) {
let clean = () => {};
- const timer = setTimeout(() => {
- const container = document.querySelector(`#diff-root${id()}`);
+ const container = document.querySelector(`#diff-root${id()}`);
- const elements = Array.from(container?.querySelectorAll(selector()) || []);
+ const elements = Array.from(container?.querySelectorAll(selector()) || []);
- const wrappers = Array.from(container?.querySelectorAll(wrapper()) || []);
+ const wrappers = wrapper() ? Array.from(container?.querySelectorAll(wrapper()) || []) : elements;
- if (elements.length === 2 && wrappers.length === 2) {
- const ele1 = elements[0] as HTMLElement;
- const ele2 = elements[1] as HTMLElement;
+ if (elements.length === 2 && wrappers.length === 2) {
+ const ele1 = elements[0] as HTMLElement;
+ const ele2 = elements[1] as HTMLElement;
- const wrapper1 = wrappers[0] as HTMLElement;
- const wrapper2 = wrappers[1] as HTMLElement;
+ const wrapper1 = wrappers[0] as HTMLElement;
+ const wrapper2 = wrappers[1] as HTMLElement;
- const target = ele1.getAttribute("data-side") === side() ? ele1 : ele2;
+ const target = ele1.getAttribute("data-side") === side() ? ele1 : ele2;
- const cb = () => {
- ele1.style.height = "auto";
- ele2.style.height = "auto";
- const rect1 = ele1.getBoundingClientRect();
- const rect2 = ele2.getBoundingClientRect();
- const maxHeight = Math.max(rect1.height, rect2.height);
- wrapper1.style.height = maxHeight + "px";
- wrapper2.style.height = maxHeight + "px";
- wrapper1.setAttribute("data-sync-height", String(maxHeight));
- wrapper2.setAttribute("data-sync-height", String(maxHeight));
- };
+ const typedTarget = target as ObserveElement;
- cb();
+ const cb = () => {
+ ele1.style.height = "auto";
+ ele2.style.height = "auto";
+ const rect1 = ele1.getBoundingClientRect();
+ const rect2 = ele2.getBoundingClientRect();
+ const maxHeight = Math.max(rect1.height, rect2.height);
+ wrapper1.style.height = maxHeight + "px";
+ wrapper2.style.height = maxHeight + "px";
+ wrapper1.setAttribute("data-sync-height", String(maxHeight));
+ wrapper2.setAttribute("data-sync-height", String(maxHeight));
+ };
- const observer = new ResizeObserver(cb);
+ cb();
- observer.observe(target);
+ const cleanCb = () => {
+ typedTarget.__observeCallback?.delete(cb);
+ if (typedTarget.__observeCallback?.size === 0) {
+ typedTarget.__observeInstance?.disconnect();
+ target.removeAttribute("data-observe");
+ delete typedTarget.__observeCallback;
+ delete typedTarget.__observeInstance;
+ }
+ };
- target.setAttribute("data-observe", "height");
+ if (typedTarget.__observeCallback) {
+ typedTarget.__observeCallback.add(cb);
- clean = () => {
- observer.disconnect();
- target?.removeAttribute("data-observe");
- };
+ clean = cleanCb;
+ return;
}
- });
- onCleanup(() => {
- clean();
- clearTimeout(timer);
- });
+ typedTarget.__observeCallback = new Set();
+
+ typedTarget.__observeCallback.add(cb);
+
+ const observer = new ResizeObserver(() => typedTarget.__observeCallback?.forEach((cb) => cb()));
+
+ typedTarget.__observeInstance = observer;
+
+ observer.observe(target);
+
+ target.setAttribute("data-observe", "height");
+
+ clean = cleanCb;
+ }
+
+ onCleanup(() => clean());
}
};
diff --git a/packages/vue/src/components/DiffSplitContentLineNormal.tsx b/packages/vue/src/components/DiffSplitContentLineNormal.tsx
index 20e154a..0e21e57 100644
--- a/packages/vue/src/components/DiffSplitContentLineNormal.tsx
+++ b/packages/vue/src/components/DiffSplitContentLineNormal.tsx
@@ -38,7 +38,7 @@ export const DiffSplitContentLine = defineComponent(
const currentLineHasHidden = ref(currentLine.value?.isHidden);
- const currentLineHasContent = ref(currentLine.value.lineNumber);
+ const currentLineHasContent = ref(currentLine.value?.lineNumber);
const currentSyntaxLine = ref(
props.side === SplitSide.old
@@ -49,7 +49,7 @@ export const DiffSplitContentLine = defineComponent(
const currentPlainLine = ref(
props.side === SplitSide.old
? props.diffFile.getOldPlainLine(currentLine.value?.lineNumber)
- : props.diffFile.getNewPlainLine(currentLine.value.lineNumber)
+ : props.diffFile.getNewPlainLine(currentLine.value?.lineNumber)
);
useSubscribeDiffFile(props, (diffFile) => {
@@ -72,7 +72,7 @@ export const DiffSplitContentLine = defineComponent(
currentLineHasHidden.value = currentLine.value?.isHidden;
- currentLineHasContent.value = currentLine.value.lineNumber;
+ currentLineHasContent.value = currentLine.value?.lineNumber;
});
const onOpenAddWidget = (lineNumber: number, side: SplitSide) => setWidget({ side: side, lineNumber: lineNumber });
@@ -132,8 +132,8 @@ export const DiffSplitContentLine = defineComponent(
{
oldLine.value = diffFile.getSplitLeftLine(props.index);
@@ -69,10 +65,6 @@ export const DiffSplitContentLine = defineComponent(
checkDiffLineIncludeChange(oldLine.value?.diff) || checkDiffLineIncludeChange(newLine.value?.diff);
hasHidden.value = oldLine.value?.isHidden && newLine.value?.isHidden;
-
- oldLineIsDelete.value = oldLine.value?.diff?.type === DiffLineType.Delete;
-
- newLineIsAdded.value = newLine.value?.diff?.type === DiffLineType.Add;
});
const onOpenAddWidget = (lineNumber: number, side: SplitSide) => setWidget({ side: side, lineNumber: lineNumber });
@@ -84,13 +76,17 @@ export const DiffSplitContentLine = defineComponent(
const hasNewLine = !!newLine.value?.lineNumber;
- const oldLineContentBG = getContentBG(false, oldLineIsDelete.value, hasDiff.value);
+ const oldLineIsDelete = oldLine.value?.diff?.type === DiffLineType.Delete;
+
+ const newLineIsAdded = newLine.value?.diff?.type === DiffLineType.Add;
+
+ const oldLineContentBG = getContentBG(false, oldLineIsDelete, hasDiff.value);
- const oldLineNumberBG = getLineNumberBG(false, oldLineIsDelete.value, hasDiff.value);
+ const oldLineNumberBG = getLineNumberBG(false, oldLineIsDelete, hasDiff.value);
- const newLineContentBG = getContentBG(newLineIsAdded.value, false, hasDiff.value);
+ const newLineContentBG = getContentBG(newLineIsAdded, false, hasDiff.value);
- const newLineNumberBG = getLineNumberBG(newLineIsAdded.value, false, hasDiff.value);
+ const newLineNumberBG = getLineNumberBG(newLineIsAdded, false, hasDiff.value);
return (
@@ -137,8 +133,8 @@ export const DiffSplitContentLine = defineComponent(
SplitSide[props.side]);
-
const lineSelector = computed(() => `div[data-line="${props.lineNumber}-extend-content"]`);
const lineWrapperSelector = computed(() => `tr[data-line="${props.lineNumber}-extend"]`);
@@ -36,7 +34,7 @@ export const DiffSplitExtendLine = defineComponent(
const newLineExtend = ref(extendData.value?.newFile?.[newLine.value.lineNumber]);
- const currentItem = computed(() => (props.side === SplitSide.old ? oldLine.value : newLine.value));
+ const currentItem = ref(props.side === SplitSide.old ? oldLine.value : newLine.value);
const currentIsHidden = ref(currentItem.value.isHidden);
@@ -51,6 +49,8 @@ export const DiffSplitExtendLine = defineComponent(
enableExpand.value = diffFile.getExpandEnabled();
+ currentItem.value = props.side === SplitSide.old ? oldLine.value : newLine.value;
+
currentIsHidden.value = currentItem.value.isHidden;
});
@@ -70,10 +70,14 @@ export const DiffSplitExtendLine = defineComponent(
() => (props.side === SplitSide.old ? !!oldLineExtend.value : !!newLineExtend.value) && currentIsShow.value
);
+ const extendSide = computed(
+ () => SplitSide[currentExtend.value ? props.side : props.side === SplitSide.new ? SplitSide.old : SplitSide.new]
+ );
+
useSyncHeight({
selector: lineSelector,
wrapper: lineWrapperSelector,
- side: currentSide,
+ side: extendSide,
enable: currentIsShow,
});
diff --git a/packages/vue/src/components/DiffSplitWidgetLineNormal.tsx b/packages/vue/src/components/DiffSplitWidgetLineNormal.tsx
index f67ec90..a87984d 100644
--- a/packages/vue/src/components/DiffSplitWidgetLineNormal.tsx
+++ b/packages/vue/src/components/DiffSplitWidgetLineNormal.tsx
@@ -36,7 +36,7 @@ export const DiffSplitWidgetLine = defineComponent(
widget.value.lineNumber === newLine.value.lineNumber
);
- const currentLine = computed(() => (props.side === SplitSide.old ? oldLine.value : newLine.value));
+ const currentLine = ref(props.side === SplitSide.old ? oldLine.value : newLine.value);
const currentIsHidden = ref(currentLine.value.isHidden);
@@ -45,6 +45,8 @@ export const DiffSplitWidgetLine = defineComponent(
newLine.value = diffFile.getSplitRightLine(props.index);
+ currentLine.value = props.side === SplitSide.old ? oldLine.value : newLine.value;
+
currentIsHidden.value = currentLine.value.isHidden;
});
@@ -56,15 +58,17 @@ export const DiffSplitWidgetLine = defineComponent(
props.side === SplitSide.old ? ".old-diff-table-wrapper" : ".new-diff-table-wrapper"
);
- const observeSide = computed(() => SplitSide[props.side]);
+ const currentWidget = computed(() => (props.side === SplitSide.old ? oldLineWidget.value : newLineWidget.value));
+
+ const observeSide = computed(
+ () => SplitSide[currentWidget ? props.side : props.side === SplitSide.old ? SplitSide.new : SplitSide.old]
+ );
const currentIsShow = computed(
() => (!!oldLineWidget.value || !!newLineWidget.value) && !currentIsHidden.value && !!slots.widget
);
- const currentWidget = computed(
- () => (props.side === SplitSide.old ? oldLineWidget.value : newLineWidget.value) && !!currentIsShow.value
- );
+ const currentEnable = computed(() => currentWidget.value && !!currentIsShow.value);
const onCloseWidget = () => setWidget({});
@@ -77,7 +81,7 @@ export const DiffSplitWidgetLine = defineComponent(
const width = useDomWidth({
selector: wrapperSelector,
- enable: currentWidget,
+ enable: currentEnable,
});
return () => {
diff --git a/packages/vue/src/hooks/useDomWidth.ts b/packages/vue/src/hooks/useDomWidth.ts
index 17d3768..b1c0f33 100644
--- a/packages/vue/src/hooks/useDomWidth.ts
+++ b/packages/vue/src/hooks/useDomWidth.ts
@@ -4,7 +4,7 @@ import { useId, useIsMounted } from "../context";
import type { Ref } from "vue";
-type ObserveElement = HTMLElement & {
+export type ObserveElement = HTMLElement & {
__observeCallback: Set<() => void>;
__observeInstance: ResizeObserver;
};
diff --git a/packages/vue/src/hooks/useSyncHeight.ts b/packages/vue/src/hooks/useSyncHeight.ts
index 0bc3cc2..e20c98a 100644
--- a/packages/vue/src/hooks/useSyncHeight.ts
+++ b/packages/vue/src/hooks/useSyncHeight.ts
@@ -2,6 +2,8 @@ import { type Ref, watchPostEffect } from "vue";
import { useId, useIsMounted } from "../context";
+import type { ObserveElement } from "./useDomWidth";
+
// TODO
export const useSyncHeight = ({
selector,
@@ -24,53 +26,70 @@ export const useSyncHeight = ({
if (enable.value) {
let clean = () => {};
- const timer = setTimeout(() => {
- const container = document.querySelector(`#diff-root${id.value}`);
+ const container = document.querySelector(`#diff-root${id.value}`);
- const elements = Array.from(container?.querySelectorAll(selector.value) || []);
+ const elements = Array.from(container?.querySelectorAll(selector.value) || []);
- const wrappers = Array.from(container?.querySelectorAll(wrapper?.value) || []);
+ const wrappers = wrapper.value ? Array.from(container?.querySelectorAll(wrapper?.value) || []) : elements;
- if (elements.length === 2 && wrappers.length === 2) {
- const ele1 = elements[0] as HTMLElement;
- const ele2 = elements[1] as HTMLElement;
+ if (elements.length === 2 && wrappers.length === 2) {
+ const ele1 = elements[0] as HTMLElement;
+ const ele2 = elements[1] as HTMLElement;
- const wrapper1 = wrappers[0] as HTMLElement;
- const wrapper2 = wrappers[1] as HTMLElement;
+ const wrapper1 = wrappers[0] as HTMLElement;
+ const wrapper2 = wrappers[1] as HTMLElement;
- const target = ele1.getAttribute("data-side") === side.value ? ele1 : ele2;
+ const target = ele1.getAttribute("data-side") === side.value ? ele1 : ele2;
- const cb = () => {
- ele1.style.height = "auto";
- ele2.style.height = "auto";
- const rect1 = ele1.getBoundingClientRect();
- const rect2 = ele2.getBoundingClientRect();
- const maxHeight = Math.max(rect1.height, rect2.height);
- wrapper1.style.height = maxHeight + "px";
- wrapper2.style.height = maxHeight + "px";
- wrapper1.setAttribute("data-sync-height", String(maxHeight));
- wrapper2.setAttribute("data-sync-height", String(maxHeight));
- };
+ const typedTarget = target as ObserveElement;
- cb();
+ const cb = () => {
+ ele1.style.height = "auto";
+ ele2.style.height = "auto";
+ const rect1 = ele1.getBoundingClientRect();
+ const rect2 = ele2.getBoundingClientRect();
+ const maxHeight = Math.max(rect1.height, rect2.height);
+ wrapper1.style.height = maxHeight + "px";
+ wrapper2.style.height = maxHeight + "px";
+ wrapper1.setAttribute("data-sync-height", String(maxHeight));
+ wrapper2.setAttribute("data-sync-height", String(maxHeight));
+ };
- const observer = new ResizeObserver(cb);
+ cb();
- observer.observe(target);
+ const cleanCb = () => {
+ typedTarget.__observeCallback.delete(cb);
+ if (typedTarget.__observeCallback.size === 0) {
+ typedTarget.__observeInstance?.disconnect();
+ target.removeAttribute("data-observe");
+ delete typedTarget.__observeCallback;
+ delete typedTarget.__observeInstance;
+ }
+ };
- target.setAttribute("data-observe", "height");
+ if (typedTarget.__observeCallback) {
+ typedTarget.__observeCallback.add(cb);
- clean = () => {
- observer.disconnect();
- target?.removeAttribute("data-observe");
- };
+ clean = cleanCb;
+ return;
}
- });
- onCancel(() => {
- clean();
- clearTimeout(timer);
- });
+ typedTarget.__observeCallback = new Set();
+
+ typedTarget.__observeCallback.add(cb);
+
+ const observer = new ResizeObserver(() => typedTarget.__observeCallback.forEach((cb) => cb()));
+
+ typedTarget.__observeInstance = observer;
+
+ observer.observe(target);
+
+ target.setAttribute("data-observe", "height");
+
+ clean = cleanCb;
+ }
+
+ onCancel(() => clean());
}
};
|