import * as React from 'react'; import { diffWords } from 'diff'; import { trim } from 'lodash'; import { Div, Span } from '@neliosoft/inlexa/components'; import { css } from '@neliosoft/inlexa/css'; import { colors } from '@neliosoft/inlexa/theme'; export type ExampleProps = { readonly exclusive: string; readonly inclusive: string; }; export const Example = ( { exclusive, inclusive, }: ExampleProps ): JSX.Element => (
{ diffExample( exclusive, inclusive ).map( ( { mode, value }, key ) => ( { value }{ ' ' } ) ) }
); // ========= // STYLES // ========= const EXAMPLE = css( { marginBottom: '1em', lineHeight: '1.5em', } ); const BOX = { borderRadius: '2px', padding: '0 0.2em', }; const ADDITION = css( { ...BOX, backgroundColor: colors.examples.addition.background, color: colors.examples.addition.foreground, } ); const REMOVAL = css( { ...BOX, backgroundColor: colors.examples.removal.background, color: colors.examples.removal.foreground, textDecoration: 'line-through', } ); // ========= // HELPERS // ========= type Diff = { readonly value: string; readonly mode: 'normal' | 'add' | 'remove'; }; const getStyle = ( mode: Diff[ 'mode' ] ): string => { switch ( mode ) { case 'add': return ADDITION; case 'remove': return REMOVAL; case 'normal': return ''; } //end switch }; function diffExample( exclusive: string, inclusive: string ): ReadonlyArray< Diff > { const clearExample = ( text = '' ) => text.replace( /\s/g, ' ' ).trim().replace( /\.+/g, '.' ); exclusive = clearExample( exclusive ); inclusive = clearExample( inclusive ); const result = diffWords( exclusive, inclusive ) .map( ( diff ) => ( { ...diff, value: trim( diff.value ), } ) ) .filter( ( { value } ) => !! value ) .map( ( diff ): Diff => ( { value: diff.value, mode: 'normal', ...( diff.added && { mode: 'add' } ), ...( diff.removed && { mode: 'remove' } ), } ) ) .reduce( ( array, current ) => { const [ last, prev, ...rest ] = array; if ( last?.mode === current.mode ) { return [ combine( last, current ), prev, ...rest ]; } //end if if ( 'normal' === current.mode || 'normal' === last?.mode ) { return [ current, last, prev, ...rest ]; } //end if if ( prev?.mode === current.mode ) { return [ last, combine( prev, current ), ...rest ]; } //end if return [ current, last, prev, ...rest ]; }, [] as ReadonlyArray< Diff | undefined > ) .filter( isDiff ); return reverse( result ); } //end diffExample() const combine = ( prev: Diff, curr: Diff ): Diff => ( { ...prev, value: `${ prev.value } ${ curr.value }`, } ); const isDiff = ( diff?: Diff ): diff is Diff => !! diff; const reverse = < T extends any >( arr: ReadonlyArray< T > ): ReadonlyArray< T > => arr.reduce( ( r, c ) => [ c, ...r ], [] as ReadonlyArray< T > );