From 2d4849c2164048d05ee50fd76a77ed377a13e686 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Thu, 28 Mar 2024 08:00:51 +0200 Subject: [PATCH] Related items implementation (#1931) * Add frontend support for related_items field * Implement related_items archive search modal * Code cleanup, fix modal * Temporary changes so related_items appears * Remove implementation from planing, move to superdesk-belga * Use UIF helper classes, move components from belga repo * 1. Reimplement layout, so preview panel stretches properly and we follow design guidelines, and component reusability 2. Use `getSortedFieldsFiltered` to improve performance by not iterating 2 more times on the same object * Remove dead code, properly get available languages for filter, move ContentDivider so it appears in the UI * Adjust styles * Move client-core import to SuperdeskAPI Also: * Fix search language for Belga archive * Use appConfig.planning.event_related_item_search_provider_name instead of hard coded url for search_provider * Remove all fields from related_items that aren't in the db schema * Add search_provider ID to article when adding it to list of articles to add --------- Co-authored-by: Mark Pittaway --- .../EditorFieldEventRelatedItems.tsx | 113 ++++++ .../EventsRelatedArticlesModal.tsx | 327 ++++++++++++++++++ .../EventRelatedArticles/PreviewArticle.tsx | 134 +++++++ .../RelatedArticlesListComponent.tsx | 213 ++++++++++++ .../editor/EventRelatedArticles/utils.ts | 187 ++++++++++ client/components/fields/resources/events.ts | 13 + .../fields/resources/registerEditorFields.tsx | 2 +- client/extension_bridge.ts | 16 +- client/interfaces.ts | 20 +- client/planning-extension/src/extension.ts | 3 + .../src/extension_bridge.ts | 13 +- client/validators/index.ts | 1 + package-lock.json | 272 +++++++++------ server/planning/events/events_schema.py | 3 +- server/planning/types/__init__.py | 1 + 15 files changed, 1202 insertions(+), 116 deletions(-) create mode 100644 client/components/fields/editor/EventRelatedArticles/EditorFieldEventRelatedItems.tsx create mode 100644 client/components/fields/editor/EventRelatedArticles/EventsRelatedArticlesModal.tsx create mode 100644 client/components/fields/editor/EventRelatedArticles/PreviewArticle.tsx create mode 100644 client/components/fields/editor/EventRelatedArticles/RelatedArticlesListComponent.tsx create mode 100644 client/components/fields/editor/EventRelatedArticles/utils.ts diff --git a/client/components/fields/editor/EventRelatedArticles/EditorFieldEventRelatedItems.tsx b/client/components/fields/editor/EventRelatedArticles/EditorFieldEventRelatedItems.tsx new file mode 100644 index 000000000..c2df97f64 --- /dev/null +++ b/client/components/fields/editor/EventRelatedArticles/EditorFieldEventRelatedItems.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; + +import {IArticle} from 'superdesk-api'; +import {IEditorFieldProps, IEventItem, IProfileSchemaTypeList} from 'interfaces'; +import {superdeskApi} from '../../../../superdeskApi'; + +import {cleanArticlesFields} from './utils'; + +import {ButtonGroup, Button, Spacer} from 'superdesk-ui-framework/react'; +import {showModal} from '@superdesk/common'; +import {EventsRelatedArticlesModal} from './EventsRelatedArticlesModal'; +import {RelatedArticlesListComponent} from './RelatedArticlesListComponent'; +import {Row} from '../../../UI/Form'; + +import '../EventRelatedPlannings/style.scss'; + +interface IProps extends IEditorFieldProps { + item: IEventItem; + schema?: IProfileSchemaTypeList; +} + +interface IState { + selectedRelatedArticles: Array>; +} + +export class EditorFieldEventRelatedItems extends React.PureComponent { + constructor(props: IProps) { + super(props); + + this.state = { + selectedRelatedArticles: this.props.item.related_items as Array>, + }; + } + + componentDidUpdate(prevProps: Readonly): void { + const relatedItemsUpdated = this.props.item.related_items as Array>; + + if (JSON.stringify(relatedItemsUpdated) !== JSON.stringify(prevProps.item.related_items)) { + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ + selectedRelatedArticles: relatedItemsUpdated, + }); + } + } + + render() { + const disabled = this.props.disabled || this.props.schema?.read_only; + const {gettext} = superdeskApi.localization; + + return ( +
+ + + {disabled ? null : ( + +
+ ); + } +} diff --git a/client/components/fields/editor/EventRelatedArticles/EventsRelatedArticlesModal.tsx b/client/components/fields/editor/EventRelatedArticles/EventsRelatedArticlesModal.tsx new file mode 100644 index 000000000..2f1fc86c7 --- /dev/null +++ b/client/components/fields/editor/EventRelatedArticles/EventsRelatedArticlesModal.tsx @@ -0,0 +1,327 @@ +import React from 'react'; + +import {IArticle, IRestApiResponse, ISuperdeskQuery} from 'superdesk-api'; +import {IPlanningConfig} from '../../../../interfaces'; +import {superdeskApi} from '../../../../superdeskApi'; +import {appConfig as config} from 'appConfig'; + +import {cleanArticlesFields} from './utils'; + +import { + SearchBar, + Modal, + Dropdown, + Spacer, + Button, + WithPagination, + Loader, + Panel, + PanelHeader, + PanelContent, + PanelContentBlock, + LayoutContainer, + HeaderPanel, + MainPanel, + RightPanel, + SubNav, +} from 'superdesk-ui-framework/react'; +import {RelatedArticlesListComponent} from './RelatedArticlesListComponent'; +import {PreviewArticle} from './PreviewArticle'; + +import '../../../../components/Archive/ArchivePreview/style.scss'; + +const appConfig = config as IPlanningConfig; + +interface IProps { + closeModal: () => void; + selectedArticles?: Array>; + onChange: (value: Array>) => void; +} + +interface IState { + articles: Array>; + searchQuery: string; + loading: boolean; + currentlySelectedArticles?: Array>; + activeLanguage: {code: string; label: string;}; + previewItem: Partial | null; + repo: string | null; + languages: Array<{label: string, code: string}>; +} + + +export class EventsRelatedArticlesModal extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + articles: [], + searchQuery: '', + loading: true, + currentlySelectedArticles: this.props.selectedArticles, + activeLanguage: {label: 'All languages', code: ''}, + previewItem: null, + repo: null, + languages: [], + }; + } + + componentDidMount() { + const {httpRequestJsonLocal} = superdeskApi; + const {getLanguageVocabulary} = superdeskApi.entities.vocabulary; + const searchProviderName = appConfig.planning.event_related_item_search_provider_name; + + if (searchProviderName != null) { + httpRequestJsonLocal>({ + method: 'GET', + path: '/search_providers', + urlParams: { + manage: 1, + } + }).then((result) => { + const repoId = result._items.find((provider) => ( + provider.search_provider === searchProviderName + ))?._id; + + this.setState({ + repo: repoId, + languages: [ + ...getLanguageVocabulary().items.map(({name, qcode}) => ({label: name, code: qcode})), + { + label: 'All languages', + code: '' + } + ] + }); + }); + } + } + + componentDidUpdate(_prevProps: Readonly, prevState: Readonly): void { + if (prevState.activeLanguage.code !== this.state.activeLanguage.code + || prevState.searchQuery !== this.state.searchQuery + || prevState.repo !== this.state.repo + ) { + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ + loading: true, + }); + } + } + + render(): React.ReactNode { + const {closeModal} = this.props; + const {gettext} = superdeskApi.localization; + const {getProjectedFieldsArticle} = superdeskApi.entities.article; + const {httpRequestJsonLocal} = superdeskApi; + const {superdeskToElasticQuery} = superdeskApi.helpers; + + return ( + +