diff.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import { type FileContents, FileDiff, type DiffLineAnnotation, FileDiffOptions } from "@pierre/precision-diffs"
  2. import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
  3. import { ComponentProps, createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
  4. import { isServer } from "solid-js/web"
  5. import { createDefaultOptions, styleVariables } from "./pierre"
  6. export type DiffProps<T = {}> = FileDiffOptions<T> & {
  7. preloadedDiff?: PreloadMultiFileDiffResult<T>
  8. before: FileContents
  9. after: FileContents
  10. annotations?: DiffLineAnnotation<T>[]
  11. class?: string
  12. classList?: ComponentProps<"div">["classList"]
  13. }
  14. // interface ThreadMetadata {
  15. // threadId: string
  16. // }
  17. //
  18. //
  19. export function Diff<T>(props: DiffProps<T>) {
  20. let container!: HTMLDivElement
  21. let fileDiffRef!: HTMLElement
  22. const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
  23. let fileDiffInstance: FileDiff<T> | undefined
  24. const cleanupFunctions: Array<() => void> = []
  25. createEffect(() => {
  26. if (props.preloadedDiff) return
  27. container.innerHTML = ""
  28. if (!fileDiffInstance) {
  29. fileDiffInstance = new FileDiff<T>({
  30. ...createDefaultOptions(props.diffStyle),
  31. ...others,
  32. ...(props.preloadedDiff ?? {}),
  33. })
  34. }
  35. fileDiffInstance.render({
  36. oldFile: local.before,
  37. newFile: local.after,
  38. lineAnnotations: local.annotations,
  39. containerWrapper: container,
  40. })
  41. })
  42. onMount(() => {
  43. if (isServer || !props.preloadedDiff) return
  44. fileDiffInstance = new FileDiff<T>({
  45. ...createDefaultOptions(props.diffStyle),
  46. ...others,
  47. ...(props.preloadedDiff ?? {}),
  48. })
  49. // @ts-expect-error - fileContainer is private but needed for SSR hydration
  50. fileDiffInstance.fileContainer = fileDiffRef
  51. fileDiffInstance.hydrate({
  52. oldFile: local.before,
  53. newFile: local.after,
  54. lineAnnotations: local.annotations,
  55. fileContainer: fileDiffRef,
  56. containerWrapper: container,
  57. })
  58. // Hydrate annotation slots with interactive SolidJS components
  59. // if (props.annotations.length > 0 && props.renderAnnotation != null) {
  60. // for (const annotation of props.annotations) {
  61. // const slotName = `annotation-${annotation.side}-${annotation.lineNumber}`;
  62. // const slotElement = fileDiffRef.querySelector(
  63. // `[slot="${slotName}"]`
  64. // ) as HTMLElement;
  65. //
  66. // if (slotElement != null) {
  67. // // Clear the static server-rendered content from the slot
  68. // slotElement.innerHTML = '';
  69. //
  70. // // Mount a fresh SolidJS component into this slot using render().
  71. // // This enables full SolidJS reactivity (signals, effects, etc.)
  72. // const dispose = render(
  73. // () => props.renderAnnotation!(annotation),
  74. // slotElement
  75. // );
  76. // cleanupFunctions.push(dispose);
  77. // }
  78. // }
  79. // }
  80. })
  81. onCleanup(() => {
  82. // Clean up FileDiff event handlers and dispose SolidJS components
  83. fileDiffInstance?.cleanUp()
  84. cleanupFunctions.forEach((dispose) => dispose())
  85. })
  86. return (
  87. <div data-component="diff" style={styleVariables} ref={container}>
  88. <file-diff ref={fileDiffRef} id="ssr-diff">
  89. <Show when={isServer && props.preloadedDiff}>
  90. {(preloadedDiff) => <template shadowrootmode="open" innerHTML={preloadedDiff().prerenderedHTML} />}
  91. </Show>
  92. </file-diff>
  93. </div>
  94. )
  95. }