@cdc/markup-include 4.26.2 → 4.26.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/cdcmarkupinclude.js +7889 -7755
- package/package.json +4 -4
- package/src/CdcMarkupInclude.tsx +72 -109
- package/src/_stories/MarkupInclude.Editor.stories.tsx +11 -16
- package/src/cdcMarkupInclude.style.css +15 -11
- package/src/components/EditorPanel/EditorPanel.styles.css +1 -1
- package/src/components/EditorPanel/EditorPanel.tsx +60 -4
- package/src/scss/main.scss +17 -13
- package/src/test/CdcMarkupInclude.test.jsx +2 -2
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/markup-include",
|
|
3
|
-
"version": "4.26.
|
|
3
|
+
"version": "4.26.3",
|
|
4
4
|
"description": "React component for displaying HTML content from an outside link",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Rob Shelnutt <rob@blackairplane.com>",
|
|
7
7
|
"bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@cdc/core": "^4.26.
|
|
9
|
+
"@cdc/core": "^4.26.3",
|
|
10
10
|
"axios": "^1.13.2",
|
|
11
|
-
"
|
|
11
|
+
"dompurify": "^3.3.1",
|
|
12
12
|
"lodash": "^4.17.23",
|
|
13
13
|
"react-accessible-accordion": "^5.0.1"
|
|
14
14
|
},
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
|
20
20
|
"vite-plugin-svgr": "^4.2.0"
|
|
21
21
|
},
|
|
22
|
-
"gitHead": "
|
|
22
|
+
"gitHead": "d50e45a074fbefa56cac904917e707d57f237737",
|
|
23
23
|
"homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
|
|
24
24
|
"main": "dist/cdcmarkupinclude",
|
|
25
25
|
"moduleName": "CdcMarkupInclude",
|
package/src/CdcMarkupInclude.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useEffect, useCallback, useRef, useReducer, useMemo } from 'react'
|
|
2
|
-
import _ from 'lodash'
|
|
3
2
|
// external
|
|
4
|
-
import
|
|
3
|
+
import DOMPurify from 'dompurify'
|
|
5
4
|
import axios from 'axios'
|
|
6
5
|
|
|
7
6
|
// cdc
|
|
@@ -11,6 +10,7 @@ import { processMarkupVariables } from '@cdc/core/helpers/markupProcessor'
|
|
|
11
10
|
import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
|
|
12
11
|
import ConfigContext from './ConfigContext'
|
|
13
12
|
import coveUpdateWorker from '@cdc/core/helpers/coveUpdateWorker'
|
|
13
|
+
import fetchRemoteData from '@cdc/core/helpers/fetchRemoteData'
|
|
14
14
|
import EditorPanel from '../src/components/EditorPanel'
|
|
15
15
|
import defaults from './data/initial-state'
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ import Loading from '@cdc/core/components/Loading'
|
|
|
19
19
|
import Filters from '@cdc/core/components/Filters'
|
|
20
20
|
import useDataVizClasses from '@cdc/core/helpers/useDataVizClasses'
|
|
21
21
|
import markupIncludeReducer from './store/markupInclude.reducer'
|
|
22
|
-
import
|
|
22
|
+
import { VisualizationContainer, VisualizationContent } from '@cdc/core/components/Layout'
|
|
23
23
|
// styles
|
|
24
24
|
import './cdcMarkupInclude.style.css'
|
|
25
25
|
import './scss/main.scss'
|
|
@@ -79,10 +79,6 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
|
|
|
79
79
|
|
|
80
80
|
const { inlineHTML, srcUrl, title, useInlineHTML } = contentEditor || {}
|
|
81
81
|
|
|
82
|
-
const shouldApplyTopPadding =
|
|
83
|
-
visual?.border || visual?.background || (contentEditor?.title && contentEditor?.titleStyle === 'legacy')
|
|
84
|
-
const shouldApplySidePadding = visual?.border || visual?.accent || visual?.background
|
|
85
|
-
|
|
86
82
|
// Default Functions
|
|
87
83
|
const updateConfig = newConfig => {
|
|
88
84
|
Object.keys(defaults).forEach(key => {
|
|
@@ -112,8 +108,9 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
|
|
|
112
108
|
let responseData = response.data ?? {}
|
|
113
109
|
|
|
114
110
|
if (response.dataUrl) {
|
|
115
|
-
const
|
|
116
|
-
responseData =
|
|
111
|
+
const { data, dataMetadata } = await fetchRemoteData(response.dataUrl)
|
|
112
|
+
responseData = data
|
|
113
|
+
response.dataMetadata = dataMetadata
|
|
117
114
|
}
|
|
118
115
|
|
|
119
116
|
response.data = responseData
|
|
@@ -203,63 +200,29 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
|
|
|
203
200
|
}
|
|
204
201
|
|
|
205
202
|
/**
|
|
206
|
-
*
|
|
207
|
-
* This ensures that the CSS is applied only to this COVE visualization.
|
|
203
|
+
* Extracts <style> tags from HTML and scopes them using CSS nesting under the given scope ID.
|
|
208
204
|
*/
|
|
209
|
-
const
|
|
210
|
-
if (!html || typeof html !== 'string') return html
|
|
205
|
+
const extractAndScopeStyles = (html: string, scopeId: string): { scopedCSS: string; cleanHTML: string } => {
|
|
206
|
+
if (!html || typeof html !== 'string') return { scopedCSS: '', cleanHTML: html }
|
|
211
207
|
|
|
212
|
-
// Use DOMParser to parse HTML
|
|
213
208
|
const parser = new DOMParser()
|
|
214
209
|
const doc = parser.parseFromString(html, 'text/html')
|
|
215
210
|
|
|
216
|
-
// Extract all <style> elements
|
|
217
211
|
const styleElements = doc.querySelectorAll('style')
|
|
218
|
-
if (styleElements.length === 0) return html
|
|
219
|
-
|
|
220
|
-
// Parse CSS rules
|
|
221
|
-
const sheet = new CSSStyleSheet()
|
|
222
|
-
const cssRules: Array<{ selector: string; styles: string }> = []
|
|
212
|
+
if (styleElements.length === 0) return { scopedCSS: '', cleanHTML: html }
|
|
223
213
|
|
|
214
|
+
const cssFragments: string[] = []
|
|
224
215
|
styleElements.forEach(styleEl => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Extract parsed rules from the stylesheet
|
|
230
|
-
for (let i = 0; i < sheet.cssRules.length; i++) {
|
|
231
|
-
const rule = sheet.cssRules[i]
|
|
232
|
-
if (rule instanceof CSSStyleRule) {
|
|
233
|
-
cssRules.push({
|
|
234
|
-
selector: rule.selectorText,
|
|
235
|
-
styles: rule.style.cssText
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} catch (e) {
|
|
240
|
-
console.warn('Markup Include: Invalid CSS in style tag', e)
|
|
216
|
+
const text = styleEl.textContent?.trim()
|
|
217
|
+
if (text) {
|
|
218
|
+
cssFragments.push(text)
|
|
241
219
|
}
|
|
242
|
-
|
|
243
220
|
styleEl.remove()
|
|
244
221
|
})
|
|
245
222
|
|
|
246
|
-
|
|
247
|
-
for (const rule of cssRules) {
|
|
248
|
-
try {
|
|
249
|
-
const elements = doc.body.querySelectorAll(rule.selector)
|
|
250
|
-
|
|
251
|
-
elements.forEach(el => {
|
|
252
|
-
const existingStyle = el.getAttribute('style') || ''
|
|
253
|
-
const newStyle = existingStyle ? `${existingStyle}; ${rule.styles}` : rule.styles
|
|
254
|
-
el.setAttribute('style', newStyle)
|
|
255
|
-
})
|
|
256
|
-
} catch (e) {
|
|
257
|
-
// Skip invalid selectors (e.g., pseudo-selectors like :hover won't match)
|
|
258
|
-
console.warn(`Markup Include: Could not apply CSS rule for selector "${rule.selector}"`, e)
|
|
259
|
-
}
|
|
260
|
-
}
|
|
223
|
+
const scopedCSS = cssFragments.length > 0 ? `#${scopeId} {\n${cssFragments.join('\n')}\n}` : ''
|
|
261
224
|
|
|
262
|
-
return doc.body.innerHTML
|
|
225
|
+
return { scopedCSS, cleanHTML: doc.body.innerHTML }
|
|
263
226
|
}
|
|
264
227
|
|
|
265
228
|
//Load initial config
|
|
@@ -289,69 +252,69 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
|
|
|
289
252
|
allowHideSection,
|
|
290
253
|
filters: config?.filters || [],
|
|
291
254
|
datasets,
|
|
292
|
-
configDataKey: config?.dataKey
|
|
255
|
+
configDataKey: config?.dataKey,
|
|
256
|
+
locale: config?.locale,
|
|
257
|
+
dataMetadata: config?.dataMetadata
|
|
293
258
|
})
|
|
294
259
|
: { processedContent: parseBodyMarkup(urlMarkup), shouldHideSection: false, shouldShowNoDataMessage: false }
|
|
295
260
|
|
|
296
|
-
const
|
|
261
|
+
const scopeId = `cove-mi-${config?.runtime?.uniqueId || 'default'}`
|
|
262
|
+
const { scopedCSS, cleanHTML } = extractAndScopeStyles(processedMarkup.processedContent, scopeId)
|
|
263
|
+
const sanitizedHTML = cleanHTML ? DOMPurify.sanitize(cleanHTML) : ''
|
|
297
264
|
|
|
298
265
|
const hideMarkupInclude = processedMarkup.shouldHideSection
|
|
299
266
|
const _showNoDataMessage = processedMarkup.shouldShowNoDataMessage
|
|
300
267
|
|
|
301
268
|
if (loading === false) {
|
|
302
|
-
content = (
|
|
303
|
-
|
|
304
|
-
{
|
|
305
|
-
|
|
306
|
-
{
|
|
307
|
-
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
enableMarkupVariables={config?.enableMarkupVariables}
|
|
349
|
-
data={data}
|
|
350
|
-
/>
|
|
351
|
-
</div>
|
|
352
|
-
</Layout.Responsive>
|
|
269
|
+
content = !hideMarkupInclude && (
|
|
270
|
+
<VisualizationContent
|
|
271
|
+
innerClassName={`markup-include-content-container ${innerContainerClasses.join(' ')}`.trim()}
|
|
272
|
+
bodyClassName={`markup-include-component ${contentClasses.join(' ')}`.trim()}
|
|
273
|
+
message={
|
|
274
|
+
config.filters && config.filters.length > 0 ? (
|
|
275
|
+
<Filters
|
|
276
|
+
config={config}
|
|
277
|
+
setFilters={setFilters}
|
|
278
|
+
excludedData={data || []}
|
|
279
|
+
dimensions={[0, 0]}
|
|
280
|
+
interactionLabel={interactionLabel || 'markup-include'}
|
|
281
|
+
/>
|
|
282
|
+
) : null
|
|
283
|
+
}
|
|
284
|
+
header={
|
|
285
|
+
<Title
|
|
286
|
+
title={title}
|
|
287
|
+
isDashboard={isDashboard}
|
|
288
|
+
titleStyle={contentEditor?.titleStyle}
|
|
289
|
+
config={config}
|
|
290
|
+
classes={[`${theme}`, 'mb-0']}
|
|
291
|
+
noContent={!sanitizedHTML}
|
|
292
|
+
/>
|
|
293
|
+
}
|
|
294
|
+
footer={
|
|
295
|
+
<FootnotesStandAlone
|
|
296
|
+
config={configObj?.footnotes}
|
|
297
|
+
filters={config?.filters || []}
|
|
298
|
+
markupVariables={markupVariables}
|
|
299
|
+
enableMarkupVariables={config?.enableMarkupVariables}
|
|
300
|
+
data={data}
|
|
301
|
+
dataMetadata={config?.dataMetadata}
|
|
302
|
+
/>
|
|
303
|
+
}
|
|
304
|
+
>
|
|
305
|
+
{_showNoDataMessage && (
|
|
306
|
+
<div className='no-data-message'>
|
|
307
|
+
<p>{`${noDataMessageText}`}</p>
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
{!markupError && !_showNoDataMessage && (
|
|
311
|
+
<div id={scopeId}>
|
|
312
|
+
{scopedCSS && <style>{scopedCSS}</style>}
|
|
313
|
+
<div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />
|
|
314
|
+
</div>
|
|
353
315
|
)}
|
|
354
|
-
|
|
316
|
+
{markupError && srcUrl && !_showNoDataMessage && <div className='warning'>{errorMessage}</div>}
|
|
317
|
+
</VisualizationContent>
|
|
355
318
|
)
|
|
356
319
|
}
|
|
357
320
|
|
|
@@ -370,9 +333,9 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
|
|
|
370
333
|
<ErrorBoundary component='CdcMarkupInclude'>
|
|
371
334
|
<ConfigContext.Provider value={{ config, updateConfig, loading, data: data, setParentConfig, isDashboard }}>
|
|
372
335
|
{!config?.newViz && config?.runtime && config?.runtime.editorErrorMessage && <Error />}
|
|
373
|
-
<
|
|
336
|
+
<VisualizationContainer config={config} isEditor={isEditor} editorPanel={<EditorPanel datasets={datasets} />}>
|
|
374
337
|
{content}
|
|
375
|
-
</
|
|
338
|
+
</VisualizationContainer>
|
|
376
339
|
</ConfigContext.Provider>
|
|
377
340
|
</ErrorBoundary>
|
|
378
341
|
)
|
|
@@ -138,7 +138,7 @@ export const GeneralSectionTests: Story = {
|
|
|
138
138
|
'Title Update',
|
|
139
139
|
() => {
|
|
140
140
|
const modernTitle = canvasElement.querySelector('.cove-title')
|
|
141
|
-
const legacyTitle = canvasElement.querySelector('.cove-
|
|
141
|
+
const legacyTitle = canvasElement.querySelector('.cove-visualization__header h2')
|
|
142
142
|
const titleElement = modernTitle || legacyTitle
|
|
143
143
|
return titleElement?.textContent?.trim() || ''
|
|
144
144
|
},
|
|
@@ -150,7 +150,7 @@ export const GeneralSectionTests: Story = {
|
|
|
150
150
|
)
|
|
151
151
|
|
|
152
152
|
const modernHeader = canvasElement.querySelector('.cove-title')
|
|
153
|
-
const legacyHeader = canvasElement.querySelector('.cove-
|
|
153
|
+
const legacyHeader = canvasElement.querySelector('.cove-visualization__header h2')
|
|
154
154
|
const headerElement = modernHeader || legacyHeader
|
|
155
155
|
expect(headerElement).toBeTruthy()
|
|
156
156
|
expect(headerElement!.textContent?.trim()).toBe('Updated Markup Include Title E2E')
|
|
@@ -226,7 +226,7 @@ export const ContentEditorTests: Story = {
|
|
|
226
226
|
await performAndAssert(
|
|
227
227
|
'HTML Content Update',
|
|
228
228
|
() => {
|
|
229
|
-
const contentElement = canvasElement.querySelector('.cove-
|
|
229
|
+
const contentElement = canvasElement.querySelector('.cove-visualization__body')
|
|
230
230
|
return contentElement?.innerHTML || ''
|
|
231
231
|
},
|
|
232
232
|
async () => {
|
|
@@ -255,7 +255,7 @@ export const ContentEditorTests: Story = {
|
|
|
255
255
|
'Source URL Update and Content Loading',
|
|
256
256
|
() => ({
|
|
257
257
|
inputValue: srcUrlInput.value,
|
|
258
|
-
contentText: canvasElement.querySelector('.cove-
|
|
258
|
+
contentText: canvasElement.querySelector('.cove-visualization__body')?.textContent || ''
|
|
259
259
|
}),
|
|
260
260
|
async () => {
|
|
261
261
|
await userEvent.clear(srcUrlInput)
|
|
@@ -296,7 +296,7 @@ export const VisualSectionTests: Story = {
|
|
|
296
296
|
await waitForEditor(canvas)
|
|
297
297
|
await openAccordion(canvas, 'Visual')
|
|
298
298
|
|
|
299
|
-
const contentContainer = () => canvasElement.querySelector('.cove-
|
|
299
|
+
const contentContainer = () => canvasElement.querySelector('.cove-visualization__body') as HTMLElement
|
|
300
300
|
const visualContainer = () => canvasElement.querySelector('.markup-include-component') as HTMLElement
|
|
301
301
|
expect(contentContainer()).toBeTruthy()
|
|
302
302
|
expect(visualContainer()).toBeTruthy()
|
|
@@ -306,21 +306,16 @@ export const VisualSectionTests: Story = {
|
|
|
306
306
|
// Expectation: Theme class changes on component
|
|
307
307
|
// ============================================================================
|
|
308
308
|
const getThemeState = () => {
|
|
309
|
-
// Use the contentContainer like other tests, and check its parent for theme classes
|
|
310
309
|
const content = contentContainer()
|
|
311
310
|
if (!content) return { theme: '', classes: '', element: 'content not found' }
|
|
312
311
|
|
|
313
|
-
//
|
|
312
|
+
// Theme is applied to the outer cove-visualization wrapper — traverse up to find it
|
|
313
|
+
const themeWrapper = content.closest('[class*="theme-"]') as HTMLElement
|
|
314
|
+
const theme = themeWrapper ? Array.from(themeWrapper.classList).find(cls => cls.startsWith('theme-')) || '' : ''
|
|
315
|
+
|
|
314
316
|
const contentClasses = Array.from(content.classList).join(' ')
|
|
315
317
|
const parentClasses = content.parentElement ? Array.from(content.parentElement.classList).join(' ') : ''
|
|
316
318
|
|
|
317
|
-
const contentTheme = Array.from(content.classList).find(cls => cls.startsWith('theme-')) || ''
|
|
318
|
-
const parentTheme = content.parentElement
|
|
319
|
-
? Array.from(content.parentElement.classList).find(cls => cls.startsWith('theme-')) || ''
|
|
320
|
-
: ''
|
|
321
|
-
|
|
322
|
-
const theme = contentTheme || parentTheme || ''
|
|
323
|
-
|
|
324
319
|
return {
|
|
325
320
|
theme,
|
|
326
321
|
classes: contentClasses + ' | parent: ' + parentClasses,
|
|
@@ -380,7 +375,7 @@ export const VisualSectionTests: Story = {
|
|
|
380
375
|
'Border Color Theme Toggle',
|
|
381
376
|
() => ({
|
|
382
377
|
checked: borderColorThemeCheckbox.checked,
|
|
383
|
-
hasBorderColorTheme: visualContainer().classList.contains('component--has-
|
|
378
|
+
hasBorderColorTheme: visualContainer().classList.contains('component--has-border-color-theme')
|
|
384
379
|
}),
|
|
385
380
|
async () => {
|
|
386
381
|
const checkboxWrapper =
|
|
@@ -455,7 +450,7 @@ export const VisualSectionTests: Story = {
|
|
|
455
450
|
'Hide Background Color Toggle',
|
|
456
451
|
() => ({
|
|
457
452
|
checked: hideBackgroundCheckbox.checked,
|
|
458
|
-
hideBackground: visualContainer().classList.contains('component--
|
|
453
|
+
hideBackground: visualContainer().classList.contains('component--hide-background-color')
|
|
459
454
|
}),
|
|
460
455
|
async () => {
|
|
461
456
|
const checkboxWrapper = hideBackgroundCheckbox.closest('.cove-input__checkbox--small') || hideBackgroundCheckbox
|
|
@@ -1,39 +1,43 @@
|
|
|
1
|
-
.
|
|
1
|
+
.cove-visualization.markup-include {
|
|
2
2
|
.spacing-wrapper {
|
|
3
3
|
background-color: var(--lightGray) !important;
|
|
4
4
|
}
|
|
5
|
+
|
|
5
6
|
.cove-tooltip-variable {
|
|
6
7
|
display: none;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
.markup-include-content-container.cove-
|
|
10
|
-
.markup-include-component.cove-
|
|
10
|
+
c .markup-include-content-container.cove-visualization__inner {
|
|
11
|
+
.markup-include-component.cove-visualization__body:not(.component--hide-background-color, .component--has-background) {
|
|
11
12
|
background-color: white;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
.
|
|
17
|
+
.cove-visualization.markup-include.is-editor {
|
|
17
18
|
.cove-tooltip-variable {
|
|
18
|
-
position: relative;
|
|
19
|
-
display: inline;
|
|
20
19
|
background: var(--orange-tertiary);
|
|
20
|
+
display: inline;
|
|
21
|
+
position: relative;
|
|
22
|
+
|
|
21
23
|
.cove-tooltip-value {
|
|
22
|
-
display: none;
|
|
23
|
-
position: absolute;
|
|
24
24
|
background: var(--white);
|
|
25
25
|
border: 1px solid black;
|
|
26
|
+
display: none;
|
|
26
27
|
font-size: 16px;
|
|
28
|
+
max-width: 500px;
|
|
27
29
|
padding: 10px;
|
|
30
|
+
position: absolute;
|
|
28
31
|
width: 100vw;
|
|
29
|
-
max-width: 500px;
|
|
30
32
|
z-index: 9999;
|
|
31
33
|
}
|
|
32
|
-
|
|
34
|
+
|
|
35
|
+
&:hover>.cove-tooltip-value {
|
|
33
36
|
display: block;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
39
|
+
|
|
36
40
|
.cove-markup-include-variable-value {
|
|
37
41
|
display: none !important;
|
|
38
42
|
}
|
|
39
|
-
}
|
|
43
|
+
}
|
|
@@ -14,7 +14,8 @@ import Icon from '@cdc/core/components/ui/Icon'
|
|
|
14
14
|
import Tooltip from '@cdc/core/components/ui/Tooltip'
|
|
15
15
|
import MarkupVariablesEditor from '@cdc/core/components/EditorPanel/components/MarkupVariablesEditor'
|
|
16
16
|
import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
|
|
17
|
-
import
|
|
17
|
+
import StyleTreatmentSection from '@cdc/core/components/EditorPanel/sections/StyleTreatmentSection'
|
|
18
|
+
import { HeaderThemeSelector } from '@cdc/core/components/HeaderThemeSelector'
|
|
18
19
|
import { Datasets } from '@cdc/core/types/DataSet'
|
|
19
20
|
|
|
20
21
|
// styles
|
|
@@ -26,9 +27,24 @@ type MarkupIncludeEditorPanelProps = {
|
|
|
26
27
|
|
|
27
28
|
const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
28
29
|
const { config, data, isDashboard, loading, setParentConfig, updateConfig } = useContext(ConfigContext)
|
|
29
|
-
const { contentEditor, theme, visual } = config
|
|
30
|
+
const { contentEditor, theme, visual } = config || {}
|
|
30
31
|
const { inlineHTML, srcUrl, title, useInlineHTML } = contentEditor || {}
|
|
31
32
|
const updateField = updateFieldFactory(config, updateConfig, true)
|
|
33
|
+
const styleTreatment = visual?.tp5Treatment ? 'tp5' : 'legacy'
|
|
34
|
+
|
|
35
|
+
const handleStyleTreatmentChange = (value: string) => {
|
|
36
|
+
const useTp5Treatment = value === 'tp5'
|
|
37
|
+
updateConfig({
|
|
38
|
+
...config,
|
|
39
|
+
visual: {
|
|
40
|
+
...config.visual,
|
|
41
|
+
tp5Treatment: useTp5Treatment,
|
|
42
|
+
border: useTp5Treatment ? false : config.visual?.border,
|
|
43
|
+
borderColorTheme: useTp5Treatment ? false : config.visual?.borderColorTheme,
|
|
44
|
+
accent: useTp5Treatment ? false : config.visual?.accent
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}
|
|
32
48
|
|
|
33
49
|
const textAreaInEditorContainer = useRef(null)
|
|
34
50
|
|
|
@@ -67,7 +83,7 @@ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
|
67
83
|
updateField={updateField}
|
|
68
84
|
/>
|
|
69
85
|
<Select
|
|
70
|
-
value={contentEditor
|
|
86
|
+
value={contentEditor?.titleStyle || 'small'}
|
|
71
87
|
section='contentEditor'
|
|
72
88
|
fieldName='titleStyle'
|
|
73
89
|
label='Title Style'
|
|
@@ -92,6 +108,29 @@ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
|
92
108
|
</Tooltip>
|
|
93
109
|
}
|
|
94
110
|
/>
|
|
111
|
+
<Select
|
|
112
|
+
value={config.locale}
|
|
113
|
+
fieldName='locale'
|
|
114
|
+
label='Language for dates and numbers'
|
|
115
|
+
updateField={updateField}
|
|
116
|
+
options={[
|
|
117
|
+
{ value: 'en-US', label: 'English (en-US)' },
|
|
118
|
+
{ value: 'es-MX', label: 'Spanish (es-MX)' }
|
|
119
|
+
]}
|
|
120
|
+
tooltip={
|
|
121
|
+
<Tooltip style={{ textTransform: 'none' }}>
|
|
122
|
+
<Tooltip.Target>
|
|
123
|
+
<Icon display='question' style={{ marginLeft: '0.5rem' }} />
|
|
124
|
+
</Tooltip.Target>
|
|
125
|
+
<Tooltip.Content>
|
|
126
|
+
<p>
|
|
127
|
+
Change the language (locale) for this visualization to alter the way dates and numbers are
|
|
128
|
+
formatted.
|
|
129
|
+
</p>
|
|
130
|
+
</Tooltip.Content>
|
|
131
|
+
</Tooltip>
|
|
132
|
+
}
|
|
133
|
+
/>
|
|
95
134
|
</Accordion.Section>
|
|
96
135
|
<Accordion.Section title='Content Editor'>
|
|
97
136
|
<span className='divider-heading'>Enter Markup</span>
|
|
@@ -131,7 +170,23 @@ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
|
131
170
|
</div>
|
|
132
171
|
</Accordion.Section>
|
|
133
172
|
<Accordion.Section title='Visual'>
|
|
134
|
-
<
|
|
173
|
+
<HeaderThemeSelector
|
|
174
|
+
selectedTheme={config.theme}
|
|
175
|
+
onThemeSelect={theme => updateConfig({ ...config, theme })}
|
|
176
|
+
/>
|
|
177
|
+
<StyleTreatmentSection
|
|
178
|
+
styleTreatment={styleTreatment}
|
|
179
|
+
onStyleTreatmentChange={handleStyleTreatmentChange}
|
|
180
|
+
showStyleTreatment={false}
|
|
181
|
+
border={config.visual?.border}
|
|
182
|
+
borderColorTheme={config.visual?.borderColorTheme}
|
|
183
|
+
accent={config.visual?.accent}
|
|
184
|
+
background={config.visual?.background}
|
|
185
|
+
hideBackgroundColor={config.visual?.hideBackgroundColor}
|
|
186
|
+
showBackground
|
|
187
|
+
showHideBackgroundColor
|
|
188
|
+
updateField={updateField}
|
|
189
|
+
/>
|
|
135
190
|
</Accordion.Section>
|
|
136
191
|
{isDashboard && (
|
|
137
192
|
<Accordion.Section title='Footnotes'>
|
|
@@ -147,6 +202,7 @@ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
|
|
|
147
202
|
onChange={handleMarkupVariablesChange}
|
|
148
203
|
enableMarkupVariables={config.enableMarkupVariables || false}
|
|
149
204
|
onToggleEnable={handleToggleEnable}
|
|
205
|
+
dataMetadata={config.dataMetadata}
|
|
150
206
|
/>
|
|
151
207
|
</Accordion.Section>
|
|
152
208
|
</Accordion>
|
package/src/scss/main.scss
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
.cove-component__content-wrap {
|
|
3
|
-
padding: 0;
|
|
1
|
+
@import '@cdc/core/styles/layout/wrapper-padding';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
.cove-visualization.type-markup-include {
|
|
4
|
+
.cove-visualization__body {
|
|
5
|
+
@include cove-visualization-body-padding;
|
|
6
|
+
}
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
padding-right: 1rem;
|
|
13
|
-
}
|
|
8
|
+
.cove-visualization__body-wrap {
|
|
9
|
+
@include cove-visualization-body-wrap-inline-padding;
|
|
14
10
|
|
|
15
11
|
h1,
|
|
16
12
|
h2,
|
|
@@ -22,9 +18,16 @@
|
|
|
22
18
|
margin-top: auto;
|
|
23
19
|
margin-bottom: auto;
|
|
24
20
|
}
|
|
21
|
+
|
|
22
|
+
// keep markup include images inlines on dashboards
|
|
23
|
+
// e.g. requested to help here: https://www.cdc.gov/fluview/surveillance/2026-week-10.html
|
|
24
|
+
// needed for backwards compatibility with existing dashboards, but also to prevent any future issues with images in markup include visualizations being forced to display as blocks
|
|
25
|
+
span>img {
|
|
26
|
+
display: inline;
|
|
27
|
+
}
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
.cove-
|
|
30
|
+
.cove-visualization__body.component--hide-background-color {
|
|
28
31
|
background: transparent;
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -47,7 +50,8 @@
|
|
|
47
50
|
float: left;
|
|
48
51
|
}
|
|
49
52
|
}
|
|
53
|
+
|
|
50
54
|
.cove-editor .cove-editor__content {
|
|
51
55
|
padding-left: 350px;
|
|
52
56
|
display: flex;
|
|
53
|
-
}
|
|
57
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import path from 'path'
|
|
1
|
+
import path from 'node:path'
|
|
2
2
|
import { testStandaloneBuild } from '@cdc/core/helpers/tests/testStandaloneBuild.ts'
|
|
3
3
|
import { describe, it, expect } from 'vitest'
|
|
4
4
|
|
|
5
5
|
describe('Markup Include', () => {
|
|
6
6
|
it('Can be built in isolation', async () => {
|
|
7
7
|
const pkgDir = path.join(__dirname, '..')
|
|
8
|
-
const result = testStandaloneBuild(pkgDir)
|
|
8
|
+
const result = await testStandaloneBuild(pkgDir)
|
|
9
9
|
expect(result).toBe(true)
|
|
10
10
|
}, 300000)
|
|
11
11
|
})
|