superdesk/superdesk-client-core

View on GitHub
scripts/apps/search/components/fields/authors.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
/* eslint-disable react/no-multi-comp */

import React from 'react';

import {IUser, IVocabulary, IVocabularyItem, IArticle} from 'superdesk-api';
import {IPropsItemListInfo} from '../ListItemInfo';
import {SuperdeskReactComponent} from 'core/SuperdeskReactComponent';
import {getVocabularyItemNameTranslated, gettext} from 'core/utils';
import {Popover} from 'superdesk-ui-framework/react';
import {IRelatedEntitiesToFetch} from '.';

const endpointUsers = '/users';
const endpointVocabularies = '/vocabularies';

const SEPARATOR = <span style={{opacity: 0.5, marginInlineStart: 4, marginInlineEnd: 4}}>/</span>;
const AUTHORS_TO_SHOW_AT_ONCE: number = 2;

export class Authors extends SuperdeskReactComponent<IPropsItemListInfo> {
    public static getRelatedEntities(item: IArticle): IRelatedEntitiesToFetch {
        if (item.authors == null) {
            return [];
        } else {
            const userIds = item.authors
                .filter(({_id}) => _id != null) // _id is not present in ingested items
                .map((author) => ({endpoint: endpointUsers, id: author._id[0]}));

            return [
                ...userIds,
                {endpoint: endpointVocabularies, id: 'author_roles'},
            ];
        }
    }

    private related: {
        getUser: (id: string) => IUser;
        getAuthorRole: (id: string) => IVocabularyItem;
    };

    constructor(props: IPropsItemListInfo) {
        super(props);

        this.related = {
            getUser: (id) => this.props.relatedEntities[endpointUsers].get(id),
            getAuthorRole: (qcode: string) => {
                const authorRoles: IVocabulary = this.props.relatedEntities[endpointVocabularies].get('author_roles');

                return authorRoles.items.find((role) => role.qcode === qcode);
            },
        };
    }

    render() {
        const {options} = this.props;

        if (this.props.item.authors == null || options == null) {
            return null;
        }

        const authors = this.props.item.authors
            .filter(({_id}) => _id != null) // _id is not present in ingested items
            .map(({_id}) => ({userId: _id[0], roleId: _id[1]}))
            .filter(({roleId}) => options.includeRoles.includes(roleId));

        if (authors.length < 1) {
            return null;
        }

        const renderAuthorRole = (roleQcode: string) => {
            return (
                <em style={{opacity: 0.85, fontWeight: 'normal'}}>
                    {getVocabularyItemNameTranslated(this.related.getAuthorRole(roleQcode))}:{' '}
                </em>
            );
        };

        const renderUser = (userId: string) => {
            const user = this.related.getUser(userId);

            return (
                <strong>{user[options.displayField] ?? user.display_name}</strong>
            );
        };

        return (
            <React.Fragment>
                <span className="container" style={{marginInlineEnd: 0}}>
                    {
                        authors.slice(0, AUTHORS_TO_SHOW_AT_ONCE).map(({userId, roleId}, index) => (
                            <span key={userId}>
                                {index > 0 && SEPARATOR}
                                <span>{renderAuthorRole(roleId)}</span>
                                <span>{renderUser(userId)}</span>{' '}
                            </span>
                        ))
                    }
                </span>

                {/** rendering outside of .container in order to prevent ellipsis hiding "All authors" button */}
                {
                    authors.length > AUTHORS_TO_SHOW_AT_ONCE && (
                        <span>
                            {SEPARATOR}

                            <button
                                id="more-authors-button"
                                className="icon-button--small"
                                aria-label={gettext('All authors')}
                            >
                                <i className="icon-dots" />
                            </button>

                            <Popover
                                title={gettext('Authors')}
                                placement="bottom-end"
                                triggerSelector="#more-authors-button"
                                zIndex={1031}
                            >
                                <table style={{lineHeight: 1.5}}>
                                    <tbody>
                                        {
                                            authors.map(({userId, roleId}) => (
                                                <tr key={userId}>
                                                    <td style={{paddingInlineEnd: 4, opacity: 0.6}}>
                                                        {renderAuthorRole(roleId)}
                                                    </td>
                                                    <td>{renderUser(userId)}</td>
                                                </tr>
                                            ))
                                        }
                                    </tbody>
                                </table>
                            </Popover>
                        </span>
                    )
                }

                <span style={{marginInlineEnd: '1.2rem'}} />
            </React.Fragment>
        );
    }
}