dsifford/academic-bloggers-toolkit

View on GitHub
src/js/gutenberg/formats/citation/citation.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import { parse, serialize } from '@wordpress/blocks';
import { compose } from '@wordpress/compose';
import { withDispatch, withSelect } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { create, FormatProps, insert } from '@wordpress/rich-text';
import { get } from 'lodash';
 
import { ToolbarButton } from 'gutenberg/sidebar/toolbar';
import { ZERO_WIDTH_SPACE } from 'utils/constants';
import { createSelector } from 'utils/dom';
import { CitationElement } from 'utils/element';
import { getNeighbors, iterate, mergeItems } from 'utils/formats';
 
import { name as NAME } from './';
 
import './citation.scss?global';
 
interface DispatchProps {
mergeLegacyCitations(): void;
parseCitations(): void;
}
 
interface SelectProps {
selectedItems: string[];
}
 
type OwnProps = FormatProps;
type Props = DispatchProps & SelectProps & OwnProps;
 
function Citation(props: Props) {
useEffect(() => props.mergeLegacyCitations(), []);
const { selectedItems } = props;
return (
<>
<ToolbarButton
disabled={selectedItems.length === 0}
icon="exit"
label={__('Insert citation', 'academic-bloggers-toolkit')}
onClick={() => insertCitation(props)}
/>
</>
);
}
 
Function `insertCitation` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.
function insertCitation({
onChange,
parseCitations,
selectedItems,
value,
}: Props) {
const { activeFormats = [] } = value;
const activeCitation = activeFormats.find(f => f.type === NAME);
 
// If a citation format is currently selected, merge selected references
// into that format.
if (activeCitation) {
const selectedId = get(activeCitation, ['attributes', 'id']);
for (const { attributes = {} } of iterate(value, NAME)) {
if (attributes.id === selectedId) {
attributes.items = mergeItems(selectedItems, attributes.items);
}
}
onChange(value);
}
// If no citations are currently selected, check to see if the cursor is
// currently touching up against an existing format. If so, merge into
// that citation format.
else {
const formats = getNeighbors(NAME, value);
if (formats.length > 0) {
for (const format of formats) {
format.attributes = format.attributes || {};
format.attributes = {
...format.attributes,
items: mergeItems(selectedItems, format.attributes.items),
};
}
onChange(value);
}
// Otherwise just insert a new citation format.
else {
const newValue = create({
html: CitationElement.create(selectedItems),
});
onChange(insert(value, newValue));
}
}
return parseCitations();
}
 
const legacyCitationSelector = createSelector(
...CitationElement.legacyClassNames.map(cls => ({
classNames: [cls],
attributes: { 'data-reflist': true },
})),
);
 
export default compose(
withDispatch<DispatchProps, OwnProps>((dispatch, _, { select }) => ({
Function `mergeLegacyCitations` has 35 lines of code (exceeds 25 allowed). Consider refactoring.
mergeLegacyCitations() {
const selectedBlock = select(
'core/block-editor',
).getSelectedBlock();
if (!selectedBlock) {
return;
}
const block = document.createElement('div');
block.innerHTML = serialize([selectedBlock]);
const legacyNodes = block.querySelectorAll<HTMLElement>(
legacyCitationSelector,
);
if (legacyNodes.length === 0) {
return;
}
for (const node of legacyNodes) {
node.className = CitationElement.className;
node.contentEditable = 'false';
node.dataset.items = node.dataset.reflist;
delete node.dataset.reflist;
Identical blocks of code found in 2 locations. Consider refactoring.
if (node.firstElementChild) {
node.dataset.hasChildren = 'true';
node.firstElementChild.innerHTML =
ZERO_WIDTH_SPACE +
node.firstElementChild.innerHTML +
ZERO_WIDTH_SPACE;
} else {
node.innerHTML =
ZERO_WIDTH_SPACE + node.innerHTML + ZERO_WIDTH_SPACE;
}
}
const { clientId, ...updates } = parse(block.innerHTML)[0];
dispatch('core/block-editor').updateBlock(
selectedBlock.clientId,
updates,
);
},
parseCitations() {
dispatch('abt/ui').clearSelectedItems();
dispatch('abt/data').parseCitations();
},
})),
withSelect<SelectProps, OwnProps & DispatchProps>(select => {
const referenceIds = select('abt/data')
.getItems()
.map(({ id }) => id);
const selectedItems = select('abt/ui')
.getSelectedItems()
.filter(id => referenceIds.includes(id));
return {
selectedItems,
};
}),
)(Citation);