diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1fd5869..d72e069 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,5 +5,5 @@ module.exports = { }, // eslint will auto add `eslint-config` for a no scope package(which not start with '@' chart), so here use absolute file path extends: [require.resolve("project-tool/baseLint")], - ignorePatterns: ["dist", "dev", "scripts", "node_modules", "next-app-example", "next-page-example"], + ignorePatterns: ["dist", "dev", "scripts", "node_modules", "next-app-example", "next-page-example", "packages/solid"], }; diff --git a/package.json b/package.json index f18e8ab..5a7e8c7 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dev:vue-ssr": "cd ui/vue-ssr-example && pnpm run dev", "dev:next-page": "cd ui/next-page-example && pnpm run dev", "dev:next-app": "cd ui/next-app-example && pnpm run dev", + "dev:solid": "cd ui/solid-example && pnpm run dev", "build:react": "cd ui/react-example && pnpm run build", "pre:release": "pnpm run lint && pnpm run prettier && pnpm run clean && pnpm run build:packages", "release": "pnpm run pre:release && pnpm run start:release", diff --git a/packages/angular/package.json b/packages/angular/package.json new file mode 100644 index 0000000..4f03a06 --- /dev/null +++ b/packages/angular/package.json @@ -0,0 +1,3 @@ +{ + "name": "@git-diff-view/angular" +} \ No newline at end of file diff --git a/packages/core/src/diff-file.ts b/packages/core/src/diff-file.ts index 5e80249..bc46f64 100644 --- a/packages/core/src/diff-file.ts +++ b/packages/core/src/diff-file.ts @@ -1411,6 +1411,7 @@ export class DiffFile { return this.#newFileSyntaxLines?.[lineNumber]; }; + // TODO improve subscribe = (listener: (() => void) & { isSyncExternal?: boolean }) => { this.#listeners.push(listener); @@ -1578,7 +1579,7 @@ export class DiffFile { } }; - _getHighlighterName = () => this.#highlighterName; + _getHighlighterName = () => this.#highlighterName || ''; _getIsPureDiffRender = () => this.#composeByDiff; diff --git a/packages/dom/package.json b/packages/dom/package.json new file mode 100644 index 0000000..a6dc1c7 --- /dev/null +++ b/packages/dom/package.json @@ -0,0 +1,3 @@ +{ + "name": "@git-diff-view/dom" +} \ No newline at end of file diff --git a/packages/react/src/components/DiffView.tsx b/packages/react/src/components/DiffView.tsx index 270f729..25a23be 100644 --- a/packages/react/src/components/DiffView.tsx +++ b/packages/react/src/components/DiffView.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { DiffFile, _cacheMap, SplitSide } from "@git-diff-view/core"; -import { diffFontSizeName } from "@git-diff-view/utils"; +import { diffFontSizeName, DiffModeEnum } from "@git-diff-view/utils"; import { memo, useEffect, useMemo, forwardRef, useImperativeHandle, useRef } from "react"; import * as React from "react"; @@ -20,16 +20,7 @@ import type { CSSProperties, ForwardedRef, ReactNode } from "react"; _cacheMap.name = "@git-diff-view/react"; -export { SplitSide }; - -export enum DiffModeEnum { - // github like - SplitGitHub = 1, - // gitlab like - SplitGitLab = 2, - Split = 1 | 2, - Unified = 4, -} +export { SplitSide, DiffModeEnum }; export type DiffViewProps = { data?: { diff --git a/packages/solid/.eslintrc.cjs b/packages/solid/.eslintrc.cjs new file mode 100644 index 0000000..e256508 --- /dev/null +++ b/packages/solid/.eslintrc.cjs @@ -0,0 +1,5 @@ +module.exports = { + plugins: ["solid"], + extends: ["../../.eslintrc.cjs", "plugin:solid/typescript"], + ignorePatterns: ["dist", "dev", "node_modules"], +}; diff --git a/packages/solid/package.json b/packages/solid/package.json new file mode 100644 index 0000000..6a20886 --- /dev/null +++ b/packages/solid/package.json @@ -0,0 +1,62 @@ +{ + "name": "@git-diff-view/solid", + "description": "@git-diff-view/solid", + "author": "MrWangJustToDo", + "license": "MIT", + "version": "0.0.25", + "main": "index.js", + "type": "module", + "types": "index.d.ts", + "files": [ + "dist", + "index.cjs", + "index.d.ts" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/MrWangJustToDo/git-diff-view.git", + "directory": "packages/solid" + }, + "scripts": { + "build": "vite build", + "gen:type": "dts-bundle-generator -o index.d.ts dist/index.d.ts", + "gen:css1": "postcss src/tailwind.css -o dist/css/diff-view.css", + "gen:css2": "postcss src/tailwind_pure.css -o dist/css/diff-view-pure.css", + "gen:css": "npm run gen:css1 && npm run gen:css2" + }, + "homepage": "https://mrwangjusttodo.github.io/git-diff-view", + "exports": { + ".": { + "require": "./index.cjs", + "types": "./index.d.ts", + "import": "./dist/vue-git-diff-view.mjs" + }, + "./styles/*": "./dist/css/*", + "./package.json": "./package.json" + }, + "keywords": [ + "diff component", + "solid diff component" + ], + "dependencies": { + "@git-diff-view/core": "^0.0.29", + "@types/hast": "^3.0.0", + "highlight.js": "^11.11.0", + "lowlight": "^3.3.0", + "fast-diff": "^1.3.0", + "reactivity-store": "^0.3.11" + }, + "devDependencies": { + "solid-js": "^1.9.0", + "vite-plugin-solid": "^2.11.0", + "autoprefixer": "^10.4.20", + "eslint-plugin-solid": "^0.14.5", + "postcss": "^8.5.1", + "tailwindcss": "^3.4.17", + "vite": "^5.4.19", + "vite-plugin-dts": "^4.5.0" + }, + "peerDependencies": { + "solid-js": "^1.9.0" + } +} \ No newline at end of file diff --git a/packages/solid/postcss.config.js b/packages/solid/postcss.config.js new file mode 100644 index 0000000..c917e1b --- /dev/null +++ b/packages/solid/postcss.config.js @@ -0,0 +1,32 @@ +export default { + plugins: { + tailwindcss: { config: "./tailwind.config.js" }, + "postcss-prefix-selector": { + prefix: ".diff-tailwindcss-wrapper", + transform: function (prefix, selector, prefixedSelector, _filePath, rule) { + const filePath = rule.source?.input?.file; + // ignore base css + // TODO next release update + if (rule.source?.start?.line === 1 && rule.source?.start?.column === 1) { + return selector; + } + if (selector.includes("diff-line-extend-wrapper") || selector.includes("diff-line-widget-wrapper")) { + return selector; + } + if (selector.includes("[data-theme")) { + return prefix + selector; + } + if (filePath.includes("node_modules")) { + if (filePath.includes("dark.css")) { + return `${prefix}[data-theme="dark"] .diff-line-syntax-raw ${selector}`; + } else { + return `${prefix}[data-theme="light"] .diff-line-syntax-raw ${selector}`; + } + } else { + return prefixedSelector; + } + }, + }, + autoprefixer: {}, + }, +}; diff --git a/packages/solid/src/_base.css b/packages/solid/src/_base.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/packages/solid/src/_base.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/solid/src/_base_pure.css b/packages/solid/src/_base_pure.css new file mode 100644 index 0000000..7c05d4d --- /dev/null +++ b/packages/solid/src/_base_pure.css @@ -0,0 +1,2 @@ +@tailwind components; +@tailwind utilities; diff --git a/packages/solid/src/_com.css b/packages/solid/src/_com.css new file mode 100644 index 0000000..aaed963 --- /dev/null +++ b/packages/solid/src/_com.css @@ -0,0 +1,172 @@ +* { + box-sizing: border-box; +} + +[data-theme="light"] .diff-style-root { + --diff-border--: #dedede; + --diff-add-content--: #e6ffec; + --diff-del-content--: #ffebe9; + --diff-add-lineNumber--: #ccffd8; + --diff-del-lineNumber--: #ffd7d5; + --diff-plain-content--: #ffffff; + --diff-expand-content--: #fafafa; + --diff-plain-lineNumber--: #fafafa; + --diff-expand-lineNumber--: #fafafa; + --diff-plain-lineNumber-color--: #555555; + --diff-expand-lineNumber-color--: #555555; + --diff-hunk-content--: #ddf4ff; + --diff-hunk-lineNumber--: #c7ecff; + --diff-hunk-lineNumber-hover--: #0969da; + --diff-add-content-highlight--: #abf2bc; + --diff-del-content-highlight--: #ffb3ad; + --diff-add-widget--: #0969d2; + --diff-add-widget-color--: #ffffff; + --diff-empty-content--: #fafafa; + --diff-hunk-content-color--: #777777; + + color: black; +} + +.diff-style-root .diff-line-syntax-raw *, +[data-theme="light"] .diff-line-syntax-raw * { + color: var(--diff-view-light, inherit); + font-weight: var(--diff-view-light-font-weight, inherit); +} + +[data-theme="dark"] .diff-style-root { + --diff-border--: #3d444d; + --diff-add-content--: #14261f; + --diff-del-content--: #311b1f; + --diff-add-lineNumber--: #1f4429; + --diff-del-lineNumber--: #552527; + --diff-plain-content--: #0d1117; + --diff-expand-content--: #161b22; + --diff-plain-lineNumber--: #161b22; + --diff-expand-lineNumber--: #161b22; + --diff-plain-lineNumber-color--: #a0aaab; + --diff-expand-lineNumber-color--: #a0aaab; + --diff-hunk-content--: #131d2e; + --diff-hunk-lineNumber--: #204274; + --diff-hunk-lineNumber-hover--: #1f6feb; + --diff-add-content-highlight--: #1f572d; + --diff-del-content-highlight--: #80312f; + --diff-add-widget--: #0969d2; + --diff-add-widget-color--: #ffffff; + --diff-empty-content--: #161b22; + --diff-hunk-content-color--: #9298a0; + + color: white; +} + +[data-theme="dark"] .diff-line-syntax-raw * { + color: var(--diff-view-dark, inherit); + font-weight: var(--diff-view-dark-font-weight, inherit); +} + +table, +tr, +td { + border-color: transparent; + border-width: 0px; + text-align: left; +} + +.diff-line-old-num, +.diff-line-new-num, +.diff-line-num { + text-align: right; +} + +.diff-style-root tr { + content-visibility: auto; +} + +.diff-add-widget-wrapper { + transform-origin: center; + transform: translateX(-50%) !important; +} + +.diff-line-old-content .diff-add-widget-wrapper, +.diff-line-new-content .diff-add-widget-wrapper { + transform: translateX(50%) !important; +} + +.diff-add-widget-wrapper:hover { + transform: translateX(-50%) scale(1.1) !important; +} + +.diff-line-old-content .diff-add-widget-wrapper:hover, +.diff-line-new-content .diff-add-widget-wrapper:hover { + transform: translateX(50%) scale(1.1) !important; +} + +.diff-widget-tooltip { + position: relative; +} + +.diff-add-widget, +.diff-widget-tooltip { + font-family: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + font-size: 100%; + font-weight: inherit; + line-height: inherit; + letter-spacing: inherit; + color: inherit; + margin: 0; + text-transform: none; + border-width: 0px; + background-color: transparent; + background-image: none; +} + +.diff-widget-tooltip::after { + display: none; + box-sizing: border-box; + background-color: #555555; + position: absolute; + content: attr(data-title); + font-size: 11px; + padding: 1px 2px; + border-radius: 4px; + overflow: hidden; + top: 50%; + white-space: nowrap; + transform: translateY(-50%); + left: calc(100% + 8px); + color: #ffffff; +} + +.diff-widget-tooltip::before { + display: none; + box-sizing: border-box; + content: ""; + position: absolute; + top: 50%; + left: calc(100% - 2px); + transform: translateY(-50%); + border: 6px solid transparent; + border-right-color: #555555; +} + +.diff-widget-tooltip:hover { + background-color: var(--diff-hunk-lineNumber-hover--); + color: white; +} + +.diff-widget-tooltip:hover::before { + display: block; +} + +.diff-widget-tooltip:hover::after { + display: block; +} + +.diff-line-extend-wrapper * { + color: initial; +} + +.diff-line-widget-wrapper * { + color: initial; +} diff --git a/packages/solid/src/_theme.css b/packages/solid/src/_theme.css new file mode 100644 index 0000000..3302cba --- /dev/null +++ b/packages/solid/src/_theme.css @@ -0,0 +1,2 @@ +@import url(highlight.js/styles/github.css); +@import url(highlight.js/styles/github-dark.css); diff --git a/packages/solid/src/components/DiffAddWidget.tsx b/packages/solid/src/components/DiffAddWidget.tsx new file mode 100644 index 0000000..c1f5f86 --- /dev/null +++ b/packages/solid/src/components/DiffAddWidget.tsx @@ -0,0 +1,73 @@ +import { diffFontSizeName, addWidgetColorName, addWidgetBGName } from "@git-diff-view/utils"; + +import type { DiffFile, SplitSide } from "@git-diff-view/core"; + +export const DiffSplitAddWidget = (props: { + index: number; + class?: string; + lineNumber: number; + diffFile: DiffFile; + side: SplitSide; + onOpenAddWidget: (lineNumber: number, side: SplitSide) => void; + onWidgetClick?: (event: "onAddWidgetClick", lineNumber: number, side: SplitSide) => void; +}) => { + return ( +
+ +
+ ); +}; + +export const DiffUnifiedAddWidget = (props: { + index: number; + diffFile: DiffFile; + lineNumber: number; + side: SplitSide; + onOpenAddWidget: (lineNumber: number, side: SplitSide) => void; + onWidgetClick?: (event: "onAddWidgetClick", lineNumber: number, side: SplitSide) => void; +}) => { + return ( + + ); +}; diff --git a/packages/solid/src/components/DiffContent.tsx b/packages/solid/src/components/DiffContent.tsx new file mode 100644 index 0000000..c2d6a90 --- /dev/null +++ b/packages/solid/src/components/DiffContent.tsx @@ -0,0 +1,380 @@ +import { + DiffLineType, + getSyntaxDiffTemplate, + type File, + type DiffFile, + type DiffLine, + getSyntaxLineTemplate, + getPlainDiffTemplate, + getPlainLineTemplate, +} from "@git-diff-view/core"; +import { + addContentHighlightBGName, + delContentHighlightBGName, + diffFontSizeName, + getSymbol, + NewLineSymbol, +} from "@git-diff-view/utils"; +import { createMemo, For, Show } from "solid-js"; + +import { DiffNoNewLine } from "./DiffNoNewLine"; + +const DiffString = (props: { + rawLine: string; + diffLine?: DiffLine; + operator?: "add" | "del"; + plainLine?: File["plainFile"][number]; + enableWrap?: boolean; + enableTemplate?: boolean; +}) => { + const getRange = () => props.diffLine?.changes?.range; + + const getStr1 = () => props.rawLine.slice(0, getRange?.()?.location); + + const getStr2 = () => + props.rawLine.slice(getRange?.()?.location, (getRange?.()?.location || 0) + (getRange?.()?.length || 0)); + + const getStr3 = () => props.rawLine.slice((getRange?.()?.location || 0) + (getRange?.()?.length || 0)); + + const getIsLast = () => getStr2().includes("\n"); + + const get_Str2 = () => (getIsLast() ? getStr2().replace("\n", "").replace("\r", "") : getStr2()); + + const getIsNewLineSymbolChanged = () => (getStr3() === "" ? props.diffLine?.changes?.newLineSymbol : null); + + const initTemplateIfNeed = () => { + if ( + props.enableTemplate && + props.operator && + props.diffLine && + !props.diffLine?.plainTemplate && + typeof getPlainDiffTemplate === "function" + ) { + getPlainDiffTemplate({ diffLine: props.diffLine, rawLine: props.rawLine, operator: props.operator }); + } + + if (props.enableTemplate && props.plainLine && !props.plainLine.template) { + props.plainLine.template = getPlainLineTemplate(props.plainLine.value); + } + }; + + // eslint-disable-next-line solid/reactivity + createMemo(initTemplateIfNeed); + + return ( + {props.rawLine}} + > + + {/* eslint-disable-next-line solid/no-innerhtml */} + + + + } + > + + + {getStr1()} + + {getIsLast() ? ( + <> + {get_Str2()} + {getSymbol(getIsNewLineSymbolChanged())} + + ) : ( + getStr2() + )} + + {getStr3()} + + {getIsNewLineSymbolChanged() === NewLineSymbol.NEWLINE && ( + + + + )} + + } + > + + {/* eslint-disable-next-line solid/no-innerhtml */} + + {getIsNewLineSymbolChanged() === NewLineSymbol.NEWLINE && ( + + + + )} + + + + ); +}; + +const DiffSyntax = (props: { + rawLine: string; + diffLine?: DiffLine; + syntaxLine?: File["syntaxFile"][number]; + operator?: "add" | "del"; + enableWrap?: boolean; + enableTemplate?: boolean; +}) => { + const getRange = () => props.diffLine?.changes?.range; + + const getIsNewLineSymbolChanged = () => props.diffLine?.changes?.newLineSymbol; + + const initTemplateIfNeed = () => { + if ( + props.enableTemplate && + props.diffLine && + props.syntaxLine && + props.operator && + !props.diffLine?.syntaxTemplate && + typeof getSyntaxDiffTemplate === "function" + ) { + getSyntaxDiffTemplate({ diffLine: props.diffLine, syntaxLine: props.syntaxLine, operator: props.operator }); + } + + if (props.enableTemplate && props.syntaxLine && !props.syntaxLine?.template) { + props.syntaxLine.template = getSyntaxLineTemplate(props.syntaxLine); + } + }; + + // eslint-disable-next-line solid/reactivity + createMemo(initTemplateIfNeed); + + return ( + + } + > + + + {({ node, wrapper }) => ( + + {node.value} + + )} + + + } + > + + {/* eslint-disable-next-line solid/no-innerhtml */} + + + + } + > + + + + {({ node, wrapper }) => { + if ( + node.endIndex < getRange()!.location || + getRange()!.location + getRange()!.length < node.startIndex + ) { + return ( + + {node.value} + + ); + } else { + const index1 = getRange()!.location - node.startIndex; + const index2 = index1 < 0 ? 0 : index1; + const str1 = node.value.slice(0, index2); + const str2 = node.value.slice(index2, index1 + getRange()!.length); + const str3 = node.value.slice(index1 + getRange()!.length); + const isStart = str1.length || getRange()!.location === node.startIndex; + const isEnd = str3.length || node.endIndex === getRange()!.location + getRange()!.length - 1; + const isLast = str2.includes("\n"); + const _str2 = isLast ? str2.replace("\n", "").replace("\r", "") : str2; + return ( + + {str1} + + {isLast ? ( + <> + {_str2} + {getSymbol(getIsNewLineSymbolChanged())} + + ) : ( + str2 + )} + + {str3} + + ); + } + }} + + + {getIsNewLineSymbolChanged() === NewLineSymbol.NEWLINE && ( + + + + )} + + } + > + + {/* eslint-disable-next-line solid/no-innerhtml */} + + {getIsNewLineSymbolChanged() === NewLineSymbol.NEWLINE && ( + + + + )} + + + + + ); +}; + +export const DiffContent = (props: { + rawLine: string; + plainLine?: File["plainFile"][number]; + syntaxLine?: File["syntaxFile"][number]; + diffLine?: DiffLine; + diffFile: DiffFile; + enableWrap: boolean; + enableHighlight: boolean; +}) => { + const getIsAdded = () => props.diffLine?.type === DiffLineType.Add; + + const getIsDelete = () => props.diffLine?.type === DiffLineType.Delete; + + const getIsMaxLineLengthToIgnoreSyntax = () => + props.syntaxLine?.nodeList ? props.syntaxLine?.nodeList?.length > 150 : false; + + const getIsEnableTemplate = () => props.diffFile.getIsEnableTemplate?.() ?? true; + + return ( +
+ + {getIsAdded() ? "+" : getIsDelete() ? "-" : " "} + + {props.enableHighlight && props.syntaxLine && !getIsMaxLineLengthToIgnoreSyntax() ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/packages/solid/src/components/DiffExpand.tsx b/packages/solid/src/components/DiffExpand.tsx new file mode 100644 index 0000000..b2576d7 --- /dev/null +++ b/packages/solid/src/components/DiffExpand.tsx @@ -0,0 +1,23 @@ +export const ExpandDown = (props: { class: string }) => { + return ( + + ); +}; + +export const ExpandUp = (props: { class?: string }) => { + return ( + + ); +}; + +export const ExpandAll = (props: { class?: string }) => { + return ( + + ); +}; diff --git a/packages/solid/src/components/DiffNoNewLine.tsx b/packages/solid/src/components/DiffNoNewLine.tsx new file mode 100644 index 0000000..fdd9ce1 --- /dev/null +++ b/packages/solid/src/components/DiffNoNewLine.tsx @@ -0,0 +1,8 @@ +export const DiffNoNewLine = () => { + return ( + + + + + ); +}; diff --git a/packages/solid/src/components/DiffSplitContentLineWrap.tsx b/packages/solid/src/components/DiffSplitContentLineWrap.tsx new file mode 100644 index 0000000..74963c0 --- /dev/null +++ b/packages/solid/src/components/DiffSplitContentLineWrap.tsx @@ -0,0 +1,208 @@ +import { checkDiffLineIncludeChange, DiffLineType, SplitSide, type DiffFile } from "@git-diff-view/core"; +import { + borderColorName, + emptyBGName, + expandLineNumberColorName, + getContentBG, + getLineNumberBG, + plainLineNumberColorName, +} from "@git-diff-view/utils"; +import { createEffect, createSignal, onCleanup, Show } from "solid-js"; + +import { useEnableAddWidget, useEnableHighlight, useOnAddWidgetClick } from "../hooks"; + +import { DiffSplitAddWidget } from "./DiffAddWidget"; +import { DiffContent } from "./DiffContent"; +import { useDiffWidgetContext } from "./DiffWidgetContext"; + +export const DiffSplitContentLine = (props: { index: number; diffFile: DiffFile; lineNumber: number }) => { + const [_, setWidget] = useDiffWidgetContext() || []; + + const enableAddWidget = useEnableAddWidget(); + + const enableHighlight = useEnableHighlight(); + + const onAddWidgetClick = useOnAddWidgetClick(); + + const [oldLine, setOldLine] = createSignal(props.diffFile.getSplitLeftLine(props.index)); + + const [newLine, setNewLine] = createSignal(props.diffFile.getSplitRightLine(props.index)); + + const [oldSyntaxLine, setOldSyntaxLine] = createSignal(props.diffFile.getOldSyntaxLine(oldLine()?.lineNumber || 0)); + + const [newSyntaxLine, setNewSyntaxLine] = createSignal(props.diffFile.getNewSyntaxLine(newLine()?.lineNumber || 0)); + + const [oldPlainLine, setOldPlainLine] = createSignal(props.diffFile.getOldPlainLine(oldLine()?.lineNumber || 0)); + + const [newPlainLine, setNewPlainLine] = createSignal(props.diffFile.getNewPlainLine(newLine()?.lineNumber || 0)); + + const [hasDiff, setHasDiff] = createSignal(!!oldLine()?.diff || !!newLine()?.diff); + + const [hasChange, setHasChange] = createSignal( + checkDiffLineIncludeChange(oldLine().diff) || checkDiffLineIncludeChange(newLine().diff) + ); + + const [hasHidden, setHasHidden] = createSignal(oldLine()?.isHidden && newLine()?.isHidden); + + const [oldLineIsDelete, setOldLineIsDelete] = createSignal(oldLine()?.diff?.type === DiffLineType.Delete); + + const [newLineIsAdded, setNewLineIsAdded] = createSignal(newLine()?.diff?.type === DiffLineType.Add); + + createEffect(() => { + const init = () => { + setOldLine(props.diffFile.getSplitLeftLine(props.index)); + setNewLine(props.diffFile.getSplitRightLine(props.index)); + setOldSyntaxLine(props.diffFile.getOldSyntaxLine(oldLine()?.lineNumber || 0)); + 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); + }; + + init(); + + const cb = props.diffFile.subscribe(init); + + onCleanup(cb); + }); + + const onOpenAddWidget = (lineNumber: number, side: SplitSide) => setWidget?.({ side: side, lineNumber: lineNumber }); + + return ( + + + {oldLine().lineNumber ? ( + <> + + {hasDiff() && enableAddWidget() && ( + + )} + + {oldLine().lineNumber} + + + + {hasDiff() && enableAddWidget() && ( + + )} + + + + ) : ( + + + + )} + {newLine().lineNumber ? ( + <> + + {hasDiff() && enableAddWidget() && ( + + )} + + {newLine().lineNumber} + + + + {hasDiff() && enableAddWidget() && ( + + )} + + + + ) : ( + + + + )} + + + ); +}; diff --git a/packages/solid/src/components/DiffSplitExtendLineWrap.tsx b/packages/solid/src/components/DiffSplitExtendLineWrap.tsx new file mode 100644 index 0000000..1d41c3e --- /dev/null +++ b/packages/solid/src/components/DiffSplitExtendLineWrap.tsx @@ -0,0 +1,108 @@ +import { SplitSide, type DiffFile } from "@git-diff-view/core"; +import { borderColorName, emptyBGName } from "@git-diff-view/utils"; +import { createEffect, createSignal, onCleanup, Show } from "solid-js"; + +import { useExtendData, useRenderExtend } from "../hooks"; + +export const DiffSplitExtendLine = (props: { index: number; diffFile: DiffFile; lineNumber: number }) => { + const extendData = useExtendData(); + + const renderExtend = useRenderExtend(); + + const [oldLine, setOldLine] = createSignal(props.diffFile.getSplitLeftLine(props.index)); + + const [newLine, setNewLine] = createSignal(props.diffFile.getSplitRightLine(props.index)); + + const [enableExpand, setEnableExpand] = createSignal(props.diffFile.getExpandEnabled()); + + const [oldLineExtend, setOldLineExtend] = createSignal(extendData()?.oldFile?.[oldLine()?.lineNumber || ""]); + + const [newLineExtend, setNewLineExtend] = createSignal(extendData()?.newFile?.[newLine()?.lineNumber || ""]); + + const checkIsShow = () => { + const oldExtend = oldLineExtend(); + const newExtend = newLineExtend(); + const oldLineData = oldLine(); + const newLineData = newLine(); + const enableExpandValue = enableExpand(); + const renderExtendValue = renderExtend(); + + return ( + (oldExtend || newExtend) && + ((!oldLineData?.isHidden && !newLineData?.isHidden) || enableExpandValue) && + renderExtendValue + ); + }; + + const [currentIsShow, setCurrentIsShow] = createSignal(!!checkIsShow()); + + createEffect(() => { + const init = () => { + 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 || ""]); + setCurrentIsShow(!!checkIsShow()); + }; + + init(); + + const cb = props.diffFile.subscribe(init); + + onCleanup(cb); + }); + + return ( + + + {renderExtend() ? ( + +
+ {renderExtend()?.({ + diffFile: props.diffFile, + side: SplitSide.old, + lineNumber: oldLine()?.lineNumber || 0, + data: oldLineExtend()?.data, + onUpdate: props.diffFile.notifyAll, + })} +
+ + ) : ( + + )} + {renderExtend() ? ( + +
+ {renderExtend()?.({ + diffFile: props.diffFile, + side: SplitSide.new, + lineNumber: newLine()?.lineNumber || 0, + data: newLineExtend()?.data, + onUpdate: props.diffFile.notifyAll, + })} +
+ + ) : ( + + )} + +
+ ); +}; diff --git a/packages/solid/src/components/DiffSplitHunkLineWrap.tsx b/packages/solid/src/components/DiffSplitHunkLineWrap.tsx new file mode 100644 index 0000000..96de9d8 --- /dev/null +++ b/packages/solid/src/components/DiffSplitHunkLineWrap.tsx @@ -0,0 +1,370 @@ +import { composeLen, type DiffFile } from "@git-diff-view/core"; +import { + borderColorName, + DiffModeEnum, + hunkContentBGName, + hunkContentColorName, + hunkLineNumberBGName, + plainLineNumberColorName, +} from "@git-diff-view/utils"; +import { createEffect, createMemo, createSignal, Show } from "solid-js"; + +import { useMode } from "../hooks"; + +import { ExpandAll, ExpandDown, ExpandUp } from "./DiffExpand"; + +const DiffSplitHunkLineGitHub = (props: { index: number; diffFile: DiffFile; lineNumber: number }) => { + const [currentHunk, setCurrentHunk] = createSignal(props.diffFile.getSplitHunkLine(props.index)); + + const [enableExpand, setEnableExpand] = createSignal(props.diffFile.getExpandEnabled()); + + const [couldExpand, setCouldExpand] = createSignal(enableExpand() && currentHunk()?.splitInfo); + + const checkCurrentShowExpandAll = () => { + const hunk = currentHunk(); + return hunk && hunk.splitInfo && hunk.splitInfo.endHiddenIndex - hunk.splitInfo.startHiddenIndex >= composeLen; + }; + + const [currentShowExpandAll, setCurrentShowExpandAll] = createSignal(checkCurrentShowExpandAll()); + + const checkCurrentIsShow = () => { + const hunk = currentHunk(); + return hunk && hunk.splitInfo && hunk.splitInfo.startHiddenIndex < hunk.splitInfo.endHiddenIndex; + }; + + const [currentIsShow, setCurrentIsShow] = createSignal(checkCurrentIsShow()); + + const currentIsFirstLine = createMemo(() => { + const hunk = currentHunk(); + return hunk && hunk.isFirst; + }); + + const currentIsPureHunk = createMemo(() => { + const hunk = currentHunk(); + return hunk && !hunk.splitInfo; + }); + + const currentIsLastLine = createMemo(() => { + const hunk = currentHunk(); + return hunk && hunk.isLast; + }); + + createEffect(() => { + const init = () => { + setCurrentHunk(props.diffFile.getSplitHunkLine(props.index)); + setEnableExpand(props.diffFile.getExpandEnabled()); + setCouldExpand(enableExpand() && currentHunk()?.splitInfo); + setCurrentShowExpandAll(checkCurrentShowExpandAll()); + setCurrentIsShow(checkCurrentIsShow()); + }; + + init(); + + const cb = props.diffFile.subscribe(init); + + return () => { + cb(); + }; + }); + + return ( + + + + {couldExpand() ? ( + currentIsFirstLine() ? ( + + ) : currentIsLastLine() ? ( + + ) : currentShowExpandAll() ? ( + + ) : ( + <> + + + + ) + ) : ( +
+ )} + + +
+ {currentHunk().splitInfo?.plainText || currentHunk().text} +
+ + +
+ ); +}; + +const DiffSplitHunkLineGitLab = (props: { index: number; diffFile: DiffFile; lineNumber: number }) => { + const [currentHunk, setCurrentHunk] = createSignal(props.diffFile.getSplitHunkLine(props.index)); + + const [enableExpand, setEnableExpand] = createSignal(props.diffFile.getExpandEnabled()); + + const [couldExpand, setCouldExpand] = createSignal(enableExpand() && currentHunk()?.splitInfo); + + const checkCurrentShowExpandAll = () => { + const hunk = currentHunk(); + return hunk && hunk.splitInfo && hunk.splitInfo.endHiddenIndex - hunk.splitInfo.startHiddenIndex >= composeLen; + }; + + const [currentShowExpandAll, setCurrentShowExpandAll] = createSignal(checkCurrentShowExpandAll()); + + const checkCurrentIsShow = () => { + const hunk = currentHunk(); + return hunk && hunk.splitInfo && hunk.splitInfo.startHiddenIndex < hunk.splitInfo.endHiddenIndex; + }; + + const [currentIsShow, setCurrentIsShow] = createSignal(checkCurrentIsShow()); + + const currentIsFirstLine = createMemo(() => { + const hunk = currentHunk(); + return hunk && hunk.isFirst; + }); + + const currentIsPureHunk = createMemo(() => { + const hunk = currentHunk(); + return hunk && !hunk.splitInfo; + }); + + const currentIsLastLine = createMemo(() => { + const hunk = currentHunk(); + return hunk && hunk.isLast; + }); + + createEffect(() => { + const init = () => { + setCurrentHunk(props.diffFile.getSplitHunkLine(props.index)); + setEnableExpand(props.diffFile.getExpandEnabled()); + setCouldExpand(enableExpand() && currentHunk()?.splitInfo); + setCurrentShowExpandAll(checkCurrentShowExpandAll()); + setCurrentIsShow(checkCurrentIsShow()); + }; + + init(); + + const cb = props.diffFile.subscribe(init); + + return () => { + cb(); + }; + }); + + return ( + + + + {couldExpand() ? ( + currentIsFirstLine() ? ( + + ) : currentIsLastLine() ? ( + + ) : currentShowExpandAll() ? ( + + ) : ( + <> + + + + ) + ) : ( +
+ )} + + +
+ {currentHunk().splitInfo?.plainText || currentHunk().text} +
+ + + {couldExpand() ? ( + currentIsFirstLine() ? ( + + ) : currentIsLastLine() ? ( + + ) : currentShowExpandAll() ? ( + + ) : ( + <> + + + + ) + ) : ( +
+ )} + + +
+ {currentHunk().splitInfo?.plainText || currentHunk().text} +
+ + +
+ ); +}; + +export const DiffSplitHunkLine = (props: { index: number; diffFile: DiffFile; lineNumber: number }) => { + const mode = useMode(); + + return ( + } + > + + + ); +}; diff --git a/packages/solid/src/components/DiffSplitView.tsx b/packages/solid/src/components/DiffSplitView.tsx new file mode 100644 index 0000000..02486da --- /dev/null +++ b/packages/solid/src/components/DiffSplitView.tsx @@ -0,0 +1,18 @@ +import { Show } from "solid-js"; + +import { useEnableWrap } from "../hooks"; + +import { DiffSplitViewNormal } from "./DiffSplitViewNormal"; +import { DiffSplitViewWrap } from "./DiffSplitViewWrap"; + +import type { DiffFile } from "@git-diff-view/core"; + +export const DiffSplitView = (props: { diffFile: DiffFile }) => { + const enableWrap = useEnableWrap(); + + return ( + }> + + + ); +}; diff --git a/packages/solid/src/components/DiffSplitViewNormal.tsx b/packages/solid/src/components/DiffSplitViewNormal.tsx new file mode 100644 index 0000000..6261570 --- /dev/null +++ b/packages/solid/src/components/DiffSplitViewNormal.tsx @@ -0,0 +1,5 @@ +import type { DiffFile } from "@git-diff-view/core"; + +export const DiffSplitViewNormal = (props: { diffFile: DiffFile }) => { + return
12
; +}; diff --git a/packages/solid/src/components/DiffSplitViewWrap.tsx b/packages/solid/src/components/DiffSplitViewWrap.tsx new file mode 100644 index 0000000..bd9d411 --- /dev/null +++ b/packages/solid/src/components/DiffSplitViewWrap.tsx @@ -0,0 +1,128 @@ +import { getSplitContentLines, SplitSide } from "@git-diff-view/core"; +import { diffAsideWidthName, diffFontSizeName, removeAllSelection } from "@git-diff-view/utils"; +import { createEffect, createMemo, createSignal, onCleanup, For } from "solid-js"; + +import { useFontSize, useTextWidth } from "../hooks"; + +import type { DiffFile } from "@git-diff-view/core"; + +const Style = (props: { splitSideInfo: { side?: SplitSide }; id: string }) => { + return ( + + ); +}; + +export const DiffSplitViewWrap = (props: { diffFile: DiffFile }) => { + const [lines, setLines] = createSignal(getSplitContentLines(props.diffFile)); + + const [maxText, setMaxText] = createSignal(props.diffFile.splitLineLength.toString()); + + const [splitSideInfo, setSplitInfo] = createSignal<{ side?: SplitSide }>({ side: undefined }); + + const fontSize = useFontSize(); + + const font = createMemo(() => ({ fontSize: `${fontSize() || 14}px`, fontFamily: "Menlo, Consolas, monospace" })); + + createEffect(() => { + const init = () => { + setLines(getSplitContentLines(props.diffFile)); + setMaxText(props.diffFile.splitLineLength.toString()); + }; + + init(); + + const cb = props.diffFile.subscribe(init); + + onCleanup(cb); + }); + + const onMouseDown = (e: MouseEvent) => { + let ele = e.target; + + // need remove all the selection + if (ele && ele instanceof HTMLElement && ele.nodeName === "BUTTON") { + removeAllSelection(); + return; + } + + while (ele && ele instanceof HTMLElement) { + const state = ele.getAttribute("data-state"); + const side = ele.getAttribute("data-side"); + if (side) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + setSplitInfo({ side: SplitSide[side] }); + removeAllSelection(); + } + if (state) { + if (state === "extend" || state === "hunk" || state === "widget") { + setSplitInfo({ side: undefined }); + removeAllSelection(); + return; + } else { + return; + } + } + + ele = ele.parentElement; + } + }; + + const width = useTextWidth({ text: maxText, font }); + + const memoWidth = createMemo(() => Math.max(40, width() + 25)); + + return ( +
+
+