Implements improved search

This commit is contained in:
Alicia Sykes 2021-08-22 13:36:54 +01:00
parent 4dc25e6fc6
commit 11f38c9177
2 changed files with 32 additions and 49 deletions

View File

@ -1,3 +1,4 @@
<!-- Main homepage for default view -->
<template> <template>
<div class="home" :style="getBackgroundImage()"> <div class="home" :style="getBackgroundImage()">
<!-- Search bar, layout options and settings --> <!-- Search bar, layout options and settings -->
@ -9,7 +10,7 @@
:displayLayout="layout" :displayLayout="layout"
:iconSize="itemSizeBound" :iconSize="itemSizeBound"
:externalThemes="getExternalCSSLinks()" :externalThemes="getExternalCSSLinks()"
:sections="getSections(sections)" :sections="allSections"
:appConfig="appConfig" :appConfig="appConfig"
:pageInfo="pageInfo" :pageInfo="pageInfo"
:modalOpen="modalOpen" :modalOpen="modalOpen"
@ -19,17 +20,19 @@
<div v-if="checkTheresData(sections)" <div v-if="checkTheresData(sections)"
:class="`item-group-container orientation-${layout} item-size-${itemSizeBound}`"> :class="`item-group-container orientation-${layout} item-size-${itemSizeBound}`">
<Section <Section
v-for="(section, index) in getSections(sections)" v-for="(section, index) in filteredTiles"
:key="index" :key="index"
:title="section.name" :title="section.name"
:icon="section.icon || undefined" :icon="section.icon || undefined"
:displayData="getDisplayData(section)" :displayData="getDisplayData(section)"
:groupId="`section-${index}`" :groupId="`section-${index}`"
:items="filterTiles(section.items)" :items="filterTiles(section.items, searchValue)"
:searchTerm="searchValue"
:itemSize="itemSizeBound" :itemSize="itemSizeBound"
@itemClicked="finishedSearching()" @itemClicked="finishedSearching()"
@change-modal-visibility="updateModalVisibility" @change-modal-visibility="updateModalVisibility"
:class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''" :class="
(searchValue && filterTiles(section.items, searchValue).length === 0) ? 'no-results' : ''"
/> />
</div> </div>
<!-- Show message when there's no data to show --> <!-- Show message when there's no data to show -->
@ -43,6 +46,7 @@
import SettingsContainer from '@/components/Settings/SettingsContainer.vue'; import SettingsContainer from '@/components/Settings/SettingsContainer.vue';
import Section from '@/components/LinkItems/Section.vue'; import Section from '@/components/LinkItems/Section.vue';
import SearchUtil from '@/utils/Search';
import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults'; import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults';
export default { export default {
@ -63,6 +67,21 @@ export default {
modalOpen: false, // When true, keybindings are disabled modalOpen: false, // When true, keybindings are disabled
}), }),
computed: { computed: {
/* Combines sections from config file, with those in local storage */
allSections() {
// If the user has stored sections in local storage, return those
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
if (localSections) {
const json = JSON.parse(localSections);
if (json.length >= 1) return json;
}
// Otherwise, return the usuall data from conf.yml
return this.sections;
},
filteredTiles() {
const sections = this.allSections;
return sections.filter((section) => this.filterTiles(section.items, this.searchValue));
},
/* Updates layout (when button clicked), and saves in local storage */ /* Updates layout (when button clicked), and saves in local storage */
layoutOrientation: { layoutOrientation: {
get() { return this.appConfig.layout || Defaults.layout; }, get() { return this.appConfig.layout || Defaults.layout; },
@ -86,17 +105,6 @@ export default {
const localSections = localStorage[localStorageKeys.CONF_SECTIONS]; const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
return (sections && sections.length >= 1) || (localSections && localSections.length >= 1); return (sections && sections.length >= 1) || (localSections && localSections.length >= 1);
}, },
/* Returns sections from local storage if available, otherwise uses the conf.yml */
getSections(sections) {
// If the user has stored sections in local storage, return those
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
if (localSections) {
const json = JSON.parse(localSections);
if (json.length >= 1) return json;
}
// Otherwise, return the usuall data from conf.yml
return sections;
},
/* Updates local data with search value, triggered from filter comp */ /* Updates local data with search value, triggered from filter comp */
searching(searchValue) { searching(searchValue) {
this.searchValue = searchValue || ''; this.searchValue = searchValue || '';
@ -105,26 +113,9 @@ export default {
finishedSearching() { finishedSearching() {
this.$refs.filterComp.clearFilterInput(); this.$refs.filterComp.clearFilterInput();
}, },
/* Extracts the site name from domain, used for the searching functionality */
getDomainFromUrl(url) {
if (!url) return '';
const urlPattern = /^(?:https?:\/\/)?(?:w{3}\.)?([a-z\d.-]+)\.(?:[a-z.]{2,10})(?:[/\w.-]*)*/;
const domainPattern = url.match(urlPattern);
return domainPattern ? domainPattern[1] : '';
},
/* Returns only the tiles that match the users search query */ /* Returns only the tiles that match the users search query */
filterTiles(allTiles) { filterTiles(allTiles, searchTerm) {
if (!allTiles) return []; return SearchUtil(allTiles, searchTerm);
return allTiles.filter((tile) => {
const {
title, description, provider, url,
} = tile;
const searchTerm = this.searchValue.toLowerCase();
return (title && title.toLowerCase().includes(searchTerm))
|| (provider && provider.toLowerCase().includes(searchTerm))
|| (description && description.toLowerCase().includes(searchTerm))
|| this.getDomainFromUrl(url).includes(searchTerm);
});
}, },
/* Returns optional section display preferences if available */ /* Returns optional section display preferences if available */
getDisplayData(section) { getDisplayData(section) {
@ -163,8 +154,8 @@ export default {
/* Checks if any sections or items use icons from a given CDN */ /* Checks if any sections or items use icons from a given CDN */
checkIfIconLibraryNeeded(prefix) { checkIfIconLibraryNeeded(prefix) {
let isNeeded = false; let isNeeded = false;
if (!this.sections) return false; if (!this.allSections) return false;
this.sections.forEach((section) => { this.allSections.forEach((section) => {
if (section.icon && section.icon.includes(prefix)) isNeeded = true; if (section.icon && section.icon.includes(prefix)) isNeeded = true;
section.items.forEach((item) => { section.items.forEach((item) => {
if (item.icon && item.icon.includes(prefix)) isNeeded = true; if (item.icon && item.icon.includes(prefix)) isNeeded = true;
@ -203,11 +194,11 @@ export default {
}, },
/* Returns true if there is more than 1 sub-result visible during searching */ /* Returns true if there is more than 1 sub-result visible during searching */
checkIfResults() { checkIfResults() {
if (!this.sections) return false; if (!this.allSections) return false;
else { else {
let itemsFound = true; let itemsFound = true;
this.sections.forEach((section) => { this.allSections.forEach((section) => {
if (this.filterTiles(section.items).length > 0) itemsFound = false; if (this.filterTiles(section.items, this.searchValue).length > 0) itemsFound = false;
}); });
return itemsFound; return itemsFound;
} }

View File

@ -54,6 +54,7 @@ import MinimalSection from '@/components/MinimalView/MinimalSection.vue';
import MinimalHeading from '@/components/MinimalView/MinimalHeading.vue'; import MinimalHeading from '@/components/MinimalView/MinimalHeading.vue';
import MinimalSearch from '@/components/MinimalView/MinimalSearch.vue'; import MinimalSearch from '@/components/MinimalView/MinimalSearch.vue';
import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHelper'; import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHelper';
import SearchUtil from '@/utils/Search';
import Defaults, { localStorageKeys } from '@/utils/defaults'; import Defaults, { localStorageKeys } from '@/utils/defaults';
import ConfigLauncher from '@/components/Settings/ConfigLauncher'; import ConfigLauncher from '@/components/Settings/ConfigLauncher';
@ -122,16 +123,7 @@ export default {
/* Returns only the tiles that match the users search query */ /* Returns only the tiles that match the users search query */
filterTiles(allTiles) { filterTiles(allTiles) {
if (!allTiles) return []; if (!allTiles) return [];
return allTiles.filter((tile) => { return SearchUtil(allTiles, this.searchValue);
const {
title, description, provider, url,
} = tile;
const searchTerm = this.searchValue.toLowerCase();
return (title && title.toLowerCase().includes(searchTerm))
|| (provider && provider.toLowerCase().includes(searchTerm))
|| (description && description.toLowerCase().includes(searchTerm))
|| this.getDomainFromUrl(url).includes(searchTerm);
});
}, },
/* Update data when modal is open (so that key bindings can be disabled) */ /* Update data when modal is open (so that key bindings can be disabled) */
updateModalVisibility(modalState) { updateModalVisibility(modalState) {