dsifford/academic-bloggers-toolkit

View on GitHub
src/js/gutenberg/blocks/static-bibliography/edit.tsx

Summary

Maintainability
B
5 hrs
Test Coverage
import {
    BlockFormatControls,
    InspectorControls,
    RichText,
} from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { PanelBody, Placeholder, Toolbar } from '@wordpress/components';
import { select as globalSelect, useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import classNames from 'classnames';

import CountIcon from 'components/count-icon';
import ReferenceItem from 'gutenberg/components/reference-item';
import SidebarItemList from 'gutenberg/components/sidebar-item-list';
import { localeCache, styleCache } from 'utils/cache';
import { swapWith } from 'utils/data';
import { stripListItem } from 'utils/editor';
import Processor from 'utils/processor';

import { Attributes } from './';
import LiveItem from './live-item';
import styles from './style.scss';

type Props = BlockEditProps<Attributes>;

export default function StaticBibEdit(props: Props) {
    const {
        attributes: { orderedList, items },
        setAttributes,
    } = props;
    const references = useSelect(select => {
        const existingIds = items.map(item => item.id);
        return select('abt/data')
            .getSortedItems()
            .filter(item => !existingIds.includes(item.id));
    });
    return (
        <>
            <InspectorControls>
                <PanelBody>
                    {__(
                        'Double click on one or more items in the list below to insert them into the end of the list.',
                        'academic-bloggers-toolkit',
                    )}
                </PanelBody>
                <PanelBody
                    icon={<CountIcon count={references.length} />}
                    initialOpen={false}
                    title={__(
                        'Available References',
                        'academic-bloggers-toolkit',
                    )}
                >
                    <SidebarItemList
                        items={references}
                        renderItem={item => <ReferenceItem item={item} />}
                        onItemDoubleClick={async id =>
                            addItem(id, references, items, setAttributes)
                        }
                    />
                </PanelBody>
            </InspectorControls>
            <BlockFormatControls>
                <Toolbar
                    controls={[
                        {
                            icon: 'editor-ul',
                            title: __(
                                'Convert to unordered list',
                                'academic-bloggers-toolkit',
                            ),
                            isActive: orderedList === false,
                            onClick() {
                                setAttributes({ orderedList: false });
                            },
                        },
                        {
                            icon: 'editor-ol',
                            title: __(
                                'Convert to ordered list',
                                'academic-bloggers-toolkit',
                            ),
                            isActive: orderedList === true,
                            onClick() {
                                setAttributes({ orderedList: true });
                            },
                        },
                    ]}
                />
            </BlockFormatControls>
            {/* eslint-disable-next-line jsx-a11y/no-redundant-roles */}
            <section
                aria-label={__('References', 'academic-bloggers-toolkit')}
                className="abt-static-bib"
                role="region"
            >
                <ItemList {...props} />
            </section>
        </>
    );
}

function ItemList({
    attributes: { items, orderedList },
    isSelected,
    setAttributes,
}: Props) {
    const List = orderedList ? 'ol' : 'ul';
    if (items.length === 0) {
        return (
            <Placeholder
                icon="welcome-learn-more"
                label={__(
                    'No references added yet',
                    'academic-bloggers-toolkit',
                )}
            >
                {__(
                    'Add references to this list by double clicking one or more items listed in the "Avaliable References" section of the block settings panel.',
                    'academic-bloggers-toolkit',
                )}
            </Placeholder>
        );
    }
    if (isSelected) {
        return (
            <List className="abt-bibliography__body">
                {items.map(({ content, id }, i) => (
                    <LiveItem
                        key={id}
                        content={content}
                        id={id}
                        onMoveDown={
                            i === items.length - 1
                                ? undefined
                                : () =>
                                      setAttributes({
                                          items: swapWith(items, i, i + 1),
                                      })
                        }
                        onMoveUp={
                            i === 0
                                ? undefined
                                : () =>
                                      setAttributes({
                                          items: swapWith(items, i, i - 1),
                                      })
                        }
                        onRemove={() =>
                            setAttributes({
                                items: [
                                    ...items.slice(0, i),
                                    ...items.slice(i + 1),
                                ],
                            })
                        }
                    />
                ))}
            </List>
        );
    }
    return (
        <List className="abt-bibliography__body">
            {items.map(({ content, id }) => (
                <RichText.Content
                    key={id}
                    className={classNames('csl-entry', styles.item)}
                    data-id={id}
                    tagName="li"
                    value={content}
                />
            ))}
        </List>
    );
}

async function addItem(
    id: string,
    references: CSL.Data[],
    items: Processor.BibItem[],
    setAttributes: (attrs: Partial<Attributes>) => void,
) {
    const data = references.find(item => item.id === id);
    if (!data) {
        return;
    }
    const styleXml = await styleCache.fetchItem(
        globalSelect('abt/data').getStyle().value,
    );
    await localeCache.fetchItem(styleXml);
    const processor = new Processor(styleXml);
    processor.parseCitations([
        {
            citationID: '',
            citationItems: [
                {
                    id: data.id,
                    item: data,
                },
            ],
            properties: {
                index: 0,
                noteIndex: 0,
            },
        },
    ]);
    const newItem = processor.bibliography.items[0];
    if (newItem) {
        setAttributes({
            items: [
                ...items,
                {
                    ...newItem,
                    content: stripListItem(newItem.content),
                },
            ],
        });
    }
}