Răsfoiți Sursa

Merge pull request #12884 from overleaf/td-history-diff-line-highlights

History migration: Add line and gutter highlights for changes in diff viewer

GitOrigin-RevId: 11d637647d147210f63e5111305adfe1d285ba3e
Tim Down 3 ani în urmă
părinte
comite
6b78c3025a

+ 1 - 1
services/web/frontend/js/features/history/extensions/highlight-locations.ts

@@ -32,7 +32,7 @@ function calculateHighlightLocations(view: EditorView): HighlightLocations {
   let previous
 
   const highlights =
-    view.state.field(highlightDecorationsField).highlights || []
+    view.state.field(highlightDecorationsField)?.highlights || []
 
   if (highlights.length === 0) {
     return { before: 0, after: 0 }

+ 92 - 3
services/web/frontend/js/features/history/extensions/highlights.ts

@@ -11,6 +11,9 @@ import {
   DecorationSet,
   EditorView,
   showTooltip,
+  gutter,
+  gutterLineClass,
+  GutterMarker,
   Tooltip,
   ViewPlugin,
   WidgetType,
@@ -97,6 +100,20 @@ const theme = EditorView.baseTheme({
   '.ol-cm-empty-line-addition-marker': {
     padding: 'var(--half-leading) 2px',
   },
+  '.ol-cm-changed-line': {
+    backgroundColor: 'rgba(0, 0, 0, 0.03)',
+  },
+  '.ol-cm-change-gutter': {
+    width: '3px',
+    paddingLeft: '1px',
+  },
+  '.ol-cm-changed-line-gutter': {
+    backgroundColor: 'hsl(var(--hue), 70%, 40%)',
+    height: '100%',
+  },
+  '.ol-cm-highlighted-line-gutter': {
+    backgroundColor: 'rgba(0, 0, 0, 0.03)',
+  },
 })
 
 function createHighlightTooltip(pos: number, highlight: Highlight) {
@@ -272,10 +289,61 @@ function createEmptyLineHighlightMarkers(lineStatuses: LineStatuses) {
   return RangeSet.of(markers)
 }
 
+class ChangeGutterMarker extends GutterMarker {
+  constructor(readonly hue: number) {
+    super()
+  }
+
+  toDOM(view: EditorView) {
+    const el = document.createElement('div')
+    el.className = 'ol-cm-changed-line-gutter'
+    el.style.setProperty('--hue', this.hue.toString())
+
+    return el
+  }
+}
+
+function createGutterMarkers(lineStatuses: LineStatuses) {
+  const gutterMarkers: Range<GutterMarker>[] = []
+  for (const lineStatus of lineStatuses.values()) {
+    gutterMarkers.push(
+      new ChangeGutterMarker(lineStatus.highlights[0].hue).range(
+        lineStatus.line.from
+      )
+    )
+  }
+  return RangeSet.of(gutterMarkers)
+}
+
+const lineHighlight = Decoration.line({ class: 'ol-cm-changed-line' })
+
+function createLineHighlights(lineStatuses: LineStatuses) {
+  const lineHighlights: Range<Decoration>[] = []
+  for (const lineStatus of lineStatuses.values()) {
+    lineHighlights.push(lineHighlight.range(lineStatus.line.from))
+  }
+  return RangeSet.of(lineHighlights)
+}
+
+const changeLineGutterMarker = new (class extends GutterMarker {
+  elementClass = 'ol-cm-highlighted-line-gutter'
+})()
+
+function createGutterHighlights(lineStatuses: LineStatuses) {
+  const gutterMarkers: Range<GutterMarker>[] = []
+  for (const lineStatus of lineStatuses.values()) {
+    gutterMarkers.push(changeLineGutterMarker.range(lineStatus.line.from))
+  }
+  return RangeSet.of(gutterMarkers, true)
+}
+
 type HighlightDecorations = {
   highlights: Highlight[]
   highlightMarkers: DecorationSet
   emptyLineHighlightMarkers: DecorationSet
+  lineHighlights: DecorationSet
+  gutterMarkers: RangeSet<GutterMarker>
+  gutterHighlights: RangeSet<GutterMarker>
 }
 
 export const highlightDecorationsField =
@@ -285,9 +353,12 @@ export const highlightDecorationsField =
         highlights: [],
         highlightMarkers: Decoration.none,
         emptyLineHighlightMarkers: Decoration.none,
+        lineHighlights: Decoration.none,
+        gutterMarkers: RangeSet.empty,
+        gutterHighlights: RangeSet.empty,
       }
     },
-    update(highlightMarkers, tr) {
+    update(highlightDecorations, tr) {
       for (const effect of tr.effects) {
         if (effect.is(setHighlightsEffect)) {
           const highlights = effect.value
@@ -295,14 +366,20 @@ export const highlightDecorationsField =
           const highlightMarkers = createMarkers(highlights)
           const emptyLineHighlightMarkers =
             createEmptyLineHighlightMarkers(lineStatuses)
+          const lineHighlights = createLineHighlights(lineStatuses)
+          const gutterMarkers = createGutterMarkers(lineStatuses)
+          const gutterHighlights = createGutterHighlights(lineStatuses)
           return {
             highlights,
             highlightMarkers,
             emptyLineHighlightMarkers,
+            lineHighlights,
+            gutterMarkers,
+            gutterHighlights,
           }
         }
       }
-      return highlightMarkers
+      return highlightDecorations
     },
     provide: field => [
       EditorView.decorations.from(field, value => value.highlightMarkers),
@@ -310,11 +387,23 @@ export const highlightDecorationsField =
         field,
         value => value.emptyLineHighlightMarkers
       ),
+      EditorView.decorations.from(field, value => value.lineHighlights),
       theme,
       highlightTooltipPlugin,
     ],
   })
 
+const changeGutter = gutter({
+  class: 'ol-cm-change-gutter',
+  markers: view => view.state.field(highlightDecorationsField).gutterMarkers,
+  renderEmptyElements: false,
+})
+
+const gutterHighlighter = gutterLineClass.from(
+  highlightDecorationsField,
+  value => value.gutterHighlights
+)
+
 export function highlights() {
-  return highlightDecorationsField
+  return [highlightDecorationsField, changeGutter, gutterHighlighter]
 }

+ 8 - 2
services/web/frontend/stories/history/document-diff-viewer.stories.tsx

@@ -13,8 +13,8 @@ const highlights: Highlight[] = [
   {
     type: 'deletion',
     range: { from: 15, to: 25 },
-    hue: 200,
-    label: 'Deleted by Wombat on Monday',
+    hue: 62,
+    label: 'Deleted by Duck on Monday',
   },
   {
     type: 'addition',
@@ -22,6 +22,12 @@ const highlights: Highlight[] = [
     hue: 200,
     label: 'Added by Wombat on Friday',
   },
+  {
+    type: 'deletion',
+    range: { from: 564, to: 565 },
+    hue: 200,
+    label: 'Deleted by Wombat on Friday',
+  },
   {
     type: 'addition',
     range: { from: 1770, to: 1780 },