From a29185196b1440aeabefc47590fe8db70435a77d Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 27 Mar 2024 15:13:01 +0000 Subject: [PATCH 001/215] Add piping for answer labels in list additional pages --- eq-author-api/schema/resolvers/utils/folderGetters.js | 8 ++++++++ .../customKeywords/validatePipingAnswerInLabel.js | 7 +++++-- .../src/App/page/Design/answers/BasicAnswer/index.js | 7 ++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/eq-author-api/schema/resolvers/utils/folderGetters.js b/eq-author-api/schema/resolvers/utils/folderGetters.js index b51ce09db0..27c685d4e8 100644 --- a/eq-author-api/schema/resolvers/utils/folderGetters.js +++ b/eq-author-api/schema/resolvers/utils/folderGetters.js @@ -10,9 +10,17 @@ const getFolderById = (ctx, id) => find(getFolders(ctx), { id }); const getFolderByPageId = (ctx, id) => find(getFolders(ctx), ({ pages }) => pages && some(pages, { id })); +const getFolderByAnswerId = (ctx, id) => + find( + getFolders(ctx), + ({ pages }) => + pages && some(pages, ({ answers }) => answers && some(answers, { id })) + ); + module.exports = { getFolders, getFoldersBySectionId, getFolderById, getFolderByPageId, + getFolderByAnswerId, }; diff --git a/eq-author-api/src/validation/customKeywords/validatePipingAnswerInLabel.js b/eq-author-api/src/validation/customKeywords/validatePipingAnswerInLabel.js index 96937ccc6e..552c7c1109 100644 --- a/eq-author-api/src/validation/customKeywords/validatePipingAnswerInLabel.js +++ b/eq-author-api/src/validation/customKeywords/validatePipingAnswerInLabel.js @@ -9,6 +9,7 @@ const { idExists, getListByAnswerId, getSupplementaryDataAsCollectionListbyFieldId, + getFolderByAnswerId, } = require("../../../schema/resolvers/utils"); const pipedAnswerIdRegex = @@ -31,6 +32,7 @@ module.exports = (ajv) => rootData: questionnaire, } ) { + const folder = getFolderByAnswerId({ questionnaire }, parentData.id); isValid.errors = []; const pipedIdList = []; @@ -78,8 +80,9 @@ module.exports = (ajv) => if (list) { if (!(dataPiped === "supplementary" && list.listName === "")) { if ( - list.id !== parentData.repeatingLabelAndInputListId || - !parentData.repeatingLabelAndInput + !folder.listId && + (list.id !== parentData.repeatingLabelAndInputListId || + !parentData.repeatingLabelAndInput) ) { return hasError(PIPING_TITLE_DELETED); } diff --git a/eq-author/src/App/page/Design/answers/BasicAnswer/index.js b/eq-author/src/App/page/Design/answers/BasicAnswer/index.js index f2b766905a..842e129e71 100644 --- a/eq-author/src/App/page/Design/answers/BasicAnswer/index.js +++ b/eq-author/src/App/page/Design/answers/BasicAnswer/index.js @@ -124,7 +124,12 @@ export const StatelessBasicAnswer = ({ controls={pipingControls} size="large" allowableTypes={[ANSWER, METADATA]} - listId={answer.repeatingLabelAndInputListId ?? null} + listId={ + (answer.repeatingLabelAndInputListId || + page.section?.repeatingSectionListId || + page.folder?.listId) ?? + null + } hasLabelErrors={hasLabelErrors(answer.validationErrorInfo?.errors)} autoFocus={!answer.label} /> From f9dc7fa5cb79948a09bdb6d43a70c0f9e83906ce Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 9 Apr 2024 08:55:59 +0100 Subject: [PATCH 002/215] Add strong tags for highlighted text in rich text editors --- .../src/components/RichTextEditor/Toolbar.js | 4 +-- .../src/components/RichTextEditor/index.js | 2 +- .../RichTextEditor/utils/convert.js | 27 +++++++++++++++++-- .../utils/createFormatStripper.js | 2 +- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/eq-author/src/components/RichTextEditor/Toolbar.js b/eq-author/src/components/RichTextEditor/Toolbar.js index c37639a989..73919cd328 100644 --- a/eq-author/src/components/RichTextEditor/Toolbar.js +++ b/eq-author/src/components/RichTextEditor/Toolbar.js @@ -31,11 +31,11 @@ export const styleButtons = [ style: "BOLD", }, { - id: "emphasis", + id: "highlight", title: "Highlight", icon: iconEmphasis, type: STYLE_INLINE, - style: "ITALIC", + style: "HIGHLIGHT", }, ]; diff --git a/eq-author/src/components/RichTextEditor/index.js b/eq-author/src/components/RichTextEditor/index.js index d595305eb8..3aa6b685f0 100644 --- a/eq-author/src/components/RichTextEditor/index.js +++ b/eq-author/src/components/RichTextEditor/index.js @@ -32,7 +32,7 @@ import PasteModal, { } from "components/modals/PasteModal"; const styleMap = { - ITALIC: { + HIGHLIGHT: { backgroundColor: "#cbe2c8", }, }; diff --git a/eq-author/src/components/RichTextEditor/utils/convert.js b/eq-author/src/components/RichTextEditor/utils/convert.js index 3abf1dce9d..7c9e3769fc 100644 --- a/eq-author/src/components/RichTextEditor/utils/convert.js +++ b/eq-author/src/components/RichTextEditor/utils/convert.js @@ -1,3 +1,4 @@ +import React from "react"; import { convertToHTML, convertFromHTML } from "draft-convert"; export const toHTML = (entityMap) => { @@ -6,7 +7,18 @@ export const toHTML = (entityMap) => { return mapper ? mapper(entity) : originalText; }; - const convert = convertToHTML({ entityToHTML }); + // Adds strong tags with class names to text based on the text's style + const styleToHTML = (style) => { + if (style === "BOLD") { + return ; + } + + if (style === "HIGHLIGHT") { + return ; + } + }; + + const convert = convertToHTML({ styleToHTML, entityToHTML }); return (editorState) => convert(editorState.getCurrentContent()); }; @@ -21,5 +33,16 @@ export const fromHTML = (nodeToFn) => { return entity || null; }; - return convertFromHTML({ htmlToEntity }); + // Applies specific styles based on tags and class names - buttons in the toolbar are activated based on these styles + const htmlToStyle = (nodeName, node, currentStyle) => { + if (nodeName === "strong" && node.className === "highlight") { + return currentStyle.add("HIGHLIGHT").remove("BOLD"); + } + if (nodeName === "strong" && node.className === "bold") { + return currentStyle.add("BOLD"); + } + return currentStyle; + }; + + return convertFromHTML({ htmlToStyle, htmlToEntity }); }; diff --git a/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js b/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js index 1dec08f961..7c75b29e86 100644 --- a/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js +++ b/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js @@ -5,7 +5,7 @@ import { filterConfig as linkFilterConfig } from "components/RichTextEditor/Link const mapper = { bold: { format: "BOLD", type: "styles" }, - emphasis: { format: "ITALIC", type: "styles" }, + highlight: { format: "HIGHLIGHT", type: "styles" }, list: { format: "unordered-list-item", type: "blocks" }, heading: { format: "header-two", type: "blocks" }, }; From 8adada303e3c5a037e2bebfcbab2da0325c027d1 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 9 Apr 2024 09:07:56 +0100 Subject: [PATCH 003/215] Update snapshot --- .../__snapshots__/RichTextEditor.test.js.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eq-author/src/components/RichTextEditor/__snapshots__/RichTextEditor.test.js.snap b/eq-author/src/components/RichTextEditor/__snapshots__/RichTextEditor.test.js.snap index 044c021cca..dec5001f28 100644 --- a/eq-author/src/components/RichTextEditor/__snapshots__/RichTextEditor.test.js.snap +++ b/eq-author/src/components/RichTextEditor/__snapshots__/RichTextEditor.test.js.snap @@ -160,7 +160,7 @@ exports[`components/RichTextEditor should allow multiline input 1`] = ` blockStyleFn={[Function]} customStyleMap={ Object { - "ITALIC": Object { + "HIGHLIGHT": Object { "backgroundColor": "#cbe2c8", }, } @@ -497,7 +497,7 @@ exports[`components/RichTextEditor should render 1`] = ` blockStyleFn={[Function]} customStyleMap={ Object { - "ITALIC": Object { + "HIGHLIGHT": Object { "backgroundColor": "#cbe2c8", }, } @@ -892,7 +892,7 @@ exports[`components/RichTextEditor should render existing content 1`] = ` blockStyleFn={[Function]} customStyleMap={ Object { - "ITALIC": Object { + "HIGHLIGHT": Object { "backgroundColor": "#cbe2c8", }, } @@ -1279,7 +1279,7 @@ exports[`components/RichTextEditor should show as disabled and readonly when dis blockStyleFn={[Function]} customStyleMap={ Object { - "ITALIC": Object { + "HIGHLIGHT": Object { "backgroundColor": "#cbe2c8", }, } From 9783e7bb44ba43ed3e7fd3d1e2a2288f53c5e370 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 9 Apr 2024 12:17:16 +0100 Subject: [PATCH 004/215] Add conversion tests for bold and highlight styles --- .../RichTextEditor/utils/convert.test.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/eq-author/src/components/RichTextEditor/utils/convert.test.js b/eq-author/src/components/RichTextEditor/utils/convert.test.js index f1206f0365..ed11151e04 100644 --- a/eq-author/src/components/RichTextEditor/utils/convert.test.js +++ b/eq-author/src/components/RichTextEditor/utils/convert.test.js @@ -33,6 +33,30 @@ describe("convert", () => { expect(html).toEqual(htmlEntity); }); + + it("should convert BOLD style to HTML", () => { + const rawBoldText = new Raw() + .addBlock("Test text") + .addInlineStyle("BOLD", 0, 4); // Applies BOLD style to first four characters in "Test text" + const expectedHtmlBold = `

Test text

`; + + const convert = toHTML({}); + const convertedHtml = convert(rawBoldText.toEditorState()); + + expect(convertedHtml).toEqual(expectedHtmlBold); + }); + + it("should convert HIGHLIGHT style to HTML", () => { + const rawHighlightText = new Raw() + .addBlock("Test text") + .addInlineStyle("HIGHLIGHT", 0, 4); // Applies HIGHLIGHT style to first four characters in "Test text" + const expectedHtmlHighlight = `

Test text

`; + + const convert = toHTML({}); + const convertedHtml = convert(rawHighlightText.toEditorState()); + + expect(convertedHtml).toEqual(expectedHtmlHighlight); + }); }); describe("fromHTML", () => { @@ -54,5 +78,31 @@ describe("convert", () => { expect(stateToRaw(editorState)).toEqual(rawEntity.toRawContentState()); }); + + it("should convert BOLD from HTML", () => { + const htmlBold = `

Test text

`; + const rawBoldText = new Raw() + .addBlock("Test text") + .addInlineStyle("BOLD", 0, 4); // Applies BOLD style to first four characters in "Test text" + + const convert = fromHTML({}); + const editorState = EditorState.createWithContent(convert(htmlBold)); + + expect(stateToRaw(editorState)).toEqual(rawBoldText.toRawContentState()); + }); + + it("should convert HIGHLIGHT from HTML", () => { + const htmlHighlight = `

Test text

`; + const rawHighlightText = new Raw() + .addBlock("Test text") + .addInlineStyle("HIGHLIGHT", 0, 4); // Applies HIGHLIGHT style to first four characters in "Test text" + + const convert = fromHTML({}); + const editorState = EditorState.createWithContent(convert(htmlHighlight)); + + expect(stateToRaw(editorState)).toEqual( + rawHighlightText.toRawContentState() + ); + }); }); }); From f59a34a350b24909218e1f5088bf49db3fb0c936 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 11 Apr 2024 10:29:05 +0100 Subject: [PATCH 005/215] Display toolbar buttons as inactive based on prop --- .../Design/QuestionPageEditor/MetaEditor.js | 1 + .../src/components/RichTextEditor/Toolbar.js | 4 +-- .../src/components/RichTextEditor/index.js | 27 ++++++++++++++----- .../RichTextEditor/utils/convert.js | 27 ++----------------- .../utils/createFormatStripper.js | 8 +++--- 5 files changed, 30 insertions(+), 37 deletions(-) diff --git a/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js b/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js index 5ee3304c56..c592c7fb1a 100644 --- a/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js +++ b/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js @@ -56,6 +56,7 @@ export class StatelessMetaEditor extends React.Component { listId={ (page.section?.repeatingSectionListId || page.folder.listId) ?? null } + usesHighlightStyle errorValidationMsg={this.errorMsg("title")} /> diff --git a/eq-author/src/components/RichTextEditor/Toolbar.js b/eq-author/src/components/RichTextEditor/Toolbar.js index 73919cd328..449eef275c 100644 --- a/eq-author/src/components/RichTextEditor/Toolbar.js +++ b/eq-author/src/components/RichTextEditor/Toolbar.js @@ -31,11 +31,11 @@ export const styleButtons = [ style: "BOLD", }, { - id: "highlight", + id: "emphasis", title: "Highlight", icon: iconEmphasis, type: STYLE_INLINE, - style: "HIGHLIGHT", + style: "BOLD", }, ]; diff --git a/eq-author/src/components/RichTextEditor/index.js b/eq-author/src/components/RichTextEditor/index.js index 3aa6b685f0..dcc9b4157e 100644 --- a/eq-author/src/components/RichTextEditor/index.js +++ b/eq-author/src/components/RichTextEditor/index.js @@ -31,10 +31,13 @@ import PasteModal, { preserveRichFormatting, } from "components/modals/PasteModal"; -const styleMap = { - HIGHLIGHT: { - backgroundColor: "#cbe2c8", - }, +const styleMap = (usesHighlightStyle) => { + return { + BOLD: { + backgroundColor: usesHighlightStyle && "#cbe2c8", + fontWeight: !usesHighlightStyle && "bold", + }, + }; }; const heading = css` @@ -178,6 +181,7 @@ class RichTextEditor extends React.Component { linkLimit: PropTypes.number, withoutMargin: PropTypes.bool, isRepeatingSection: PropTypes.bool, + usesHighlightStyle: PropTypes.bool, allCalculatedSummaryPages: PropTypes.array, //eslint-disable-line }; @@ -371,8 +375,17 @@ class RichTextEditor extends React.Component { hasInlineStyle = (editorState, style) => editorState.getCurrentInlineStyle().has(style); - isActiveControl = ({ style, type }) => { + isActiveControl = ({ id, style, type }) => { const { editorState } = this.state; + const { usesHighlightStyle } = this.props; + + // Displays bold button as inactive when highlight style is used, and emphasis button as inactive when highlight style is not used + if ( + (usesHighlightStyle && id === "bold") || + (!usesHighlightStyle && id === "emphasis") + ) { + return false; + } return type === STYLE_BLOCK ? this.hasBlockStyle(editorState, style) @@ -485,6 +498,7 @@ class RichTextEditor extends React.Component { withoutMargin, allCalculatedSummaryPages, isRepeatingSection, + usesHighlightStyle, // Uses highlight style instead of bold for strong tags when true ...otherProps } = this.props; @@ -530,6 +544,7 @@ class RichTextEditor extends React.Component { linkLimit={linkLimit} allCalculatedSummaryPages={allCalculatedSummaryPages} isRepeatingSection={isRepeatingSection} + usesHighlightStyle={usesHighlightStyle} {...otherProps} /> @@ -539,7 +554,7 @@ class RichTextEditor extends React.Component { editorState={editorState} onChange={this.handleChange} ref={this.setEditorInstance} - customStyleMap={styleMap} + customStyleMap={styleMap(usesHighlightStyle)} blockStyleFn={getBlockStyle} handleReturn={multiline ? undefined : this.handleReturn} handlePastedText={ diff --git a/eq-author/src/components/RichTextEditor/utils/convert.js b/eq-author/src/components/RichTextEditor/utils/convert.js index 7c9e3769fc..3abf1dce9d 100644 --- a/eq-author/src/components/RichTextEditor/utils/convert.js +++ b/eq-author/src/components/RichTextEditor/utils/convert.js @@ -1,4 +1,3 @@ -import React from "react"; import { convertToHTML, convertFromHTML } from "draft-convert"; export const toHTML = (entityMap) => { @@ -7,18 +6,7 @@ export const toHTML = (entityMap) => { return mapper ? mapper(entity) : originalText; }; - // Adds strong tags with class names to text based on the text's style - const styleToHTML = (style) => { - if (style === "BOLD") { - return ; - } - - if (style === "HIGHLIGHT") { - return ; - } - }; - - const convert = convertToHTML({ styleToHTML, entityToHTML }); + const convert = convertToHTML({ entityToHTML }); return (editorState) => convert(editorState.getCurrentContent()); }; @@ -33,16 +21,5 @@ export const fromHTML = (nodeToFn) => { return entity || null; }; - // Applies specific styles based on tags and class names - buttons in the toolbar are activated based on these styles - const htmlToStyle = (nodeName, node, currentStyle) => { - if (nodeName === "strong" && node.className === "highlight") { - return currentStyle.add("HIGHLIGHT").remove("BOLD"); - } - if (nodeName === "strong" && node.className === "bold") { - return currentStyle.add("BOLD"); - } - return currentStyle; - }; - - return convertFromHTML({ htmlToStyle, htmlToEntity }); + return convertFromHTML({ htmlToEntity }); }; diff --git a/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js b/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js index 7c75b29e86..78608c6645 100644 --- a/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js +++ b/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js @@ -4,10 +4,10 @@ import { filterConfig as pipedFilterConfig } from "components/RichTextEditor/ent import { filterConfig as linkFilterConfig } from "components/RichTextEditor/LinkPlugin"; const mapper = { - bold: { format: "BOLD", type: "styles" }, - highlight: { format: "HIGHLIGHT", type: "styles" }, - list: { format: "unordered-list-item", type: "blocks" }, - heading: { format: "header-two", type: "blocks" }, + bold: { id: "bold", format: "BOLD", type: "styles" }, + emphasis: { id: "emphasis", format: "BOLD", type: "styles" }, + list: { id: "list", format: "unordered-list-item", type: "blocks" }, + heading: { id: "heading", format: "header-two", type: "blocks" }, }; export default function createFormatStripper(controls) { From 8deb8b8458bc6483f25eacc0e20f237c81a839f9 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 11 Apr 2024 10:29:15 +0100 Subject: [PATCH 006/215] Add migration --- .../migrations/convertEmTagsToStrongTags.js | 19 +++++++++++++++++++ eq-author-api/migrations/index.js | 1 + 2 files changed, 20 insertions(+) create mode 100644 eq-author-api/migrations/convertEmTagsToStrongTags.js diff --git a/eq-author-api/migrations/convertEmTagsToStrongTags.js b/eq-author-api/migrations/convertEmTagsToStrongTags.js new file mode 100644 index 0000000000..e693ddebf1 --- /dev/null +++ b/eq-author-api/migrations/convertEmTagsToStrongTags.js @@ -0,0 +1,19 @@ +const convertEmTagsToStrongTags = (inputData) => { + if (inputData !== null && inputData !== undefined) { + if (typeof inputData === "string") { + inputData = inputData + .replace(//g, "") + .replace(/<\/em>/g, ""); + } else if (Array.isArray(inputData)) { + inputData = inputData.map((item) => convertEmTagsToStrongTags(item)); + } else if (typeof inputData === "object") { + Object.keys(inputData).forEach((key) => { + inputData[key] = convertEmTagsToStrongTags(inputData[key]); + }); + } + } + + return inputData; +}; + +module.exports = (questionnaire) => convertEmTagsToStrongTags(questionnaire); diff --git a/eq-author-api/migrations/index.js b/eq-author-api/migrations/index.js index 65a48a73e3..cc793812be 100644 --- a/eq-author-api/migrations/index.js +++ b/eq-author-api/migrations/index.js @@ -54,6 +54,7 @@ const migrations = [ require("./addAdditonalContentsToAddItemPage"), require("./updateHealthThemeToPandemicMonitoring"), require("./addAllowableDataVersions"), + require("./convertEmTagsToStrongTags"), ]; const currentVersion = migrations.length; From ef2b0e567323443861e3e1a581d375f011f128a1 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 11 Apr 2024 15:16:42 +0100 Subject: [PATCH 007/215] Display bold and highlight buttons as inactive based on controls --- .../Design/QuestionPageEditor/MetaEditor.js | 1 - .../src/components/RichTextEditor/Toolbar.js | 4 ++-- .../src/components/RichTextEditor/index.js | 21 ++++++++----------- .../utils/createFormatStripper.js | 2 +- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js b/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js index c592c7fb1a..5ee3304c56 100644 --- a/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js +++ b/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js @@ -56,7 +56,6 @@ export class StatelessMetaEditor extends React.Component { listId={ (page.section?.repeatingSectionListId || page.folder.listId) ?? null } - usesHighlightStyle errorValidationMsg={this.errorMsg("title")} /> diff --git a/eq-author/src/components/RichTextEditor/Toolbar.js b/eq-author/src/components/RichTextEditor/Toolbar.js index 449eef275c..1ede91c766 100644 --- a/eq-author/src/components/RichTextEditor/Toolbar.js +++ b/eq-author/src/components/RichTextEditor/Toolbar.js @@ -31,7 +31,7 @@ export const styleButtons = [ style: "BOLD", }, { - id: "emphasis", + id: "highlight", title: "Highlight", icon: iconEmphasis, type: STYLE_INLINE, @@ -86,7 +86,7 @@ class ToolBar extends React.Component { selectionIsCollapsed: PropTypes.bool.isRequired, controls: PropTypes.shape({ bold: PropTypes.bool, - emphasis: PropTypes.bool, + highlight: PropTypes.bool, heading: PropTypes.bool, list: PropTypes.bool, piping: PropTypes.bool, diff --git a/eq-author/src/components/RichTextEditor/index.js b/eq-author/src/components/RichTextEditor/index.js index dcc9b4157e..47e16b64db 100644 --- a/eq-author/src/components/RichTextEditor/index.js +++ b/eq-author/src/components/RichTextEditor/index.js @@ -31,11 +31,11 @@ import PasteModal, { preserveRichFormatting, } from "components/modals/PasteModal"; -const styleMap = (usesHighlightStyle) => { +const styleMap = (controls) => { return { BOLD: { - backgroundColor: usesHighlightStyle && "#cbe2c8", - fontWeight: !usesHighlightStyle && "bold", + backgroundColor: controls.highlight && "#cbe2c8", + fontWeight: controls.bold && "bold", }, }; }; @@ -161,7 +161,7 @@ class RichTextEditor extends React.Component { piping: PropTypes.bool, link: PropTypes.bool, bold: PropTypes.bool, - emphasis: PropTypes.bool, + highlight: PropTypes.bool, list: PropTypes.bool, heading: PropTypes.bool, }), @@ -181,7 +181,6 @@ class RichTextEditor extends React.Component { linkLimit: PropTypes.number, withoutMargin: PropTypes.bool, isRepeatingSection: PropTypes.bool, - usesHighlightStyle: PropTypes.bool, allCalculatedSummaryPages: PropTypes.array, //eslint-disable-line }; @@ -377,12 +376,12 @@ class RichTextEditor extends React.Component { isActiveControl = ({ id, style, type }) => { const { editorState } = this.state; - const { usesHighlightStyle } = this.props; + const { controls } = this.props; - // Displays bold button as inactive when highlight style is used, and emphasis button as inactive when highlight style is not used + // Displays bold and highlight buttons as inactive when the control is not enabled if ( - (usesHighlightStyle && id === "bold") || - (!usesHighlightStyle && id === "emphasis") + (id === "bold" && !controls.bold) || + (id === "highlight" && !controls.highlight) ) { return false; } @@ -498,7 +497,6 @@ class RichTextEditor extends React.Component { withoutMargin, allCalculatedSummaryPages, isRepeatingSection, - usesHighlightStyle, // Uses highlight style instead of bold for strong tags when true ...otherProps } = this.props; @@ -544,7 +542,6 @@ class RichTextEditor extends React.Component { linkLimit={linkLimit} allCalculatedSummaryPages={allCalculatedSummaryPages} isRepeatingSection={isRepeatingSection} - usesHighlightStyle={usesHighlightStyle} {...otherProps} /> @@ -554,7 +551,7 @@ class RichTextEditor extends React.Component { editorState={editorState} onChange={this.handleChange} ref={this.setEditorInstance} - customStyleMap={styleMap(usesHighlightStyle)} + customStyleMap={styleMap(this.props.controls)} blockStyleFn={getBlockStyle} handleReturn={multiline ? undefined : this.handleReturn} handlePastedText={ diff --git a/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js b/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js index 78608c6645..760e5ccf06 100644 --- a/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js +++ b/eq-author/src/components/RichTextEditor/utils/createFormatStripper.js @@ -5,7 +5,7 @@ import { filterConfig as linkFilterConfig } from "components/RichTextEditor/Link const mapper = { bold: { id: "bold", format: "BOLD", type: "styles" }, - emphasis: { id: "emphasis", format: "BOLD", type: "styles" }, + highlight: { id: "highlight", format: "BOLD", type: "styles" }, list: { id: "list", format: "unordered-list-item", type: "blocks" }, heading: { id: "heading", format: "header-two", type: "blocks" }, }; From e14ef87bf42382d8a7ca5abc8c014a99b55f7ece Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 12 Apr 2024 11:53:27 +0100 Subject: [PATCH 008/215] Update rich text editor controls for heading and non-heading tags --- .../src/App/Submission/Design/SubmissionEditor/index.js | 1 - eq-author/src/App/history/HistoryItem.js | 1 - eq-author/src/App/history/HistoryPage.js | 1 - .../CollapsiblesEditor/CollapsibleEditor/index.js | 2 -- .../src/App/introduction/Design/IntroductionEditor/index.js | 6 ++---- .../App/page/Design/CalculatedSummaryPageEditor/index.js | 2 +- .../ListCollectorPageEditors/AddItemPageEditor/index.js | 3 ++- .../ConfirmationPageEditor/index.js | 3 ++- .../ListCollectorPageEditors/QualifierPageEditor/index.js | 3 ++- .../src/App/page/Design/QuestionPageEditor/MetaEditor.js | 2 +- .../QuestionProperties/AdditionalContentOptions.js | 4 +--- eq-author/src/App/questionConfirmation/Design/Editor.js | 2 +- .../Design/SectionEditor/SectionIntroduction/index.js | 3 +-- eq-author/src/App/section/Design/SectionEditor/index.js | 2 +- 14 files changed, 14 insertions(+), 21 deletions(-) diff --git a/eq-author/src/App/Submission/Design/SubmissionEditor/index.js b/eq-author/src/App/Submission/Design/SubmissionEditor/index.js index f0bf85dbc1..db12aa13fb 100644 --- a/eq-author/src/App/Submission/Design/SubmissionEditor/index.js +++ b/eq-author/src/App/Submission/Design/SubmissionEditor/index.js @@ -46,7 +46,6 @@ const InlineField = styled(Field)` const contentControls = { heading: true, bold: true, - emphasis: true, list: true, link: true, }; diff --git a/eq-author/src/App/history/HistoryItem.js b/eq-author/src/App/history/HistoryItem.js index 9d891a881c..448bb8f645 100644 --- a/eq-author/src/App/history/HistoryItem.js +++ b/eq-author/src/App/history/HistoryItem.js @@ -160,7 +160,6 @@ const HistoryItem = ({ value={noteState} controls={{ heading: true, - emphasis: true, list: true, bold: true, }} diff --git a/eq-author/src/App/history/HistoryPage.js b/eq-author/src/App/history/HistoryPage.js index 5f3592c74a..2fd38793d2 100644 --- a/eq-author/src/App/history/HistoryPage.js +++ b/eq-author/src/App/history/HistoryPage.js @@ -124,7 +124,6 @@ const HistoryPageContent = ({ match }) => { value={noteState.value} controls={{ heading: true, - emphasis: true, list: true, bold: true, }} diff --git a/eq-author/src/App/introduction/Design/IntroductionEditor/CollapsiblesEditor/CollapsibleEditor/index.js b/eq-author/src/App/introduction/Design/IntroductionEditor/CollapsiblesEditor/CollapsibleEditor/index.js index c128cf98bb..4ad7f2d30d 100644 --- a/eq-author/src/App/introduction/Design/IntroductionEditor/CollapsiblesEditor/CollapsibleEditor/index.js +++ b/eq-author/src/App/introduction/Design/IntroductionEditor/CollapsiblesEditor/CollapsibleEditor/index.js @@ -28,7 +28,6 @@ const Detail = styled.div` position: relative; `; - const DetailHeader = styled.div` position: absolute; top: 0.5em; @@ -100,7 +99,6 @@ export const CollapsibleEditor = ({ onUpdate={onChangeUpdate} multiline controls={{ - emphasis: true, piping: true, list: true, bold: true, diff --git a/eq-author/src/App/introduction/Design/IntroductionEditor/index.js b/eq-author/src/App/introduction/Design/IntroductionEditor/index.js index 28761ce7a2..197dbf9b2a 100644 --- a/eq-author/src/App/introduction/Design/IntroductionEditor/index.js +++ b/eq-author/src/App/introduction/Design/IntroductionEditor/index.js @@ -45,13 +45,12 @@ const SectionDescription = styled.p` `; const titleControls = { - emphasis: true, + highlight: true, piping: true, }; const descriptionControls = { bold: true, - emphasis: true, piping: true, list: true, link: true, @@ -165,9 +164,8 @@ const IntroductionEditor = ({ introduction, history }) => { } controls={{ heading: true, - bold: true, link: true, - emphasis: true, + highlight: true, piping: true, }} testSelector="txt-intro-title" diff --git a/eq-author/src/App/page/Design/CalculatedSummaryPageEditor/index.js b/eq-author/src/App/page/Design/CalculatedSummaryPageEditor/index.js index da49dbf9e8..49b2b5bd8a 100644 --- a/eq-author/src/App/page/Design/CalculatedSummaryPageEditor/index.js +++ b/eq-author/src/App/page/Design/CalculatedSummaryPageEditor/index.js @@ -36,7 +36,7 @@ import CommentFragment from "graphql/fragments/comment.graphql"; import AnswerFragment from "graphql/fragments/answer.graphql"; const titleControls = { - emphasis: true, + highlight: true, piping: true, }; diff --git a/eq-author/src/App/page/Design/ListCollectorPageEditors/AddItemPageEditor/index.js b/eq-author/src/App/page/Design/ListCollectorPageEditors/AddItemPageEditor/index.js index 0acf6ff1c8..95eb8f6456 100644 --- a/eq-author/src/App/page/Design/ListCollectorPageEditors/AddItemPageEditor/index.js +++ b/eq-author/src/App/page/Design/ListCollectorPageEditors/AddItemPageEditor/index.js @@ -59,7 +59,7 @@ const StyledCollapsible = styled(Collapsible)` margin-top: 1em; `; const titleControls = { - emphasis: true, + highlight: true, piping: true, }; @@ -148,6 +148,7 @@ const AddItemPageEditor = ({ fetchAnswers, page }) => { }) } errorValidationMsg={getErrorMessage("title")} + size="large" controls={titleControls} allowableTypes={[ANSWER, METADATA, VARIABLES]} testSelector="add-item-question" diff --git a/eq-author/src/App/page/Design/ListCollectorPageEditors/ConfirmationPageEditor/index.js b/eq-author/src/App/page/Design/ListCollectorPageEditors/ConfirmationPageEditor/index.js index faa5ab56ed..9fee4ce250 100644 --- a/eq-author/src/App/page/Design/ListCollectorPageEditors/ConfirmationPageEditor/index.js +++ b/eq-author/src/App/page/Design/ListCollectorPageEditors/ConfirmationPageEditor/index.js @@ -54,7 +54,7 @@ const HorizontalSeparator = styled.hr` `; const titleControls = { - emphasis: true, + highlight: true, piping: true, }; @@ -124,6 +124,7 @@ const ConfirmationPageEditor = ({ page, onUpdateOption }) => { }) } errorValidationMsg={getErrorMessage("title")} + size="large" controls={titleControls} allowableTypes={[ANSWER, METADATA, VARIABLES]} testSelector="confirmation-question" diff --git a/eq-author/src/App/page/Design/ListCollectorPageEditors/QualifierPageEditor/index.js b/eq-author/src/App/page/Design/ListCollectorPageEditors/QualifierPageEditor/index.js index 3072731050..c871e17509 100644 --- a/eq-author/src/App/page/Design/ListCollectorPageEditors/QualifierPageEditor/index.js +++ b/eq-author/src/App/page/Design/ListCollectorPageEditors/QualifierPageEditor/index.js @@ -55,7 +55,7 @@ const HorizontalSeparator = styled.hr` `; const titleControls = { - emphasis: true, + highlight: true, piping: true, }; @@ -127,6 +127,7 @@ const QualifierPageEditor = ({ page, onUpdateOption }) => { }) } errorValidationMsg={getErrorMessage("title")} + size="large" controls={titleControls} allowableTypes={[ANSWER, METADATA, VARIABLES]} testSelector="qualifier-question" diff --git a/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js b/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js index 5ee3304c56..18b3e5f835 100644 --- a/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js +++ b/eq-author/src/App/page/Design/QuestionPageEditor/MetaEditor.js @@ -19,7 +19,7 @@ import { } from "components/ContentPickerSelectv3/content-types"; const titleControls = { - emphasis: true, + highlight: true, piping: true, }; diff --git a/eq-author/src/App/page/Design/QuestionPageEditor/QuestionProperties/AdditionalContentOptions.js b/eq-author/src/App/page/Design/QuestionPageEditor/QuestionProperties/AdditionalContentOptions.js index e572e787e1..1b413e2139 100644 --- a/eq-author/src/App/page/Design/QuestionPageEditor/QuestionProperties/AdditionalContentOptions.js +++ b/eq-author/src/App/page/Design/QuestionPageEditor/QuestionProperties/AdditionalContentOptions.js @@ -26,20 +26,18 @@ import { const contentControls = { bold: true, - emphasis: true, list: true, link: true, }; const descriptionControls = { - emphasis: true, + bold: true, piping: true, link: true, }; const definitionControls = { list: true, - emphasis: true, bold: true, link: true, }; diff --git a/eq-author/src/App/questionConfirmation/Design/Editor.js b/eq-author/src/App/questionConfirmation/Design/Editor.js index c302441ae5..ff775621de 100644 --- a/eq-author/src/App/questionConfirmation/Design/Editor.js +++ b/eq-author/src/App/questionConfirmation/Design/Editor.js @@ -65,7 +65,7 @@ export class UnwrappedEditor extends React.Component { label="Confirmation question" value={title} onUpdate={this.handleRichTextUpdate} - controls={{ bold: true, emphasis: true, piping: true }} + controls={{ highlight: true, piping: true }} size="large" testSelector="txt-confirmation-title" data-test="title-input" diff --git a/eq-author/src/App/section/Design/SectionEditor/SectionIntroduction/index.js b/eq-author/src/App/section/Design/SectionEditor/SectionIntroduction/index.js index b4c36fabc7..d5fd0b3019 100644 --- a/eq-author/src/App/section/Design/SectionEditor/SectionIntroduction/index.js +++ b/eq-author/src/App/section/Design/SectionEditor/SectionIntroduction/index.js @@ -57,7 +57,7 @@ const SectionIntroduction = ({ size="large" testSelector="txt-introduction-title" value={section?.introductionTitle} - controls={{ piping: true }} + controls={{ highlight: true, piping: true }} listId={section?.repeatingSectionListId ?? null} errorValidationMsg={introductionTitleErrorMessage} /> @@ -74,7 +74,6 @@ const SectionIntroduction = ({ bold: true, list: true, piping: true, - emphasis: true, link: true, }} listId={section?.repeatingSectionListId ?? null} diff --git a/eq-author/src/App/section/Design/SectionEditor/index.js b/eq-author/src/App/section/Design/SectionEditor/index.js index f5cd59c166..518c07f0b7 100644 --- a/eq-author/src/App/section/Design/SectionEditor/index.js +++ b/eq-author/src/App/section/Design/SectionEditor/index.js @@ -32,7 +32,7 @@ import { } from "constants/modal-content"; const titleControls = { - emphasis: true, + highlight: true, piping: true, }; From 0f9b33f040bdeb9afe6758c5d80f5a93f10e993f Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 12 Apr 2024 13:10:18 +0100 Subject: [PATCH 009/215] Fix migration to prevent wrapping text in two strong tags when original text was wrapped in strong and em tags --- eq-author-api/migrations/convertEmTagsToStrongTags.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/eq-author-api/migrations/convertEmTagsToStrongTags.js b/eq-author-api/migrations/convertEmTagsToStrongTags.js index e693ddebf1..f113983e34 100644 --- a/eq-author-api/migrations/convertEmTagsToStrongTags.js +++ b/eq-author-api/migrations/convertEmTagsToStrongTags.js @@ -1,7 +1,13 @@ const convertEmTagsToStrongTags = (inputData) => { if (inputData !== null && inputData !== undefined) { if (typeof inputData === "string") { + /* + Removes em tags wrapped in strong tags and em tags wrapped around strong tags before converting em tags + Prevents wrapping text in two strong tags when it was previously wrapped in both strong and em tags + */ inputData = inputData + .replace(/|/g, "") + .replace(/<\/em><\/strong>|<\/strong><\/em>/g, "") .replace(//g, "") .replace(/<\/em>/g, ""); } else if (Array.isArray(inputData)) { From 468c6983fa56961fce7f368e16d0ab4791a28d55 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 12 Apr 2024 15:25:13 +0100 Subject: [PATCH 010/215] Add migration tests --- .../convertEmTagsToStrongTags.test.js | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 eq-author-api/migrations/convertEmTagsToStrongTags.test.js diff --git a/eq-author-api/migrations/convertEmTagsToStrongTags.test.js b/eq-author-api/migrations/convertEmTagsToStrongTags.test.js new file mode 100644 index 0000000000..e9e004e7ea --- /dev/null +++ b/eq-author-api/migrations/convertEmTagsToStrongTags.test.js @@ -0,0 +1,78 @@ +const convertEmTagsToStrongTags = require("./convertEmTagsToStrongTags"); + +describe("convertEmTagsToStrongTags", () => { + it("should replace em tags with strong tags in a string", () => { + const emText = "Question 1"; + + expect(convertEmTagsToStrongTags(emText)).toEqual( + "Question 1" + ); + }); + + it("should not replace strong tags", () => { + const strongText = "Question 1"; + + expect(convertEmTagsToStrongTags(strongText)).toEqual(strongText); + }); + + it("should replace em tags with strong tags in an array of strings", () => { + const emArray = ["Question 1", "Question 2"]; + + expect(convertEmTagsToStrongTags(emArray)).toEqual([ + "Question 1", + "Question 2", + ]); + }); + + it("should replace em tags with strong tags in an object", () => { + const page = { + title: "Test title", + description: "Test description", + answers: [ + { + label: "Answer 1", + }, + { + label: "Answer 2", + }, + ], + }; + + expect(convertEmTagsToStrongTags(page)).toEqual({ + title: "Test title", + description: "Test description", + answers: [ + { + label: "Answer 1", + }, + { + label: "Answer 2", + }, + ], + }); + }); + + it("should not wrap text in two strong tags when text is wrapped in strong and em tags", () => { + const emTagsWrappedInStrong = "Question 1"; + const strongTagsWrappedInEm = "Question 2"; + + expect(convertEmTagsToStrongTags(emTagsWrappedInStrong)).toEqual( + "Question 1" + ); + expect(convertEmTagsToStrongTags(strongTagsWrappedInEm)).toEqual( + "Question 2" + ); + }); + + it("should return undefined if inputData is undefined", () => { + expect(convertEmTagsToStrongTags(undefined)).toBeUndefined(); // convertEmTagsToStrongTags returns inputData, which is undefined + }); + + it("should return null if inputData is null", () => { + expect(convertEmTagsToStrongTags(null)).toBeNull(); // convertEmTagsToStrongTags returns inputData, which is null + }); + + it("should return inputData if inputData is not string, array, or object", () => { + expect(convertEmTagsToStrongTags(true)).toEqual(true); + }); +}); From b1a8435bc2c4b58af6de61569603cee405e8a95c Mon Sep 17 00:00:00 2001 From: sudeep Date: Mon, 15 Apr 2024 10:49:04 +0100 Subject: [PATCH 011/215] display supplementary data list items Signed-off-by: sudeep --- eq-author/src/components/RichTextEditor/PipingMenu.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/eq-author/src/components/RichTextEditor/PipingMenu.js b/eq-author/src/components/RichTextEditor/PipingMenu.js index 189cc4d0e4..7b9d2883f7 100644 --- a/eq-author/src/components/RichTextEditor/PipingMenu.js +++ b/eq-author/src/components/RichTextEditor/PipingMenu.js @@ -126,13 +126,7 @@ const PipingMenu = ({ let allSupplementaryData = questionnaire?.supplementaryData?.data || []; - if ( - allSupplementaryData && - !( - pageType === "Introduction" && - questionnaire?.sections[0]?.repeatingSection - ) - ) { + if (allSupplementaryData && !(pageType === "Introduction")) { allSupplementaryData = allSupplementaryData.filter( (list) => list.listName === "" || list.id === listId ); From 334a049728d2693568bd3d403c7a5f91ac76fce8 Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 15 Apr 2024 10:29:48 +0100 Subject: [PATCH 012/215] Update failing tests --- .../__snapshots__/index.test.js.snap | 1 - .../__snapshots__/index.test.js.snap | 10 +- .../__snapshots__/MetaEditor.test.js.snap | 18 +-- .../Design/__snapshots__/Editor.test.js.snap | 6 +- .../__snapshots__/SectionEditor.test.js.snap | 4 +- .../RichTextEditor/RichTextEditor.test.js | 6 +- .../src/components/RichTextEditor/Toolbar.js | 2 +- .../__snapshots__/RichTextEditor.test.js.snap | 106 ++++++++++++------ .../RichTextEditor/utils/convert.test.js | 50 --------- 9 files changed, 93 insertions(+), 110 deletions(-) diff --git a/eq-author/src/App/introduction/Design/IntroductionEditor/CollapsiblesEditor/CollapsibleEditor/__snapshots__/index.test.js.snap b/eq-author/src/App/introduction/Design/IntroductionEditor/CollapsiblesEditor/CollapsibleEditor/__snapshots__/index.test.js.snap index 7dedc8e7b0..55042802f1 100644 --- a/eq-author/src/App/introduction/Design/IntroductionEditor/CollapsiblesEditor/CollapsibleEditor/__snapshots__/index.test.js.snap +++ b/eq-author/src/App/introduction/Design/IntroductionEditor/CollapsiblesEditor/CollapsibleEditor/__snapshots__/index.test.js.snap @@ -53,7 +53,6 @@ exports[`CollapsibleEditor should render 1`] = ` controls={ Object { "bold": true, - "emphasis": true, "link": true, "list": true, "piping": true, diff --git a/eq-author/src/App/introduction/Design/IntroductionEditor/__snapshots__/index.test.js.snap b/eq-author/src/App/introduction/Design/IntroductionEditor/__snapshots__/index.test.js.snap index 2a54fe0262..db7dc21d69 100644 --- a/eq-author/src/App/introduction/Design/IntroductionEditor/__snapshots__/index.test.js.snap +++ b/eq-author/src/App/introduction/Design/IntroductionEditor/__snapshots__/index.test.js.snap @@ -22,9 +22,8 @@ exports[`IntroductionEditor should render 1`] = ` autoFocus={false} controls={ Object { - "bold": true, - "emphasis": true, "heading": true, + "highlight": true, "link": true, "piping": true, } @@ -178,7 +177,6 @@ exports[`IntroductionEditor should render 1`] = ` controls={ Object { "bold": true, - "emphasis": true, "link": true, "list": true, "piping": true, @@ -281,7 +279,7 @@ exports[`IntroductionEditor should render 1`] = ` autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -303,7 +301,6 @@ exports[`IntroductionEditor should render 1`] = ` controls={ Object { "bold": true, - "emphasis": true, "link": true, "list": true, "piping": true, @@ -348,7 +345,7 @@ exports[`IntroductionEditor should render 1`] = ` autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -370,7 +367,6 @@ exports[`IntroductionEditor should render 1`] = ` controls={ Object { "bold": true, - "emphasis": true, "link": true, "list": true, "piping": true, diff --git a/eq-author/src/App/page/Design/QuestionPageEditor/__snapshots__/MetaEditor.test.js.snap b/eq-author/src/App/page/Design/QuestionPageEditor/__snapshots__/MetaEditor.test.js.snap index b0cea0380c..c9cc37b197 100644 --- a/eq-author/src/App/page/Design/QuestionPageEditor/__snapshots__/MetaEditor.test.js.snap +++ b/eq-author/src/App/page/Design/QuestionPageEditor/__snapshots__/MetaEditor.test.js.snap @@ -13,7 +13,7 @@ exports[`MetaEditor should be able to display both deleted metadata and deleted autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -55,7 +55,7 @@ exports[`MetaEditor should display the correct error message when piping answer autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -96,7 +96,7 @@ exports[`MetaEditor should display the correct error message when piping answer autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -137,7 +137,7 @@ exports[`MetaEditor should display the correct error message when the definition autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -174,7 +174,7 @@ exports[`MetaEditor should display the correct error message when the definition autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -211,7 +211,7 @@ exports[`MetaEditor should display the correct error message when the descriptio autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -248,7 +248,7 @@ exports[`MetaEditor should display the correct error message when the includes/e autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -285,7 +285,7 @@ exports[`MetaEditor should display the correct error message when the title is m autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -326,7 +326,7 @@ exports[`MetaEditor should render 1`] = ` autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } diff --git a/eq-author/src/App/questionConfirmation/Design/__snapshots__/Editor.test.js.snap b/eq-author/src/App/questionConfirmation/Design/__snapshots__/Editor.test.js.snap index 5cd2c2a363..ac6e5068f1 100644 --- a/eq-author/src/App/questionConfirmation/Design/__snapshots__/Editor.test.js.snap +++ b/eq-author/src/App/questionConfirmation/Design/__snapshots__/Editor.test.js.snap @@ -6,8 +6,7 @@ exports[`Editor Editor Component should autoFocus the title when there is not on autoFocus={true} controls={ Object { - "bold": true, - "emphasis": true, + "highlight": true, "piping": true, } } @@ -88,8 +87,7 @@ exports[`Editor Editor Component should render 1`] = ` autoFocus={false} controls={ Object { - "bold": true, - "emphasis": true, + "highlight": true, "piping": true, } } diff --git a/eq-author/src/App/section/Design/SectionEditor/__snapshots__/SectionEditor.test.js.snap b/eq-author/src/App/section/Design/SectionEditor/__snapshots__/SectionEditor.test.js.snap index 724d1e6a90..e47a4df53c 100644 --- a/eq-author/src/App/section/Design/SectionEditor/__snapshots__/SectionEditor.test.js.snap +++ b/eq-author/src/App/section/Design/SectionEditor/__snapshots__/SectionEditor.test.js.snap @@ -24,7 +24,7 @@ exports[`SectionEditor should render 1`] = ` autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } @@ -152,7 +152,7 @@ exports[`SectionEditor should render 2`] = ` autoFocus={false} controls={ Object { - "emphasis": true, + "highlight": true, "piping": true, } } diff --git a/eq-author/src/components/RichTextEditor/RichTextEditor.test.js b/eq-author/src/components/RichTextEditor/RichTextEditor.test.js index a7aaf4e7b5..f5966c8f1c 100644 --- a/eq-author/src/components/RichTextEditor/RichTextEditor.test.js +++ b/eq-author/src/components/RichTextEditor/RichTextEditor.test.js @@ -31,7 +31,7 @@ const content = `
  • Regular
  • Bold
  • -
  • Emphasis
  • +
  • Highlight
`; @@ -43,6 +43,10 @@ describe("components/RichTextEditor", function () { id: "test", name: "test-name", testSelector: "test-selector-foo", + controls: { + bold: true, + highlight: false, + }, }; editorFocus = jest.fn(); editorInstance = { diff --git a/eq-author/src/components/RichTextEditor/Toolbar.js b/eq-author/src/components/RichTextEditor/Toolbar.js index 1ede91c766..1b5b068c64 100644 --- a/eq-author/src/components/RichTextEditor/Toolbar.js +++ b/eq-author/src/components/RichTextEditor/Toolbar.js @@ -35,7 +35,7 @@ export const styleButtons = [ title: "Highlight", icon: iconEmphasis, type: STYLE_INLINE, - style: "BOLD", + style: "BOLD", // BOLD style wraps text in strong tags - used for highlighting }, ]; diff --git a/eq-author/src/components/RichTextEditor/__snapshots__/RichTextEditor.test.js.snap b/eq-author/src/components/RichTextEditor/__snapshots__/RichTextEditor.test.js.snap index dec5001f28..fd25fa0fae 100644 --- a/eq-author/src/components/RichTextEditor/__snapshots__/RichTextEditor.test.js.snap +++ b/eq-author/src/components/RichTextEditor/__snapshots__/RichTextEditor.test.js.snap @@ -31,7 +31,12 @@ exports[`components/RichTextEditor should allow multiline input 1`] = ` >
  • Regular
  • Bold
  • -
  • Emphasis
  • +
  • Highlight
  • " visible={false} @@ -892,8 +915,9 @@ exports[`components/RichTextEditor should render existing content 1`] = ` blockStyleFn={[Function]} customStyleMap={ Object { - "HIGHLIGHT": Object { - "backgroundColor": "#cbe2c8", + "BOLD": Object { + "backgroundColor": false, + "fontWeight": "bold", }, } } @@ -953,53 +977,59 @@ exports[`components/RichTextEditor should render existing content 1`] = ` "123": Immutable.Record { "key": "123", "type": "unordered-list-item", - "text": "Emphasis", + "text": "Highlight", "characterList": Immutable.List [ Immutable.Record { "style": Immutable.OrderedSet [ - "ITALIC", + "BOLD", ], "entity": null, }, Immutable.Record { "style": Immutable.OrderedSet [ - "ITALIC", + "BOLD", ], "entity": null, }, Immutable.Record { "style": Immutable.OrderedSet [ - "ITALIC", + "BOLD", ], "entity": null, }, Immutable.Record { "style": Immutable.OrderedSet [ - "ITALIC", + "BOLD", ], "entity": null, }, Immutable.Record { "style": Immutable.OrderedSet [ - "ITALIC", + "BOLD", ], "entity": null, }, Immutable.Record { "style": Immutable.OrderedSet [ - "ITALIC", + "BOLD", ], "entity": null, }, Immutable.Record { "style": Immutable.OrderedSet [ - "ITALIC", + "BOLD", ], "entity": null, }, Immutable.Record { "style": Immutable.OrderedSet [ - "ITALIC", + "BOLD", + ], + "entity": null, + }, + Immutable.Record { + "style": Immutable.OrderedSet [ + "BOLD", ], "entity": null, }, @@ -1074,12 +1104,12 @@ exports[`components/RichTextEditor should render existing content 1`] = ` "123": Immutable.List [ Immutable.Record { "start": 0, - "end": 8, + "end": 9, "decoratorKey": null, "leaves": Immutable.List [ Immutable.Record { "start": 0, - "end": 8, + "end": 9, }, ], }, @@ -1150,7 +1180,12 @@ exports[`components/RichTextEditor should show as disabled and readonly when dis > { expect(html).toEqual(htmlEntity); }); - - it("should convert BOLD style to HTML", () => { - const rawBoldText = new Raw() - .addBlock("Test text") - .addInlineStyle("BOLD", 0, 4); // Applies BOLD style to first four characters in "Test text" - const expectedHtmlBold = `

    Test text

    `; - - const convert = toHTML({}); - const convertedHtml = convert(rawBoldText.toEditorState()); - - expect(convertedHtml).toEqual(expectedHtmlBold); - }); - - it("should convert HIGHLIGHT style to HTML", () => { - const rawHighlightText = new Raw() - .addBlock("Test text") - .addInlineStyle("HIGHLIGHT", 0, 4); // Applies HIGHLIGHT style to first four characters in "Test text" - const expectedHtmlHighlight = `

    Test text

    `; - - const convert = toHTML({}); - const convertedHtml = convert(rawHighlightText.toEditorState()); - - expect(convertedHtml).toEqual(expectedHtmlHighlight); - }); }); describe("fromHTML", () => { @@ -78,31 +54,5 @@ describe("convert", () => { expect(stateToRaw(editorState)).toEqual(rawEntity.toRawContentState()); }); - - it("should convert BOLD from HTML", () => { - const htmlBold = `

    Test text

    `; - const rawBoldText = new Raw() - .addBlock("Test text") - .addInlineStyle("BOLD", 0, 4); // Applies BOLD style to first four characters in "Test text" - - const convert = fromHTML({}); - const editorState = EditorState.createWithContent(convert(htmlBold)); - - expect(stateToRaw(editorState)).toEqual(rawBoldText.toRawContentState()); - }); - - it("should convert HIGHLIGHT from HTML", () => { - const htmlHighlight = `

    Test text

    `; - const rawHighlightText = new Raw() - .addBlock("Test text") - .addInlineStyle("HIGHLIGHT", 0, 4); // Applies HIGHLIGHT style to first four characters in "Test text" - - const convert = fromHTML({}); - const editorState = EditorState.createWithContent(convert(htmlHighlight)); - - expect(stateToRaw(editorState)).toEqual( - rawHighlightText.toRawContentState() - ); - }); }); }); From 164f09253bf5f2e056d94d6888d6dfab4a663901 Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 15 Apr 2024 15:15:12 +0100 Subject: [PATCH 013/215] Add highlight to preview pages --- eq-author/src/App/history/HistoryItem.js | 14 -------------- .../Preview/IntroductionPreview/index.js | 5 ----- .../App/page/Preview/CalculatedSummaryPreview.js | 10 ++++------ .../App/page/Preview/ListCollectorPagePreview.js | 5 ----- .../AddItemPagePreview/index.js | 5 ----- .../ConfirmationPagePreview/index.js | 5 ----- .../QualifierPagePreview/index.js | 5 ----- .../src/App/page/Preview/QuestionPagePreview.js | 5 ----- .../src/App/questionConfirmation/Preview/index.js | 5 ----- .../src/App/section/Preview/SectionIntroPreview.js | 5 ----- .../src/components/preview/elements/PageTitle.js | 5 +++++ 11 files changed, 9 insertions(+), 60 deletions(-) diff --git a/eq-author/src/App/history/HistoryItem.js b/eq-author/src/App/history/HistoryItem.js index 448bb8f645..63fb59e5eb 100644 --- a/eq-author/src/App/history/HistoryItem.js +++ b/eq-author/src/App/history/HistoryItem.js @@ -69,16 +69,6 @@ const EventText = styled.div` p { margin: 0 0 1em; word-break: break-all; - em { - background-color: ${colors.highlightGreen}; - font-style: normal; - } - } - h2 { - em { - background-color: ${colors.highlightGreen}; - font-style: normal; - } } ul { margin-top: 0; @@ -89,10 +79,6 @@ const EventText = styled.div` span { font-weight: bold; } - em { - background-color: ${colors.highlightGreen}; - font-style: normal; - } } } `; diff --git a/eq-author/src/App/introduction/Preview/IntroductionPreview/index.js b/eq-author/src/App/introduction/Preview/IntroductionPreview/index.js index ca5d3e124e..948b0079d2 100644 --- a/eq-author/src/App/introduction/Preview/IntroductionPreview/index.js +++ b/eq-author/src/App/introduction/Preview/IntroductionPreview/index.js @@ -22,11 +22,6 @@ const Container = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: ${colors.highlightGreen}; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: #e0e0e0; padding: 0 0.125em; diff --git a/eq-author/src/App/page/Preview/CalculatedSummaryPreview.js b/eq-author/src/App/page/Preview/CalculatedSummaryPreview.js index 089727839a..93bbddb0d8 100644 --- a/eq-author/src/App/page/Preview/CalculatedSummaryPreview.js +++ b/eq-author/src/App/page/Preview/CalculatedSummaryPreview.js @@ -25,11 +25,6 @@ const Container = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: #dce5b0; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: #e0e0e0; padding: 0 0.125em; @@ -130,7 +125,10 @@ const CalculatedSummaryPagePreview = ({ page }) => { - {page.totalTitle.replace(/<\/?p>/g, "")} + { + /* Removes all HTML tags */ + page.totalTitle.replace(/<[^>]*>/g, "") + } diff --git a/eq-author/src/App/page/Preview/ListCollectorPagePreview.js b/eq-author/src/App/page/Preview/ListCollectorPagePreview.js index 54e0588e39..29af97a7fd 100644 --- a/eq-author/src/App/page/Preview/ListCollectorPagePreview.js +++ b/eq-author/src/App/page/Preview/ListCollectorPagePreview.js @@ -33,11 +33,6 @@ const Container = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: #dce5b0; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: #e0e0e0; padding: 0 0.125em; diff --git a/eq-author/src/App/page/Preview/ListCollectorPagePreviews/AddItemPagePreview/index.js b/eq-author/src/App/page/Preview/ListCollectorPagePreviews/AddItemPagePreview/index.js index 1c53a4e552..561256ae12 100644 --- a/eq-author/src/App/page/Preview/ListCollectorPagePreviews/AddItemPagePreview/index.js +++ b/eq-author/src/App/page/Preview/ListCollectorPagePreviews/AddItemPagePreview/index.js @@ -32,11 +32,6 @@ const Container = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: ${colors.highlightGreen}; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: ${colors.pipingGrey}; padding: 0 0.125em; diff --git a/eq-author/src/App/page/Preview/ListCollectorPagePreviews/ConfirmationPagePreview/index.js b/eq-author/src/App/page/Preview/ListCollectorPagePreviews/ConfirmationPagePreview/index.js index b21ea8d406..74a582950f 100644 --- a/eq-author/src/App/page/Preview/ListCollectorPagePreviews/ConfirmationPagePreview/index.js +++ b/eq-author/src/App/page/Preview/ListCollectorPagePreviews/ConfirmationPagePreview/index.js @@ -20,11 +20,6 @@ const Container = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: ${colors.highlightGreen}; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: ${colors.pipingGrey}; padding: 0 0.125em; diff --git a/eq-author/src/App/page/Preview/ListCollectorPagePreviews/QualifierPagePreview/index.js b/eq-author/src/App/page/Preview/ListCollectorPagePreviews/QualifierPagePreview/index.js index e357b302e0..6ed7a44812 100644 --- a/eq-author/src/App/page/Preview/ListCollectorPagePreviews/QualifierPagePreview/index.js +++ b/eq-author/src/App/page/Preview/ListCollectorPagePreviews/QualifierPagePreview/index.js @@ -20,11 +20,6 @@ const Container = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: ${colors.highlightGreen}; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: ${colors.pipingGrey}; padding: 0 0.125em; diff --git a/eq-author/src/App/page/Preview/QuestionPagePreview.js b/eq-author/src/App/page/Preview/QuestionPagePreview.js index 0c777197c2..2a1ec23e99 100644 --- a/eq-author/src/App/page/Preview/QuestionPagePreview.js +++ b/eq-author/src/App/page/Preview/QuestionPagePreview.js @@ -29,11 +29,6 @@ const Container = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: #dce5b0; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: #e0e0e0; padding: 0 0.125em; diff --git a/eq-author/src/App/questionConfirmation/Preview/index.js b/eq-author/src/App/questionConfirmation/Preview/index.js index 87b986557e..0202fc969f 100644 --- a/eq-author/src/App/questionConfirmation/Preview/index.js +++ b/eq-author/src/App/questionConfirmation/Preview/index.js @@ -32,11 +32,6 @@ const Container = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: #dce5b0; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: #5f7682; border-radius: 4px; diff --git a/eq-author/src/App/section/Preview/SectionIntroPreview.js b/eq-author/src/App/section/Preview/SectionIntroPreview.js index 67bb401b56..ba33dc7334 100644 --- a/eq-author/src/App/section/Preview/SectionIntroPreview.js +++ b/eq-author/src/App/section/Preview/SectionIntroPreview.js @@ -15,11 +15,6 @@ const Wrapper = styled.div` p:last-of-type { margin-bottom: 0; } - em { - background-color: #dce5b0; - padding: 0 0.125em; - font-style: normal; - } span[data-piped] { background-color: #e0e0e0; padding: 0 0.125em; diff --git a/eq-author/src/components/preview/elements/PageTitle.js b/eq-author/src/components/preview/elements/PageTitle.js index 4e4a56f613..d77f2589d3 100644 --- a/eq-author/src/components/preview/elements/PageTitle.js +++ b/eq-author/src/components/preview/elements/PageTitle.js @@ -8,6 +8,11 @@ const Title = styled.h1` font-size: 1.4em; margin: 0 0 1em; word-wrap: break-word; + strong { + background-color: #dce5b0; + padding: 0 0.125em; + font-style: normal; + } `; const PageTitle = ({ title, missingText = "Missing Page Title" }) => { From 7e62b41bffb9156c10b32e3f1ed14e75b5cf8597 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 16 Apr 2024 11:07:14 +0100 Subject: [PATCH 014/215] Add mutually exclusive answers to logic selector --- .../schema/resolvers/logic/binaryExpression2/index.js | 9 ++++++--- .../src/businessLogic/answerTypeToConditions.js | 1 + .../MultipleChoiceAnswerOptionsSelector.js | 10 ++++++---- .../src/App/page/Logic/BinaryExpressionEditor/index.js | 2 ++ eq-author/src/constants/answer-types.js | 3 ++- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/eq-author-api/schema/resolvers/logic/binaryExpression2/index.js b/eq-author-api/schema/resolvers/logic/binaryExpression2/index.js index 53c331fcbf..9638041f68 100644 --- a/eq-author-api/schema/resolvers/logic/binaryExpression2/index.js +++ b/eq-author-api/schema/resolvers/logic/binaryExpression2/index.js @@ -91,9 +91,12 @@ Resolvers.LeftSide2 = { __resolveType: ({ type, sideType }) => { if (sideType === "Answer") { if ( - [answerTypes.RADIO, answerTypes.CHECKBOX, answerTypes.SELECT].includes( - type - ) + [ + answerTypes.RADIO, + answerTypes.CHECKBOX, + answerTypes.SELECT, + answerTypes.MUTUALLY_EXCLUSIVE, + ].includes(type) ) { return "MultipleChoiceAnswer"; } diff --git a/eq-author-api/src/businessLogic/answerTypeToConditions.js b/eq-author-api/src/businessLogic/answerTypeToConditions.js index b5195f0ecc..51bdcf0aef 100644 --- a/eq-author-api/src/businessLogic/answerTypeToConditions.js +++ b/eq-author-api/src/businessLogic/answerTypeToConditions.js @@ -31,6 +31,7 @@ const answerConditions = { conditions.COUNT_OF, ], [answerTypes.SELECT]: [conditions.ONE_OF, conditions.UNANSWERED], + [answerTypes.MUTUALLY_EXCLUSIVE]: [conditions.ONE_OF, conditions.UNANSWERED], }; const isAnswerTypeSupported = (answerType) => diff --git a/eq-author/src/App/page/Logic/BinaryExpressionEditor/MultipleChoiceAnswerOptionsSelector.js b/eq-author/src/App/page/Logic/BinaryExpressionEditor/MultipleChoiceAnswerOptionsSelector.js index 2b73ffd5a1..ae0c39fae4 100644 --- a/eq-author/src/App/page/Logic/BinaryExpressionEditor/MultipleChoiceAnswerOptionsSelector.js +++ b/eq-author/src/App/page/Logic/BinaryExpressionEditor/MultipleChoiceAnswerOptionsSelector.js @@ -16,7 +16,7 @@ import { ERR_COUNT_OF_GREATER_THAN_AVAILABLE_OPTIONS, } from "constants/validationMessages"; import { colors } from "constants/theme"; -import { RADIO, SELECT } from "constants/answer-types"; +import { RADIO, SELECT, MUTUALLY_EXCLUSIVE } from "constants/answer-types"; import { Select } from "components/Forms"; import TextButton from "components/buttons/TextButton"; @@ -32,6 +32,8 @@ const answerConditions = { NOTANYOF: "NotAnyOf", }; +const exclusiveAnswers = [RADIO, SELECT, MUTUALLY_EXCLUSIVE]; + const MultipleChoiceAnswerOptions = styled.div` align-items: center; display: inline-flex; @@ -206,7 +208,7 @@ class MultipleChoiceAnswerOptionsSelector extends React.Component { return message ? {message} : null; }; - renderRadioOptionSelector(hasError) { + renderExclusiveOptionSelector(hasError) { const { expression } = this.props; const options = get(expression, "left.options", []); @@ -331,8 +333,8 @@ class MultipleChoiceAnswerOptionsSelector extends React.Component { const hasConditionError = errors.filter(({ field }) => field === "condition").length > 0; - if (answerType === RADIO || answerType === SELECT) { - return this.renderRadioOptionSelector(hasError); + if (exclusiveAnswers.includes(answerType)) { + return this.renderExclusiveOptionSelector(hasError); } else { return this.renderCheckboxOptionSelector(hasError, hasConditionError); } diff --git a/eq-author/src/App/page/Logic/BinaryExpressionEditor/index.js b/eq-author/src/App/page/Logic/BinaryExpressionEditor/index.js index a7a95d8eb5..53123572c7 100644 --- a/eq-author/src/App/page/Logic/BinaryExpressionEditor/index.js +++ b/eq-author/src/App/page/Logic/BinaryExpressionEditor/index.js @@ -14,6 +14,7 @@ import { CHECKBOX, DATE, SELECT, + MUTUALLY_EXCLUSIVE, } from "constants/answer-types"; import { TEXT, TEXT_OPTIONAL } from "constants/metadata-types"; @@ -110,6 +111,7 @@ const ANSWER_TYPE_TO_RIGHT_EDITOR = { [RADIO]: MultipleChoiceAnswerOptionsSelector, [SELECT]: MultipleChoiceAnswerOptionsSelector, [CHECKBOX]: MultipleChoiceAnswerOptionsSelector, + [MUTUALLY_EXCLUSIVE]: MultipleChoiceAnswerOptionsSelector, [NUMBER]: NumberAnswerSelector, [PERCENTAGE]: NumberAnswerSelector, [CURRENCY]: NumberAnswerSelector, diff --git a/eq-author/src/constants/answer-types.js b/eq-author/src/constants/answer-types.js index c0f3ac7aee..11860d9757 100644 --- a/eq-author/src/constants/answer-types.js +++ b/eq-author/src/constants/answer-types.js @@ -11,6 +11,7 @@ export const DATE_RANGE = "DateRange"; export const UNIT = "Unit"; export const DURATION = "Duration"; export const SELECT = "Select"; +export const MUTUALLY_EXCLUSIVE = "MutuallyExclusive"; export const ROUTING_ANSWER_TYPES = [ RADIO, @@ -21,6 +22,7 @@ export const ROUTING_ANSWER_TYPES = [ UNIT, DATE, SELECT, + MUTUALLY_EXCLUSIVE, ]; export const ROUTING_METADATA_TYPES = [TEXT.value, TEXT_OPTIONAL.value]; @@ -28,7 +30,6 @@ export const ROUTING_METADATA_TYPES = [TEXT.value, TEXT_OPTIONAL.value]; export const RADIO_OPTION = "RadioOption"; export const CHECKBOX_OPTION = "CheckboxOption"; export const SELECT_OPTION = "SelectOption"; -export const MUTUALLY_EXCLUSIVE = "MutuallyExclusive"; export const MUTUALLY_EXCLUSIVE_OPTION = "MutuallyExclusiveOption"; export const ANSWER_OPTION_TYPES = { From 4cbd93c3f50b646e64ed77372d1c13addfc70c5d Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 16 Apr 2024 11:12:18 +0100 Subject: [PATCH 015/215] Fix selecting mutually exclusive options in logic page --- eq-author-api/schema/resolvers/logic/binaryExpression2/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/eq-author-api/schema/resolvers/logic/binaryExpression2/index.js b/eq-author-api/schema/resolvers/logic/binaryExpression2/index.js index 9638041f68..127f1dc64f 100644 --- a/eq-author-api/schema/resolvers/logic/binaryExpression2/index.js +++ b/eq-author-api/schema/resolvers/logic/binaryExpression2/index.js @@ -37,6 +37,7 @@ const isLeftSideAnswerTypeCompatible = ( [answerTypes.CHECKBOX]: "SelectedOptions", [answerTypes.DATE]: "DateValue", [answerTypes.SELECT]: "SelectedOptions", + [answerTypes.MUTUALLY_EXCLUSIVE]: "SelectedOptions", }; if (secondaryCondition) { From 9e89e4ff13c59199da1643bfa591dc39ff6b1910 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Apr 2024 10:57:01 +0100 Subject: [PATCH 016/215] Remove answer types from content picker based on mutually exclusive answers --- .../getQuestionnaireQuery.graphql | 3 ++ .../RoutingAnswerContentPicker.js | 4 ++- .../Logic/BinaryExpressionEditor/index.js | 1 + eq-author/src/utils/getContentBeforeEntity.js | 29 +++++++++++++++++-- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/eq-author/src/App/QuestionnaireDesignPage/getQuestionnaireQuery.graphql b/eq-author/src/App/QuestionnaireDesignPage/getQuestionnaireQuery.graphql index 73a2b2f701..8bacacc678 100644 --- a/eq-author/src/App/QuestionnaireDesignPage/getQuestionnaireQuery.graphql +++ b/eq-author/src/App/QuestionnaireDesignPage/getQuestionnaireQuery.graphql @@ -365,6 +365,9 @@ fragment Answers on Answer { type properties advancedProperties + page { + id + } ... on BasicAnswer { secondaryQCode } diff --git a/eq-author/src/App/page/Logic/BinaryExpressionEditor/RoutingAnswerContentPicker.js b/eq-author/src/App/page/Logic/BinaryExpressionEditor/RoutingAnswerContentPicker.js index 4eba90fc6b..30fd0ad2b7 100644 --- a/eq-author/src/App/page/Logic/BinaryExpressionEditor/RoutingAnswerContentPicker.js +++ b/eq-author/src/App/page/Logic/BinaryExpressionEditor/RoutingAnswerContentPicker.js @@ -26,6 +26,7 @@ export const preprocessMetadata = (metadata) => const RoutingAnswerContentPicker = ({ includeSelf, selectedContentDisplayName, + expressionGroup, ...otherProps }) => { const { questionnaire } = useQuestionnaire(); @@ -38,8 +39,9 @@ const RoutingAnswerContentPicker = ({ id: pageId, includeTargetPage: includeSelf, preprocessAnswers, + expressionGroup, }), - [questionnaire, pageId, includeSelf] + [questionnaire, pageId, includeSelf, expressionGroup] ); const filteredPreviousAnswers = previousAnswers.map((answer) => { diff --git a/eq-author/src/App/page/Logic/BinaryExpressionEditor/index.js b/eq-author/src/App/page/Logic/BinaryExpressionEditor/index.js index 53123572c7..e7de84795e 100644 --- a/eq-author/src/App/page/Logic/BinaryExpressionEditor/index.js +++ b/eq-author/src/App/page/Logic/BinaryExpressionEditor/index.js @@ -213,6 +213,7 @@ export const UnwrappedBinaryExpressionEditor = ({ ? expression.left : undefined } + expressionGroup={expressionGroup} onSubmit={handleLeftSideChange} selectedId={expression?.left?.id} data-test="routing-answer-picker" diff --git a/eq-author/src/utils/getContentBeforeEntity.js b/eq-author/src/utils/getContentBeforeEntity.js index 472dcf38cc..ab68375d17 100644 --- a/eq-author/src/utils/getContentBeforeEntity.js +++ b/eq-author/src/utils/getContentBeforeEntity.js @@ -2,13 +2,16 @@ import { remove } from "lodash"; import isListCollectorPageType from "utils/isListCollectorPageType"; +import { MUTUALLY_EXCLUSIVE } from "constants/answer-types"; + const identity = (x) => x; const getContentBeforeEntity = ( questionnaire, id, preprocessAnswers, - includeTarget + includeTarget, + expressionGroup ) => { const sections = []; @@ -37,9 +40,27 @@ const getContentBeforeEntity = ( return sections; } - const answers = + let answers = !isListCollectorPageType(page.pageType) && (page?.answers?.flatMap(preprocessAnswers) || []); + + if (expressionGroup?.operator === "And") { + expressionGroup.expressions.forEach((expression) => { + if (expression?.left?.page?.id) { + answers = answers.filter((answer) => { + if (expression.left.page.id === answer.page.id) { + if (expression.left.type === MUTUALLY_EXCLUSIVE) { + return false; + } else { + return answer.type !== MUTUALLY_EXCLUSIVE; + } + } + return true; + }); + } + }); + } + if (answers.length) { sections[sections.length - 1].folders[ sections[sections.length - 1].folders.length - 1 @@ -75,6 +96,7 @@ export default ({ id, preprocessAnswers = identity, includeTargetPage = false, + expressionGroup, } = {}) => { if (!questionnaire || !id || questionnaire?.introduction?.id === id) { return []; @@ -85,7 +107,8 @@ export default ({ questionnaire, id, preprocessAnswers, - includeTargetPage + includeTargetPage, + expressionGroup ).filter(({ folders }) => folders.length) ); }; From d91022e0fa11918bcfd2c1d27a8fdbb724d53ae5 Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 17 Apr 2024 13:34:23 +0100 Subject: [PATCH 017/215] Add import folder button --- .../src/components/modals/ImportContentModal/index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/eq-author/src/components/modals/ImportContentModal/index.js b/eq-author/src/components/modals/ImportContentModal/index.js index 4cda609df9..e990e32458 100644 --- a/eq-author/src/components/modals/ImportContentModal/index.js +++ b/eq-author/src/components/modals/ImportContentModal/index.js @@ -37,6 +37,7 @@ const ImportQuestionReviewModal = ({ onCancel, onBack, onSelectQuestions, + onSelectFolders, onSelectSections, }) => ( - *Select individual questions or entire sections to be imported, you - cannot choose both* + Select sections, folders or question to import @@ -71,6 +71,12 @@ const ImportQuestionReviewModal = ({ > Questions + + + + + + ); +}; +FolderPicker.propTypes = { + title: PropTypes.string.isRequired, + sections: PropTypes.array.isRequired, // eslint-disable-line + startingSelectedFolders: PropTypes.array, // eslint-disable-line + showSearch: PropTypes.bool, + isOpen: PropTypes.bool, + onClose: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, +}; + +export default FolderPicker; diff --git a/eq-author/src/graphql/importFolders.graphql b/eq-author/src/graphql/importFolders.graphql new file mode 100644 index 0000000000..0acd051e72 --- /dev/null +++ b/eq-author/src/graphql/importFolders.graphql @@ -0,0 +1,128 @@ +#import "./fragments/folder.graphql" +#import "./fragments/page.graphql" +#import "./fragments/answer.graphql" +#import "./fragments/option.graphql" +#import "./fragments/comment.graphql" + +mutation ImportFolders($input: ImportFoldersInput!) { + importFolders(input: $input) { + ...Folder + pages { + ...Page + position + displayName + pageType + title + ... on QuestionPage { + alias + description + guidance + answers { + ...Answer + ... on BasicAnswer { + secondaryQCode + } + ... on MultipleChoiceAnswer { + options { + ...Option + } + mutuallyExclusiveOption { + id + displayName + label + description + value + qCode + } + } + } + confirmation { + id + qCode + displayName + comments { + ...Comment + } + } + comments { + ...Comment + } + } + ... on CalculatedSummaryPage { + id + title + alias + pageType + pageDescription + displayName + position + totalTitle + type + answers { + ...Answer + ... on BasicAnswer { + secondaryQCode + } + } + summaryAnswers { + id + } + comments { + ...Comment + } + } + ... on ListCollectorQualifierPage { + id + answers { + id + ... on MultipleChoiceAnswer { + ...Answer + options { + ...Option + } + mutuallyExclusiveOption { + id + } + } + } + comments { + ...Comment + } + } + ... on ListCollectorAddItemPage { + id + description + descriptionEnabled + guidance + guidanceEnabled + definitionLabel + definitionContent + definitionEnabled + additionalInfoLabel + additionalInfoContent + additionalInfoEnabled + comments { + ...Comment + } + } + ... on ListCollectorConfirmationPage { + id + answers { + id + ... on MultipleChoiceAnswer { + ...Answer + options { + ...Option + } + mutuallyExclusiveOption { + id + } + } + } + comments { + ...Comment + } + } + } + } +} From 055add24a7132ea26253cde0f19a7d191a1a4eb0 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 18 Apr 2024 13:42:41 +0100 Subject: [PATCH 019/215] Fix folder picker search --- .../src/components/FolderPicker/index.js | 15 +++++++++++--- .../searchByFolderTitleOrShortCode.js | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 eq-author/src/utils/searchFunctions/searchByFolderTitleOrShortCode.js diff --git a/eq-author/src/components/FolderPicker/index.js b/eq-author/src/components/FolderPicker/index.js index 24e35d2cbf..260c595512 100644 --- a/eq-author/src/components/FolderPicker/index.js +++ b/eq-author/src/components/FolderPicker/index.js @@ -84,9 +84,6 @@ const Folder = ({ folder }) => { /> ); }; -Folder.propTypes = { - folder: PropTypes.object, // eslint-disable-line -}; const Section = ({ section }) => { const { displayName, folders } = section; @@ -104,6 +101,9 @@ const Section = ({ section }) => { ); } + + // Handles sections with no folders matching the search term + return ; }; const FolderPicker = ({ @@ -186,6 +186,15 @@ const FolderPicker = ({ ); }; + +Folder.propTypes = { + folder: PropTypes.object, // eslint-disable-line +}; + +Section.propTypes = { + section: PropTypes.object, // eslint-disable-line +}; + FolderPicker.propTypes = { title: PropTypes.string.isRequired, sections: PropTypes.array.isRequired, // eslint-disable-line diff --git a/eq-author/src/utils/searchFunctions/searchByFolderTitleOrShortCode.js b/eq-author/src/utils/searchFunctions/searchByFolderTitleOrShortCode.js new file mode 100644 index 0000000000..30606f92f4 --- /dev/null +++ b/eq-author/src/utils/searchFunctions/searchByFolderTitleOrShortCode.js @@ -0,0 +1,20 @@ +const searchByFolderTitleOrShortCode = (data, searchTerm) => { + if (!searchTerm || searchTerm === "") { + return data; + } + + const lowerCaseSearchTerm = searchTerm.toLowerCase(); + + const filteredData = data.map(({ folders, ...rest }) => ({ + folders: folders.filter(({ alias, title, displayName }) => + `${alias ? alias : ""} ${title ? title : displayName}` + .toLowerCase() + .includes(lowerCaseSearchTerm) + ), + ...rest, + })); + + return filteredData; +}; + +export default searchByFolderTitleOrShortCode; From cf2661800e844a05ec5ce1e1052f7da4a9af37db Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 18 Apr 2024 15:05:47 +0100 Subject: [PATCH 020/215] Add folder review modal --- eq-author/src/App/importingContent/index.js | 29 ++- .../src/components/FolderPicker/index.js | 16 +- .../modals/ImportFolderReviewModal/index.js | 203 ++++++++++++++++++ 3 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 eq-author/src/components/modals/ImportFolderReviewModal/index.js diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index c20812ee03..eac712e547 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -24,6 +24,7 @@ import IMPORT_SECTIONS from "graphql/importSections.graphql"; import QuestionnaireSelectModal from "components/modals/QuestionnaireSelectModal"; import ReviewQuestionsModal from "components/modals/ImportQuestionReviewModal"; +import ReviewFoldersModal from "components/modals/ImportFolderReviewModal"; import ReviewSectionsModal from "components/modals/ImportSectionReviewModal"; import SelectContentModal from "components/modals/ImportContentModal"; import QuestionPicker from "components/QuestionPicker"; @@ -176,6 +177,17 @@ const ImportingContent = ({ setSelectingContent(false); }; + const onFolderPickerSubmit = (selection) => { + setFoldersToImport(selection); + setSelectingQuestions(false); + setReviewingQuestions(false); + setReviewingSections(false); + setSelectingSections(false); + setSelectingFolders(false); + setReviewingFolders(true); + setSelectingContent(false); + }; + // TODO: Reviewing folders to import const onRemoveAllSelectedContent = () => { @@ -580,12 +592,27 @@ const ImportingContent = ({ showSearch onClose={onGlobalCancel} // onCancel={onFolderPickerCancel} - // onSubmit={onFolderPickerSubmit} + onSubmit={onFolderPickerSubmit} /> ); }} )} + {reviewingFolders && ( + + )} {reviewingSections && ( { ); }; +Folder.propTypes = { + folder: PropTypes.object, // eslint-disable-line +}; + const Section = ({ section }) => { const { displayName, folders } = section; @@ -106,6 +110,10 @@ const Section = ({ section }) => { return ; }; +Section.propTypes = { + section: PropTypes.object, // eslint-disable-line +}; + const FolderPicker = ({ title, sections, @@ -187,14 +195,6 @@ const FolderPicker = ({ ); }; -Folder.propTypes = { - folder: PropTypes.object, // eslint-disable-line -}; - -Section.propTypes = { - section: PropTypes.object, // eslint-disable-line -}; - FolderPicker.propTypes = { title: PropTypes.string.isRequired, sections: PropTypes.array.isRequired, // eslint-disable-line diff --git a/eq-author/src/components/modals/ImportFolderReviewModal/index.js b/eq-author/src/components/modals/ImportFolderReviewModal/index.js new file mode 100644 index 0000000000..079b4fd81f --- /dev/null +++ b/eq-author/src/components/modals/ImportFolderReviewModal/index.js @@ -0,0 +1,203 @@ +import React from "react"; +import PropTypes from "prop-types"; +import styled from "styled-components"; +import { colors, radius, focusStyle, getTextHoverStyle } from "constants/theme"; + +import Wizard, { + Header, + Heading, + Subheading, + Content, + Warning, + SpacedRow, +} from "components/modals/Wizard"; +import Button from "components/buttons/Button"; + +const FoldersPane = styled.div` + max-height: 17em; + overflow: hidden; + overflow-y: scroll; + margin-bottom: 0.5em; +`; + +const FolderContainer = styled.div` + background-color: ${colors.blue}; + border-radius: ${radius}; + margin: 0 0 0.5em; + color: ${colors.white}; + padding: 0.5em 1em; + p { + margin: 0; + } +`; + +const commonButtonStyling = ` + background: transparent; + border: none; + cursor: pointer; + &:focus { + ${focusStyle} + } +`; + +const RemoveButton = styled.button` + ${commonButtonStyling} + color: ${colors.white}; + ${getTextHoverStyle(colors.white)} +`; + +const RemoveAllButton = styled.button` + ${commonButtonStyling} + font-weight: bold; + color: ${colors.blue}; + font-size: 1em; + ${getTextHoverStyle(colors.blue)} +`; + +const ContentHeading = styled.h4` + margin: 1em 0; + color: ${colors.textLight}; +`; + +const Container = styled.div` + display: flex; + gap: 0.5em; +`; + +const WarningWrapper = styled.div` + .warning-icon { + margin-top: -1.1em; + } + .warning-flex-container { + width: 40em; + } +`; + +const FolderRow = ({ folder: { alias, title, displayName }, onRemove }) => ( + + +
    +

    {title && alias}

    +

    {title || displayName}

    +
    + + + ✕ + + +
    +
    +); + +FolderRow.propTypes = { + folder: PropTypes.shape({ + alias: PropTypes.string, + title: PropTypes.string, + displayName: PropTypes.string, + }), + onRemove: PropTypes.func.isRequired, +}; + +const ImportFolderReviewModal = ({ + questionnaire, + startingSelectedFolders, + isOpen, + onConfirm, + onCancel, + onBack, + onSelectQuestions, + onSelectFolders, + onSelectSections, + onRemoveSingle, + onRemoveAll, +}) => ( + onConfirm(startingSelectedFolders)} + onCancel={onCancel} + onBack={onBack} + confirmEnabled={Boolean(startingSelectedFolders?.length) || false} + > +
    + Import content from {questionnaire.title} + + + + Question logic, piping and Qcodes will not be imported. Any extra + spaces in lines of text will be removed. + + + +
    + + {startingSelectedFolders?.length ? ( + <> + + + Folder{startingSelectedFolders.length > 1 && "s"} to import + + Remove all + + + {startingSelectedFolders.map((folder, index) => ( + onRemoveSingle(index)} + /> + ))} + + + ) : ( + + Select sections, folders or question to import + + )} + + {startingSelectedFolders?.length === 0 && ( + + )} + + {startingSelectedFolders?.length === 0 && ( + + )} + + +
    +); + +ImportFolderReviewModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onConfirm: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + onBack: PropTypes.func.isRequired, + onSelectQuestions: PropTypes.func.isRequired, + onSelectFolders: PropTypes.func.isRequired, + onSelectSections: PropTypes.func.isRequired, + onRemoveSingle: PropTypes.func.isRequired, + onRemoveAll: PropTypes.func.isRequired, + questionnaire: PropTypes.shape({ + title: PropTypes.string.isRequired, + }), + startingSelectedFolders: PropTypes.array, // eslint-disable-line +}; + +export default ImportFolderReviewModal; From cbb21c92582204cc613fd7d600fb9e23ae5d502f Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 19 Apr 2024 08:27:52 +0100 Subject: [PATCH 021/215] Display modal when folder picker cancel button is clicked --- eq-author/src/App/importingContent/index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index eac712e547..8dcf6300a9 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -177,6 +177,16 @@ const ImportingContent = ({ setSelectingContent(false); }; + const onFolderPickerCancel = () => { + setSelectingQuestions(false); + setReviewingQuestions(false); + setReviewingSections(false); + setSelectingSections(false); + setSelectingFolders(false); + setReviewingFolders(true); + setSelectingContent(false); + }; + const onFolderPickerSubmit = (selection) => { setFoldersToImport(selection); setSelectingQuestions(false); @@ -591,7 +601,7 @@ const ImportingContent = ({ startingSelectedFolders={foldersToImport} showSearch onClose={onGlobalCancel} - // onCancel={onFolderPickerCancel} + onCancel={onFolderPickerCancel} onSubmit={onFolderPickerSubmit} /> ); From f47e69f8250fcd3fbb97558515af1f511cb0e621 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 19 Apr 2024 08:41:44 +0100 Subject: [PATCH 022/215] Display import folder button when cancel button clicked on select modals --- eq-author/src/App/importingContent/index.js | 2 ++ .../modals/ImportContentModal/index.js | 12 +++---- .../modals/ImportQuestionReviewModal/index.js | 31 ++++++++++++------- .../modals/ImportSectionReviewModal/index.js | 31 ++++++++++++------- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index 8dcf6300a9..86121ba629 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -535,6 +535,7 @@ const ImportingContent = ({ onConfirm={onReviewQuestionsSubmit} onBack={onBackFromReviewingQuestions} onSelectQuestions={onSelectQuestions} + onSelectFolders={onSelectFolders} onSelectSections={onSelectSections} onRemoveAll={onRemoveAllSelectedContent} onRemoveSingle={onRemoveSingleSelectedContent} @@ -632,6 +633,7 @@ const ImportingContent = ({ onConfirm={onReviewSectionsSubmit} onBack={onBackFromReviewingSections} onSelectQuestions={onSelectQuestions} + onSelectFolders={onSelectFolders} onSelectSections={onSelectSections} onRemoveAll={onRemoveAllSelectedContent} onRemoveSingle={onRemoveSingleSelectedContent} diff --git a/eq-author/src/components/modals/ImportContentModal/index.js b/eq-author/src/components/modals/ImportContentModal/index.js index e990e32458..b70f68caeb 100644 --- a/eq-author/src/components/modals/ImportContentModal/index.js +++ b/eq-author/src/components/modals/ImportContentModal/index.js @@ -66,10 +66,10 @@ const ImportQuestionReviewModal = ({
    diff --git a/eq-author/src/components/modals/ImportQuestionReviewModal/index.js b/eq-author/src/components/modals/ImportQuestionReviewModal/index.js index 0c770087bc..6e6a8af2a1 100644 --- a/eq-author/src/components/modals/ImportQuestionReviewModal/index.js +++ b/eq-author/src/components/modals/ImportQuestionReviewModal/index.js @@ -106,6 +106,7 @@ const ImportQuestionReviewModal = ({ onCancel, onBack, onSelectQuestions, + onSelectFolders, onSelectSections, onRemoveSingle, onRemoveAll, @@ -151,19 +152,10 @@ const ImportQuestionReviewModal = ({ ) : ( - *Select individual questions or entire sections to be imported, you - cannot choose both* + Select sections, folders or question to import )} - {startingSelectedQuestions?.length === 0 && ( + )} +
    @@ -182,8 +190,9 @@ ImportQuestionReviewModal.propTypes = { onConfirm: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, onBack: PropTypes.func.isRequired, - onSelectQuestions: PropTypes.func.isRequired, onSelectSections: PropTypes.func.isRequired, + onSelectFolders: PropTypes.func.isRequired, + onSelectQuestions: PropTypes.func.isRequired, onRemoveSingle: PropTypes.func.isRequired, onRemoveAll: PropTypes.func.isRequired, questionnaire: PropTypes.shape({ diff --git a/eq-author/src/components/modals/ImportSectionReviewModal/index.js b/eq-author/src/components/modals/ImportSectionReviewModal/index.js index 9dd7f5ba01..a6e4c6a76e 100644 --- a/eq-author/src/components/modals/ImportSectionReviewModal/index.js +++ b/eq-author/src/components/modals/ImportSectionReviewModal/index.js @@ -107,6 +107,7 @@ const ImportSectionReviewModal = ({ onCancel, onBack, onSelectQuestions, + onSelectFolders, onSelectSections, onRemoveSingle, onRemoveAll, @@ -151,19 +152,10 @@ const ImportSectionReviewModal = ({ ) : ( - *Select individual questions or entire sections to be imported, you - cannot choose both* + Select sections, folders or question to import )} - {startingSelectedSections?.length === 0 && ( - - )} + )} + {startingSelectedSections?.length === 0 && ( + + )} @@ -182,8 +190,9 @@ ImportSectionReviewModal.propTypes = { onConfirm: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, onBack: PropTypes.func.isRequired, - onSelectQuestions: PropTypes.func.isRequired, onSelectSections: PropTypes.func.isRequired, + onSelectFolders: PropTypes.func.isRequired, + onSelectQuestions: PropTypes.func.isRequired, onRemoveSingle: PropTypes.func.isRequired, onRemoveAll: PropTypes.func.isRequired, questionnaire: PropTypes.shape({ From 0e5b69e29664c69048269eefe8db2d5ef3c6d728 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 19 Apr 2024 08:59:13 +0100 Subject: [PATCH 023/215] Enable removing folders from folder review modal --- eq-author/src/App/importingContent/index.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index 86121ba629..5b0b36ce33 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -203,7 +203,11 @@ const ImportingContent = ({ const onRemoveAllSelectedContent = () => { if (reviewingQuestions) { setQuestionsToImport([]); - } else { + } + if (reviewingFolders) { + setFoldersToImport([]); + } + if (reviewingSections) { setSectionsToImport([]); } }; @@ -212,7 +216,12 @@ const ImportingContent = ({ if (reviewingQuestions) { const filteredQuestions = questionsToImport.filter((_, i) => i !== index); setQuestionsToImport(filteredQuestions); - } else { + } + if (reviewingFolders) { + const filteredFolders = foldersToImport.filter((_, i) => i !== index); + setFoldersToImport(filteredFolders); + } + if (reviewingSections) { const filteredSections = sectionsToImport.filter((_, i) => i !== index); setSectionsToImport(filteredSections); } @@ -620,8 +629,8 @@ const ImportingContent = ({ onSelectQuestions={onSelectQuestions} onSelectFolders={onSelectFolders} onSelectSections={onSelectSections} - // onRemoveAll={onRemoveAllSelectedContent} - // onRemoveSingle={onRemoveSingleSelectedContent} + onRemoveAll={onRemoveAllSelectedContent} + onRemoveSingle={onRemoveSingleSelectedContent} /> )} {reviewingSections && ( From bf183443f99331e03334560e1473f9f58bf7f2be Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 19 Apr 2024 09:12:18 +0100 Subject: [PATCH 024/215] Fix back button for folder review modal --- eq-author/src/App/importingContent/index.js | 38 ++++++++++++++------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index 5b0b36ce33..ee0b107e72 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -117,6 +117,7 @@ const ImportingContent = ({ setQuestionnaireImportingFrom(questionnaire); setSelectingQuestionnaire(false); setReviewingQuestions(false); + setReviewingFolders(false); setReviewingSections(false); setSelectingContent(true); }; @@ -160,6 +161,8 @@ const ImportingContent = ({ setReviewingQuestions(false); setSelectingQuestionnaire(true); setQuestionsToImport([]); + setSelectingFolders(false); + setReviewingFolders(false); setReviewingSections(false); setSelectingSections(false); setSelectingContent(false); @@ -167,16 +170,6 @@ const ImportingContent = ({ // Selecting folders to import - const onSelectFolders = () => { - setReviewingQuestions(false); - setSelectingQuestions(false); - setSelectingFolders(true); - setReviewingFolders(false); - setReviewingSections(false); - setSelectingSections(false); - setSelectingContent(false); - }; - const onFolderPickerCancel = () => { setSelectingQuestions(false); setReviewingQuestions(false); @@ -198,7 +191,28 @@ const ImportingContent = ({ setSelectingContent(false); }; - // TODO: Reviewing folders to import + // Reviewing folders to import + + const onSelectFolders = () => { + setReviewingQuestions(false); + setSelectingQuestions(false); + setSelectingFolders(true); + setReviewingFolders(false); + setReviewingSections(false); + setSelectingSections(false); + setSelectingContent(false); + }; + + const onBackFromReviewingFolders = () => { + setReviewingQuestions(false); + setSelectingQuestionnaire(true); + setQuestionsToImport([]); + setSelectingFolders(false); + setReviewingFolders(false); + setReviewingSections(false); + setSelectingSections(false); + setSelectingContent(false); + }; const onRemoveAllSelectedContent = () => { if (reviewingQuestions) { @@ -625,7 +639,7 @@ const ImportingContent = ({ startingSelectedFolders={foldersToImport} onCancel={onGlobalCancel} // onConfirm={onReviewFoldersSubmit} - // onBack={onBackFromReviewingFolders} + onBack={onBackFromReviewingFolders} onSelectQuestions={onSelectQuestions} onSelectFolders={onSelectFolders} onSelectSections={onSelectSections} From 453376e093f90708f352325f3a48080b2dd7a9f7 Mon Sep 17 00:00:00 2001 From: sudeep Date: Fri, 19 Apr 2024 09:34:13 +0100 Subject: [PATCH 025/215] hide array types Signed-off-by: sudeep --- eq-author/src/components/RichTextEditor/PipingMenu.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eq-author/src/components/RichTextEditor/PipingMenu.js b/eq-author/src/components/RichTextEditor/PipingMenu.js index 7b9d2883f7..e7065ba9b6 100644 --- a/eq-author/src/components/RichTextEditor/PipingMenu.js +++ b/eq-author/src/components/RichTextEditor/PipingMenu.js @@ -132,7 +132,7 @@ const PipingMenu = ({ ); } - const supplementaryData = allSupplementaryData.flatMap((list) => { + let supplementaryData = allSupplementaryData.flatMap((list) => { return list.schemaFields.map((schemaField) => { return { listName: list.listName, @@ -141,6 +141,10 @@ const PipingMenu = ({ }); }); + supplementaryData = supplementaryData.filter( + (list) => !(list.listName !== "" && list.type === "array") + ); + const handlePickerContent = (contentType) => { switch (contentType) { case METADATA: From 9b9173edc357025020e649283d2a08be8b3df830 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 19 Apr 2024 12:54:35 +0100 Subject: [PATCH 026/215] Add import folder functionality --- eq-author-api/schema/resolvers/importing.js | 65 ++++++ .../schema/resolvers/utils/folderGetters.js | 4 + eq-author-api/schema/typeDefs.js | 7 + eq-author/src/App/importingContent/index.js | 109 +++++++++- eq-author/src/graphql/importFolders.graphql | 196 +++++++++--------- 5 files changed, 283 insertions(+), 98 deletions(-) diff --git a/eq-author-api/schema/resolvers/importing.js b/eq-author-api/schema/resolvers/importing.js index d599f3b1e1..a357044525 100644 --- a/eq-author-api/schema/resolvers/importing.js +++ b/eq-author-api/schema/resolvers/importing.js @@ -1,6 +1,7 @@ const { getPagesByIds, getFolderById, + getFoldersByIds, getSectionById, getSectionByFolderId, stripQCodes, @@ -98,6 +99,70 @@ module.exports = { return section; } ), + importFolders: createMutation( + async (_, { input: { questionnaireId, folderIds, position } }, ctx) => { + const { sectionId, index: insertionIndex } = position; + + if (!sectionId) { + throw new UserInputError("Target section ID must be provided."); + } + + const sourceQuestionnaire = await getQuestionnaire(questionnaireId); + if (!sourceQuestionnaire) { + throw new UserInputError( + `Questionnaire with ID ${questionnaireId} does not exist.` + ); + } + + const sourceFolders = getFoldersByIds( + { questionnaire: sourceQuestionnaire }, + folderIds + ); + + if (sourceFolders.length !== folderIds.length) { + throw new UserInputError( + `Not all folder IDs in [${folderIds}] exist in source questionnaire ${questionnaireId}.` + ); + } + + sourceFolders.forEach((folder) => { + remapAllNestedIds(folder); + removeExtraSpaces(folder); + folder.skipConditions = null; + if (folder.listId) { + folder.listId = ""; + } + folder.pages.forEach((page) => { + page.routing = null; + page.skipConditions = null; + if (page.answers !== undefined) { + page.answers.forEach((answer) => { + return stripQCodes(answer); + }); + } + + if (page.answers?.length === 1) { + if (page.answers[0].repeatingLabelAndInputListId) { + page.answers[0].repeatingLabelAndInputListId = ""; + } + } + }); + }); + + const section = getSectionById(ctx, sectionId); + + if (!section) { + throw new UserInputError( + `Section with ID ${sectionId} doesn't exist in target questionnaire.` + ); + } + + section.folders.splice(insertionIndex, 0, ...sourceFolders); + setDataVersion(ctx); + + return section; + } + ), importSections: createMutation( async (_, { input: { questionnaireId, sectionIds, position } }, ctx) => { const { sectionId, index: insertionIndex } = position; diff --git a/eq-author-api/schema/resolvers/utils/folderGetters.js b/eq-author-api/schema/resolvers/utils/folderGetters.js index 27c685d4e8..a917a3ec50 100644 --- a/eq-author-api/schema/resolvers/utils/folderGetters.js +++ b/eq-author-api/schema/resolvers/utils/folderGetters.js @@ -17,10 +17,14 @@ const getFolderByAnswerId = (ctx, id) => pages && some(pages, ({ answers }) => answers && some(answers, { id })) ); +const getFoldersByIds = (ctx, ids) => + getFolders(ctx).filter(({ id }) => ids.includes(id)); + module.exports = { getFolders, getFoldersBySectionId, getFolderById, getFolderByPageId, getFolderByAnswerId, + getFoldersByIds, }; diff --git a/eq-author-api/schema/typeDefs.js b/eq-author-api/schema/typeDefs.js index f77ab93712..1b445c28f1 100644 --- a/eq-author-api/schema/typeDefs.js +++ b/eq-author-api/schema/typeDefs.js @@ -985,6 +985,12 @@ input ImportQuestionsInput { position: Position! } +input ImportFoldersInput { + questionnaireId: ID! + folderIds: [ID!]! + position: Position! +} + input ImportSectionsInput { questionnaireId: ID! sectionIds: [ID!]! @@ -999,6 +1005,7 @@ type Mutation { setQuestionnaireLocked(input: SetQuestionnaireLockedInput!): Questionnaire importQuestions(input: ImportQuestionsInput!): Section + importFolders(input: ImportFoldersInput!): Section importSections(input: ImportSectionsInput!): [Section] createHistoryNote(input: createHistoryNoteInput!): [History!]! diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index ee0b107e72..476dec5b58 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -63,6 +63,8 @@ const ImportingContent = ({ const [selectingContent, setSelectingContent] = useState(false); const [showQuestionExtraSpaceModal, setShowQuestionExtraSpaceModal] = useState(false); + const [showFolderExtraSpaceModal, setShowFolderExtraSpaceModal] = + useState(false); const [showSectionExtraSpaceModal, setShowSectionExtraSpaceModal] = useState(false); @@ -385,6 +387,91 @@ const ImportingContent = ({ } }; + const onReviewFoldersSubmit = (selectedFolders) => { + const folderIds = selectedFolders.map(({ id }) => id); + let folderContainsExtraSpaces = false; + + selectedFolders.forEach((selectedFolder) => { + if (containsExtraSpaces(selectedFolder)) { + folderContainsExtraSpaces = true; + } + }); + + if (folderContainsExtraSpaces && !showFolderExtraSpaceModal) { + setReviewingQuestions(false); + setSelectingQuestions(false); + setReviewingSections(false); + setSelectingSections(false); + setSelectingFolders(false); + setReviewingFolders(false); + setSelectingContent(false); + setShowFolderExtraSpaceModal(true); + } else { + let input = { + folderIds, + questionnaireId: questionnaireImportingFrom.id, + }; + + switch (currentEntityName) { + case "section": { + input.position = { + sectionId: currentEntityId, + index: 0, + }; + + break; + } + case "folder": { + const { id: sectionId } = getSectionByFolderId( + sourceQuestionnaire, + currentEntityId + ); + + const { position } = getFolderById( + sourceQuestionnaire, + currentEntityId + ); + + input.position = { + sectionId, + }; + + input.position.index = position + 1; + + break; + } + case "page": { + const { id: sectionId } = getSectionByPageId( + sourceQuestionnaire, + currentEntityId + ); + + input.position = { + sectionId, + }; + + const { position: folderPosition } = getFolderByPageId( + sourceQuestionnaire, + currentEntityId + ); + + input.position.index = folderPosition + 1; + + break; + } + default: { + throw new Error("Unknown entity"); + } + } + + importFolders({ + variables: { input }, + refetchQueries: ["GetQuestionnaire"], + }); + onGlobalCancel(); + } + }; + // Selecting sections to import const onSectionPickerCancel = () => { @@ -638,7 +725,7 @@ const ImportingContent = ({ questionnaire={questionnaireImportingFrom} startingSelectedFolders={foldersToImport} onCancel={onGlobalCancel} - // onConfirm={onReviewFoldersSubmit} + onConfirm={onReviewFoldersSubmit} onBack={onBackFromReviewingFolders} onSelectQuestions={onSelectQuestions} onSelectFolders={onSelectFolders} @@ -716,6 +803,26 @@ const ImportingContent = ({ )} + {showFolderExtraSpaceModal && ( + + onReviewFoldersSubmit(foldersToImport)} + onClose={onGlobalCancel} + > +

    + The selected content contains extra spaces at the start of lines + of text, between words, or at the end of lines of text. +

    +

    + Extra spaces need to be removed before this content can be + imported. +

    +
    +
    + )} {showSectionExtraSpaceModal && ( Date: Fri, 19 Apr 2024 13:04:20 +0100 Subject: [PATCH 027/215] Update text --- eq-author/src/components/modals/ImportContentModal/index.js | 2 +- .../src/components/modals/ImportFolderReviewModal/index.js | 2 +- .../src/components/modals/ImportQuestionReviewModal/index.js | 2 +- .../src/components/modals/ImportSectionReviewModal/index.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eq-author/src/components/modals/ImportContentModal/index.js b/eq-author/src/components/modals/ImportContentModal/index.js index b70f68caeb..636e6dcc84 100644 --- a/eq-author/src/components/modals/ImportContentModal/index.js +++ b/eq-author/src/components/modals/ImportContentModal/index.js @@ -61,7 +61,7 @@ const ImportQuestionReviewModal = ({ - Select sections, folders or question to import + Select sections, folders or questions to import diff --git a/eq-author/src/components/modals/ImportFolderReviewModal/index.js b/eq-author/src/components/modals/ImportFolderReviewModal/index.js index 079b4fd81f..25ea0b7c1e 100644 --- a/eq-author/src/components/modals/ImportFolderReviewModal/index.js +++ b/eq-author/src/components/modals/ImportFolderReviewModal/index.js @@ -151,7 +151,7 @@ const ImportFolderReviewModal = ({ ) : ( - Select sections, folders or question to import + Select sections, folders or questions to import )} diff --git a/eq-author/src/components/modals/ImportQuestionReviewModal/index.js b/eq-author/src/components/modals/ImportQuestionReviewModal/index.js index 6e6a8af2a1..720b28d3fa 100644 --- a/eq-author/src/components/modals/ImportQuestionReviewModal/index.js +++ b/eq-author/src/components/modals/ImportQuestionReviewModal/index.js @@ -152,7 +152,7 @@ const ImportQuestionReviewModal = ({ ) : ( - Select sections, folders or question to import + Select sections, folders or questions to import )} diff --git a/eq-author/src/components/modals/ImportSectionReviewModal/index.js b/eq-author/src/components/modals/ImportSectionReviewModal/index.js index a6e4c6a76e..8e9a2ddced 100644 --- a/eq-author/src/components/modals/ImportSectionReviewModal/index.js +++ b/eq-author/src/components/modals/ImportSectionReviewModal/index.js @@ -152,7 +152,7 @@ const ImportSectionReviewModal = ({ ) : ( - Select sections, folders or question to import + Select sections, folders or questions to import )} From d507feeb9ef50bdf4b16c45b2bee950e24a56cf7 Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 19 Apr 2024 13:12:49 +0100 Subject: [PATCH 028/215] Remove cannot import folders text --- eq-author/src/App/importingContent/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index 476dec5b58..8df48ff627 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -675,7 +675,6 @@ const ImportingContent = ({ isOpen={selectingQuestions} sections={sections} startingSelectedQuestions={questionsToImport} - warningPanel="You cannot import folders but you can import any questions they contain." showSearch onClose={onGlobalCancel} onCancel={onQuestionPickerCancel} @@ -773,7 +772,6 @@ const ImportingContent = ({ isOpen={selectingSections} sections={sections} startingSelectedSections={sectionsToImport} - warningPanel="You cannot import folders but you can import any questions they contain." showSearch onClose={onGlobalCancel} onCancel={onSectionPickerCancel} From c5b3477a0a6c683778eb3d22b0efc109ddfdd41d Mon Sep 17 00:00:00 2001 From: farres1 Date: Fri, 19 Apr 2024 14:57:24 +0100 Subject: [PATCH 029/215] Remove list collector folders when importing into a repeating section --- eq-author/src/App/importingContent/index.js | 44 ++++++++++++++++++- .../src/graphql/getQuestionnaire.graphql | 3 ++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index 8df48ff627..8cf2dba18f 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -279,6 +279,32 @@ const ImportingContent = ({ } }; + const isInsideRepeatingSection = () => { + switch (currentEntityName) { + case "section": { + const section = getSectionById(sourceQuestionnaire, currentEntityId); + return section.repeatingSection; + } + case "folder": { + const section = getSectionByFolderId( + sourceQuestionnaire, + currentEntityId + ); + return section.repeatingSection; + } + case "page": { + const section = getSectionByPageId( + sourceQuestionnaire, + currentEntityId + ); + return section.repeatingSection; + } + default: { + return false; + } + } + }; + const onReviewQuestionsSubmit = (selectedQuestions) => { const questionIds = selectedQuestions.map(({ id }) => id); let questionContainsExtraSpaces = false; @@ -702,12 +728,28 @@ const ImportingContent = ({ } const { sections } = data.questionnaire; + const sectionsToDisplay = []; + + // Removes list collector folders from sections containing selectable folders when importing into a repeating section + if (isInsideRepeatingSection()) { + sections.forEach((section) => { + const foldersWithoutListCollectors = section.folders.filter( + (folder) => folder.listId == null + ); + sectionsToDisplay.push({ + ...section, + folders: foldersWithoutListCollectors, + }); + }); + } else { + sectionsToDisplay.push(...sections); + } return ( Date: Mon, 22 Apr 2024 07:36:49 +0100 Subject: [PATCH 030/215] Display warning message when importing into repeating section --- eq-author/src/App/importingContent/index.js | 4 ++ .../src/components/FolderPicker/index.js | 44 ++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index 8cf2dba18f..1280425757 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -751,6 +751,10 @@ const ImportingContent = ({ isOpen={selectingFolders} sections={sectionsToDisplay} startingSelectedFolders={foldersToImport} + warningMessage={ + isInsideRepeatingSection() && + "You cannot import list collector folders into a repeating section" + } showSearch onClose={onGlobalCancel} onCancel={onFolderPickerCancel} diff --git a/eq-author/src/components/FolderPicker/index.js b/eq-author/src/components/FolderPicker/index.js index 5b4fe0165d..6122b41592 100644 --- a/eq-author/src/components/FolderPicker/index.js +++ b/eq-author/src/components/FolderPicker/index.js @@ -11,8 +11,11 @@ import SelectedFolderContext, { SelectedFoldersProvider, } from "./SelectedFoldersContext"; +import { ReactComponent as WarningIcon } from "assets/icon-warning-round.svg"; + import Modal from "components/modals/Modal"; import SearchBar from "components/SearchBar"; +import IconText from "components/IconText"; import Button from "components/buttons/Button"; import ButtonGroup from "components/buttons/ButtonGroup"; import ScrollPane from "components/ScrollPane"; @@ -29,12 +32,8 @@ const StyledModal = styled(Modal)` } `; -const Header = styled.header` +const Header = styled.div` margin: 0 1.5em; - - > * { - margin-bottom: 2em; - } `; const Main = styled.main` @@ -53,6 +52,22 @@ const Title = styled.h2` margin-bottom: 0.75em; `; +const WarningPanel = styled(IconText)` + font-weight: bold; + font-size: 1.1em; + margin-bottom: 0.8em; + + svg { + width: 3em; + height: 3em; + margin-right: 0.5em; + } +`; + +const SearchBarWrapper = styled.div` + margin-bottom: 1.5em; +`; + const isSelected = (items, target) => items.find(({ id }) => id === target.id); const Folder = ({ folder }) => { @@ -119,6 +134,7 @@ const FolderPicker = ({ sections, showSearch, isOpen, + warningMessage, onClose, onCancel, onSubmit, @@ -146,12 +162,19 @@ const FolderPicker = ({
    {title} + {warningMessage && ( + + {warningMessage} + + )} {showSearch && ( - setSearchTerm(value)} - placeholder="Search folders" - /> + + setSearchTerm(value)} + placeholder="Search folders" + /> + )}
    @@ -200,6 +223,7 @@ FolderPicker.propTypes = { sections: PropTypes.array.isRequired, // eslint-disable-line startingSelectedFolders: PropTypes.array, // eslint-disable-line showSearch: PropTypes.bool, + warningMessage: PropTypes.string, isOpen: PropTypes.bool, onClose: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, From 298f8ec3660e7f225fb7574424982b086882fd70 Mon Sep 17 00:00:00 2001 From: farres1 Date: Mon, 22 Apr 2024 13:54:54 +0100 Subject: [PATCH 031/215] Display error message when no folders found matching search --- eq-author/src/components/FolderPicker/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eq-author/src/components/FolderPicker/index.js b/eq-author/src/components/FolderPicker/index.js index 6122b41592..321f2bed5c 100644 --- a/eq-author/src/components/FolderPicker/index.js +++ b/eq-author/src/components/FolderPicker/index.js @@ -180,7 +180,10 @@ const FolderPicker = ({
    {searchTerm === "" || (searchTerm !== "" && - searchByFolderTitleOrShortCode(sections, searchTerm).length > 0) ? ( + // Checks if there are any sections with folders matching the search term + searchByFolderTitleOrShortCode(sections, searchTerm).some( + (section) => section.folders.length > 0 + )) ? ( Date: Mon, 22 Apr 2024 14:33:57 +0100 Subject: [PATCH 032/215] Fix heading accessibility error --- .../components/modals/ImportFolderReviewModal/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/eq-author/src/components/modals/ImportFolderReviewModal/index.js b/eq-author/src/components/modals/ImportFolderReviewModal/index.js index 25ea0b7c1e..bf338607ff 100644 --- a/eq-author/src/components/modals/ImportFolderReviewModal/index.js +++ b/eq-author/src/components/modals/ImportFolderReviewModal/index.js @@ -6,7 +6,6 @@ import { colors, radius, focusStyle, getTextHoverStyle } from "constants/theme"; import Wizard, { Header, Heading, - Subheading, Content, Warning, SpacedRow, @@ -73,6 +72,13 @@ const WarningWrapper = styled.div` } `; +const Subheading = styled.h3` + margin: 0 0.5em 0 0; + display: block; + font-weight: bold; + font-size: 1em; +`; + const FolderRow = ({ folder: { alias, title, displayName }, onRemove }) => ( From 0f820d9493530caee0024850ac65c28587d73e7b Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 23 Apr 2024 09:33:48 +0100 Subject: [PATCH 033/215] Display list collector badge for list collector folders in import menu --- eq-author/src/components/FolderPicker/Item/index.js | 12 ++++++++++++ eq-author/src/components/FolderPicker/index.js | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/eq-author/src/components/FolderPicker/Item/index.js b/eq-author/src/components/FolderPicker/Item/index.js index 1854539ba7..165ae882e4 100644 --- a/eq-author/src/components/FolderPicker/Item/index.js +++ b/eq-author/src/components/FolderPicker/Item/index.js @@ -111,9 +111,19 @@ const Subtitle = styled.span` color: ${colors.darkGrey}; `; +const ContentBadge = styled.span` + font-size: 0.8em; + position: absolute; + padding: 0.3em 0.7em; + right: 1.5em; + background: ${colors.lightMediumGrey}; + border-radius: 1em; +`; + const WrappedItem = ({ title, subtitle, + isListCollector, variant, selected, unselectable = false, @@ -141,6 +151,7 @@ const WrappedItem = ({ > {variant !== "heading" && subtitle && {subtitle}} {variant !== "heading" && {title}} + {isListCollector && List collector} {variant === "heading" && {title}} {children} @@ -152,6 +163,7 @@ WrappedItem.propTypes = { unselectable: PropTypes.bool, title: PropTypes.string.isRequired, subtitle: PropTypes.string, + isListCollector: PropTypes.bool, variant: PropTypes.string, selected: PropTypes.bool, onClick: PropTypes.func, diff --git a/eq-author/src/components/FolderPicker/index.js b/eq-author/src/components/FolderPicker/index.js index 321f2bed5c..0a253c7740 100644 --- a/eq-author/src/components/FolderPicker/index.js +++ b/eq-author/src/components/FolderPicker/index.js @@ -71,7 +71,7 @@ const SearchBarWrapper = styled.div` const isSelected = (items, target) => items.find(({ id }) => id === target.id); const Folder = ({ folder }) => { - const { title, alias, displayName } = folder; + const { title, alias, displayName, listId } = folder; const { selectedFolders, updateSelectedFolders } = useContext( SelectedFolderContext ); @@ -93,6 +93,7 @@ const Folder = ({ folder }) => { Date: Tue, 23 Apr 2024 10:01:29 +0100 Subject: [PATCH 034/215] Fix console errors --- eq-author/src/App/importingContent/index.js | 5 +++-- eq-author/src/components/FolderPicker/Item/index.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/eq-author/src/App/importingContent/index.js b/eq-author/src/App/importingContent/index.js index 1280425757..9c15175b22 100644 --- a/eq-author/src/App/importingContent/index.js +++ b/eq-author/src/App/importingContent/index.js @@ -752,8 +752,9 @@ const ImportingContent = ({ sections={sectionsToDisplay} startingSelectedFolders={foldersToImport} warningMessage={ - isInsideRepeatingSection() && - "You cannot import list collector folders into a repeating section" + isInsideRepeatingSection() + ? "You cannot import list collector folders into a repeating section" + : "" } showSearch onClose={onGlobalCancel} diff --git a/eq-author/src/components/FolderPicker/Item/index.js b/eq-author/src/components/FolderPicker/Item/index.js index 165ae882e4..fb10988ecb 100644 --- a/eq-author/src/components/FolderPicker/Item/index.js +++ b/eq-author/src/components/FolderPicker/Item/index.js @@ -143,7 +143,7 @@ const WrappedItem = ({ variant={variant} className={`${variant}`} aria-selected={selected} - unselectable={unselectable} + $unselectable={unselectable} tabIndex={unselectable ? -1 : 0} onClick={onClick} onKeyUp={({ keyCode }) => onEnterUp(keyCode, onClick)} From c9ff3abb038a5fa93e5d6e8d3d31e2e11d898bc0 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 23 Apr 2024 14:04:25 +0100 Subject: [PATCH 035/215] Fix failing tests --- eq-author/src/App/importingContent/index.test.js | 16 ++++------------ .../ImportQuestionReviewModal/index.test.js | 4 +--- .../ImportSectionReviewModal/index.test.js | 4 +--- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/eq-author/src/App/importingContent/index.test.js b/eq-author/src/App/importingContent/index.test.js index 09ec0eb4eb..f97fa9e78a 100644 --- a/eq-author/src/App/importingContent/index.test.js +++ b/eq-author/src/App/importingContent/index.test.js @@ -420,9 +420,7 @@ describe("Importing content", () => { expect(queryByText("Page 1")).not.toBeInTheDocument(); expect( - queryByText( - "*Select individual questions or entire sections to be imported, you cannot choose both*" - ) + queryByText("Select sections, folders or questions to import") ).toBeInTheDocument(); }); @@ -478,9 +476,7 @@ describe("Importing content", () => { expect(queryByText("Page 1")).not.toBeInTheDocument(); expect(queryByText("Page 2")).not.toBeInTheDocument(); expect( - getByText( - "*Select individual questions or entire sections to be imported, you cannot choose both*" - ) + getByText("Select sections, folders or questions to import") ).toBeInTheDocument(); }); @@ -1240,9 +1236,7 @@ describe("Importing content", () => { expect(queryByText("Section 1")).not.toBeInTheDocument(); expect( - queryByText( - "*Select individual questions or entire sections to be imported, you cannot choose both*" - ) + queryByText("Select sections, folders or questions to import") ).toBeInTheDocument(); }); @@ -1298,9 +1292,7 @@ describe("Importing content", () => { expect(queryByText("Section 1")).not.toBeInTheDocument(); expect(queryByText("Section 2")).not.toBeInTheDocument(); expect( - getByText( - "*Select individual questions or entire sections to be imported, you cannot choose both*" - ) + getByText("Select sections, folders or questions to import") ).toBeInTheDocument(); }); diff --git a/eq-author/src/components/modals/ImportQuestionReviewModal/index.test.js b/eq-author/src/components/modals/ImportQuestionReviewModal/index.test.js index 8d7c95e0ea..d3ab3c4d14 100644 --- a/eq-author/src/components/modals/ImportQuestionReviewModal/index.test.js +++ b/eq-author/src/components/modals/ImportQuestionReviewModal/index.test.js @@ -36,9 +36,7 @@ describe("Import questions review modal", () => { ); expect( - screen.queryByText( - /Select individual questions or entire sections to be imported, you cannot choose both/ - ) + screen.queryByText(/Select sections, folders or questions to import/) ).toBeTruthy(); // Import button should be disabled when no questions selected expect(screen.getByText(/^Import$/)).toBeDisabled(); diff --git a/eq-author/src/components/modals/ImportSectionReviewModal/index.test.js b/eq-author/src/components/modals/ImportSectionReviewModal/index.test.js index fb6dea9fcc..dc2ea15212 100644 --- a/eq-author/src/components/modals/ImportSectionReviewModal/index.test.js +++ b/eq-author/src/components/modals/ImportSectionReviewModal/index.test.js @@ -31,9 +31,7 @@ describe("Import sections review modal", () => { ); expect( - screen.queryByText( - /Select individual questions or entire sections to be imported, you cannot choose both/ - ) + screen.queryByText(/Select sections, folders or questions to import/) ).toBeTruthy(); // Import button should be disabled when no questions selected expect(screen.getByText(/^Import$/)).toBeDisabled(); From 44df8ede3826431e80ebde772bdd6b39b6245555 Mon Sep 17 00:00:00 2001 From: farres1 Date: Tue, 23 Apr 2024 15:07:53 +0100 Subject: [PATCH 036/215] Add tests for folder title and short code search --- .../searchByFolderTitleOrShortCode.test.js | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 eq-author/src/utils/searchFunctions/searchByFolderTitleOrShortCode.test.js diff --git a/eq-author/src/utils/searchFunctions/searchByFolderTitleOrShortCode.test.js b/eq-author/src/utils/searchFunctions/searchByFolderTitleOrShortCode.test.js new file mode 100644 index 0000000000..ec67ee6e9e --- /dev/null +++ b/eq-author/src/utils/searchFunctions/searchByFolderTitleOrShortCode.test.js @@ -0,0 +1,68 @@ +import searchByFolderTitleOrShortCode from "./searchByFolderTitleOrShortCode"; + +describe("searchByFolderTitleOrShortCode", () => { + let data; + + beforeEach(() => { + data = [ + { + folders: [ + { + id: "folder-1", + alias: "Folder 1", + }, + { + id: "folder-2", + alias: "Folder 2", + }, + ], + }, + { + folders: [ + { + id: "list-folder-1", + listId: "list-1", + alias: "List 1", + title: "List folder 1", + }, + { + id: "list-folder-2", + listId: "list-2", + title: "List folder 2", + }, + ], + }, + ]; + }); + + it("should return data when searchTerm is empty string", () => { + const searchResult = searchByFolderTitleOrShortCode(data, ""); + + expect(searchResult).toEqual(data); + }); + + it("should return filtered data when searchTerm is not empty string", () => { + const searchResult = searchByFolderTitleOrShortCode(data, "folder 1"); + + expect(searchResult).toEqual([ + { + folders: [ + { + id: "folder-1", + alias: "Folder 1", + }, + ], + }, + { + folders: [ + { + id: "list-folder-1", + listId: "list-1", + alias: "List 1", + title: "List folder 1", + }, + ], + }, + ]); + }); +}); From 464f74a7d62ccd86093d5ce15f6248b747f0113a Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 24 Apr 2024 12:47:47 +0100 Subject: [PATCH 037/215] Add folder picker item tests --- .../src/components/FolderPicker/Item/index.js | 36 +++++--- .../FolderPicker/Item/index.test.js | 86 +++++++++++++++++++ .../src/components/FolderPicker/index.js | 8 +- 3 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 eq-author/src/components/FolderPicker/Item/index.test.js diff --git a/eq-author/src/components/FolderPicker/Item/index.js b/eq-author/src/components/FolderPicker/Item/index.js index fb10988ecb..747367c8a1 100644 --- a/eq-author/src/components/FolderPicker/Item/index.js +++ b/eq-author/src/components/FolderPicker/Item/index.js @@ -121,6 +121,7 @@ const ContentBadge = styled.span` `; const WrappedItem = ({ + id, title, subtitle, isListCollector, @@ -129,16 +130,15 @@ const WrappedItem = ({ unselectable = false, onClick, children, - dataTest, }) => { - const onEnterUp = (keyCode, callback) => { - if (keyCode === 13) { - callback(); + const handleEnterUp = (key, onEnter) => { + if (key === "Enter") { + onEnter(); } }; return ( - + onEnterUp(keyCode, onClick)} - data-test="folder-picker-item" + onKeyUp={({ key }) => handleEnterUp(key, onClick)} + data-test={`folder-picker-item-${id}`} > - {variant !== "heading" && subtitle && {subtitle}} - {variant !== "heading" && {title}} - {isListCollector && List collector} - {variant === "heading" && {title}} + {variant !== "heading" && subtitle && ( + + {subtitle} + + )} + {variant !== "heading" && ( + {title} + )} + {isListCollector && ( + + List collector + + )} + {variant === "heading" && ( + {title} + )} {children} ); }; WrappedItem.propTypes = { + id: PropTypes.string, icon: PropTypes.node, unselectable: PropTypes.bool, title: PropTypes.string.isRequired, @@ -168,7 +181,6 @@ WrappedItem.propTypes = { selected: PropTypes.bool, onClick: PropTypes.func, children: PropTypes.node, - dataTest: PropTypes.string, }; export default WrappedItem; diff --git a/eq-author/src/components/FolderPicker/Item/index.test.js b/eq-author/src/components/FolderPicker/Item/index.test.js new file mode 100644 index 0000000000..25cc55c6c1 --- /dev/null +++ b/eq-author/src/components/FolderPicker/Item/index.test.js @@ -0,0 +1,86 @@ +import React from "react"; +import { render, fireEvent } from "tests/utils/rtl"; + +import Item from "."; + +import { colors } from "constants/theme"; + +const renderFolderPickerItem = (props) => { + return render(); +}; + +describe("FolderPicker item", () => { + let props; + + beforeEach(() => { + props = { + id: "folder-1", + title: "Folder 1", + subtitle: "1", + }; + }); + + it("should display heading if variant is heading", () => { + const { getByTestId } = renderFolderPickerItem({ + id: "section-1", + title: "Section 1", + variant: "heading", + }); + + expect(getByTestId("folder-picker-item-section-1")).toHaveStyleRule( + "background-color", + colors.lightMediumGrey + ); + expect(getByTestId("folder-picker-heading-section-1")).toHaveTextContent( + "Section 1" + ); + }); + + it("should display title and subtitle when both are defined and variant is not heading", () => { + const { getByTestId } = renderFolderPickerItem({ + ...props, + }); + + expect(getByTestId("folder-picker-title-folder-1")).toHaveTextContent( + "Folder 1" + ); + expect(getByTestId("folder-picker-subtitle-folder-1")).toHaveTextContent( + "1" + ); + }); + + it("should display content badge for list collector folders", () => { + const { getByTestId } = renderFolderPickerItem({ + ...props, + isListCollector: true, + }); + + expect( + getByTestId("folder-picker-content-badge-folder-1") + ).toHaveTextContent("List collector"); + }); + + it("should call onClick when clicked", () => { + const onClick = jest.fn(); + const { getByTestId } = renderFolderPickerItem({ + ...props, + onClick, + }); + + fireEvent.click(getByTestId("folder-picker-item-folder-1")); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + it("should call onClick when enter key is pressed", () => { + const onClick = jest.fn(); + const { getByTestId } = renderFolderPickerItem({ + ...props, + onClick, + }); + + fireEvent.keyUp(getByTestId("folder-picker-item-folder-1"), { + key: "Enter", + }); + expect(onClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/eq-author/src/components/FolderPicker/index.js b/eq-author/src/components/FolderPicker/index.js index 0a253c7740..b3257d6286 100644 --- a/eq-author/src/components/FolderPicker/index.js +++ b/eq-author/src/components/FolderPicker/index.js @@ -71,7 +71,7 @@ const SearchBarWrapper = styled.div` const isSelected = (items, target) => items.find(({ id }) => id === target.id); const Folder = ({ folder }) => { - const { title, alias, displayName, listId } = folder; + const { id, title, alias, displayName, listId } = folder; const { selectedFolders, updateSelectedFolders } = useContext( SelectedFolderContext ); @@ -91,12 +91,12 @@ const Folder = ({ folder }) => { return ( ); }; @@ -106,13 +106,13 @@ Folder.propTypes = { }; const Section = ({ section }) => { - const { displayName, folders } = section; + const { id, displayName, folders } = section; const numOfFoldersInSection = getFolders({ sections: [section] }).length; if (numOfFoldersInSection > 0) { return ( - + {folders.map((folder) => { return ; From f99e966c6e9404e6e7bd56287599f5136ac71caa Mon Sep 17 00:00:00 2001 From: farres1 Date: Wed, 24 Apr 2024 13:02:51 +0100 Subject: [PATCH 038/215] Add folder picker list test --- eq-author/src/components/FolderPicker/List/index.js | 1 + .../src/components/FolderPicker/List/index.test.js | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 eq-author/src/components/FolderPicker/List/index.test.js diff --git a/eq-author/src/components/FolderPicker/List/index.js b/eq-author/src/components/FolderPicker/List/index.js index 0ced1e35cc..babfb16aa7 100644 --- a/eq-author/src/components/FolderPicker/List/index.js +++ b/eq-author/src/components/FolderPicker/List/index.js @@ -14,6 +14,7 @@ const List = ({ children, className }) => { ); }; + List.propTypes = { children: PropTypes.node.isRequired, className: PropTypes.string, diff --git a/eq-author/src/components/FolderPicker/List/index.test.js b/eq-author/src/components/FolderPicker/List/index.test.js new file mode 100644 index 0000000000..56286f7d80 --- /dev/null +++ b/eq-author/src/components/FolderPicker/List/index.test.js @@ -0,0 +1,12 @@ +import React from "react"; +import { render } from "tests/utils/rtl"; + +import List from "."; + +describe("FolderPicker list", () => { + it("should render list", () => { + const { getByTestId } = render(Test); + + expect(getByTestId("folder-picker-list")).toBeInTheDocument(); + }); +}); From 25b41f135061c3a8020d400e0d529bddb3034fd6 Mon Sep 17 00:00:00 2001 From: farres1 Date: Thu, 25 Apr 2024 09:19:22 +0100 Subject: [PATCH 039/215] Add folder picker tests --- .../src/components/FolderPicker/index.js | 19 ++- .../src/components/FolderPicker/index.test.js | 131 ++++++++++++++++++ 2 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 eq-author/src/components/FolderPicker/index.test.js diff --git a/eq-author/src/components/FolderPicker/index.js b/eq-author/src/components/FolderPicker/index.js index b3257d6286..b5f63e2347 100644 --- a/eq-author/src/components/FolderPicker/index.js +++ b/eq-author/src/components/FolderPicker/index.js @@ -32,7 +32,7 @@ const StyledModal = styled(Modal)` } `; -const Header = styled.div` +const Header = styled.header` margin: 0 1.5em; `; @@ -161,15 +161,15 @@ const FolderPicker = ({ return ( -
    - {title} +
    + {title} {warningMessage && ( {warningMessage} )} {showSearch && ( - + setSearchTerm(value)} @@ -178,7 +178,7 @@ const FolderPicker = ({ )}
    -
    +
    {searchTerm === "" || (searchTerm !== "" && // Checks if there are any sections with folders matching the search term @@ -203,9 +203,13 @@ const FolderPicker = ({ /> )}
    -