/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { DiffFile, _cacheMap, SplitSide, highlighter as buildInHighlighter } from "@git-diff-view/core"; import { diffFontSizeName, DiffModeEnum } from "@git-diff-view/utils"; import { memo, useEffect, useMemo, forwardRef, useImperativeHandle, useRef } from "react"; import { useIsMounted } from "../hooks/useIsMounted"; import { useUnmount } from "../hooks/useUnmount"; import { DiffSplitView } from "./DiffSplitView"; import { DiffUnifiedView } from "./DiffUnifiedView"; import { DiffViewContext } from "./DiffViewContext"; import { createDiffConfigStore } from "./tools"; // import { DiffSplitView } from "./v2/DiffSplitView_v2"; import type { createDiffWidgetStore } from "./tools"; import type { DiffHighlighter, DiffHighlighterLang } from "@git-diff-view/core"; import type { CSSProperties, ForwardedRef, JSX, ReactNode, RefObject } from "react"; _cacheMap.name = "@git-diff-view/react"; export { SplitSide, DiffModeEnum }; export type DiffViewProps = { data?: { oldFile?: { fileName?: string | null; fileLang?: DiffHighlighterLang | string | null; content?: string | null }; newFile?: { fileName?: string | null; fileLang?: DiffHighlighterLang | string | null; content?: string | null }; hunks: string[]; }; extendData?: { oldFile?: Record; newFile?: Record }; diffFile?: DiffFile; className?: string; style?: CSSProperties; /** * provide a custom highlighter * eg: lowlight, refractor, starry-night, shiki */ registerHighlighter?: Omit; diffViewMode?: DiffModeEnum; diffViewWrap?: boolean; diffViewTheme?: "light" | "dark"; diffViewFontSize?: number; diffViewHighlight?: boolean; diffViewAddWidget?: boolean; renderWidgetLine?: ({ diffFile, side, lineNumber, onClose, }: { lineNumber: number; side: SplitSide; diffFile: DiffFile; onClose: () => void; }) => ReactNode; renderExtendLine?: ({ diffFile, side, data, lineNumber, onUpdate, }: { lineNumber: number; side: SplitSide; data: T; diffFile: DiffFile; onUpdate: () => void; }) => ReactNode; onAddWidgetClick?: (lineNumber: number, side: SplitSide) => void; onCreateUseWidgetHook?: (hook: ReturnType) => void; }; type DiffViewProps_1 = Omit, "data"> & { data?: { oldFile?: { fileName?: string | null; fileLang?: DiffHighlighterLang | null; content?: string | null }; newFile?: { fileName?: string | null; fileLang?: DiffHighlighterLang | null; content?: string | null }; hunks: string[]; }; }; type DiffViewProps_2 = Omit, "data"> & { data?: { oldFile?: { fileName?: string | null; fileLang?: string | null; content?: string | null }; newFile?: { fileName?: string | null; fileLang?: string | null; content?: string | null }; hunks: string[]; }; }; const InternalDiffView = ( props: Omit, "data" | "registerHighlighter"> & { isMounted: boolean; wrapperRef: RefObject; } ) => { const { diffFile, className, style, wrapperRef, diffViewMode, diffViewWrap, diffViewFontSize, diffViewHighlight, renderWidgetLine, renderExtendLine, extendData, diffViewAddWidget, onAddWidgetClick, onCreateUseWidgetHook, isMounted, } = props; const diffFileId = useMemo(() => diffFile!.getId(), [diffFile]); // performance optimization const useDiffContext = useMemo( () => createDiffConfigStore(props, diffFileId), // eslint-disable-next-line react-hooks/exhaustive-deps [] ); useEffect(() => { const { id, setId, mode, setMode, mounted, setMounted, enableAddWidget, setEnableAddWidget, enableHighlight, setEnableHighlight, enableWrap, setEnableWrap, setExtendData, fontSize, setFontSize, onAddWidgetClick: _onAddWidgetClick, setOnAddWidgetClick, renderExtendLine: _renderExtendLine, setRenderExtendLine, renderWidgetLine: _renderWidgetLine, setRenderWidgetLine, onCreateUseWidgetHook: _onCreateUseWidgetHook, setOnCreateUseWidgetHook, } = useDiffContext.getReadonlyState(); if (diffFileId && diffFileId !== id) { setId(diffFileId); } if (diffViewMode && diffViewMode !== mode) { setMode(diffViewMode); } if (mounted !== isMounted) { setMounted(isMounted); } if (diffViewAddWidget !== enableAddWidget) { setEnableAddWidget(!!diffViewAddWidget); } if (diffViewHighlight !== enableHighlight) { setEnableHighlight(!!diffViewHighlight); } if (diffViewWrap !== enableWrap) { setEnableWrap(!!diffViewWrap); } if (extendData) { setExtendData(extendData); } if (diffViewFontSize && diffViewFontSize !== fontSize) { setFontSize(diffViewFontSize); } if (onAddWidgetClick !== _onAddWidgetClick.current) { setOnAddWidgetClick({ current: onAddWidgetClick }); } if (onCreateUseWidgetHook !== _onCreateUseWidgetHook) { setOnCreateUseWidgetHook(onCreateUseWidgetHook); } if (renderExtendLine !== _renderExtendLine) { setRenderExtendLine(renderExtendLine); } if (renderWidgetLine !== _renderWidgetLine) { setRenderWidgetLine(renderWidgetLine); } }, [ useDiffContext, diffViewFontSize, diffViewHighlight, diffViewMode, diffViewWrap, diffViewAddWidget, diffFileId, isMounted, renderWidgetLine, renderExtendLine, extendData, onAddWidgetClick, onCreateUseWidgetHook, ]); useEffect(() => { if (wrapperRef?.current) { useDiffContext.getReadonlyState().setDom(wrapperRef?.current); } }, [useDiffContext, wrapperRef]); const value = useMemo(() => ({ useDiffContext }), [useDiffContext]); return (
{diffViewMode! & DiffModeEnum.Split ? ( ) : ( )}
); }; const MemoedInternalDiffView = memo(InternalDiffView) as typeof InternalDiffView; const DiffViewWithRef = ( props: DiffViewProps, ref: ForwardedRef<{ getDiffFileInstance: () => DiffFile | null }> ) => { const { registerHighlighter, data, diffViewTheme, diffFile: _diffFile, ...restProps } = props; const diffFile = useMemo(() => { if (_diffFile) { // missing data for plain file render // TODO next release update ? // will cause more complex for diffFile flow control, keep current const diffFile = DiffFile.createInstance({}); diffFile._mergeFullBundle(_diffFile._getFullBundle()); return diffFile; } else if (data) { return new DiffFile( data?.oldFile?.fileName || "", data?.oldFile?.content || "", data?.newFile?.fileName || "", data?.newFile?.content || "", data?.hunks || [], data?.oldFile?.fileLang || "", data?.newFile?.fileLang || "" ); } return null; }, [data, _diffFile]); const diffFileRef = useRef(diffFile); const wrapperRef = useRef(null); // eslint-disable-next-line react-hooks/refs if (diffFileRef.current && diffFileRef.current !== diffFile) { // eslint-disable-next-line react-hooks/refs diffFileRef.current.clear?.(); diffFileRef.current = diffFile; } const isMounted = useIsMounted(); useEffect(() => { if (_diffFile && diffFile) { _diffFile._addClonedInstance(diffFile); return () => { _diffFile._delClonedInstance(diffFile); }; } }, [diffFile, _diffFile]); useEffect(() => { if (!diffFile) return; diffFile.initTheme(diffViewTheme); diffFile.initRaw(); diffFile.buildSplitDiffLines(); diffFile.buildUnifiedDiffLines(); }, [diffFile, diffViewTheme]); useEffect(() => { if (!diffFile) return; if (props.diffViewHighlight) { if (registerHighlighter) { if ( registerHighlighter.name !== diffFile._getHighlighterName() || registerHighlighter.type !== diffFile._getHighlighterType() || registerHighlighter.type !== "class" ) { diffFile.initSyntax({ registerHighlighter: registerHighlighter }); diffFile.notifyAll(); } } else if ( (!diffFile._getIsCloned() && diffFile._getHighlighterName() !== buildInHighlighter.name) || diffFile._getHighlighterType() !== "class" ) { diffFile.initSyntax(); diffFile.notifyAll(); } } }, [diffFile, props.diffViewHighlight, registerHighlighter, diffViewTheme]); useEffect(() => { if (!diffFile) return; 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, diffViewTheme]); // fix react strict mode error useUnmount(() => (__DEV__ ? diffFile?._destroy?.() : diffFile?.clear?.()), [diffFile]); useImperativeHandle(ref, () => ({ getDiffFileInstance: () => diffFile }), [diffFile]); if (!diffFile) return null; return ( ); }; // type helper function // eslint-disable-next-line @typescript-eslint/no-unused-vars function ReactDiffView( props: DiffViewProps_1 & { ref?: ForwardedRef<{ getDiffFileInstance: () => DiffFile | null }> } ): JSX.Element; function ReactDiffView( props: DiffViewProps_2 & { ref?: ForwardedRef<{ getDiffFileInstance: () => DiffFile | null }> } ): JSX.Element; function ReactDiffView( _props: DiffViewProps & { ref?: ForwardedRef<{ getDiffFileInstance: () => DiffFile | null }> } ) { return <>; } const InnerDiffView = forwardRef(DiffViewWithRef); InnerDiffView.displayName = "DiffView"; export const DiffView = InnerDiffView as typeof ReactDiffView; export const version = __VERSION__;