Skip to content

Commit 5926cb1

Browse files
committed
Add primary language filtering to useBaseSearch.
Allow language selection logic to be reused.
1 parent 953fc26 commit 5926cb1

File tree

14 files changed

+411
-601
lines changed

14 files changed

+411
-601
lines changed

kolibri/plugins/learn/assets/src/composables/__tests__/useSearch.spec.js

Lines changed: 0 additions & 438 deletions
This file was deleted.

kolibri/plugins/learn/assets/src/composables/useContentLink.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { get } from '@vueuse/core';
22
import isEmpty from 'lodash/isEmpty';
33
import pick from 'lodash/pick';
44
import { computed, getCurrentInstance } from 'vue';
5+
import { primaryLanguageKey } from 'kolibri-common/composables/useBaseSearch';
56
import { ExternalPagePaths, PageNames } from '../constants';
67

78
function _decodeBackLinkQuery(query) {
@@ -17,6 +18,10 @@ export default function useContentLink(store) {
1718

1819
function _makeNodeLink(id, isResource, query, deviceId) {
1920
const params = get(route).params;
21+
const oldQuery = get(route).query || {};
22+
if (!isResource && oldQuery[primaryLanguageKey]) {
23+
query[primaryLanguageKey] = oldQuery[primaryLanguageKey];
24+
}
2025
return {
2126
name: isResource ? PageNames.TOPICS_CONTENT : PageNames.TOPICS_TOPIC,
2227
params: pick({ id, deviceId: deviceId || params.deviceId }, ['id', 'deviceId']),

kolibri/plugins/learn/assets/src/views/LibraryPage/index.vue

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,16 @@
4949
v-else-if="!displayingSearchResults && !rootNodesLoading"
5050
data-test="channels"
5151
>
52-
<h1 class="channels-label">
53-
{{ channelsLabel }}
54-
</h1>
52+
<KFixedGrid numCols="4">
53+
<KFixedGridItem span="3">
54+
<h1 class="channels-label">
55+
{{ channelsLabel }}
56+
</h1>
57+
</KFixedGridItem>
58+
<KFixedGridItem span="1">
59+
<LanguageSelector :primary="true" />
60+
</KFixedGridItem>
61+
</KFixedGrid>
5562
<p
5663
v-if="isLocalLibraryEmpty"
5764
data-test="nothing-in-lib-label"
@@ -109,6 +116,7 @@
109116
:class="windowIsLarge ? 'side-panel' : ''"
110117
data-test="side-panel-local"
111118
:width="`${sidePanelWidth}px`"
119+
:showLanguages="displayingSearchResults"
112120
/>
113121
</div>
114122

@@ -122,6 +130,7 @@
122130
v-model="searchTerms"
123131
data-test="side-panel"
124132
:width="`${sidePanelWidth}px`"
133+
:showLanguages="displayingSearchResults"
125134
/>
126135
</SidePanelModal>
127136

@@ -176,20 +185,22 @@
176185
<script>
177186
178187
import { get, set } from '@vueuse/core';
188+
import orderBy from 'lodash/orderBy';
179189
180190
import { onMounted, getCurrentInstance, ref, watch } from 'vue';
181191
import commonCoreStrings from 'kolibri/uiText/commonCoreStrings';
182192
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
183193
import useUser from 'kolibri/composables/useUser';
184194
import samePageCheckGenerator from 'kolibri-common/utils/samePageCheckGenerator';
195+
import { getContentLangActive } from 'kolibri/utils/i18n';
185196
import ContentNodeResource from 'kolibri-common/apiResources/ContentNodeResource';
186197
import { mapState } from 'vuex';
187198
import MeteredConnectionNotificationModal from 'kolibri-common/components/MeteredConnectionNotificationModal.vue';
188199
import appCapabilities, { checkCapability } from 'kolibri/utils/appCapabilities';
189200
import LearningActivityChip from 'kolibri-common/components/ResourceDisplayAndSearch/LearningActivityChip.vue';
190-
import { searchKeys } from 'kolibri-common/composables/useBaseSearch';
191201
import SidePanelModal from 'kolibri-common/components/SidePanelModal';
192202
import SearchFiltersPanel from 'kolibri-common/components/SearchFiltersPanel';
203+
import LanguageSelector from 'kolibri-common/components/SearchFiltersPanel/LanguageSelector';
193204
import useChannels from 'kolibri-common/composables/useChannels';
194205
import { KolibriStudioId, PageNames } from '../../constants';
195206
import useCardViewStyle from '../../composables/useCardViewStyle';
@@ -233,6 +244,7 @@
233244
LearnAppBarPage,
234245
OtherLibraries,
235246
PostSetupModalGroup,
247+
LanguageSelector,
236248
},
237249
mixins: [commonLearnStrings, commonCoreStrings],
238250
setup(props) {
@@ -261,6 +273,9 @@
261273
removeFilterTag,
262274
clearSearch,
263275
currentRoute,
276+
createBaseSearchGetParams,
277+
ensurePrimaryLanguage,
278+
primaryLanguage,
264279
} = useSearch();
265280
search();
266281
const { fetchResumableContentNodes } = useLearnerResources();
@@ -300,20 +315,35 @@
300315
// we want them to be in the same order as the channels list
301316
set(
302317
rootNodes,
303-
channels
304-
.map(channel => {
305-
const node = channelCollection.find(n => n.channel_id === channel.id);
306-
if (node) {
307-
// The `channel` comes with additional data that is
308-
// not returned from the ContentNodeResource.
309-
// Namely thumbnail, description and tagline (so far)
310-
node.title = channel.name || node.title;
311-
node.thumbnail = channel.thumbnail;
312-
node.description = channel.tagline || channel.description;
313-
return node;
314-
}
315-
})
316-
.filter(Boolean),
318+
// Sort channels by relevance to the current user interface language
319+
orderBy(
320+
channels
321+
.map(channel => {
322+
const node = channelCollection.find(n => n.channel_id === channel.id);
323+
if (node) {
324+
// The `channel` comes with additional data that is
325+
// not returned from the ContentNodeResource.
326+
// Namely thumbnail, description and tagline (so far)
327+
node.title = channel.name || node.title;
328+
node.thumbnail = channel.thumbnail;
329+
node.description = channel.tagline || channel.description;
330+
node.included_languages = channel.included_languages;
331+
return node;
332+
}
333+
})
334+
.filter(Boolean),
335+
[
336+
c => getContentLangActive(c.lang, get(primaryLanguage)),
337+
c =>
338+
Math.max(
339+
...c.included_languages.map(l =>
340+
getContentLangActive(l, get(primaryLanguage)),
341+
),
342+
),
343+
'title',
344+
],
345+
['desc', 'desc', 'asc'],
346+
),
317347
);
318348
319349
store.commit('CORE_SET_PAGE_LOADING', false);
@@ -332,7 +362,8 @@
332362
}
333363
334364
function _showLibrary(baseurl) {
335-
return fetchChannels({ baseurl }).then(channels => {
365+
const params = createBaseSearchGetParams();
366+
return fetchChannels(params).then(channels => {
336367
if (!channels.length && isUserLoggedIn) {
337368
router.replace({ name: PageNames.CONTENT_UNAVAILABLE });
338369
return;
@@ -342,9 +373,7 @@
342373
return;
343374
}
344375
345-
const query = currentRoute().query;
346-
347-
if (searchKeys.some(key => query[key])) {
376+
if (get(displayingSearchResults)) {
348377
// If currently on a route with search terms
349378
// just finish early and let the component handle loading
350379
store.commit('CORE_SET_PAGE_LOADING', false);
@@ -357,7 +386,10 @@
357386
});
358387
}
359388
360-
function showLibrary() {
389+
async function showLibrary() {
390+
if (!(await ensurePrimaryLanguage())) {
391+
return;
392+
}
361393
set(rootNodesLoading, true);
362394
store.commit('CORE_SET_PAGE_LOADING', true);
363395
if (props.deviceId) {
@@ -386,6 +418,12 @@
386418
}
387419
});
388420
421+
watch(primaryLanguage, () => {
422+
if (!displayingSearchResults.value) {
423+
showLibrary();
424+
}
425+
});
426+
389427
showLibrary();
390428
391429
return {

kolibri/plugins/learn/assets/src/views/TopicsPage/index.vue

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,14 @@
370370
removeFilterTag,
371371
clearSearch,
372372
currentRoute,
373+
createBaseSearchGetParams,
374+
ensurePrimaryLanguage,
373375
} = useSearch(topic);
374376
const { back, genContentLinkKeepCurrentBackLink } = useContentLink();
375377
const { windowBreakpoint, windowIsLarge, windowIsSmall } = useKResponsiveWindow();
376378
const { channelsMap, fetchChannels } = useChannels();
377379
const { fetchContentNodeProgress, fetchContentNodeTreeProgress } = useContentNodeProgress();
378-
const { isUserLoggedIn, isCoach, isAdmin, isSuperuser } = useUser();
380+
const { isUserLoggedIn } = useUser();
379381
const { fetchUserDownloadRequests } = useDownloadRequests(store);
380382
381383
const isRoot = ref(false);
@@ -430,10 +432,7 @@
430432
let id = props.id;
431433
const route = currentRoute();
432434
const skip = route.query && route.query.skip === 'true';
433-
const params = {
434-
include_coach_content: get(isAdmin) || get(isCoach) || get(isSuperuser),
435-
baseurl,
436-
};
435+
const params = createBaseSearchGetParams(false);
437436
if (get(isUserLoggedIn) && !baseurl) {
438437
fetchContentNodeTreeProgress({ id, params });
439438
}
@@ -486,7 +485,10 @@
486485
});
487486
}
488487
489-
function showTopicsTopic() {
488+
async function showTopicsTopic() {
489+
if (!(await ensurePrimaryLanguage())) {
490+
return;
491+
}
490492
return store.dispatch('loading').then(() => {
491493
const route = currentRoute();
492494
store.commit('SET_PAGE_NAME', route.name);
@@ -557,8 +559,6 @@
557559
type: String,
558560
default: null,
559561
},
560-
// Our linting doesn't detect usage in the setup function yet.
561-
// eslint-disable-next-line vue/no-unused-properties
562562
id: {
563563
type: String,
564564
required: true,

kolibri/plugins/learn/assets/test/views/library-page.spec.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ const CHANNEL = {
3636
name: 'test channel',
3737
root: 'test root',
3838
thumbnail: 'test thumbnail',
39+
lang: {
40+
id: 'es-es',
41+
lang_name: 'Español',
42+
lang_code: 'es',
43+
lang_subcode: 'es',
44+
lang_direction: 'ltr',
45+
},
46+
included_languages: ['es-es'],
3947
};
4048

4149
jest.mock('kolibri-common/composables/useChannels');
@@ -53,7 +61,13 @@ jest.mock('kolibri/urls');
5361

5462
async function makeWrapper({ options, fullMount = false } = {}) {
5563
const store = new Store({
56-
state: { core: { loading: false } },
64+
state: {
65+
core: { loading: false },
66+
route: {
67+
query: {},
68+
name: PageNames.TOPICS_TOPIC,
69+
},
70+
},
5771
getters: {
5872
isUserLoggedIn: jest.fn(),
5973
isLearner: jest.fn(),
@@ -70,6 +84,9 @@ async function makeWrapper({ options, fullMount = false } = {}) {
7084
SET_PAGE_NAME: jest.fn(),
7185
CORE_SET_PAGE_LOADING: jest.fn(),
7286
CORE_SET_ERROR: jest.fn(),
87+
SET_QUERY(state, query) {
88+
state.route.query = query;
89+
},
7390
},
7491
});
7592
let wrapper;
@@ -94,8 +111,10 @@ describe('LibraryPage', () => {
94111
}),
95112
);
96113
ContentNodeResource.fetchCollection.mockImplementation(() =>
97-
Promise.resolve([{ id: 'test', title: 'test', channel_id: CHANNEL_ID }]),
114+
Promise.resolve([{ id: 'test', title: 'test', channel_id: CHANNEL_ID, lang: CHANNEL.lang }]),
98115
);
116+
router.push = jest.fn().mockReturnValue(Promise.resolve());
117+
router.replace = jest.fn().mockReturnValue(Promise.resolve());
99118
});
100119
describe('filters button', () => {
101120
it('is visible when the page is not large and channels are available', async () => {

packages/kolibri-common/components/SearchChips.vue

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,21 @@
4040
name: 'SearchChips',
4141
mixins: [commonCoreStrings],
4242
setup() {
43-
const { availableLanguages } = injectBaseSearch();
44-
const languagesMap = availableLanguages.value.reduce((map, lang) => {
45-
map[lang.id] = lang;
43+
const { languageOptions, activeSearchTerms } = injectBaseSearch();
44+
const languagesMap = languageOptions.value.reduce((map, lang) => {
45+
map[lang.value] = lang.label;
4646
return map;
4747
}, {});
4848
const { channelsMap } = useChannels();
4949
return {
5050
channelsMap,
5151
languagesMap,
52+
activeSearchTerms,
5253
};
5354
},
54-
props: {
55-
searchTerms: {
56-
type: Object,
57-
default: () => ({}),
58-
},
59-
},
6055
computed: {
6156
items() {
62-
return flatMap(this.searchTerms, (value, key) => {
57+
return flatMap(this.activeSearchTerms, (value, key) => {
6358
if (key === 'keywords' && value && value.length) {
6459
return [
6560
{
@@ -82,7 +77,7 @@
8277
methods: {
8378
translate(key, value) {
8479
if (key === 'languages') {
85-
return this.languagesMap[value].lang_name;
80+
return this.languagesMap[value];
8681
}
8782
if (key === 'channels') {
8883
return this.channelsMap[value].name;

0 commit comments

Comments
 (0)