codevise/pageflow

View on GitHub
entry_types/scrolled/package/src/frontend/EditableText.js

Summary

Maintainability
A
3 hrs
Test Coverage
import React from 'react';
import classNames from 'classnames';

import {camelize} from './utils/camelize';
import {paletteColor} from './paletteColor';
import {withInlineEditingAlternative} from './inlineEditing';
import {useChapter, useFile} from '../entryState';
import {useDarkBackground} from './backgroundColor';
import {Text} from './Text';
import textStyles from './Text.module.css';

import styles from './EditableText.module.css';

const defaultValue = [{
  type: 'paragraph',
  children: [{ text: '' }],
}];

export const EditableText = withInlineEditingAlternative('EditableText', function EditableText({
  value, className, scaleCategory = 'body'
}) {
  return (
    <div className={classNames(styles.root, className)}>
      <Text scaleCategory={scaleCategory}>
        {render(value || defaultValue)}
      </Text>
    </div>
  );
});

function render(children) {
  return children.map((element, index) => {
    if (element.type) {
      return renderElement({attributes: {key: index}, element, children: render(element.children)});
    }
    else {
      return renderLeaf({
        attributes: {key: index},
        leaf: element,
        children: children.length === 1 &&
                  element.text.trim() === '' ? '\uFEFF' : element.text
      });
    }
  });
}

export function renderElement({attributes, children, element}) {
  const variantClassName = element.variant &&
                           ['typography-textBlock',
                            camelize(element.type),
                            element.variant].join('-');

  const styles = element.color &&
                 {color: paletteColor(element.color)};

  switch (element.type) {
  case 'block-quote':
    return (
      <blockquote {...attributes}
                  className={variantClassName}
                  style={styles}>
        {children}
      </blockquote>
    );
  case 'bulleted-list':
    return (
      <ul {...attributes}
          className={variantClassName}
          style={styles}>
        {children}
      </ul>
    );
  case 'numbered-list':
    return (
      <ol {...attributes}
          className={variantClassName}
          style={styles}>
        {children}
      </ol>
    );
  case 'list-item':
    return <li {...attributes}>{children}</li>;
  case 'heading':
    const {key, ...otherAttributes} = attributes;

    return (
      <Heading key={key}
               attributes={otherAttributes}
               variantClassName={variantClassName}
               styles={styles}>
        {children}
      </Heading>
    );
  case 'link':
    return renderLink({attributes, children, element});
  default:
    return (
      <p {...attributes}
         className={variantClassName}
         style={styles}>
        {children}
      </p>
    );
  }
}

function Heading({attributes, variantClassName, styles: inlineStyles, children}) {
  const darkBackground = useDarkBackground();

  return (
    <h2 {...attributes}
        className={classNames(variantClassName,
                              darkBackground ? styles.light : styles.dark,
                              'scope-headings',
                              textStyles['heading-xs'])}
        style={inlineStyles}>
      {children}
    </h2>
  );
}

function renderLink({attributes, children, element}) {
  if (element?.href?.chapter) {
    const {key, ...otherAttributes} = attributes;

    return (
      <ChapterLink key={key}
                   attributes={otherAttributes}
                   chapterPermaId={element.href.chapter}>
        {children}
      </ChapterLink>
    );
  }
  else if (element?.href?.section) {
    return <a {...attributes}
              className={styles.link}
              href={`#section-${element.href.section}`}>
      {children}
    </a>;
  }
  if (element?.href?.file) {
    const {key, ...otherAttributes} = attributes;

    return (
      <FileLink key={key}
                attributes={otherAttributes}
                fileOptions={element.href.file}>
        {children}
      </FileLink>
    );
  }
  else {
    const targetAttributes = element.openInNewTab ?
                             {target: '_blank', rel: 'noopener noreferrer'} :
                             {};

    return <a {...attributes}
              {...targetAttributes}
              className={styles.link}
              href={element.href}>
      {children}
    </a>;
  }
}

function ChapterLink({attributes, children, chapterPermaId}) {
  const chapter = useChapter({permaId: chapterPermaId});

  return <a {...attributes}
            className={styles.link}
            href={`#${chapter?.chapterSlug || ''}`}>
    {children}
  </a>;
}

function FileLink({attributes, children, fileOptions}) {
  const file = useFile(fileOptions);

  return <a {...attributes}
            className={styles.link}
            target="_blank"
            rel="noopener noreferrer"
            href={file?.urls.original || '#'}>
    {children}
  </a>;
}

export function renderLeaf({attributes, children, leaf}) {
  if (leaf.bold) {
    children = <strong>{children}</strong>
  }

  if (leaf.italic) {
    children = <em>{children}</em>
  }

  if (leaf.underline) {
    children = <u>{children}</u>
  }

  if (leaf.strikethrough) {
    children = <s>{children}</s>
  }

  return <span {...attributes}>{children}</span>
}