diff --git a/app/editor/.yarn/cache/tno-core-npm-0.1.139-c5c4c82c68-92382ad294.zip b/app/editor/.yarn/cache/tno-core-file-d1c5d7637f-a560860593.zip similarity index 86% rename from app/editor/.yarn/cache/tno-core-npm-0.1.139-c5c4c82c68-92382ad294.zip rename to app/editor/.yarn/cache/tno-core-file-d1c5d7637f-a560860593.zip index 5fd8e655b0..ada23b315c 100644 Binary files a/app/editor/.yarn/cache/tno-core-npm-0.1.139-c5c4c82c68-92382ad294.zip and b/app/editor/.yarn/cache/tno-core-file-d1c5d7637f-a560860593.zip differ diff --git a/app/editor/package.json b/app/editor/package.json index 4a121910c8..b40aa1123d 100644 --- a/app/editor/package.json +++ b/app/editor/package.json @@ -61,7 +61,7 @@ "redux-logger": "3.0.6", "styled-components": "6.1.11", "stylis": "4.3.2", - "tno-core": "0.1.139" + "tno-core": "./tno-core-0.1.139.tgz" }, "devDependencies": { "@simbathesailor/use-what-changed": "2.0.0", diff --git a/app/editor/tno-core-0.1.139.tgz b/app/editor/tno-core-0.1.139.tgz new file mode 100644 index 0000000000..31875e044d Binary files /dev/null and b/app/editor/tno-core-0.1.139.tgz differ diff --git a/app/editor/yarn.lock b/app/editor/yarn.lock index 186100d24e..15a0eaeba2 100644 --- a/app/editor/yarn.lock +++ b/app/editor/yarn.lock @@ -11074,7 +11074,7 @@ __metadata: sass-extract-loader: 1.1.0 styled-components: 6.1.11 stylis: 4.3.2 - tno-core: 0.1.139 + tno-core: ./tno-core-0.1.139.tgz typescript: 4.9.5 languageName: unknown linkType: soft @@ -15258,9 +15258,9 @@ __metadata: languageName: node linkType: hard -"tno-core@npm:0.1.139": +"tno-core@file:./tno-core-0.1.139.tgz::locator=mmi-editor-app%40workspace%3A.": version: 0.1.139 - resolution: "tno-core@npm:0.1.139" + resolution: "tno-core@file:./tno-core-0.1.139.tgz::locator=mmi-editor-app%40workspace%3A." dependencies: "@elastic/elasticsearch": ^8.13.1 "@fortawesome/free-solid-svg-icons": ^6.4.2 @@ -15293,7 +15293,7 @@ __metadata: styled-components: ^6.1.11 stylis: ^4.3.2 yup: ^1.1.1 - checksum: 92382ad294654dbbb8f8387726b6a96f889ba4d65ea452560dfc6cc9b1fc663ba26152b569e19ee5007067ee1734af1a75a2c06b57c33d3292cf235e2d8c1775 + checksum: a560860593ae77c9d75a13223b2c123473c685451375346927b7b52506e22fa3a64e4dc57669ef30546e241e22fd87f49652539efbd856718864e5d0e1631133 languageName: node linkType: hard diff --git a/app/subscriber/.yarn/cache/tno-core-npm-0.1.139-c5c4c82c68-92382ad294.zip b/app/subscriber/.yarn/cache/tno-core-file-20cc8360b9-a560860593.zip similarity index 86% rename from app/subscriber/.yarn/cache/tno-core-npm-0.1.139-c5c4c82c68-92382ad294.zip rename to app/subscriber/.yarn/cache/tno-core-file-20cc8360b9-a560860593.zip index 5fd8e655b0..ada23b315c 100644 Binary files a/app/subscriber/.yarn/cache/tno-core-npm-0.1.139-c5c4c82c68-92382ad294.zip and b/app/subscriber/.yarn/cache/tno-core-file-20cc8360b9-a560860593.zip differ diff --git a/app/subscriber/package.json b/app/subscriber/package.json index 2902e359be..82f8f1f22a 100644 --- a/app/subscriber/package.json +++ b/app/subscriber/package.json @@ -44,7 +44,7 @@ "sheetjs": "file:packages/xlsx-0.20.1.tgz", "styled-components": "6.1.11", "stylis": "4.3.2", - "tno-core": "0.1.139" + "tno-core": "./tno-core-0.1.139.tgz" }, "devDependencies": { "@testing-library/jest-dom": "6.4.5", diff --git a/app/subscriber/src/features/my-reports/edit/content/ContentEditForm.tsx b/app/subscriber/src/features/my-reports/edit/content/ContentEditForm.tsx index 4c8e0fa3be..384cb0e5af 100644 --- a/app/subscriber/src/features/my-reports/edit/content/ContentEditForm.tsx +++ b/app/subscriber/src/features/my-reports/edit/content/ContentEditForm.tsx @@ -215,7 +215,14 @@ export const ContentEditForm = React.forwardRef diff --git a/app/subscriber/src/features/my-reports/edit/content/stories/ContentForm.tsx b/app/subscriber/src/features/my-reports/edit/content/stories/ContentForm.tsx index 205fc630bd..8cceb709db 100644 --- a/app/subscriber/src/features/my-reports/edit/content/stories/ContentForm.tsx +++ b/app/subscriber/src/features/my-reports/edit/content/stories/ContentForm.tsx @@ -1,9 +1,11 @@ +import { Form, Formik } from 'formik'; import React from 'react'; -import { useApp } from 'store/hooks'; +import { useApp, useLookup } from 'store/hooks'; import { useProfileStore } from 'store/slices'; import { Col, ContentTypeName, + FormikSentiment, IContentModel, Loading, Show, @@ -12,6 +14,8 @@ import { Wysiwyg, } from 'tno-core'; +import { sentimentFormSchema } from '../../validation/SentimentFormSchema'; + export interface IContentFormProps extends Omit, 'content'> { /** The content being edited */ content?: IContentModel; @@ -43,20 +47,47 @@ export const ContentForm: React.FC = ({ }) => { const [{ userInfo }] = useApp(); const [{ impersonate }] = useProfileStore(); + const [{ tonePools }] = useLookup(); if (!content) return null; const userId = impersonate?.id ?? userInfo?.id ?? 0; const isAV = content.contentType === ContentTypeName.AudioVideo; - const versions = content.versions?.[userId] ?? { - byline: content.byline, - headline: content.headline, - summary: '', - body: isAV - ? content.isApproved && content.body - ? content.body - : content.summary - : content.body, + + const sentiment = + content.versions?.[userId]?.tone !== undefined && content.versions?.[userId]?.tone != null + ? content.versions?.[userId]?.tone + : content.tonePools.length + ? content.tonePools[0].value + : undefined; + + const versions = { + ...content.versions?.[userId], + byline: content.versions?.[userId]?.byline ?? content.byline, + headline: content.versions?.[userId]?.headline ?? content.headline, + summary: content.versions?.[userId]?.summary ?? '', + body: + content.versions?.[userId]?.body ?? + (isAV ? (content.isApproved && content.body ? content.body : content.summary) : content.body), + tonePools: + sentiment !== undefined && sentiment !== null + ? [{ value: sentiment, id: tonePools[0].id }] + : [], + }; + + const handleSentimentChange = (newTonePools: any) => { + const validTonePools = Array.isArray(newTonePools) ? newTonePools : []; + const updatedContent = { + ...content, + versions: { + ...content.versions, + [userId]: { + ...content.versions?.[userId], + tone: validTonePools[0]?.value, + }, + }, + }; + onContentChange?.(updatedContent); }; return show === 'none' ? null : ( @@ -152,6 +183,19 @@ export const ContentForm: React.FC = ({ onContentChange?.(values); }} /> + {}}> + {() => ( +
+ + + )} +
); diff --git a/app/subscriber/src/features/my-reports/edit/validation/SentimentFormSchema.ts b/app/subscriber/src/features/my-reports/edit/validation/SentimentFormSchema.ts new file mode 100644 index 0000000000..a48f63d476 --- /dev/null +++ b/app/subscriber/src/features/my-reports/edit/validation/SentimentFormSchema.ts @@ -0,0 +1,9 @@ +import { array, number, object } from 'yup'; + +export const sentimentFormSchema = object().shape({ + tonePools: array().of( + object().shape({ + value: number().required('Tone value is required'), + }), + ), +}); diff --git a/app/subscriber/src/store/hooks/lookup/useLookup.ts b/app/subscriber/src/store/hooks/lookup/useLookup.ts index e87ae665de..ee36eddd03 100644 --- a/app/subscriber/src/store/hooks/lookup/useLookup.ts +++ b/app/subscriber/src/store/hooks/lookup/useLookup.ts @@ -23,6 +23,7 @@ import { ITopicScoreRuleModel, IUserModel, StorageKeys, + useApiEditorTonePools, useApiSubscriberCache, useApiSubscriberMinisters, } from 'tno-core'; @@ -44,6 +45,7 @@ export const useLookup = (): [ILookupState, ILookupController] => { const cache = useApiSubscriberCache(); const lookups = useApiLookups(); const ministers = useApiSubscriberMinisters(); + const tonePools = useApiEditorTonePools(); const controller = React.useMemo( () => ({ @@ -156,13 +158,27 @@ export const useLookup = (): [ILookupState, ILookupController] => { 'lookup', ); }, + getTonePools: async () => { + return await fetchIfNoneMatch( + StorageKeys.TonePools, + dispatch, + (etag) => tonePools.getTonePools(etag), + (results) => { + const values = results ?? []; + store.storeTonePools(values); + return values; + }, + true, + 'lookup', + ); + }, init: async () => { // TODO: Handle failures await controller.getLookups(); store.storeIsReady(true); }, }), - [cache, dispatch, lookups, ministers, store], + [cache, dispatch, lookups, ministers, tonePools, store], ); return [state, controller]; diff --git a/app/subscriber/tno-core-0.1.139.tgz b/app/subscriber/tno-core-0.1.139.tgz new file mode 100644 index 0000000000..31875e044d Binary files /dev/null and b/app/subscriber/tno-core-0.1.139.tgz differ diff --git a/app/subscriber/yarn.lock b/app/subscriber/yarn.lock index 7565257c93..8ad6229b7f 100644 --- a/app/subscriber/yarn.lock +++ b/app/subscriber/yarn.lock @@ -10697,7 +10697,7 @@ __metadata: sheetjs: "file:packages/xlsx-0.20.1.tgz" styled-components: 6.1.11 stylis: 4.3.2 - tno-core: 0.1.139 + tno-core: ./tno-core-0.1.139.tgz typescript: 4.9.5 languageName: unknown linkType: soft @@ -14729,9 +14729,9 @@ __metadata: languageName: node linkType: hard -"tno-core@npm:0.1.139": +"tno-core@file:./tno-core-0.1.139.tgz::locator=mmi-subscriber-app%40workspace%3A.": version: 0.1.139 - resolution: "tno-core@npm:0.1.139" + resolution: "tno-core@file:./tno-core-0.1.139.tgz::locator=mmi-subscriber-app%40workspace%3A." dependencies: "@elastic/elasticsearch": ^8.13.1 "@fortawesome/free-solid-svg-icons": ^6.4.2 @@ -14764,7 +14764,7 @@ __metadata: styled-components: ^6.1.11 stylis: ^4.3.2 yup: ^1.1.1 - checksum: 92382ad294654dbbb8f8387726b6a96f889ba4d65ea452560dfc6cc9b1fc663ba26152b569e19ee5007067ee1734af1a75a2c06b57c33d3292cf235e2d8c1775 + checksum: a560860593ae77c9d75a13223b2c123473c685451375346927b7b52506e22fa3a64e4dc57669ef30546e241e22fd87f49652539efbd856718864e5d0e1631133 languageName: node linkType: hard diff --git a/libs/net/entities/Models/ContentVersion.cs b/libs/net/entities/Models/ContentVersion.cs index 2ac38f2a06..8d70790fdd 100644 --- a/libs/net/entities/Models/ContentVersion.cs +++ b/libs/net/entities/Models/ContentVersion.cs @@ -56,6 +56,10 @@ public class ContentVersion /// public bool IsPrivate { get; set; } = false; + /// + /// get/set - Tone for the content + /// + public int? Tone {get;set;} #endregion @@ -82,6 +86,7 @@ public ContentVersion(Content content, User owner) this.Body = content.Body; this.SourceUrl = content.SourceUrl; this.IsPrivate = content.IsPrivate; + this.Tone = content.TonePools.Count > 0 ? (int?)content.TonePools[0].Id : null; } /// diff --git a/libs/npm/core/src/components/formik/sentiment/FormikSentiment.tsx b/libs/npm/core/src/components/formik/sentiment/FormikSentiment.tsx index 359488dee0..acf80065d7 100644 --- a/libs/npm/core/src/components/formik/sentiment/FormikSentiment.tsx +++ b/libs/npm/core/src/components/formik/sentiment/FormikSentiment.tsx @@ -1,5 +1,6 @@ import { getIn, useFormikContext } from 'formik'; import React from 'react'; +import { FaFrown, FaMeh, FaSmile } from 'react-icons/fa'; import FaceFrownOpen from '../../../assets/face-frown-open.svg'; import FaceGrinWide from '../../../assets/face-grin-wide.svg'; @@ -17,10 +18,14 @@ export interface IFormikSentimentProps { label?: string; /** Sentiment options. */ options: ITonePoolModel[]; + /** Sentiment icon colored */ + coloredIcon?: boolean; /** The name of the default tone pool */ defaultTonePoolName?: string; /** The id of the default tone pool */ defaultTonePoolId?: number; + /** Update upper form */ + onSentimentChange?: (tonePools: any) => void; /** Whether this field is required. */ required?: boolean; } @@ -34,8 +39,10 @@ export const FormikSentiment = ({ name, label = 'Sentiment', options, + coloredIcon = false, defaultTonePoolId, defaultTonePoolName = 'Default', + onSentimentChange, ...rest }: IFormikSentimentProps) => { const { values, setFieldValue, touched, errors } = useFormikContext(); @@ -44,8 +51,10 @@ export const FormikSentiment = ({ name, label, options, + coloredIcon, defaultTonePoolId, defaultTonePoolName, + onSentimentChange, ...rest, }; const defaultTonePool = options.find( @@ -68,11 +77,23 @@ export const FormikSentiment = ({ const determineIndicator = (option: number) => { if (option === 5) { - return {option.toString()}; + return coloredIcon ? ( + + ) : ( + {option.toString()} + ); } else if (option === 0) { - return {option.toString()}; + return coloredIcon ? ( + + ) : ( + {option.toString()} + ); } else if (option === -5) { - return {option.toString()}; + return coloredIcon ? ( + + ) : ( + {option.toString()} + ); } else { return  ; } @@ -92,11 +113,11 @@ export const FormikSentiment = ({ key={option} onClick={() => { setActive(option); - setFieldValue( - name.toString(), - !!defaultTonePool ? [{ ...defaultTonePool, value: option }] : [], - true, - ); + const updatedTonePools = !!defaultTonePool + ? [{ ...defaultTonePool, value: option }] + : []; + setFieldValue(name.toString(), updatedTonePools, true); + onSentimentChange?.(updatedTonePools); }} > {option} diff --git a/libs/npm/core/src/components/formik/sentiment/styled/FormikSentiment.tsx b/libs/npm/core/src/components/formik/sentiment/styled/FormikSentiment.tsx index 4826efdf02..fe2288be35 100644 --- a/libs/npm/core/src/components/formik/sentiment/styled/FormikSentiment.tsx +++ b/libs/npm/core/src/components/formik/sentiment/styled/FormikSentiment.tsx @@ -50,4 +50,10 @@ export const FormikSentiment = styled.div.attrs>(({ r color: red; } } + + .tone-icon { + font-size: 1.4rem; + margin-bottom: 0rem; + margin-left: 0.5rem; + } `; diff --git a/libs/npm/core/src/hooks/api/interfaces/IContentVersionModel.ts b/libs/npm/core/src/hooks/api/interfaces/IContentVersionModel.ts index c4d79510ce..a83a24618e 100644 --- a/libs/npm/core/src/hooks/api/interfaces/IContentVersionModel.ts +++ b/libs/npm/core/src/hooks/api/interfaces/IContentVersionModel.ts @@ -9,4 +9,5 @@ export interface IContentVersionModel extends IAuditColumnsModel { page?: string; summary?: string; body?: string; + tone?: number; }