| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- import { useCallback, useEffect, useState } from 'react'
- import {
- EditorSelection,
- EditorState,
- Extension,
- StateEffect,
- } from '@codemirror/state'
- import { EditorView, lineNumbers } from '@codemirror/view'
- import { indentationMarkers } from '@replit/codemirror-indentation-markers'
- import { highlights, setHighlightsEffect } from '../../extensions/highlights'
- import { useUserSettingsContext } from '@/shared/context/user-settings-context'
- import { theme, Options, setOptionsTheme } from '../../extensions/theme'
- import { indentUnit } from '@codemirror/language'
- import { Highlight } from '../../services/types/doc'
- import useIsMounted from '../../../../shared/hooks/use-is-mounted'
- import {
- highlightLocations,
- highlightLocationsField,
- scrollToHighlight,
- } from '../../extensions/highlight-locations'
- import { useTranslation } from 'react-i18next'
- import { inlineBackground } from '../../../source-editor/extensions/inline-background'
- import OLButton from '@/features/ui/components/ol/ol-button'
- function extensions(themeOptions: Options): Extension[] {
- return [
- EditorView.editable.of(false),
- EditorState.readOnly.of(true),
- EditorView.contentAttributes.of({ tabindex: '0' }),
- lineNumbers(),
- EditorView.lineWrapping,
- indentUnit.of(' '), // TODO: Vary this by file type
- indentationMarkers({ hideFirstIndent: true, highlightActiveBlock: false }),
- highlights(),
- highlightLocations(),
- theme(themeOptions),
- inlineBackground(false),
- ]
- }
- function DocumentDiffViewer({
- doc,
- highlights,
- }: {
- doc: string
- highlights: Highlight[]
- }) {
- const { userSettings } = useUserSettingsContext()
- const { fontFamily, fontSize, lineHeight } = userSettings
- const isMounted = useIsMounted()
- const { t } = useTranslation()
- const [state, setState] = useState(() => {
- return EditorState.create({
- doc,
- extensions: extensions({
- fontSize,
- fontFamily,
- lineHeight,
- }),
- })
- })
- const [view] = useState<EditorView>(() => {
- return new EditorView({
- state,
- dispatch: tr => {
- view.update([tr])
- if (isMounted.current) {
- setState(view.state)
- }
- },
- })
- })
- const highlightLocations = state.field(highlightLocationsField)
- // Append the editor view DOM to the container node when mounted
- const containerRef = useCallback(
- (node: HTMLDivElement) => {
- if (node) {
- node.appendChild(view.dom)
- }
- },
- [view]
- )
- const scrollToPrevious = useCallback(() => {
- if (highlightLocations.previous) {
- scrollToHighlight(view, highlightLocations.previous)
- }
- }, [highlightLocations.previous, view])
- const scrollToNext = useCallback(() => {
- if (highlightLocations.next) {
- scrollToHighlight(view, highlightLocations.next)
- }
- }, [highlightLocations.next, view])
- const { before, after } = highlightLocations
- useEffect(() => {
- const effects: StateEffect<unknown>[] = [setHighlightsEffect.of(highlights)]
- if (highlights.length > 0) {
- const { from, to } = highlights[0].range
- effects.push(
- EditorView.scrollIntoView(EditorSelection.range(from, to), {
- y: 'center',
- })
- )
- }
- view.dispatch({
- changes: { from: 0, to: view.state.doc.length, insert: doc },
- effects,
- })
- }, [doc, highlights, view])
- // Update the document diff viewer theme whenever the font size, font family
- // or line height user setting changes
- useEffect(() => {
- view.dispatch(
- setOptionsTheme({
- fontSize,
- fontFamily,
- lineHeight,
- })
- )
- }, [view, fontSize, fontFamily, lineHeight])
- return (
- <div className="document-diff-container">
- <div ref={containerRef} className="cm-viewer-container" />
- {before > 0 ? (
- <OLButton
- variant="secondary"
- leadingIcon="arrow_upward"
- onClick={scrollToPrevious}
- className="previous-highlight-button"
- >
- {t('n_more_updates_above', { count: before })}
- </OLButton>
- ) : null}
- {after > 0 ? (
- <OLButton
- variant="secondary"
- leadingIcon="arrow_downward"
- onClick={scrollToNext}
- className="next-highlight-button"
- >
- {t('n_more_updates_below', { count: after })}
- </OLButton>
- ) : null}
- </div>
- )
- }
- export default DocumentDiffViewer
|