From 74d58eb0837df949bfb0cbc143a4e7eccaa1658c Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Fri, 24 Feb 2023 12:08:09 +0200 Subject: [PATCH 1/6] Metadata widget --- .../metadata/metadata-item.tsx | 30 ++ .../article-widgets/metadata/metadata.tsx | 280 ++++++++++++++++++ .../authoring-integration-wrapper.tsx | 2 + .../apps/authoring-react/authoring-react.tsx | 10 +- .../manage-widget-registration.ts | 2 + scripts/core/superdesk-api.d.ts | 2 + 6 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 scripts/apps/authoring-react/article-widgets/metadata/metadata-item.tsx create mode 100644 scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx diff --git a/scripts/apps/authoring-react/article-widgets/metadata/metadata-item.tsx b/scripts/apps/authoring-react/article-widgets/metadata/metadata-item.tsx new file mode 100644 index 0000000000..5fb85601a8 --- /dev/null +++ b/scripts/apps/authoring-react/article-widgets/metadata/metadata-item.tsx @@ -0,0 +1,30 @@ +import {gettext} from 'core/utils'; +import React from 'react'; +import {ContentDivider} from 'superdesk-ui-framework'; + +interface IProps { + label: string; + value: string | number; +} + +export class MetadataItem extends React.Component { + render(): React.ReactNode { + const {label, value} = this.props; + + return ( + <> +
+
{gettext(label)}:
+
{value}
+
+ + + ); + } +} diff --git a/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx b/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx new file mode 100644 index 0000000000..c34e13c585 --- /dev/null +++ b/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx @@ -0,0 +1,280 @@ +import React from 'react'; +import {IArticleSideWidget, IExtensionActivationResult} from 'superdesk-api'; +import {gettext} from 'core/utils'; +import {AuthoringWidgetHeading} from 'apps/dashboard/widget-heading'; +import {AuthoringWidgetLayout} from 'apps/dashboard/widget-layout'; +import {Spacer} from 'core/ui/components/Spacer'; +import {Input, Select, Switch, Option, Heading, ContentDivider} from 'superdesk-ui-framework/react'; +import {MetadataItem} from './metadata-item'; +import {dataApi} from 'core/helpers/CrudManager'; +import {ILanguage} from 'superdesk-interfaces/Language'; + +// Can't call `gettext` in the top level +const getLabel = () => gettext('Metadata'); +const getNotForPublicationLabel = () => gettext('Not for publication'); +const getLegalLabel = () => gettext('Legal'); + +type IProps = React.ComponentProps< + IExtensionActivationResult['contributions']['authoringSideWidgets'][0]['component'] +>; + +interface IState { + languages: Array; +} + +class MetadataWidget extends React.PureComponent { + constructor(props: IProps) { + super(props); + + this.state = { + languages: [], + }; + } + + componentDidMount(): void { + dataApi.query( + 'languages', + 1, + {field: 'language', direction: 'ascending'}, + {}, + ).then(({_items}) => { + this.setState({ + languages: _items, + }); + }); + } + + render() { + const { + flags, + usageterms, + pubstatus, + state, + expiry, + urgency, + priority, + word_count, + source, + anpa_take_key, + genre, + dateline, + byline, + sign_off, + version, + created, + used_updated, + guid, + _id, + unique_name, + type, + language, + } = this.props.article; + + const {onArticleChange} = this.props; + + return ( + + )} + body={( + + + + {getNotForPublicationLabel()} + + { + onArticleChange({ + ...this.props.article, + flags: {...flags, marked_for_not_publication: !flags.marked_for_not_publication}, + }); + }} + value={flags.marked_for_not_publication} + /> + + + + + {getLegalLabel()} + + { + onArticleChange({ + ...this.props.article, + flags: {...flags, marked_for_legal: !flags.marked_for_legal}, + }); + }} + value={flags.marked_for_legal} + /> + + + { + onArticleChange({ + ...this.props.article, + usageterms: value, + }); + }} + /> + + + + + + + + + + + +
+ Genre: + { + genre.map((genre) => ( +
+ {genre.name} +
+ )) + } +
+
+
+ Metadata: +
+
+
{dateline.date}
+
{dateline.located.city}
+
+
+ + + + + + + + { + onArticleChange({ + ...this.props.article, + unique_name: value, + }); + }} + /> + +
+ )} + /> + ); + } +} + +export function getMetadataWidget() { + const metadataWidget: IArticleSideWidget = { + _id: 'metadata-widget', + label: getLabel(), + order: 1, + icon: 'info', + component: MetadataWidget, + }; + + return metadataWidget; +} diff --git a/scripts/apps/authoring-react/authoring-integration-wrapper.tsx b/scripts/apps/authoring-react/authoring-integration-wrapper.tsx index f205ea1b76..ebcb0bee2b 100644 --- a/scripts/apps/authoring-react/authoring-integration-wrapper.tsx +++ b/scripts/apps/authoring-react/authoring-integration-wrapper.tsx @@ -448,6 +448,7 @@ export class AuthoringIntegrationWrapper extends React.PureComponent { const OpenWidgetComponent = (() => { if (panelState.active === true) { @@ -487,6 +488,7 @@ export class AuthoringIntegrationWrapper extends React.PureComponent handleUnsavedChanges()} + onArticleChange={onArticleChange} /> ); } diff --git a/scripts/apps/authoring-react/authoring-react.tsx b/scripts/apps/authoring-react/authoring-react.tsx index cf00442f69..e7efb3b4bb 100644 --- a/scripts/apps/authoring-react/authoring-react.tsx +++ b/scripts/apps/authoring-react/authoring-react.tsx @@ -973,6 +973,13 @@ export class AuthoringReact extends React.PureCo }; } + onArticleChange(state: IStateLoaded, itemWithChanges: T) { + this.setState({ + ...state, + itemWithChanges, + }); + } + render() { const state = this.state; const {authoringStorage, fieldsAdapter, storageAdapter, getLanguage, getSidePanel} = this.props; @@ -1001,11 +1008,12 @@ export class AuthoringReact extends React.PureCo save: () => this.save(state), initiateClosing: () => this.initiateClosing(state), keepChangesAndClose: () => this.props.onClose(), + onArticleChange: (item: T) => this.onArticleChange(state, item), stealLock: () => this.forceLock(state), authoringStorage: authoringStorage, storageAdapter: storageAdapter, fieldsAdapter: fieldsAdapter, - sideWidget: this.props.sideWidget.name, + sideWidget: this.props.sideWidget?.name, toggleSideWidget: (name) => { if (name == null || this.props.sideWidget?.name === name) { this.props.onSideWidgetChange(null); diff --git a/scripts/apps/authoring-react/manage-widget-registration.ts b/scripts/apps/authoring-react/manage-widget-registration.ts index e38b9ec064..d5bbc5f449 100644 --- a/scripts/apps/authoring-react/manage-widget-registration.ts +++ b/scripts/apps/authoring-react/manage-widget-registration.ts @@ -10,6 +10,7 @@ import {getVersionsAndItemHistoryWidget} from './article-widgets/versions-and-it import {getTranslationsWidget} from './article-widgets/translations/translations'; import {getMacrosWidget} from './macros/macros'; import {getPackagesWidget} from './packages'; +import {getMetadataWidget} from './article-widgets/metadata/metadata'; const authoringReactWidgetsExtension = 'authoring-react-widgets'; @@ -20,6 +21,7 @@ export function registerAuthoringReactWidgets() { getTranslationsWidget(), getMacrosWidget(), getPackagesWidget(), + getMetadataWidget(), ]; // comments order: 3 diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 9733c1845c..5c5c1c6624 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -145,6 +145,7 @@ declare module 'superdesk-api' { hasUnsavedChanges(): boolean; handleUnsavedChanges(): Promise; handleFieldsDataChange(fieldsData: IFieldsData): void; + onArticleChange(item: T): void; save(): Promise; initiateClosing(): void; keepChangesAndClose(): void; @@ -540,6 +541,7 @@ declare module 'superdesk-api' { fieldsAdapter: IFieldsAdapter; storageAdapter: IStorageAdapter; + onArticleChange?(article: IArticle): void; onFieldsDataChange?(fieldsData?: OrderedMap): void; /** * Will prompt user to save changes. The promise will get rejected if user cancels saving. From 18e0ca8492de34111b87eea4f5cf18e6c71907f6 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Mon, 27 Feb 2023 15:41:53 +0200 Subject: [PATCH 2/6] Improvements, add more fields --- .../metadata/metadata-item.tsx | 20 +- .../article-widgets/metadata/metadata.tsx | 407 ++++++++++++------ scripts/core/superdesk-api.d.ts | 10 + 3 files changed, 302 insertions(+), 135 deletions(-) diff --git a/scripts/apps/authoring-react/article-widgets/metadata/metadata-item.tsx b/scripts/apps/authoring-react/article-widgets/metadata/metadata-item.tsx index 5fb85601a8..1dc1612c09 100644 --- a/scripts/apps/authoring-react/article-widgets/metadata/metadata-item.tsx +++ b/scripts/apps/authoring-react/article-widgets/metadata/metadata-item.tsx @@ -1,10 +1,10 @@ -import {gettext} from 'core/utils'; +import {Spacer} from 'core/ui/components/Spacer'; import React from 'react'; -import {ContentDivider} from 'superdesk-ui-framework'; +import {ContentDivider, Heading} from 'superdesk-ui-framework/react'; interface IProps { label: string; - value: string | number; + value: string | number | JSX.Element; } export class MetadataItem extends React.Component { @@ -13,16 +13,12 @@ export class MetadataItem extends React.Component { return ( <> -
-
{gettext(label)}:
+ + + {label.toUpperCase()} +
{value}
-
+ ); diff --git a/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx b/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx index c34e13c585..a5e6e5eb8c 100644 --- a/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx +++ b/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx @@ -4,10 +4,11 @@ import {gettext} from 'core/utils'; import {AuthoringWidgetHeading} from 'apps/dashboard/widget-heading'; import {AuthoringWidgetLayout} from 'apps/dashboard/widget-layout'; import {Spacer} from 'core/ui/components/Spacer'; -import {Input, Select, Switch, Option, Heading, ContentDivider} from 'superdesk-ui-framework/react'; +import {Input, Select, Switch, Option, Heading, ContentDivider, Label} from 'superdesk-ui-framework/react'; import {MetadataItem} from './metadata-item'; import {dataApi} from 'core/helpers/CrudManager'; import {ILanguage} from 'superdesk-interfaces/Language'; +import {DateTime} from 'core/ui/components/DateTime'; // Can't call `gettext` in the top level const getLabel = () => gettext('Metadata'); @@ -58,16 +59,34 @@ class MetadataWidget extends React.PureComponent { anpa_take_key, genre, dateline, + slugline, byline, sign_off, - version, - created, - used_updated, + guid, - _id, unique_name, type, language, + copyrightholder, + copyrightnotice, + creditline, + original_source, + ingest_provider_sequence, + archive_description, + ingest_provider, + keywords, + signal, + anpa_category, + place, + ednote, + _current_version, + firstcreated, + versioncreated, + renditions, + original_id, + originalCreator, + versioncreator, + description_text, } = this.props.article; const {onArticleChange} = this.props; @@ -81,12 +100,9 @@ class MetadataWidget extends React.PureComponent { /> )} body={( - - - + + + {getNotForPublicationLabel()} { onChange={() => { onArticleChange({ ...this.props.article, - flags: {...flags, marked_for_not_publication: !flags.marked_for_not_publication}, + flags: { + ...flags, + marked_for_not_publication: !flags.marked_for_not_publication, + }, }); }} value={flags.marked_for_not_publication} /> - - - + + + + + {getLegalLabel()} { value={flags.marked_for_legal} /> + + { }); }} /> + + - - - - - - - - - -
- Genre: { - genre.map((genre) => ( -
- {genre.name} -
- )) + this.state.languages.map((lang) => + , + ) } -
-
-
- Metadata: -
-
-
{dateline.date}
-
{dateline.located.city}
-
-
- - - - - - - + + + + + {(pubstatus?.length ?? 0) > 0 && ( + + )} + + {(original_source?.length ?? 0) > 0 && ( + + )} + + {(copyrightholder?.length ?? 0) > 0 && ( + + )} + + {(copyrightnotice?.length ?? 0) > 0 && ( + + )} + + {(creditline?.length ?? 0) > 0 && ( + + )} + + { + <> + + + {gettext('STATE')} + + + + + + + } + + {ingest_provider != null && ( + + )} + + { + (ingest_provider_sequence?.length ?? 0) > 0 && ( + + ) + } + + {expiry && } + + {(slugline?.length ?? 0) > 0 && } + + {(urgency?.length ?? 0) > 0 && } + + {priority && } + + {word_count > 0 && } + + {keywords && } + + {(source?.length ?? 0) > 0 && } + + + + { + signal && ( + {(signal.map((val) => <>{val.name ?? val.qcode}))}} + /> + ) + } + + {anpa_category && } + + { + (genre.length ?? 0) > 0 && ( + + + {gettext('GENRE')} + + { + genre.map((val) => ( + + {val.name} + + )) + } + + ) + } + + { + (place.length ?? 0) > 0 && ( + + + {gettext('PLACE')} + + { + place.map((val) => ( + + {val.name} + + )) + } + + ) + } + + {(ednote?.length ?? 0) > 0 && } + + + + + + {gettext('DATELINE')} + + + / + {dateline.located.city} + + + + + + + + + + {_current_version && } + + {firstcreated && ( + + )} + /> + )} + + {versioncreated && ( + } + /> + )} + + + + {(originalCreator?.length ?? 0) > 0 && ( + + )} + + {(versioncreator?.length ?? 0) > 0 && ( + + )} + + + { }); }} /> - + + + + + + { + renditions?.original != null && ( + + ) + } + + { + (archive_description?.length ?? 0) > 0 && archive_description !== description_text && ( + + ) + }
)} /> diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 5c5c1c6624..508f47e48d 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -1175,6 +1175,16 @@ declare module 'superdesk-api' { // holds info on packages groups?: Array; + creditline?: string; + original_source?: string; + ingest_provider_sequence?: string; + expiry?: string; + archive_description?: string; + original_id?: string; + originalCreator?: string; + versioncreator?: string; + archive_description?: string; + // other fields which don't exist in the database, don't belong to this entity and should be removed error?: any; _editable?: any; From a54bfc4716c9ef011165fd9e173cf146df1f8ac7 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Tue, 28 Feb 2023 11:43:04 +0200 Subject: [PATCH 3/6] Add vocabulary fields --- scripts/api/vocabularies.ts | 18 ++++++++- .../article-widgets/metadata/metadata.tsx | 40 +++++++++++++++---- scripts/apps/authoring/metadata/metadata.ts | 15 +------ 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/scripts/api/vocabularies.ts b/scripts/api/vocabularies.ts index 7503263710..ff9019c97c 100644 --- a/scripts/api/vocabularies.ts +++ b/scripts/api/vocabularies.ts @@ -1,6 +1,7 @@ import {OrderedMap} from 'immutable'; import ng from 'core/services/ng'; -import {IVocabulary} from 'superdesk-api'; +import {IArticle, IVocabulary, IVocabularyItem} from 'superdesk-api'; +import {getVocabularyItemNameTranslated} from 'core/utils'; function getAll(): OrderedMap { return OrderedMap( @@ -14,6 +15,20 @@ function isCustomFieldVocabulary(vocabulary: IVocabulary): boolean { return vocabulary.field_type != null || vocabulary.custom_field_type != null; } +function getLocaleName(term: IVocabularyItem, item: IArticle): string { + if (!term) { + return 'None'; + } + + // Item can be anything here. It might be an article object or search filters object + // depending where the function is called from. + // It's checked if language is a string in order not to confuse it when language + // is an array when called from global search filters. + const language = typeof item.language === 'string' ? item.language : undefined; + + return getVocabularyItemNameTranslated(term, language); +} + /** * Selection vocabularies may be configured to be included in content profiles. */ @@ -28,4 +43,5 @@ export const vocabularies = { getAll, isCustomFieldVocabulary, isSelectionVocabulary, + getLocaleName, }; diff --git a/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx b/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx index a5e6e5eb8c..ba8f2f8be0 100644 --- a/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx +++ b/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx @@ -9,9 +9,11 @@ import {MetadataItem} from './metadata-item'; import {dataApi} from 'core/helpers/CrudManager'; import {ILanguage} from 'superdesk-interfaces/Language'; import {DateTime} from 'core/ui/components/DateTime'; +import {vocabularies} from 'api/vocabularies'; // Can't call `gettext` in the top level const getLabel = () => gettext('Metadata'); + const getNotForPublicationLabel = () => gettext('Not for publication'); const getLegalLabel = () => gettext('Legal'); @@ -46,6 +48,8 @@ class MetadataWidget extends React.PureComponent { } render() { + const {article} = this.props; + const { flags, usageterms, @@ -62,7 +66,6 @@ class MetadataWidget extends React.PureComponent { slugline, byline, sign_off, - guid, unique_name, type, @@ -87,7 +90,7 @@ class MetadataWidget extends React.PureComponent { originalCreator, versioncreator, description_text, - } = this.props.article; + } = article; const {onArticleChange} = this.props; @@ -109,7 +112,7 @@ class MetadataWidget extends React.PureComponent { label={getNotForPublicationLabel()} onChange={() => { onArticleChange({ - ...this.props.article, + ...article, flags: { ...flags, marked_for_not_publication: !flags.marked_for_not_publication, @@ -130,7 +133,7 @@ class MetadataWidget extends React.PureComponent { label={getLegalLabel()} onChange={() => { onArticleChange({ - ...this.props.article, + ...article, flags: {...flags, marked_for_legal: !flags.marked_for_legal}, }); }} @@ -147,7 +150,7 @@ class MetadataWidget extends React.PureComponent { value={usageterms} onChange={(value) => { onArticleChange({ - ...this.props.article, + ...article, usageterms: value, }); }} @@ -161,7 +164,7 @@ class MetadataWidget extends React.PureComponent { value={language} onChange={(val) => { onArticleChange({ - ...this.props.article, + ...article, language: val, }); }} @@ -293,7 +296,28 @@ class MetadataWidget extends React.PureComponent { ) } - {anpa_category && } + { + anpa_category.name != null && ( + + ) + } + + { + vocabularies + .getAll() + .filter((cv) => article[cv.schema_field] != null) + .toArray() + .map((filtered) => ( + + )) + } { (genre.length ?? 0) > 0 && ( @@ -394,7 +418,7 @@ class MetadataWidget extends React.PureComponent { value={unique_name} onChange={(value) => { onArticleChange({ - ...this.props.article, + ...article, unique_name: value, }); }} diff --git a/scripts/apps/authoring/metadata/metadata.ts b/scripts/apps/authoring/metadata/metadata.ts index 0019031578..4815d9acd2 100644 --- a/scripts/apps/authoring/metadata/metadata.ts +++ b/scripts/apps/authoring/metadata/metadata.ts @@ -8,6 +8,7 @@ import {appConfig} from 'appConfig'; import {ISubject} from 'superdesk-api'; import {reactToAngular1} from 'superdesk-ui-framework'; import {MetaDataDropdownSingleSelectReact} from './views/MetaDataDropdownSingleSelectReact'; +import {sdApi} from 'api'; MetadataCtrl.$inject = [ '$scope', 'desks', 'metadata', 'privileges', 'datetimeHelper', 'userList', @@ -1366,19 +1367,7 @@ export function MetadataService(api, subscribersService, vocabularies, $rootScop priorityByValue: function(value) { return this._priorityByValue[value] || null; }, - getLocaleName: function(term, item: any) { - if (!term) { - return 'None'; - } - - // Item can be anything here. It might be an article object or search filters object - // depending where the function is called from. - // It's checked if language is a string in order not to confuse it when language - // is an array when called from global search filters. - const language = typeof item.language === 'string' ? item.language : undefined; - - return getVocabularyItemNameTranslated(term, language); - }, + getLocaleName: sdApi.vocabularies.getLocaleName, }; $rootScope.$on('subscriber:create', () => service.fetchSubscribers()); From d44534dfdb458a6495459f2b6eccc6701d0513e8 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Wed, 1 Mar 2023 17:55:20 +0200 Subject: [PATCH 4/6] Port annotations, place, optimizations, fix lint --- scripts/api/filters.ts | 41 +++++ scripts/api/index.ts | 2 + scripts/api/vocabularies.ts | 4 +- .../apps/archive/directives/HtmlPreview.ts | 10 +- scripts/apps/archive/styles/html-preview.scss | 7 + .../metadata/AnnotationsPreview.tsx | 42 +++++ .../article-widgets/metadata/metadata.tsx | 166 ++++++++++-------- .../apps/authoring-react/authoring-react.tsx | 1 + scripts/apps/authoring/metadata/metadata.ts | 2 +- scripts/core/filters/index.ts | 23 +-- scripts/core/superdesk-api.d.ts | 1 + 11 files changed, 196 insertions(+), 103 deletions(-) create mode 100644 scripts/api/filters.ts create mode 100644 scripts/apps/authoring-react/article-widgets/metadata/AnnotationsPreview.tsx diff --git a/scripts/api/filters.ts b/scripts/api/filters.ts new file mode 100644 index 0000000000..abc0c7ef3b --- /dev/null +++ b/scripts/api/filters.ts @@ -0,0 +1,41 @@ +import {IVocabularyItem} from 'superdesk-api'; + +function mergeArrayToString( + array: Array, + propertyName?: string, + schemeName?: string, + returnArray?: boolean, +): string | Array { + let subjectMerged = []; + + array.forEach((item) => { + const value = propertyName == null ? item : item[propertyName]; + + if (value) { + subjectMerged.push(value); + + if ((schemeName?.length ?? 0) && item.scheme !== schemeName) { + subjectMerged.pop(); + } + } + }); + + if (returnArray) { + return subjectMerged; + } + + return subjectMerged.join(', '); +} + +interface IFiltersApi { + mergeArrayToString( + array: Array, + propertyName?: string, + schemeName?: string, + returnArray?: boolean, + ): string | Array; +} + +export const filters: IFiltersApi = { + mergeArrayToString, +}; diff --git a/scripts/api/index.ts b/scripts/api/index.ts index c7b8e31fc2..3cdd361c79 100644 --- a/scripts/api/index.ts +++ b/scripts/api/index.ts @@ -10,6 +10,7 @@ import {templates} from './templates'; import {time} from './time'; import {user} from './user'; import {vocabularies} from './vocabularies'; +import {filters} from './filters'; /** * This is core API, not extensions API. @@ -29,4 +30,5 @@ export const sdApi = { user, vocabularies, highlights, + filters, }; diff --git a/scripts/api/vocabularies.ts b/scripts/api/vocabularies.ts index ff9019c97c..31ba1c6d74 100644 --- a/scripts/api/vocabularies.ts +++ b/scripts/api/vocabularies.ts @@ -15,7 +15,7 @@ function isCustomFieldVocabulary(vocabulary: IVocabulary): boolean { return vocabulary.field_type != null || vocabulary.custom_field_type != null; } -function getLocaleName(term: IVocabularyItem, item: IArticle): string { +function getVocabularyItemLabel(term: IVocabularyItem, item: IArticle): string { if (!term) { return 'None'; } @@ -43,5 +43,5 @@ export const vocabularies = { getAll, isCustomFieldVocabulary, isSelectionVocabulary, - getLocaleName, + getVocabularyItemLabel, }; diff --git a/scripts/apps/archive/directives/HtmlPreview.ts b/scripts/apps/archive/directives/HtmlPreview.ts index e36678ba92..31037da364 100644 --- a/scripts/apps/archive/directives/HtmlPreview.ts +++ b/scripts/apps/archive/directives/HtmlPreview.ts @@ -16,7 +16,15 @@ function getAnnotationTypesAsync(scope) { }); } -function getAllAnnotations(item) { +interface IAnotationData { + body: string; + id: number; + index: number; + styleName: string; + type: string; +} + +export function getAllAnnotations(item): Array { const annotations = []; for (const field in item[META_FIELD_NAME]) { diff --git a/scripts/apps/archive/styles/html-preview.scss b/scripts/apps/archive/styles/html-preview.scss index 14d1cdcb39..a188494d2d 100644 --- a/scripts/apps/archive/styles/html-preview.scss +++ b/scripts/apps/archive/styles/html-preview.scss @@ -22,3 +22,10 @@ sup.annotation-id { padding: 0; } } + +.annotation-body-react { + border-bottom: 1px dotted; + p { + display: inline; + } +} diff --git a/scripts/apps/authoring-react/article-widgets/metadata/AnnotationsPreview.tsx b/scripts/apps/authoring-react/article-widgets/metadata/AnnotationsPreview.tsx new file mode 100644 index 0000000000..8aefa61ed6 --- /dev/null +++ b/scripts/apps/authoring-react/article-widgets/metadata/AnnotationsPreview.tsx @@ -0,0 +1,42 @@ +import {getAllAnnotations} from 'apps/archive/directives/HtmlPreview'; +import {Spacer} from 'core/ui/components/Spacer'; +import {gettext} from 'core/utils'; +import React from 'react'; +import {IArticle} from 'superdesk-api'; +import {Label, ToggleBox} from 'superdesk-ui-framework/react'; + +interface IProps { + article: IArticle; +} + +export class AnnotationsPreview extends React.Component { + render(): React.ReactNode { + const {article} = this.props; + + return ( +
+
+ + { + (article?.annotations?.length ?? 0) > 0 && ( + getAllAnnotations(article).map((a) => ( + + + )) + ) + } + +
+ ); + } +} diff --git a/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx b/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx index ba8f2f8be0..82499cb222 100644 --- a/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx +++ b/scripts/apps/authoring-react/article-widgets/metadata/metadata.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Fragment} from 'react'; import {IArticleSideWidget, IExtensionActivationResult} from 'superdesk-api'; import {gettext} from 'core/utils'; import {AuthoringWidgetHeading} from 'apps/dashboard/widget-heading'; @@ -10,13 +10,14 @@ import {dataApi} from 'core/helpers/CrudManager'; import {ILanguage} from 'superdesk-interfaces/Language'; import {DateTime} from 'core/ui/components/DateTime'; import {vocabularies} from 'api/vocabularies'; +import Datetime from 'core/datetime/datetime'; +import {sdApi} from 'api'; +import {StateComponent} from 'apps/search/components/fields/state'; +import {AnnotationsPreview} from './AnnotationsPreview'; // Can't call `gettext` in the top level const getLabel = () => gettext('Metadata'); -const getNotForPublicationLabel = () => gettext('Not for publication'); -const getLegalLabel = () => gettext('Legal'); - type IProps = React.ComponentProps< IExtensionActivationResult['contributions']['authoringSideWidgets'][0]['component'] >; @@ -54,7 +55,6 @@ class MetadataWidget extends React.PureComponent { flags, usageterms, pubstatus, - state, expiry, urgency, priority, @@ -75,7 +75,6 @@ class MetadataWidget extends React.PureComponent { creditline, original_source, ingest_provider_sequence, - archive_description, ingest_provider, keywords, signal, @@ -89,11 +88,15 @@ class MetadataWidget extends React.PureComponent { original_id, originalCreator, versioncreator, - description_text, + rewritten_by, } = article; const {onArticleChange} = this.props; + const getNotForPublicationLabel: string = gettext('Not For Publication'); + const getLegalLabel: string = gettext('Legal'); + const allVocabularies = sdApi.vocabularies.getAll(); + return ( { - {getNotForPublicationLabel()} + {getNotForPublicationLabel} { onArticleChange({ ...article, @@ -127,10 +130,10 @@ class MetadataWidget extends React.PureComponent { - {getLegalLabel()} + {getLegalLabel} { onArticleChange({ ...article, @@ -144,7 +147,7 @@ class MetadataWidget extends React.PureComponent { { { } { - (archive_description?.length ?? 0) > 0 && archive_description !== description_text && ( - - ) + ['picture'].includes(article.type) + && article?.archive_description !== article?.description_text + && ( + + ) } )} diff --git a/scripts/apps/authoring-react/authoring-react.tsx b/scripts/apps/authoring-react/authoring-react.tsx index e7efb3b4bb..f1449b82c9 100644 --- a/scripts/apps/authoring-react/authoring-react.tsx +++ b/scripts/apps/authoring-react/authoring-react.tsx @@ -257,6 +257,7 @@ export class AuthoringReact extends React.PureCo this.getVocabularyItems = this.getVocabularyItems.bind(this); this.toggleField = this.toggleField.bind(this); this.updateItemWithChanges = this.updateItemWithChanges.bind(this); + this.onArticleChange = this.onArticleChange.bind(this); const setStateOriginal = this.setState.bind(this); diff --git a/scripts/apps/authoring/metadata/metadata.ts b/scripts/apps/authoring/metadata/metadata.ts index 4815d9acd2..ce6eb97f2d 100644 --- a/scripts/apps/authoring/metadata/metadata.ts +++ b/scripts/apps/authoring/metadata/metadata.ts @@ -1367,7 +1367,7 @@ export function MetadataService(api, subscribersService, vocabularies, $rootScop priorityByValue: function(value) { return this._priorityByValue[value] || null; }, - getLocaleName: sdApi.vocabularies.getLocaleName, + getLocaleName: sdApi.vocabularies.getVocabularyItemLabel, }; $rootScope.$on('subscriber:create', () => service.fetchSubscribers()); diff --git a/scripts/core/filters/index.ts b/scripts/core/filters/index.ts index d444ad9003..435ee4f3c6 100644 --- a/scripts/core/filters/index.ts +++ b/scripts/core/filters/index.ts @@ -1,6 +1,7 @@ import _ from 'lodash'; import {stripHtmlTags} from '../utils'; import {formatDatelineText} from 'apps/authoring/authoring/helpers'; +import {sdApi} from 'api'; export function removeLodash(value) { var cleanedValue = value || ''; @@ -24,27 +25,7 @@ export default angular.module('superdesk.core.filters', []) return texts.join('\n'); }) .filter('mergeWords', [function() { - return function(array, propertyName, schemeName, returnArray) { - var subjectMerged = []; - - _.forEach(array, (item) => { - var value = _.isNil(propertyName) ? item : item[propertyName]; - - if (value) { - subjectMerged.push(value); - - if (schemeName && item.scheme !== schemeName) { - subjectMerged.pop(); - } - } - }); - - if (returnArray) { - return subjectMerged; - } - - return subjectMerged.join(', '); - }; + return sdApi.filters.mergeArrayToString; }]) .filter('splitWords', () => function(word) { var split = []; diff --git a/scripts/core/superdesk-api.d.ts b/scripts/core/superdesk-api.d.ts index 508f47e48d..cc02e41dc2 100644 --- a/scripts/core/superdesk-api.d.ts +++ b/scripts/core/superdesk-api.d.ts @@ -1184,6 +1184,7 @@ declare module 'superdesk-api' { originalCreator?: string; versioncreator?: string; archive_description?: string; + rewritten_by?: string; // other fields which don't exist in the database, don't belong to this entity and should be removed error?: any; From 404a1b57785a0e135f3d0de334ac2ff67919e185 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Wed, 8 Mar 2023 16:34:05 +0200 Subject: [PATCH 5/6] Changes after review --- scripts/api/filters.ts | 41 ------------ scripts/api/vocabularies.ts | 66 ++++++++++++++++++- .../apps/archive/directives/HtmlPreview.ts | 3 +- scripts/apps/archive/styles/html-preview.scss | 7 -- .../metadata/AnnotationsPreview.tsx | 13 ++-- .../metadata/annotations-preview.scss | 6 ++ .../article-widgets/metadata/metadata.tsx | 31 ++++----- scripts/apps/authoring/metadata/metadata.ts | 6 +- scripts/core/filters/index.ts | 2 +- 9 files changed, 99 insertions(+), 76 deletions(-) delete mode 100644 scripts/api/filters.ts create mode 100644 scripts/apps/authoring-react/article-widgets/metadata/annotations-preview.scss diff --git a/scripts/api/filters.ts b/scripts/api/filters.ts deleted file mode 100644 index abc0c7ef3b..0000000000 --- a/scripts/api/filters.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {IVocabularyItem} from 'superdesk-api'; - -function mergeArrayToString( - array: Array, - propertyName?: string, - schemeName?: string, - returnArray?: boolean, -): string | Array { - let subjectMerged = []; - - array.forEach((item) => { - const value = propertyName == null ? item : item[propertyName]; - - if (value) { - subjectMerged.push(value); - - if ((schemeName?.length ?? 0) && item.scheme !== schemeName) { - subjectMerged.pop(); - } - } - }); - - if (returnArray) { - return subjectMerged; - } - - return subjectMerged.join(', '); -} - -interface IFiltersApi { - mergeArrayToString( - array: Array, - propertyName?: string, - schemeName?: string, - returnArray?: boolean, - ): string | Array; -} - -export const filters: IFiltersApi = { - mergeArrayToString, -}; diff --git a/scripts/api/vocabularies.ts b/scripts/api/vocabularies.ts index 31ba1c6d74..e154c3d284 100644 --- a/scripts/api/vocabularies.ts +++ b/scripts/api/vocabularies.ts @@ -29,19 +29,81 @@ function getVocabularyItemLabel(term: IVocabularyItem, item: IArticle): string { return getVocabularyItemNameTranslated(term, language); } +const vocabularyItemsToString = ( + array: Array, + propertyName?: keyof IVocabularyItem, + schemeName?: string, +): string => + getVocabularyItemsByPropertyName(array, propertyName, schemeName).join(', '); + +const getVocabularyItemsByPropertyName = ( + array: Array, + propertyName?: keyof IVocabularyItem, + schemeName?: string, +): Array => { + let subjectMerged = []; + + array.forEach((item) => { + const value = propertyName == null ? item : item[propertyName]; + + if (value) { + subjectMerged.push(value); + + if ((schemeName?.length ?? 0) && item.scheme !== schemeName) { + subjectMerged.pop(); + } + } + }); + + return subjectMerged; +}; + +const getVocabularyItemsPreview = ( + array: Array, + propertyName?: keyof IVocabularyItem, + schemeName?: string, + returnArray?: boolean, +): Array | string => { + if (returnArray) { + return getVocabularyItemsByPropertyName(array, propertyName, schemeName); + } else { + return vocabularyItemsToString(array, propertyName, schemeName); + } +}; + /** * Selection vocabularies may be configured to be included in content profiles. */ -function isSelectionVocabulary(vocabulary: IVocabulary) { +function isSelectionVocabulary(vocabulary: IVocabulary): boolean { return !isCustomFieldVocabulary(vocabulary) && ( vocabulary.selection_type === 'multi selection' || vocabulary.selection_type === 'single selection' ); } -export const vocabularies = { +interface IVocabulariesApi { + getAll: () => OrderedMap; + isCustomFieldVocabulary:(vocabulary: IVocabulary) => boolean; + isSelectionVocabulary: (vocabulary: IVocabulary) => boolean; + getVocabularyItemLabel: (term: IVocabularyItem, item: IArticle) => string; + getVocabularyItemsPreview: ( + array: Array, + propertyName?: keyof IVocabularyItem, + schemeName?: string, + returnArray?: boolean + ) => Array | string; + vocabularyItemsToString: ( + array: Array, + propertyName?: keyof IVocabularyItem, + schemeName?: string, + ) => string; +} + +export const vocabularies: IVocabulariesApi = { getAll, isCustomFieldVocabulary, isSelectionVocabulary, getVocabularyItemLabel, + getVocabularyItemsPreview, + vocabularyItemsToString, }; diff --git a/scripts/apps/archive/directives/HtmlPreview.ts b/scripts/apps/archive/directives/HtmlPreview.ts index 31037da364..51e4e5fdcc 100644 --- a/scripts/apps/archive/directives/HtmlPreview.ts +++ b/scripts/apps/archive/directives/HtmlPreview.ts @@ -2,6 +2,7 @@ import {getAnnotationsFromItem} from 'core/editor3/helpers/editor3CustomData'; import {META_FIELD_NAME} from 'core/editor3/helpers/fieldsMeta'; import ng from 'core/services/ng'; import {gettext} from 'core/utils'; +import {IArticle} from 'superdesk-api'; function getAnnotationTypesAsync(scope) { ng.get('metadata').initialize() @@ -24,7 +25,7 @@ interface IAnotationData { type: string; } -export function getAllAnnotations(item): Array { +export function getAllAnnotations(item: IArticle): Array { const annotations = []; for (const field in item[META_FIELD_NAME]) { diff --git a/scripts/apps/archive/styles/html-preview.scss b/scripts/apps/archive/styles/html-preview.scss index a188494d2d..14d1cdcb39 100644 --- a/scripts/apps/archive/styles/html-preview.scss +++ b/scripts/apps/archive/styles/html-preview.scss @@ -22,10 +22,3 @@ sup.annotation-id { padding: 0; } } - -.annotation-body-react { - border-bottom: 1px dotted; - p { - display: inline; - } -} diff --git a/scripts/apps/authoring-react/article-widgets/metadata/AnnotationsPreview.tsx b/scripts/apps/authoring-react/article-widgets/metadata/AnnotationsPreview.tsx index 8aefa61ed6..12de91baed 100644 --- a/scripts/apps/authoring-react/article-widgets/metadata/AnnotationsPreview.tsx +++ b/scripts/apps/authoring-react/article-widgets/metadata/AnnotationsPreview.tsx @@ -4,6 +4,7 @@ import {gettext} from 'core/utils'; import React from 'react'; import {IArticle} from 'superdesk-api'; import {Label, ToggleBox} from 'superdesk-ui-framework/react'; +import './annotations-preview.scss'; interface IProps { article: IArticle; @@ -18,17 +19,17 @@ export class AnnotationsPreview extends React.Component {
{ - (article?.annotations?.length ?? 0) > 0 && ( - getAllAnnotations(article).map((a) => ( - -