@asd20/ui-next 2.0.15 → 2.0.17
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/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/src/components/atoms/Asd20Button/index.vue +12 -2
- package/src/components/atoms/Asd20DistrictLogo/index.vue +11 -0
- package/src/components/atoms/Asd20Logo/index.vue +11 -0
- package/src/components/atoms/Asd20Messaging/index.vue +10 -1
- package/src/components/atoms/Asd20RichBodyContent/index.vue +15 -0
- package/src/components/molecules/Asd20Card/index.vue +11 -0
- package/src/components/molecules/Asd20Notification/index.vue +10 -0
- package/src/components/organisms/Asd20PageFooter/index.vue +11 -0
- package/src/components/organisms/Asd20SchoolHomepageVideoHeader/index.vue +8 -0
- package/src/components/organisms/Asd20SchoolPageHeader/index.vue +10 -0
- package/src/components/organisms/Asd20SecondaryHeader/index.vue +69 -33
- package/src/components/organisms/Asd20SiteMenu/index.vue +13 -0
- package/src/components/templates/Asd20CampusTemplate/index.vue +11 -0
- package/src/components/templates/Asd20LoginsTemplate/index.vue +23 -15
- package/src/components/templates/Asd20SchoolHomeTemplate/index.vue +9 -0
- package/src/components/templates/Asd20SchoolHomeVideoTemplate/index.vue +11 -0
- package/src/helpers/linkPolicy.js +125 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.17](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.16...ui-next-v2.0.17) (2026-04-02)
|
|
4
|
+
|
|
5
|
+
## [2.0.16](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.15...ui-next-v2.0.16) (2026-04-01)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* fix secondary header image display ([8ed8d6f](https://github.com/academydistrict20/asd20-ui-next/commit/8ed8d6f0e2bedab9e108948963f65cd3daf20972))
|
|
11
|
+
|
|
3
12
|
## [2.0.15](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.14...ui-next-v2.0.15) (2026-04-01)
|
|
4
13
|
|
|
5
14
|
## [2.0.14](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.13...ui-next-v2.0.14) (2026-03-31)
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<component
|
|
3
3
|
:is="tag"
|
|
4
4
|
v-bind="rootAttrs"
|
|
5
|
-
@click="
|
|
5
|
+
@click="handleClick"
|
|
6
6
|
@mouseup="forwardEvent('mouseup', $event)"
|
|
7
7
|
@keyup="forwardEvent('keyup', $event)"
|
|
8
8
|
@focusin="forwardEvent('focusin', $event)"
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
<script>
|
|
26
26
|
import Asd20Badge from '../Asd20Badge'
|
|
27
27
|
import Asd20Icon from '../Asd20Icon'
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
navigateInternalHref,
|
|
30
|
+
shouldOpenInNewWindow,
|
|
31
|
+
} from '../../../helpers/linkPolicy'
|
|
29
32
|
|
|
30
33
|
export default {
|
|
31
34
|
name: 'Asd20Button',
|
|
@@ -111,6 +114,13 @@ export default {
|
|
|
111
114
|
},
|
|
112
115
|
},
|
|
113
116
|
methods: {
|
|
117
|
+
handleClick(event) {
|
|
118
|
+
navigateInternalHref(this, event, this.link, {
|
|
119
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
120
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
121
|
+
})
|
|
122
|
+
this.forwardEvent('click', event)
|
|
123
|
+
},
|
|
114
124
|
forwardEvent(name, event) {
|
|
115
125
|
this.$emit(name, event)
|
|
116
126
|
},
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
class="asd20-district-logo"
|
|
5
5
|
aria-label="Academy District 20 Home - We educate and inspire students to thrive."
|
|
6
6
|
alt="The Academy District 20 logo."
|
|
7
|
+
@click="onClick"
|
|
7
8
|
>
|
|
8
9
|
<svg viewBox="0 0 460.94 62.52">
|
|
9
10
|
<path
|
|
@@ -30,12 +31,22 @@
|
|
|
30
31
|
</template>
|
|
31
32
|
|
|
32
33
|
<script>
|
|
34
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
35
|
+
|
|
33
36
|
export default {
|
|
34
37
|
name: 'Asd20DistrictLogo',
|
|
35
38
|
props: {
|
|
36
39
|
link: { type: String, default: '/' },
|
|
37
40
|
tagline: { type: Boolean, default: false },
|
|
38
41
|
},
|
|
42
|
+
methods: {
|
|
43
|
+
onClick(event) {
|
|
44
|
+
navigateInternalHref(this, event, this.link, {
|
|
45
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
46
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
47
|
+
})
|
|
48
|
+
},
|
|
49
|
+
},
|
|
39
50
|
}
|
|
40
51
|
</script>
|
|
41
52
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
? `${abbreviatedTitle} ${subtitle}, An Academy District 20 School`
|
|
9
9
|
: `${abbreviatedTitle} ${subtitle}`
|
|
10
10
|
"
|
|
11
|
+
@click="onClick"
|
|
11
12
|
>
|
|
12
13
|
<figure class="asd20-logo__image">
|
|
13
14
|
<img
|
|
@@ -40,6 +41,8 @@
|
|
|
40
41
|
</template>
|
|
41
42
|
|
|
42
43
|
<script>
|
|
44
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
45
|
+
|
|
43
46
|
export default {
|
|
44
47
|
name: 'Asd20Logo',
|
|
45
48
|
props: {
|
|
@@ -90,6 +93,14 @@ export default {
|
|
|
90
93
|
return this.title.trim()
|
|
91
94
|
},
|
|
92
95
|
},
|
|
96
|
+
methods: {
|
|
97
|
+
onClick(event) {
|
|
98
|
+
navigateInternalHref(this, event, this.link, {
|
|
99
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
100
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
},
|
|
93
104
|
}
|
|
94
105
|
</script>
|
|
95
106
|
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
:href="detailLink"
|
|
36
36
|
:target="detailLinkTarget"
|
|
37
37
|
:rel="detailLinkRel"
|
|
38
|
+
@click="onDetailLinkClick"
|
|
38
39
|
>
|
|
39
40
|
{{ detailLinkLabel }}
|
|
40
41
|
</a>
|
|
@@ -98,6 +99,7 @@ import Asd20Button from '../Asd20Button'
|
|
|
98
99
|
import Asd20Icon from '../Asd20Icon'
|
|
99
100
|
import Asd20RichBodyContent from '../Asd20RichBodyContent'
|
|
100
101
|
import {
|
|
102
|
+
navigateInternalHref,
|
|
101
103
|
resolveCurrentHostname,
|
|
102
104
|
shouldOpenInNewWindow,
|
|
103
105
|
shouldShowExternalIcon,
|
|
@@ -176,7 +178,14 @@ export default {
|
|
|
176
178
|
},
|
|
177
179
|
},
|
|
178
180
|
|
|
179
|
-
methods: {
|
|
181
|
+
methods: {
|
|
182
|
+
onDetailLinkClick(event) {
|
|
183
|
+
navigateInternalHref(this, event, this.detailLink, {
|
|
184
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
185
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
},
|
|
180
189
|
}
|
|
181
190
|
</script>
|
|
182
191
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<div
|
|
3
3
|
v-if="renderNodes.length"
|
|
4
4
|
class="asd20-rich-body-content"
|
|
5
|
+
@click="onContentClick"
|
|
5
6
|
>
|
|
6
7
|
<template v-for="(node, index) in renderNodes">
|
|
7
8
|
<div
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
<script>
|
|
38
39
|
import Asd20BodyAccordion from '../../molecules/Asd20BodyAccordion'
|
|
39
40
|
import Asd20TableauEmbed from '../../molecules/Asd20TableauEmbed'
|
|
41
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
40
42
|
|
|
41
43
|
const BODY_BLOCK_PLACEHOLDER_PATTERN =
|
|
42
44
|
/<div\b(?=[^>]*\bdata-cc-block=(['"])([^'"]+)\1)(?=[^>]*\bdata-block-id=(['"])([^'"]+)\3)[^>]*><\/div>/gi
|
|
@@ -145,6 +147,19 @@ export default {
|
|
|
145
147
|
mounted() {
|
|
146
148
|
this.hasMounted = true
|
|
147
149
|
},
|
|
150
|
+
|
|
151
|
+
methods: {
|
|
152
|
+
onContentClick(event) {
|
|
153
|
+
const anchor = event.target?.closest?.('a')
|
|
154
|
+
|
|
155
|
+
if (!anchor || !this.$el?.contains(anchor)) return
|
|
156
|
+
|
|
157
|
+
navigateInternalHref(this, event, anchor.getAttribute('href'), {
|
|
158
|
+
target: anchor.getAttribute('target'),
|
|
159
|
+
download: anchor.hasAttribute('download'),
|
|
160
|
+
})
|
|
161
|
+
},
|
|
162
|
+
},
|
|
148
163
|
}
|
|
149
164
|
</script>
|
|
150
165
|
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
:href="link"
|
|
55
55
|
:title="title"
|
|
56
56
|
:tabindex="disableFocus ? -1 : 0"
|
|
57
|
+
@click="onLinkClick"
|
|
57
58
|
></a>
|
|
58
59
|
</div>
|
|
59
60
|
<div
|
|
@@ -178,6 +179,7 @@
|
|
|
178
179
|
:href="link ? link : ''"
|
|
179
180
|
:title="title"
|
|
180
181
|
:tabindex="disableFocus ? -1 : 0"
|
|
182
|
+
@click="onLinkClick"
|
|
181
183
|
v-html="linkLabel"
|
|
182
184
|
></a>
|
|
183
185
|
</p>
|
|
@@ -256,6 +258,7 @@
|
|
|
256
258
|
import Asd20Icon from '../../atoms/Asd20Icon'
|
|
257
259
|
import Asd20Tag from '../../atoms/Asd20Tag'
|
|
258
260
|
import lazyImage from '../../../directives/lazy-image'
|
|
261
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
259
262
|
import truncate from 'lodash/truncate'
|
|
260
263
|
|
|
261
264
|
export default {
|
|
@@ -379,6 +382,14 @@ export default {
|
|
|
379
382
|
return plain.slice(0, 65) + '...'
|
|
380
383
|
},
|
|
381
384
|
},
|
|
385
|
+
methods: {
|
|
386
|
+
onLinkClick(event) {
|
|
387
|
+
navigateInternalHref(this, event, this.link, {
|
|
388
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
389
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
390
|
+
})
|
|
391
|
+
},
|
|
392
|
+
},
|
|
382
393
|
}
|
|
383
394
|
</script>
|
|
384
395
|
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
v-if="detailLinkUrl"
|
|
29
29
|
:tabindex="focusDisabled ? '-1' : undefined"
|
|
30
30
|
:href="detailLinkUrl"
|
|
31
|
+
@click="onDetailLinkClick"
|
|
31
32
|
>
|
|
32
33
|
{{ detailLinkLabel || detailLinkUrl }}
|
|
33
34
|
</a>
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
<script>
|
|
64
65
|
import Asd20Icon from '../../atoms/Asd20Icon'
|
|
65
66
|
import Asd20Button from '../../atoms/Asd20Button'
|
|
67
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
66
68
|
|
|
67
69
|
export default {
|
|
68
70
|
name: 'Asd20Notification',
|
|
@@ -126,6 +128,14 @@ export default {
|
|
|
126
128
|
}
|
|
127
129
|
},
|
|
128
130
|
},
|
|
131
|
+
methods: {
|
|
132
|
+
onDetailLinkClick(event) {
|
|
133
|
+
navigateInternalHref(this, event, this.detailLinkUrl, {
|
|
134
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
135
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
},
|
|
129
139
|
}
|
|
130
140
|
</script>
|
|
131
141
|
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
<a
|
|
34
34
|
v-if="websiteLogoProps"
|
|
35
35
|
:href="websiteLogoProps.logoLink ? websiteLogoProps.logoLink : ''"
|
|
36
|
+
@click="onLogoClick($event, websiteLogoProps.logoLink)"
|
|
36
37
|
>
|
|
37
38
|
<img
|
|
38
39
|
:src="
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
<a
|
|
45
46
|
v-if="websiteLogoProps2"
|
|
46
47
|
:href="websiteLogoProps2.logoLink ? websiteLogoProps2.logoLink : ''"
|
|
48
|
+
@click="onLogoClick($event, websiteLogoProps2.logoLink)"
|
|
47
49
|
>
|
|
48
50
|
<img
|
|
49
51
|
:src="
|
|
@@ -173,6 +175,7 @@ import Asd20SocialMenu from '../../../components/molecules/Asd20SocialMenu'
|
|
|
173
175
|
import Asd20Logo from '../../../components/atoms/Asd20Logo'
|
|
174
176
|
import Asd20DistrictLogo from '../../../components/atoms/Asd20DistrictLogo'
|
|
175
177
|
import organizationAddress from '../../../data/organization-address.json'
|
|
178
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
176
179
|
import {
|
|
177
180
|
organizationLevel,
|
|
178
181
|
organizationName,
|
|
@@ -265,6 +268,14 @@ export default {
|
|
|
265
268
|
return null
|
|
266
269
|
},
|
|
267
270
|
},
|
|
271
|
+
methods: {
|
|
272
|
+
onLogoClick(event, link) {
|
|
273
|
+
navigateInternalHref(this, event, link, {
|
|
274
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
275
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
276
|
+
})
|
|
277
|
+
},
|
|
278
|
+
},
|
|
268
279
|
}
|
|
269
280
|
</script>
|
|
270
281
|
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
<a
|
|
68
68
|
v-if="detailLink && !isDetailPage"
|
|
69
69
|
:href="detailLink"
|
|
70
|
+
@click="onDetailLinkClick"
|
|
70
71
|
>
|
|
71
72
|
{{ detailLinkLabel }}
|
|
72
73
|
</a>
|
|
@@ -104,6 +105,7 @@
|
|
|
104
105
|
<script>
|
|
105
106
|
import scrollTrack from '../../../directives/scroll-track'
|
|
106
107
|
import responsiveBreakpointMixin from '../../../mixins/responsiveBreakpointMixin'
|
|
108
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
107
109
|
import Asd20Button from '../../atoms/Asd20Button'
|
|
108
110
|
|
|
109
111
|
export default {
|
|
@@ -181,6 +183,12 @@ export default {
|
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
},
|
|
186
|
+
onDetailLinkClick(event) {
|
|
187
|
+
navigateInternalHref(this, event, this.detailLink, {
|
|
188
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
189
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
190
|
+
})
|
|
191
|
+
},
|
|
184
192
|
},
|
|
185
193
|
}
|
|
186
194
|
</script>
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
<a
|
|
24
24
|
v-if="detailLink && !isDetailPage"
|
|
25
25
|
:href="detailLink"
|
|
26
|
+
@click="onDetailLinkClick"
|
|
26
27
|
>
|
|
27
28
|
{{ detailLinkLabel }}
|
|
28
29
|
</a>
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
<script>
|
|
74
75
|
import scrollTrack from '../../../directives/scroll-track'
|
|
75
76
|
import responsiveBreakpointMixin from '../../../mixins/responsiveBreakpointMixin'
|
|
77
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
76
78
|
import Asd20Button from '../../atoms/Asd20Button'
|
|
77
79
|
|
|
78
80
|
export default {
|
|
@@ -105,6 +107,14 @@ export default {
|
|
|
105
107
|
)
|
|
106
108
|
},
|
|
107
109
|
},
|
|
110
|
+
methods: {
|
|
111
|
+
onDetailLinkClick(event) {
|
|
112
|
+
navigateInternalHref(this, event, this.detailLink, {
|
|
113
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
114
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
115
|
+
})
|
|
116
|
+
},
|
|
117
|
+
},
|
|
108
118
|
}
|
|
109
119
|
</script>
|
|
110
120
|
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
<div class="white-accent-band">
|
|
98
98
|
<section
|
|
99
99
|
ref="header"
|
|
100
|
-
class="
|
|
100
|
+
:class="headerClasses"
|
|
101
101
|
aria-label="Secondary message"
|
|
102
102
|
:style="secondaryHeaderStyle"
|
|
103
103
|
>
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
</div>
|
|
117
117
|
|
|
118
118
|
<asd20-messaging
|
|
119
|
-
v-bind="
|
|
119
|
+
v-bind="secondaryMessageForDisplay"
|
|
120
120
|
fullscreen
|
|
121
121
|
></asd20-messaging>
|
|
122
122
|
</section>
|
|
@@ -151,33 +151,52 @@ export default {
|
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
153
|
computed: {
|
|
154
|
+
headerClasses() {
|
|
155
|
+
return {
|
|
156
|
+
'asd20-secondary-header': true,
|
|
157
|
+
'asd20-secondary-header--hide-desktop-image':
|
|
158
|
+
!this.shouldShowDesktopInlineImage,
|
|
159
|
+
}
|
|
160
|
+
},
|
|
154
161
|
secondaryMessage() {
|
|
155
162
|
return Array.isArray(this.messages) ? this.messages[1] || {} : {}
|
|
156
163
|
},
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
secondaryMessageImages() {
|
|
165
|
+
return Array.isArray(this.secondaryMessage.images)
|
|
159
166
|
? this.secondaryMessage.images
|
|
160
167
|
: []
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
coverImageFull.filename.includes('headerimage')
|
|
172
|
-
) {
|
|
173
|
-
return ''
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return coverImageFull ? coverImageFull.url : coverImage.url || ''
|
|
168
|
+
},
|
|
169
|
+
secondaryHeaderImageObject() {
|
|
170
|
+
return (
|
|
171
|
+
this.secondaryMessageImages.find(image => image && image.isCover) ||
|
|
172
|
+
this.secondaryMessageImages[0] ||
|
|
173
|
+
null
|
|
174
|
+
)
|
|
175
|
+
},
|
|
176
|
+
secondaryHeaderImage() {
|
|
177
|
+
return this.resolveMessageImageUrl(this.secondaryHeaderImageObject)
|
|
177
178
|
},
|
|
178
179
|
hasSecondaryHeaderImage() {
|
|
179
180
|
return Boolean(this.secondaryHeaderImage)
|
|
180
181
|
},
|
|
182
|
+
shouldShowDesktopInlineImage() {
|
|
183
|
+
return Boolean(
|
|
184
|
+
this.secondaryHeaderImageObject && this.secondaryHeaderImageObject.isCover
|
|
185
|
+
)
|
|
186
|
+
},
|
|
187
|
+
secondaryMessageForDisplay() {
|
|
188
|
+
if (!this.secondaryHeaderImageObject) return this.secondaryMessage
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...this.secondaryMessage,
|
|
192
|
+
images: [
|
|
193
|
+
{
|
|
194
|
+
...this.secondaryHeaderImageObject,
|
|
195
|
+
isCover: true,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
}
|
|
199
|
+
},
|
|
181
200
|
secondaryHeaderStyle() {
|
|
182
201
|
if (!this.hasSecondaryHeaderImage) return {}
|
|
183
202
|
|
|
@@ -223,6 +242,13 @@ export default {
|
|
|
223
242
|
this.teardownParallax()
|
|
224
243
|
},
|
|
225
244
|
methods: {
|
|
245
|
+
resolveMessageImageUrl(image) {
|
|
246
|
+
if (!image) return ''
|
|
247
|
+
|
|
248
|
+
const imageFiles = Array.isArray(image.files) ? image.files : []
|
|
249
|
+
const fullImage = imageFiles.find(file => file.name === 'full')
|
|
250
|
+
return fullImage ? fullImage.url : image.url || ''
|
|
251
|
+
},
|
|
226
252
|
teardownParallax() {
|
|
227
253
|
this.disableParallax()
|
|
228
254
|
if (this.intersectionObserver) this.intersectionObserver.disconnect()
|
|
@@ -552,19 +578,6 @@ export default {
|
|
|
552
578
|
}
|
|
553
579
|
}
|
|
554
580
|
|
|
555
|
-
& :deep(.message-image) {
|
|
556
|
-
display: none;
|
|
557
|
-
margin-top: 0;
|
|
558
|
-
order: 2;
|
|
559
|
-
max-height: 400px;
|
|
560
|
-
img {
|
|
561
|
-
height: auto !important;
|
|
562
|
-
margin-left: space(1);
|
|
563
|
-
max-height: 25vw;
|
|
564
|
-
border-radius: var(--website-shape__radius-m);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
581
|
& :deep(.asd20-messaging__content) {
|
|
569
582
|
order: 1;
|
|
570
583
|
max-width: 60%;
|
|
@@ -629,6 +642,29 @@ export default {
|
|
|
629
642
|
);
|
|
630
643
|
-webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,0.3) 0%, rgba(0,0,0,1) 70%);
|
|
631
644
|
}
|
|
645
|
+
|
|
646
|
+
& :deep(.message-image) {
|
|
647
|
+
display: flex;
|
|
648
|
+
margin-top: 0;
|
|
649
|
+
order: 2;
|
|
650
|
+
max-height: 400px;
|
|
651
|
+
img {
|
|
652
|
+
height: auto !important;
|
|
653
|
+
margin-left: space(1);
|
|
654
|
+
max-height: 25vw;
|
|
655
|
+
border-radius: var(--website-shape__radius-m);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.asd20-secondary-header--hide-desktop-image {
|
|
661
|
+
& :deep(.message-image) {
|
|
662
|
+
display: none;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
& :deep(.asd20-messaging__content) {
|
|
666
|
+
max-width: 60%;
|
|
667
|
+
}
|
|
632
668
|
}
|
|
633
669
|
}
|
|
634
670
|
</style>
|
|
@@ -120,6 +120,7 @@
|
|
|
120
120
|
:href="item.url"
|
|
121
121
|
:target="getTarget(item.url)"
|
|
122
122
|
:rel="getRel(item.url)"
|
|
123
|
+
@click="onItemClick($event, item.url)"
|
|
123
124
|
>
|
|
124
125
|
<span class="asd20-site-menu__item-label">{{ item.title }}</span>
|
|
125
126
|
<!-- Keep this icon mounted so current-host checks do not introduce hydration mismatches. -->
|
|
@@ -147,6 +148,7 @@
|
|
|
147
148
|
import Asd20Icon from '../../atoms/Asd20Icon'
|
|
148
149
|
import FocusTrap from '../../utils/FocusTrap'
|
|
149
150
|
import {
|
|
151
|
+
navigateInternalHref,
|
|
150
152
|
resolveCurrentHostname,
|
|
151
153
|
shouldOpenInNewWindow,
|
|
152
154
|
shouldShowExternalIcon,
|
|
@@ -326,6 +328,17 @@ export default {
|
|
|
326
328
|
? 'noopener noreferrer'
|
|
327
329
|
: undefined
|
|
328
330
|
},
|
|
331
|
+
onItemClick(event, url) {
|
|
332
|
+
const handled = navigateInternalHref(this, event, url, {
|
|
333
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
334
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
if (handled) {
|
|
338
|
+
this.activeSectionIndex = -1
|
|
339
|
+
this.dismiss()
|
|
340
|
+
}
|
|
341
|
+
},
|
|
329
342
|
shouldShowItemExternalIcon(url) {
|
|
330
343
|
return shouldShowExternalIcon(url, this.currentHostname)
|
|
331
344
|
},
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
<a
|
|
29
29
|
v-if="websiteLogoProps"
|
|
30
30
|
:href="websiteLogoProps.logoLink ? websiteLogoProps.logoLink : ''"
|
|
31
|
+
@click="onLogoClick($event, websiteLogoProps.logoLink)"
|
|
31
32
|
>
|
|
32
33
|
<img
|
|
33
34
|
class="optionalLogo"
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
<a
|
|
43
44
|
v-if="websiteLogoProps2"
|
|
44
45
|
:href="websiteLogoProps2.logoLink ? websiteLogoProps2.logoLink : ''"
|
|
46
|
+
@click="onLogoClick($event, websiteLogoProps2.logoLink)"
|
|
45
47
|
>
|
|
46
48
|
<img
|
|
47
49
|
class="optionalLogo"
|
|
@@ -167,6 +169,7 @@ import Asd20FeedsSection from '../../../components/organisms/Asd20FeedsSection'
|
|
|
167
169
|
import Asd20PageFooter from '../../../components/organisms/Asd20PageFooter'
|
|
168
170
|
// import Asd20QuicklinksMenu from '../../../components/organisms/Asd20QuicklinksMenu'
|
|
169
171
|
import Asd20NotificationGroup from '../../../components/organisms/Asd20NotificationGroup'
|
|
172
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
170
173
|
|
|
171
174
|
|
|
172
175
|
// Mixins
|
|
@@ -200,6 +203,14 @@ export default {
|
|
|
200
203
|
)
|
|
201
204
|
},
|
|
202
205
|
},
|
|
206
|
+
methods: {
|
|
207
|
+
onLogoClick(event, link) {
|
|
208
|
+
navigateInternalHref(this, event, link, {
|
|
209
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
210
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
211
|
+
})
|
|
212
|
+
},
|
|
213
|
+
},
|
|
203
214
|
}
|
|
204
215
|
</script>
|
|
205
216
|
|
|
@@ -233,6 +233,7 @@ export default {
|
|
|
233
233
|
)
|
|
234
234
|
|
|
235
235
|
return sortedCategories.map(c => ({
|
|
236
|
+
id: `tab-${kebabCase(c)}`,
|
|
236
237
|
label: c,
|
|
237
238
|
hash: `#tab-${kebabCase(c)}`,
|
|
238
239
|
active: this.tab === `#tab-${kebabCase(c)}`,
|
|
@@ -308,31 +309,38 @@ export default {
|
|
|
308
309
|
// },
|
|
309
310
|
|
|
310
311
|
created() {
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
this.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (typeof window !== 'undefined' && this.tabsList.length) {
|
|
319
|
-
window.location.hash = this.tabsList[0].hash
|
|
320
|
-
this.tab = window.location.hash
|
|
321
|
-
console.log('forced tab =', this.tabsList[0].hash)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
312
|
+
if (typeof window === 'undefined' || !this.tabsList.length) return
|
|
313
|
+
|
|
314
|
+
const activeHash = this.tabsList.some(t => t.hash === window.location.hash)
|
|
315
|
+
? window.location.hash
|
|
316
|
+
: this.tabsList[0].hash
|
|
317
|
+
|
|
318
|
+
this.tab = activeHash
|
|
324
319
|
},
|
|
325
320
|
mounted() {
|
|
326
|
-
|
|
321
|
+
this.syncBrowserHash(this.tab)
|
|
327
322
|
},
|
|
328
323
|
methods: {
|
|
329
324
|
onTabClick(tab) {
|
|
330
|
-
window.location.hash = tab.hash
|
|
331
325
|
this.tab = tab.hash
|
|
326
|
+
this.syncBrowserHash(tab.hash)
|
|
332
327
|
},
|
|
333
328
|
isResetTab(label) {
|
|
334
329
|
return /\breset\b/i.test(label)
|
|
335
330
|
},
|
|
331
|
+
syncBrowserHash(hash) {
|
|
332
|
+
if (typeof window === 'undefined' || !hash) return
|
|
333
|
+
|
|
334
|
+
const currentUrl = new URL(window.location.href)
|
|
335
|
+
if (currentUrl.hash === hash) return
|
|
336
|
+
|
|
337
|
+
currentUrl.hash = hash
|
|
338
|
+
window.history.replaceState(
|
|
339
|
+
window.history.state,
|
|
340
|
+
'',
|
|
341
|
+
`${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`
|
|
342
|
+
)
|
|
343
|
+
},
|
|
336
344
|
},
|
|
337
345
|
}
|
|
338
346
|
</script>
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
<a
|
|
42
42
|
v-if="websiteLogoProps && showLogo"
|
|
43
43
|
:href="websiteLogoProps.logoLink ? websiteLogoProps.logoLink : ''"
|
|
44
|
+
@click="onLogoClick($event, websiteLogoProps.logoLink)"
|
|
44
45
|
>
|
|
45
46
|
<img
|
|
46
47
|
class="optionalLogo"
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
:href="
|
|
61
62
|
websiteLogoProps2.logoLink ? websiteLogoProps2.logoLink : ''
|
|
62
63
|
"
|
|
64
|
+
@click="onLogoClick($event, websiteLogoProps2.logoLink)"
|
|
63
65
|
>
|
|
64
66
|
<img
|
|
65
67
|
class="optionalLogo"
|
|
@@ -192,6 +194,7 @@ import Asd20OrganizationPicker from '../../../components/organisms/Asd20Organiza
|
|
|
192
194
|
import Asd20LanguageTranslation from '../../molecules/Asd20LanguageTranslation'
|
|
193
195
|
import Asd20BlockSchedule from '../../molecules/Asd20BlockSchedule'
|
|
194
196
|
import Asd20AiSearch from '../../organisms/Asd20AiSearch'
|
|
197
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
195
198
|
|
|
196
199
|
// Helpers
|
|
197
200
|
import _get from 'lodash/get'
|
|
@@ -304,6 +307,12 @@ export default {
|
|
|
304
307
|
handleResize() {
|
|
305
308
|
this.showLogo = window.innerWidth >= 1350
|
|
306
309
|
},
|
|
310
|
+
onLogoClick(event, link) {
|
|
311
|
+
navigateInternalHref(this, event, link, {
|
|
312
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
313
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
314
|
+
})
|
|
315
|
+
},
|
|
307
316
|
},
|
|
308
317
|
}
|
|
309
318
|
</script>
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
<a
|
|
42
42
|
v-if="websiteLogoProps"
|
|
43
43
|
:href="websiteLogoProps.logoLink ? websiteLogoProps.logoLink : ''"
|
|
44
|
+
@click="onLogoClick($event, websiteLogoProps.logoLink)"
|
|
44
45
|
>
|
|
45
46
|
<img
|
|
46
47
|
class="optionalLogo"
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
:href="
|
|
58
59
|
websiteLogoProps2.logoLink ? websiteLogoProps2.logoLink : ''
|
|
59
60
|
"
|
|
61
|
+
@click="onLogoClick($event, websiteLogoProps2.logoLink)"
|
|
60
62
|
>
|
|
61
63
|
<img
|
|
62
64
|
class="optionalLogo"
|
|
@@ -194,6 +196,7 @@ import Asd20OrganizationPicker from '../../../components/organisms/Asd20Organiza
|
|
|
194
196
|
import Asd20LanguageTranslation from '../../molecules/Asd20LanguageTranslation'
|
|
195
197
|
import Asd20BlockSchedule from '../../molecules/Asd20BlockSchedule'
|
|
196
198
|
import Asd20AiSearch from '../../organisms/Asd20AiSearch'
|
|
199
|
+
import { navigateInternalHref } from '../../../helpers/linkPolicy'
|
|
197
200
|
|
|
198
201
|
// Helpers
|
|
199
202
|
|
|
@@ -263,6 +266,14 @@ export default {
|
|
|
263
266
|
this.$emit('events-in-view')
|
|
264
267
|
}
|
|
265
268
|
},
|
|
269
|
+
methods: {
|
|
270
|
+
onLogoClick(event, link) {
|
|
271
|
+
navigateInternalHref(this, event, link, {
|
|
272
|
+
target: event.currentTarget?.getAttribute?.('target'),
|
|
273
|
+
download: event.currentTarget?.hasAttribute?.('download'),
|
|
274
|
+
})
|
|
275
|
+
},
|
|
276
|
+
},
|
|
266
277
|
}
|
|
267
278
|
</script>
|
|
268
279
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const ASD20_ROOT_DOMAIN = 'asd20.org'
|
|
2
2
|
const DEFAULT_BASE_URL = `https://${ASD20_ROOT_DOMAIN}`
|
|
3
|
+
const NON_ROUTER_PROTOCOL_PATTERN = /^(mailto:|tel:|javascript:|data:|blob:)/i
|
|
4
|
+
const FILE_LIKE_PATH_PATTERN = /\/[^/?#]+\.[a-z0-9]{1,8}$/i
|
|
3
5
|
|
|
4
6
|
export function normalizeHostname(hostname, { treatWwwAsLocal = true } = {}) {
|
|
5
7
|
if (!hostname) return ''
|
|
@@ -44,6 +46,59 @@ export function parseAbsoluteHttpUrl(href) {
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
export function resolveCurrentUrl(_vm, options = {}) {
|
|
50
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
51
|
+
return window.location.href
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (options.currentUrl) return options.currentUrl
|
|
55
|
+
if (options.currentOrigin) return options.currentOrigin
|
|
56
|
+
|
|
57
|
+
return DEFAULT_BASE_URL
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveCurrentOrigin(_vm, options = {}) {
|
|
61
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
62
|
+
return window.location.origin
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
return new URL(resolveCurrentUrl(_vm, options), DEFAULT_BASE_URL).origin
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return DEFAULT_BASE_URL
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isHashOnlyHref(href) {
|
|
73
|
+
if (!href || typeof href !== 'string') return false
|
|
74
|
+
return href.trim().startsWith('#')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function parseNavigableUrl(href, currentUrl = DEFAULT_BASE_URL) {
|
|
78
|
+
if (!href || typeof href !== 'string') return null
|
|
79
|
+
|
|
80
|
+
const trimmed = href.trim()
|
|
81
|
+
if (
|
|
82
|
+
!trimmed ||
|
|
83
|
+
isHashOnlyHref(trimmed) ||
|
|
84
|
+
NON_ROUTER_PROTOCOL_PATTERN.test(trimmed)
|
|
85
|
+
) {
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
return new URL(trimmed, currentUrl)
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isFileLikeHref(href, currentUrl = DEFAULT_BASE_URL) {
|
|
97
|
+
const url = parseNavigableUrl(href, currentUrl)
|
|
98
|
+
if (!url) return false
|
|
99
|
+
return FILE_LIKE_PATH_PATTERN.test(url.pathname)
|
|
100
|
+
}
|
|
101
|
+
|
|
47
102
|
export function shouldOpenInNewWindow(href, options) {
|
|
48
103
|
const url = parseAbsoluteHttpUrl(href)
|
|
49
104
|
if (!url) return false
|
|
@@ -73,3 +128,73 @@ export function shouldShowExternalIcon(href, currentHostname, options) {
|
|
|
73
128
|
|
|
74
129
|
return targetHostname !== normalizedCurrentHostname
|
|
75
130
|
}
|
|
131
|
+
|
|
132
|
+
export function shouldHandleClientRoutingEvent(event) {
|
|
133
|
+
if (!event) return true
|
|
134
|
+
if (event.defaultPrevented) return false
|
|
135
|
+
if (typeof event.button === 'number' && event.button !== 0) return false
|
|
136
|
+
|
|
137
|
+
return !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function shouldUseClientRouter(href, options = {}) {
|
|
141
|
+
const currentUrl = options.currentUrl || resolveCurrentUrl(null, options)
|
|
142
|
+
const {
|
|
143
|
+
router,
|
|
144
|
+
target,
|
|
145
|
+
download = false,
|
|
146
|
+
currentOrigin = options.currentOrigin || new URL(currentUrl).origin,
|
|
147
|
+
} = options
|
|
148
|
+
|
|
149
|
+
if (!router || download) return false
|
|
150
|
+
|
|
151
|
+
if (href && typeof href === 'object') {
|
|
152
|
+
return !target || target.toLowerCase() === '_self'
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (target && target.toLowerCase() !== '_self') return false
|
|
156
|
+
|
|
157
|
+
const url = parseNavigableUrl(href, currentUrl)
|
|
158
|
+
if (!url) return false
|
|
159
|
+
if (url.origin !== currentOrigin) return false
|
|
160
|
+
if (isFileLikeHref(href, currentUrl)) return false
|
|
161
|
+
|
|
162
|
+
return true
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function buildRouterLocationFromHref(href, currentUrl = DEFAULT_BASE_URL) {
|
|
166
|
+
if (href && typeof href === 'object') return href
|
|
167
|
+
|
|
168
|
+
const url = parseNavigableUrl(href, currentUrl)
|
|
169
|
+
if (!url) return null
|
|
170
|
+
|
|
171
|
+
return `${url.pathname}${url.search}${url.hash}` || '/'
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function navigateInternalHref(vm, event, href, options = {}) {
|
|
175
|
+
const router = options.router || vm?.$router
|
|
176
|
+
const currentUrl = options.currentUrl || resolveCurrentUrl(vm, options)
|
|
177
|
+
const target = options.target
|
|
178
|
+
const download = options.download || false
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
!shouldUseClientRouter(href, {
|
|
182
|
+
...options,
|
|
183
|
+
router,
|
|
184
|
+
currentUrl,
|
|
185
|
+
target,
|
|
186
|
+
download,
|
|
187
|
+
})
|
|
188
|
+
) {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!shouldHandleClientRoutingEvent(event)) return false
|
|
193
|
+
|
|
194
|
+
const location = buildRouterLocationFromHref(href, currentUrl)
|
|
195
|
+
if (!location) return false
|
|
196
|
+
|
|
197
|
+
event.preventDefault()
|
|
198
|
+
router.push(location)
|
|
199
|
+
return true
|
|
200
|
+
}
|