Skip to content

Commit 72c14ae

Browse files
committed
Add keyboard navigation links for jumping in import modal
1 parent 88c23f2 commit 72c14ae

9 files changed

Lines changed: 172 additions & 7 deletions

File tree

contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/BrowsingCard.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<template>
22

33
<VCard
4+
ref="card"
45
hover
56
@click="handleClick"
67
>
@@ -188,6 +189,12 @@
188189
this.$emit('preview');
189190
}
190191
},
192+
/**
193+
* @public
194+
*/
195+
focus() {
196+
this.$refs.card.$el.focus();
197+
},
191198
},
192199
$trs: {
193200
tagsList: 'Tags: {tags}',

contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/ChannelInfoCard.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<template>
22

33
<VCard
4+
ref="card"
45
hover
56
:to="channelRoute"
67
>
@@ -106,6 +107,14 @@
106107
};
107108
},
108109
},
110+
methods: {
111+
/**
112+
* @public
113+
*/
114+
focus() {
115+
this.$refs.card.$el.focus();
116+
},
117+
},
109118
$trs: {
110119
resourceCount: '{count, number} {count, plural, one {resource} other {resources}}',
111120
},

contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/ChannelList.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<div v-else>
4545
<ChannelInfoCard
4646
v-for="channel in channels"
47+
:ref="setFirstChannelCardRef"
4748
:key="channel.id"
4849
:channel="channel"
4950
class="mb-3"
@@ -87,6 +88,7 @@
8788
channels: [],
8889
pageCount: 0,
8990
loading: false,
91+
firstChannelCardRef: null,
9092
};
9193
},
9294
computed: {
@@ -131,6 +133,7 @@
131133
...mapActions('importFromChannels', ['loadChannels']),
132134
loadPage() {
133135
this.loading = true;
136+
this.firstChannelCardRef = null;
134137
this.loadChannels({
135138
languages: this.languageFilter,
136139
[this.channelFilter]: true,
@@ -144,6 +147,19 @@
144147
this.loading = false;
145148
});
146149
},
150+
setFirstChannelCardRef(ref) {
151+
if (!this.firstChannelCardRef) {
152+
this.firstChannelCardRef = ref;
153+
}
154+
},
155+
/**
156+
* @public
157+
*/
158+
focus() {
159+
if (this.firstChannelCardRef) {
160+
this.firstChannelCardRef.focus();
161+
}
162+
},
147163
},
148164
$trs: {
149165
channelFilterLabel: 'Channels',

contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/ContentTreeList.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<VFlex shrink>
3737
<Checkbox
3838
:key="`checkbox-${node.id}`"
39+
:ref="setFirstCardCheckboxRef"
3940
:inputValue="isSelected(node)"
4041
:disabled="ancestorIsSelected"
4142
@input="toggleSelected(node)"
@@ -108,6 +109,7 @@
108109
loading: false,
109110
more: null,
110111
moreLoading: false,
112+
firstCardCheckboxRef: null,
111113
};
112114
},
113115
computed: {
@@ -184,6 +186,7 @@
184186
...mapActions('contentNode', ['loadChildren', 'loadAncestors', 'loadContentNodes']),
185187
loadData() {
186188
this.loading = true;
189+
this.firstCardCheckboxRef = null;
187190
const params = {
188191
complete: true,
189192
};
@@ -227,6 +230,19 @@
227230
});
228231
}
229232
},
233+
setFirstCardCheckboxRef(ref) {
234+
if (!this.firstCardCheckboxRef) {
235+
this.firstCardCheckboxRef = ref;
236+
}
237+
},
238+
/**
239+
* @public
240+
*/
241+
focus() {
242+
if (this.firstCardCheckboxRef) {
243+
this.firstCardCheckboxRef.focus();
244+
}
245+
},
230246
},
231247
$trs: {
232248
allChannelsLabel: 'Channels',

contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/SearchFilterBar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22

33
<VContainer
4-
class="pt-3 px-2"
4+
class="pt-2 px-2"
55
fluid
66
>
77
<VChip

contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/SearchOrBrowseWindow.vue

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,26 @@
5858
</VTextField>
5959
</VForm>
6060

61-
<div
62-
v-if="!isBrowsing"
63-
class="my-2 px-2"
64-
>
61+
<div class="my-2">
6562
<ActionLink
66-
class="mb-3"
63+
v-if="!isBrowsing"
6764
:text="$tr('savedSearchesLabel')"
6865
:disabled="!savedSearchesExist"
6966
@click="showSavedSearches = true"
7067
/>
68+
<ActionLink
69+
v-if="shouldShowRecommendations"
70+
:class="{ 'keyboard-visibility': true, 'mx-3': !isBrowsing }"
71+
:text="$tr('jumpToRecommendations')"
72+
:style="keyboardVisibilityStyle"
73+
@click="handleJumpToRecommendations"
74+
/>
7175
</div>
76+
7277
<!-- Search or Topics Browsing -->
7378
<ChannelList
7479
v-if="isBrowsing && !$route.params.channelId"
80+
ref="channelList"
7581
@update-language="updateLanguageQuery"
7682
/>
7783
<ContentTreeList
@@ -86,11 +92,20 @@
8692
/>
8793
<SearchResultsList
8894
v-else
95+
ref="searchResultList"
8996
:selected.sync="selected"
9097
@preview="preview($event)"
9198
@change_selected="handleChangeSelected"
9299
@copy_to_clipboard="handleCopyToClipboard"
93100
/>
101+
<div style="text-align: center">
102+
<ActionLink
103+
:text="$tr('jumpToTop')"
104+
class="keyboard-visibility"
105+
:style="keyboardVisibilityStyle"
106+
@click="handleJumpToSearch"
107+
/>
108+
</div>
94109
</KGridItem>
95110

96111
<!-- Recommended resources panel >= 400px -->
@@ -105,16 +120,23 @@
105120
</h3>
106121
<div class="my-3 px-2">
107122
<ActionLink
123+
class="mr-3"
108124
:text="aboutRecommendationsText$()"
109125
@click="handleAboutRecommendations"
110126
/>
127+
<ActionLink
128+
:text="isBrowsing ? $tr('jumpToSearch') : $tr('jumpToSearchResults')"
129+
:style="keyboardVisibilityStyle"
130+
@click="handleJumpToSearch"
131+
/>
111132
</div>
112133

113134
<div class="ml-1">
114135
<KCardGrid layout="1-1-1">
115136
<RecommendedResourceCard
116137
v-for="recommendation in displayedRecommendations"
117138
:key="recommendation.id"
139+
:ref="setFirstRecommendationRef"
118140
:node="recommendation"
119141
@change_selected="handleChangeSelected"
120142
@preview="
@@ -161,6 +183,13 @@
161183
/>
162184
</div>
163185
</div>
186+
<div class="px-2">
187+
<ActionLink
188+
:text="$tr('jumpToTop')"
189+
:style="keyboardVisibilityStyle"
190+
@click="handleJumpToRecommendations"
191+
/>
192+
</div>
164193
</KGridItem>
165194
</KGrid>
166195
<SavedSearchesModal v-model="showSavedSearches" />
@@ -361,6 +390,7 @@
361390
importedNodeIds: [],
362391
rejectedNode: null,
363392
showFeedbackErrorMessage: false,
393+
firstRecommendationRef: null,
364394
};
365395
},
366396
computed: {
@@ -390,6 +420,11 @@
390420
this.searchTerm.trim() !== this.$route.params.searchTerm
391421
);
392422
},
423+
keyboardVisibilityStyle() {
424+
return {
425+
opacity: this.$inputModality === 'keyboard' ? '1' : '0',
426+
};
427+
},
393428
shouldShowRecommendations() {
394429
if (!this.isAIFeatureEnabled) {
395430
return false;
@@ -613,6 +648,27 @@
613648
handleBackToBrowse() {
614649
this.$router.push(this.backToBrowseRoute);
615650
},
651+
handleJumpToRecommendations() {
652+
if (this.firstRecommendationRef) {
653+
this.firstRecommendationRef.focus();
654+
}
655+
},
656+
handleJumpToSearch() {
657+
if (this.isBrowsing) {
658+
if (this.$route.params.channelId) {
659+
this.$refs.contentTreeList.focus();
660+
} else {
661+
this.$refs.channelList.focus();
662+
}
663+
} else {
664+
this.$refs.searchResultList.focus();
665+
}
666+
},
667+
setFirstRecommendationRef(ref) {
668+
if (!this.firstRecommendationRef) {
669+
this.firstRecommendationRef = ref;
670+
}
671+
},
616672
updateLanguageQuery(language) {
617673
this.languageFromChannelList = language;
618674
},
@@ -712,6 +768,8 @@
712768
}
713769
},
714770
async loadRecommendations(belowThreshold) {
771+
this.firstRecommendationRef = null;
772+
715773
if (this.shouldShowRecommendations) {
716774
this.recommendationsLoading = true;
717775
this.recommendationsLoadingError = false;
@@ -918,6 +976,10 @@
918976
searchLabel: 'Search for resources…',
919977
searchAction: 'Search',
920978
savedSearchesLabel: 'View saved searches',
979+
jumpToRecommendations: 'Jump to recommendations',
980+
jumpToSearch: 'Jump to search',
981+
jumpToSearchResults: 'Jump to search results',
982+
jumpToTop: 'Jump to top',
921983
922984
// Copy strings
923985
// undo: 'Undo',
@@ -966,4 +1028,12 @@
9661028
border-radius: 4px;
9671029
}
9681030
1031+
.keyboard-visibility {
1032+
cursor: default;
1033+
1034+
&:focus {
1035+
opacity: 1 !important;
1036+
}
1037+
}
1038+
9691039
</style>

contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/SearchResultsList.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
>
7474
<Checkbox
7575
:key="`checkbox-${node.id}`"
76+
:ref="setFirstCardCheckboxRef"
7677
:inputValue="isSelected(node)"
7778
class="mt-0 pt-0"
7879
@input="toggleSelected(node)"
@@ -147,6 +148,7 @@
147148
nodeIds: [],
148149
pageCount: 0,
149150
totalCount: 0,
151+
firstCardCheckboxRef: null,
150152
};
151153
},
152154
computed: {
@@ -217,6 +219,7 @@
217219
fetch() {
218220
this.loading = true;
219221
this.loadFailed = false;
222+
this.firstCardCheckboxRef = null;
220223
this.fetchResultsDebounced();
221224
this.loadSavedSearches();
222225
},
@@ -251,6 +254,19 @@
251254
toggleSelected(node) {
252255
this.$emit('change_selected', { nodes: [node], isSelected: !this.isSelected(node) });
253256
},
257+
setFirstCardCheckboxRef(ref) {
258+
if (!this.firstCardCheckboxRef) {
259+
this.firstCardCheckboxRef = ref;
260+
}
261+
},
262+
/**
263+
* @public
264+
*/
265+
focus() {
266+
if (this.firstCardCheckboxRef) {
267+
this.firstCardCheckboxRef.focus();
268+
}
269+
},
254270
},
255271
$trs: {
256272
searchResultsCount:

0 commit comments

Comments
 (0)