@explorer-1/vue 0.2.85 → 0.2.86
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/package.json +2 -2
- package/src/components/BlockStreamfield/BlockStreamfield.stories.js +2 -2
- package/src/components/BlockText/BlockText.stories.js +2 -2
- package/src/components/BlockText/BlockText.vue +4 -0
- package/src/components/NavJumpMenu/NavJumpMenu.vue +41 -14
- package/src/templates/edu/PageEduCollectionsDetail/PageEduCollectionsDetail.vue +9 -3
- package/src/templates/edu/PageEduExplainerArticle/PageEduExplainerArticle.vue +7 -2
- package/src/templates/edu/PageEduGalleryDetail/PageEduGalleryDetail.vue +1 -0
- package/src/templates/edu/PageEduLesson/PageEduLesson.vue +18 -8
- package/src/templates/edu/PageEduNewsDetail/PageEduNewsDetail.vue +8 -2
- package/src/templates/edu/PageEduStudentProject/PageEduStudentProject.vue +1 -1
- package/src/templates/edu/PageEduTeachableMoment/PageEduTeachableMoment.vue +8 -2
- package/src/utils/anchorizeBlock.ts +20 -0
- package/src/utils/anchorizeStreamfield.ts +15 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@explorer-1/vue",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.86",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"vue-bind-once": "^0.2.1",
|
|
31
31
|
"vue3-compare-image": "^1.2.5",
|
|
32
32
|
"vue3-observe-visibility": "^1.0.1",
|
|
33
|
-
"@explorer-1/common": "1.1.
|
|
33
|
+
"@explorer-1/common": "1.1.23"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@vitejs/plugin-vue": "^5.0.4",
|
|
@@ -44,7 +44,7 @@ export const BlockStreamfieldMinimalData = {
|
|
|
44
44
|
{
|
|
45
45
|
blockType: 'RichTextBlock',
|
|
46
46
|
value:
|
|
47
|
-
'<p>Lorem ipsum <a href="/missions/test-mission/">dolor</a> sit amet, consectetur adipiscing elit. Quisque vitae justo quis justo malesuada molestie. Cras sed tincidunt dui.</p>\n'
|
|
47
|
+
'<p>Lorem ipsum <a href="/missions/test-mission/">dolor</a> sit amet, consectetur adipiscing elit. Quisque vitae justo quis justo malesuada molestie. Cras sed tincidunt dui.</p><h2 data-block-key="cba9u">A heading in BlockText</h2><p>Lorem ipsum <a href="/missions/test-mission/">dolor</a> sit amet, consectetur adipiscing elit.</p><h3 data-block-key="ujfka8">A heading in BlockText</h3><p>Quisque vitae justo quis justo malesuada molestie.</p><h2 data-block-key="ujfka8">A heading in BlockText</h2><p>Cras sed tincidunt dui.</p>\n'
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
blockType: 'RichTextBlock',
|
|
@@ -59,7 +59,7 @@ export const BlockStreamfieldTruncatedData = {
|
|
|
59
59
|
{
|
|
60
60
|
blockType: 'RichTextBlock',
|
|
61
61
|
value:
|
|
62
|
-
'<p>Lorem ipsum <a href="/missions/test-mission/">dolor</a> sit amet, consectetur adipiscing elit. Quisque vitae justo quis justo malesuada molestie. Cras sed tincidunt dui.</p><
|
|
62
|
+
'<p>Lorem ipsum <a href="/missions/test-mission/">dolor</a> sit amet, consectetur adipiscing elit. Quisque vitae justo quis justo malesuada molestie. Cras sed tincidunt dui.</p><h2 data-block-key="cba9u">A heading in BlockText</h2><p>Lorem ipsum <a href="/missions/test-mission/">dolor</a> sit amet, consectetur adipiscing elit.</p><h3 data-block-key="ujfka8">A heading in BlockText</h3><p>Quisque vitae justo quis justo malesuada molestie.</p><h2 data-block-key="ujfka8">A heading in BlockText</h2><p>Cras sed tincidunt dui.</p>\n'
|
|
63
63
|
},
|
|
64
64
|
{ ...BlockHeadingData, blockId: Math.random().toString(36).slice(2) },
|
|
65
65
|
{
|
|
@@ -19,13 +19,13 @@ export default {
|
|
|
19
19
|
excludeStories: /.*Data$/
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export const RichTextMediaData = `<h2>A heading in BlockText</h2><p data-block-key="5f55p">Description for it.</p><div class="richtext-image fullwidth"><img alt="Perseverance Looks Back at &#x27;Bright Angel&#x27;" height="480" loading="lazy" src="https://picsum.photos/640/480" width="640">
|
|
22
|
+
export const RichTextMediaData = `<h2 data-block-key="cba9u">A heading in BlockText</h2><p data-block-key="5f55p">Description for it.</p><div class="richtext-image fullwidth"><img alt="Perseverance Looks Back at &#x27;Bright Angel&#x27;" height="480" loading="lazy" src="https://picsum.photos/640/480" width="640">
|
|
23
23
|
<div class="richtext-caption">
|
|
24
24
|
<div class="caption">One of the navigation cameras aboard NASAs Perseverance Mars rover captured this view looking back at the Bright Angel area on July 30, 2024.</div>
|
|
25
25
|
<span class="credit">Credit: NASA/JPL-Caltech</span>
|
|
26
26
|
<a class="caption-link" href="#">Full Image Details</a>
|
|
27
27
|
</div>
|
|
28
|
-
</div><h3>Subheading in BlockText</h3><p data-block-key="89jcq">More text and another image that's full width (above)</p><p data-block-key="6jsp"></p><div class="richtext-image left"><img alt="Carbon Mapper Coalition&#x27;s Tanager Satellite" height="336" loading="lazy" src="https://picsum.photos/640/336" width="640">
|
|
28
|
+
</div><h3 data-block-key="ois8du">Subheading in BlockText</h3><p data-block-key="89jcq">More text and another image that's full width (above)</p><p data-block-key="6jsp"></p><div class="richtext-image left"><img alt="Carbon Mapper Coalition&#x27;s Tanager Satellite" height="336" loading="lazy" src="https://picsum.photos/640/336" width="640">
|
|
29
29
|
<div class="richtext-caption">
|
|
30
30
|
<div class="caption">This artists concept depicts one of the Carbon Mapper Coalitions Tanager satellites, the first of which launched on Aug. 16, 2024. Tanager-1 will use imaging spectrometer technology developed at JPL to measure greenhouse gas point-source emissions.</div>
|
|
31
31
|
<span class="credit">Credit: Planet Labs PBC</span>
|
|
@@ -45,19 +45,18 @@ import NavSecondaryDropdown from './../NavSecondary/NavSecondaryDropdown.vue'
|
|
|
45
45
|
import NavSecondaryLink from './../NavSecondary/NavSecondaryLink.vue'
|
|
46
46
|
import NavJumpMenuContent from './../NavJumpMenu/NavJumpMenuContent.vue'
|
|
47
47
|
import type { BlockHeadingObject } from './../BlockHeading/BlockHeading.vue'
|
|
48
|
-
import type {
|
|
48
|
+
import type { BlockTextObject } from './../BlockText/BlockText.vue'
|
|
49
|
+
import type { BlockData, BreadcrumbPathObject, StreamfieldBlockData } from './../../interfaces'
|
|
49
50
|
import { getHeadingId } from '../../utils/getHeadingId'
|
|
50
51
|
|
|
51
52
|
interface NavJumpMenuProps {
|
|
52
53
|
title?: string
|
|
53
54
|
jumpLinks?: BreadcrumbPathObject[]
|
|
54
|
-
blocks?: BlockData
|
|
55
|
+
blocks?: (StreamfieldBlockData | BlockData | BlockHeadingObject | BlockTextObject)[]
|
|
55
56
|
headingLevel?: string
|
|
56
57
|
invert?: boolean
|
|
57
58
|
enabled?: boolean
|
|
58
59
|
dropdownText?: string
|
|
59
|
-
// stepsNumbering?: boolean
|
|
60
|
-
// stepClasses?: string
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
const props = withDefaults(defineProps<NavJumpMenuProps>(), {
|
|
@@ -69,8 +68,6 @@ const props = withDefaults(defineProps<NavJumpMenuProps>(), {
|
|
|
69
68
|
invert: true,
|
|
70
69
|
hidden: false,
|
|
71
70
|
dropdownText: 'Jump to…'
|
|
72
|
-
// stepsNumbering: false,
|
|
73
|
-
// stepClasses: 'text-primary'
|
|
74
71
|
})
|
|
75
72
|
|
|
76
73
|
const initialized = ref(false)
|
|
@@ -88,17 +85,47 @@ const theJumpLinks = computed(() => {
|
|
|
88
85
|
}
|
|
89
86
|
})
|
|
90
87
|
const filteredBlocks = indexedBlocks.filter((b) => {
|
|
91
|
-
return
|
|
88
|
+
return (
|
|
89
|
+
(b.blockType === 'HeadingBlock' &&
|
|
90
|
+
(b as BlockHeadingObject).level === props.headingLevel) ||
|
|
91
|
+
(b.blockType === 'RichTextBlock' &&
|
|
92
|
+
(b as BlockTextObject).value.includes(`<${props.headingLevel}`))
|
|
93
|
+
)
|
|
92
94
|
})
|
|
93
95
|
// map to the correct data shape
|
|
94
|
-
const links: BreadcrumbPathObject[] = filteredBlocks.map((h) => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
const links: (BreadcrumbPathObject | BreadcrumbPathObject[])[] = filteredBlocks.map((h) => {
|
|
97
|
+
let block = h
|
|
98
|
+
let blocks = []
|
|
99
|
+
if (h.blockType === 'HeadingBlock') {
|
|
100
|
+
block = {
|
|
101
|
+
// @ts-expect-error using parameter that was added to BlockData
|
|
102
|
+
path: '#' + getHeadingId(h.heading, h.blockId),
|
|
103
|
+
title: (h as BlockHeadingObject).heading
|
|
104
|
+
}
|
|
105
|
+
} else if (h.blockType === 'RichTextBlock') {
|
|
106
|
+
let text = (h as BlockTextObject).value
|
|
107
|
+
if (text) {
|
|
108
|
+
const regex = new RegExp(
|
|
109
|
+
`<${props.headingLevel} id="(.*?)">(.*?)</${props.headingLevel}>`,
|
|
110
|
+
'g'
|
|
111
|
+
)
|
|
112
|
+
const matches = text.matchAll(regex)
|
|
113
|
+
const headings = [...matches]
|
|
114
|
+
for (let arr of headings) {
|
|
115
|
+
const [_match, g1, g2] = arr
|
|
116
|
+
const block = {
|
|
117
|
+
path: '#' + g1,
|
|
118
|
+
title: g2
|
|
119
|
+
}
|
|
120
|
+
blocks.push(block)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return blocks.length
|
|
125
|
+
? (blocks as unknown as BreadcrumbPathObject[])
|
|
126
|
+
: (block as unknown as BreadcrumbPathObject)
|
|
100
127
|
})
|
|
101
|
-
return links
|
|
128
|
+
return links ? links.flat() : undefined
|
|
102
129
|
}
|
|
103
130
|
return []
|
|
104
131
|
})
|
|
@@ -12,6 +12,7 @@ import NavJumpMenu from './../../../components/NavJumpMenu/NavJumpMenu.vue'
|
|
|
12
12
|
import NavSecondary from './../../../components/NavSecondary/NavSecondary.vue'
|
|
13
13
|
import MetaPanel from '../../../components/MetaPanel/MetaPanel.vue'
|
|
14
14
|
import ShareButtonsEdu from '../../../components/ShareButtonsEdu/ShareButtonsEdu.vue'
|
|
15
|
+
import { anchorizeStreamfield } from './../../../utils/anchorizeStreamfield'
|
|
15
16
|
|
|
16
17
|
interface PageEduCollectionsDetail extends PageEduResourcesObject {
|
|
17
18
|
heroImage: ImageObject
|
|
@@ -39,6 +40,11 @@ const heroInline = computed((): boolean => {
|
|
|
39
40
|
return data?.heroPosition === 'inline'
|
|
40
41
|
})
|
|
41
42
|
|
|
43
|
+
const filteredBody = computed(() => {
|
|
44
|
+
// adds anchors to headings within RichTextBlock
|
|
45
|
+
return anchorizeStreamfield(data?.body)
|
|
46
|
+
})
|
|
47
|
+
|
|
42
48
|
const computedClass = computed((): string => {
|
|
43
49
|
if ((heroInline.value || !data?.heroImage) && !data?.breadcrumb) {
|
|
44
50
|
return 'pt-5 lg:pt-12'
|
|
@@ -58,7 +64,7 @@ const computedClass = computed((): string => {
|
|
|
58
64
|
v-if="data.showJumpMenu && !data.breadcrumb"
|
|
59
65
|
ref="PageEduCollectionsDetailJumpMenu"
|
|
60
66
|
:title="data.title"
|
|
61
|
-
:blocks="
|
|
67
|
+
:blocks="filteredBody"
|
|
62
68
|
dropdown-text="In this collection"
|
|
63
69
|
/>
|
|
64
70
|
|
|
@@ -69,7 +75,7 @@ const computedClass = computed((): string => {
|
|
|
69
75
|
:image="data.heroImage"
|
|
70
76
|
:summary="data.heroSummary"
|
|
71
77
|
:custom-pill-type="data.__typename"
|
|
72
|
-
:class="!data.showMetaPanel ? 'mb-10' : ''"
|
|
78
|
+
:class="!data.showMetaPanel && !data.breadcrumb ? 'mb-10' : ''"
|
|
73
79
|
/>
|
|
74
80
|
|
|
75
81
|
<!-- secondary nav -->
|
|
@@ -137,7 +143,7 @@ const computedClass = computed((): string => {
|
|
|
137
143
|
</LayoutHelper>
|
|
138
144
|
|
|
139
145
|
<!-- streamfield blocks -->
|
|
140
|
-
<BlockStreamfield :data="
|
|
146
|
+
<BlockStreamfield :data="filteredBody" />
|
|
141
147
|
|
|
142
148
|
<!-- related links -->
|
|
143
149
|
<LayoutHelper
|
|
@@ -12,6 +12,7 @@ import BlockRelatedLinks from '../../../components/BlockRelatedLinks/BlockRelate
|
|
|
12
12
|
import NavJumpMenu from './../../../components/NavJumpMenu/NavJumpMenu.vue'
|
|
13
13
|
import HeroInlineMedia from './../../../components/HeroInlineMedia/HeroInlineMedia.vue'
|
|
14
14
|
import AboutTheAuthor from './../../../components/AboutTheAuthor/AboutTheAuthor.vue'
|
|
15
|
+
import { anchorizeStreamfield } from './../../../utils/anchorizeStreamfield'
|
|
15
16
|
|
|
16
17
|
export default defineComponent({
|
|
17
18
|
name: 'PageEduExplainerArticle',
|
|
@@ -71,6 +72,10 @@ export default defineComponent({
|
|
|
71
72
|
}
|
|
72
73
|
return false
|
|
73
74
|
},
|
|
75
|
+
filteredBody() {
|
|
76
|
+
// adds anchors to headings within RichTextBlock
|
|
77
|
+
return anchorizeStreamfield(this.data?.body)
|
|
78
|
+
},
|
|
74
79
|
computedClass(): string {
|
|
75
80
|
if (this.heroInline || this.heroEmpty) {
|
|
76
81
|
return 'pt-5 lg:pt-12'
|
|
@@ -100,7 +105,7 @@ export default defineComponent({
|
|
|
100
105
|
<NavJumpMenu
|
|
101
106
|
v-if="data.showJumpMenu"
|
|
102
107
|
:title="data.title"
|
|
103
|
-
:blocks="
|
|
108
|
+
:blocks="filteredBody"
|
|
104
109
|
dropdown-text="In this article"
|
|
105
110
|
/>
|
|
106
111
|
|
|
@@ -182,7 +187,7 @@ export default defineComponent({
|
|
|
182
187
|
<!-- streamfield blocks -->
|
|
183
188
|
<BlockStreamfield
|
|
184
189
|
itemprop="articleBody"
|
|
185
|
-
:data="
|
|
190
|
+
:data="filteredBody"
|
|
186
191
|
/>
|
|
187
192
|
|
|
188
193
|
<!-- related links -->
|
|
@@ -20,6 +20,7 @@ import BlockStreamfield from './../../../components/BlockStreamfield/BlockStream
|
|
|
20
20
|
import NavJumpMenu from './../../../components/NavJumpMenu/NavJumpMenu.vue'
|
|
21
21
|
import AboutTheAuthor from './../../../components/AboutTheAuthor/AboutTheAuthor.vue'
|
|
22
22
|
import { getHeadingId } from '../../../utils/getHeadingId'
|
|
23
|
+
|
|
23
24
|
interface PageEduGalleryObject extends PageEduResourcesObject {
|
|
24
25
|
overviewString?: string
|
|
25
26
|
galleryItems?: {
|
|
@@ -20,8 +20,8 @@ import PageEduLessonSection, { type PageEduLessonSectionProps } from './PageEduL
|
|
|
20
20
|
import NavJumpMenu from './../../../components/NavJumpMenu/NavJumpMenu.vue'
|
|
21
21
|
import HeroInlineMedia from './../../../components/HeroInlineMedia/HeroInlineMedia.vue'
|
|
22
22
|
import AboutTheAuthor from './../../../components/AboutTheAuthor/AboutTheAuthor.vue'
|
|
23
|
-
|
|
24
23
|
import { HeadingLevel } from '../../../components/BaseHeading/BaseHeading.vue'
|
|
24
|
+
import { anchorizeStreamfield } from '../../../utils/anchorizeStreamfield'
|
|
25
25
|
|
|
26
26
|
interface EduLessonSectionObject extends PageEduLessonSectionProps {
|
|
27
27
|
type?: string
|
|
@@ -143,6 +143,10 @@ const sectionOrder = [
|
|
|
143
143
|
'bottom'
|
|
144
144
|
]
|
|
145
145
|
|
|
146
|
+
const filteredBody = computed(() => {
|
|
147
|
+
return anchorizeStreamfield(data?.body) || []
|
|
148
|
+
})
|
|
149
|
+
|
|
146
150
|
// mimic HeadingBlock data shape for defined section headings
|
|
147
151
|
const staticSectionHeadings = computed((): { [key: string]: BlockHeadingObject } | undefined => {
|
|
148
152
|
if (data) {
|
|
@@ -184,8 +188,9 @@ const keyedCustomSections = computed(
|
|
|
184
188
|
if (!acc[position]) {
|
|
185
189
|
acc[position] = []
|
|
186
190
|
}
|
|
191
|
+
const filteredContent = anchorizeStreamfield(section.content) || []
|
|
187
192
|
acc[position].push(section.heading)
|
|
188
|
-
acc[position].push(...
|
|
193
|
+
acc[position].push(...filteredContent)
|
|
189
194
|
return acc
|
|
190
195
|
},
|
|
191
196
|
{}
|
|
@@ -210,7 +215,8 @@ const consolidatedBlocks = computed(() => {
|
|
|
210
215
|
blocks.push(staticSectionHeadings.value[section])
|
|
211
216
|
}
|
|
212
217
|
if (section !== 'materials' && section !== 'procedures') {
|
|
213
|
-
|
|
218
|
+
const filteredBlocks = anchorizeStreamfield(data[section]) || []
|
|
219
|
+
blocks.push(...filteredBlocks)
|
|
214
220
|
} else if (section === 'procedures' && data.procedures?.length) {
|
|
215
221
|
// get blocks in nested procedures
|
|
216
222
|
data.procedures.forEach((item) => {
|
|
@@ -230,8 +236,8 @@ const consolidatedBlocks = computed(() => {
|
|
|
230
236
|
blocks.push(...keyedCustomSections.value['bottom'])
|
|
231
237
|
}
|
|
232
238
|
// include body blocks
|
|
233
|
-
if (
|
|
234
|
-
blocks.push(...
|
|
239
|
+
if (filteredBody.value) {
|
|
240
|
+
blocks.push(...filteredBody.value)
|
|
235
241
|
}
|
|
236
242
|
|
|
237
243
|
return blocks
|
|
@@ -248,7 +254,10 @@ const consolidatedSections = computed((): EduLessonSectionObject[] => {
|
|
|
248
254
|
if (data && data[section]) {
|
|
249
255
|
sections.push({
|
|
250
256
|
heading: staticSectionHeadings.value ? staticSectionHeadings.value[section] : undefined,
|
|
251
|
-
blocks:
|
|
257
|
+
blocks:
|
|
258
|
+
section !== 'materials' && section !== 'procedures'
|
|
259
|
+
? anchorizeStreamfield(data[section])
|
|
260
|
+
: undefined,
|
|
252
261
|
text: section === 'materials' ? data[section] : undefined,
|
|
253
262
|
procedures: section === 'procedures' ? data[section] : undefined,
|
|
254
263
|
image: data[`${section}Image`]
|
|
@@ -269,6 +278,7 @@ const consolidatedSections = computed((): EduLessonSectionObject[] => {
|
|
|
269
278
|
|
|
270
279
|
return filteredSections
|
|
271
280
|
})
|
|
281
|
+
|
|
272
282
|
const computedClass = computed((): string => {
|
|
273
283
|
if (heroTitle.value) {
|
|
274
284
|
return '-nav-offset'
|
|
@@ -390,8 +400,8 @@ const computedClass = computed((): string => {
|
|
|
390
400
|
|
|
391
401
|
<!-- streamfield blocks -->
|
|
392
402
|
<BlockStreamfield
|
|
393
|
-
v-if="
|
|
394
|
-
:data="
|
|
403
|
+
v-if="filteredBody?.length"
|
|
404
|
+
:data="filteredBody"
|
|
395
405
|
/>
|
|
396
406
|
|
|
397
407
|
<!-- related links -->
|
|
@@ -11,6 +11,7 @@ import BlockText from './../../../components/BlockText/BlockText.vue'
|
|
|
11
11
|
import BlockStreamfield from './../../../components/BlockStreamfield/BlockStreamfield.vue'
|
|
12
12
|
import NavJumpMenu from './../../../components/NavJumpMenu/NavJumpMenu.vue'
|
|
13
13
|
import AboutTheAuthor from './../../../components/AboutTheAuthor/AboutTheAuthor.vue'
|
|
14
|
+
import { anchorizeStreamfield } from './../../../utils/anchorizeStreamfield'
|
|
14
15
|
|
|
15
16
|
interface PageEduNewsDetailObject extends PageObject {
|
|
16
17
|
readTime: string
|
|
@@ -43,6 +44,11 @@ const heroInline = computed(() => {
|
|
|
43
44
|
return false
|
|
44
45
|
})
|
|
45
46
|
|
|
47
|
+
const filteredBody = computed(() => {
|
|
48
|
+
// adds anchors to headings within RichTextBlock
|
|
49
|
+
return anchorizeStreamfield(props.data?.body)
|
|
50
|
+
})
|
|
51
|
+
|
|
46
52
|
const computedClass = computed(() => {
|
|
47
53
|
if (heroInline.value || heroEmpty.value) {
|
|
48
54
|
return 'pt-5 lg:pt-12'
|
|
@@ -72,7 +78,7 @@ defineExpose({
|
|
|
72
78
|
v-if="data.showJumpMenu"
|
|
73
79
|
ref="PageEduNewsDetailJumpMenu"
|
|
74
80
|
:title="data.title"
|
|
75
|
-
:blocks="
|
|
81
|
+
:blocks="filteredBody"
|
|
76
82
|
dropdown-text="In this news article"
|
|
77
83
|
/>
|
|
78
84
|
|
|
@@ -150,7 +156,7 @@ defineExpose({
|
|
|
150
156
|
<!-- streamfield blocks -->
|
|
151
157
|
<BlockStreamfield
|
|
152
158
|
itemprop="articleBody"
|
|
153
|
-
:data="
|
|
159
|
+
:data="filteredBody"
|
|
154
160
|
/>
|
|
155
161
|
|
|
156
162
|
<LayoutHelper
|
|
@@ -24,9 +24,9 @@ import PageEduStudentProjectSection, {
|
|
|
24
24
|
import NavJumpMenu from './../../../components/NavJumpMenu/NavJumpMenu.vue'
|
|
25
25
|
import HeroInlineMedia from './../../../components/HeroInlineMedia/HeroInlineMedia.vue'
|
|
26
26
|
import AboutTheAuthor from './../../../components/AboutTheAuthor/AboutTheAuthor.vue'
|
|
27
|
-
|
|
28
27
|
import { HeadingLevel } from '../../../components/BaseHeading/BaseHeading.vue'
|
|
29
28
|
import StudentProjectBadge from '@explorer-1/common/src/images/svg/student-project-badge.svg'
|
|
29
|
+
|
|
30
30
|
const route = useRoute()
|
|
31
31
|
interface EduStudentProjectSectionObject extends PageEduStudentProjectSectionProps {
|
|
32
32
|
type?: string
|
|
@@ -13,6 +13,7 @@ import BlockRelatedLinks from '../../../components/BlockRelatedLinks/BlockRelate
|
|
|
13
13
|
import NavJumpMenu from './../../../components/NavJumpMenu/NavJumpMenu.vue'
|
|
14
14
|
import HeroInlineMedia from './../../../components/HeroInlineMedia/HeroInlineMedia.vue'
|
|
15
15
|
import AboutTheAuthor from './../../../components/AboutTheAuthor/AboutTheAuthor.vue'
|
|
16
|
+
import { anchorizeStreamfield } from './../../../utils/anchorizeStreamfield'
|
|
16
17
|
|
|
17
18
|
interface PageEduTeachableMomentProps {
|
|
18
19
|
data?: PageEduResourcesObject
|
|
@@ -68,6 +69,11 @@ const heroInline = computed((): boolean => {
|
|
|
68
69
|
return false
|
|
69
70
|
})
|
|
70
71
|
|
|
72
|
+
const filteredBody = computed(() => {
|
|
73
|
+
// adds anchors to headings within RichTextBlock
|
|
74
|
+
return anchorizeStreamfield(data?.body)
|
|
75
|
+
})
|
|
76
|
+
|
|
71
77
|
const computedClass = computed((): string => {
|
|
72
78
|
if (heroInline.value || heroEmpty.value) {
|
|
73
79
|
return 'pt-5 lg:pt-12'
|
|
@@ -87,7 +93,7 @@ const computedClass = computed((): string => {
|
|
|
87
93
|
v-if="data.showJumpMenu"
|
|
88
94
|
ref="PageEduTeachableMomentJumpMenu"
|
|
89
95
|
:title="data.title"
|
|
90
|
-
:blocks="
|
|
96
|
+
:blocks="filteredBody"
|
|
91
97
|
dropdown-text="In this Teachable Moment"
|
|
92
98
|
/>
|
|
93
99
|
|
|
@@ -160,7 +166,7 @@ const computedClass = computed((): string => {
|
|
|
160
166
|
</LayoutHelper>
|
|
161
167
|
|
|
162
168
|
<!-- streamfield blocks -->
|
|
163
|
-
<BlockStreamfield :data="
|
|
169
|
+
<BlockStreamfield :data="filteredBody" />
|
|
164
170
|
|
|
165
171
|
<!-- related links -->
|
|
166
172
|
<LayoutHelper
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BlockTextObject } from './../components/BlockText/BlockText.vue'
|
|
2
|
+
import { getHeadingId } from './getHeadingId'
|
|
3
|
+
|
|
4
|
+
export const anchorizeBlock = (block: BlockTextObject, headingLevel = 'h2') => {
|
|
5
|
+
if (block?.blockType === 'RichTextBlock') {
|
|
6
|
+
const regex = new RegExp(`<${headingLevel} data-block-key="(.*?)">(.*?)</${headingLevel}>`, 'g')
|
|
7
|
+
let text = block?.value
|
|
8
|
+
if (text) {
|
|
9
|
+
text = text.replaceAll(regex, (_match, g1, g2) => {
|
|
10
|
+
const headingId = getHeadingId(g2, g1)
|
|
11
|
+
return `<${headingLevel} id="${headingId}">${g2}</${headingLevel}>`
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
...block,
|
|
16
|
+
value: text
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return block
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { anchorizeBlock } from './anchorizeBlock'
|
|
2
|
+
import { StreamfieldBlockData } from '../interfaces'
|
|
3
|
+
export const anchorizeStreamfield = (blocks?: StreamfieldBlockData[], headingLevel = 'h2') => {
|
|
4
|
+
if (blocks?.length) {
|
|
5
|
+
// @ts-expect-error
|
|
6
|
+
const filteredBlocks = []
|
|
7
|
+
blocks.forEach((block) => {
|
|
8
|
+
// @ts-expect-error
|
|
9
|
+
filteredBlocks.push(anchorizeBlock(block, headingLevel))
|
|
10
|
+
})
|
|
11
|
+
// @ts-expect-error
|
|
12
|
+
return filteredBlocks
|
|
13
|
+
}
|
|
14
|
+
return blocks
|
|
15
|
+
}
|