Add ability to filter by tags in kiwix serve

This change introduces filtering by tags.
To filter, the user can click on the tag name and it will filter it.
A label is added (clickable) to show the tag filter, it can be clicked to remove the filter
This commit is contained in:
Nikhil Tanwar 2022-02-11 21:59:26 +05:30 committed by Veloman Yunkan
parent 93f2686a94
commit 43ab6dfb6a
4 changed files with 92 additions and 10 deletions

File diff suppressed because one or more lines are too long

View File

@ -77,6 +77,13 @@
return queryNode != null ? queryNode.innerHTML : ""; return queryNode != null ? queryNode.innerHTML : "";
} }
function generateTagLink(tagValue) {
tagValue = tagValue.toLowerCase();
const humanFriendlyTagValue = humanFriendlyTitle(tagValue);
const tagMessage = `Filter by tag "${humanFriendlyTagValue}"`;
return `<span class='tag__link' aria-label='${tagMessage}' title='${tagMessage}' data-tag=${tagValue}>${humanFriendlyTagValue}</span>`
}
function generateBookHtml(book, sort = false) { function generateBookHtml(book, sort = false) {
const link = book.querySelector('link[type="text/html"]').getAttribute('href'); const link = book.querySelector('link[type="text/html"]').getAttribute('href');
let iconUrl; let iconUrl;
@ -91,9 +98,9 @@
const langCode = getInnerHtml(book, 'language'); const langCode = getInnerHtml(book, 'language');
const language = languages[langCode]; const language = languages[langCode];
const tags = getInnerHtml(book, 'tags'); const tags = getInnerHtml(book, 'tags');
let tagHtml = tags.split(';').filter(tag => {return !(tag.split(':')[0].startsWith('_'))}) const tagList = tags.split(';').filter(tag => {return !(tag.startsWith('_'))});
.map((tag) => {return tag.charAt(0).toUpperCase() + tag.slice(1)}) const tagFilterLinks = tagList.map((tagValue) => generateTagLink(tagValue));
.join(' | ').replace(/_/g, ' '); const tagHtml = tagFilterLinks.join(' | ');
let downloadLink; let downloadLink;
let zimSize = 0; let zimSize = 0;
try { try {
@ -113,17 +120,21 @@
} }
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : ''; const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"'; const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
divTag.innerHTML = `<a class="book__link" href="${link}" data-hover="Preview"> divTag.innerHTML = `
<div class="book__wrapper"> <div class="book__wrapper">
<a class="book__link" href="${link}" data-hover="Preview">
<div class="book__link__wrapper">
<div class="book__icon" ${faviconAttr}></div> <div class="book__icon" ${faviconAttr}></div>
<div class="book__header"> <div class="book__header">
<div id="book__title">${title}</div> <div id="book__title">${title}</div>
${downloadLink ? `<div class="book__download"><span data-link="${downloadLink}">Download ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}</span></div>`: ''}` : ''} ${downloadLink ? `<div class="book__download"><span data-link="${downloadLink}">Download ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}</span></div>`: ''}` : ''}
</div> </div>
<div class="book__description" title="${description}">${description}</div> <div class="book__description" title="${description}">${description}</div>
</div>
</a>
<div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div> <div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div>
<div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div> <div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div>
</div></div></a>`; </div></div>`;
return divTag; return divTag;
} }
@ -333,6 +344,7 @@
insertModal(downloadButton); insertModal(downloadButton);
} }
}); });
refreshTagLinks();
} }
async function resetAndFilter(filterType = '', filterValue = '') { async function resetAndFilter(filterType = '', filterValue = '') {
@ -377,9 +389,44 @@
}); });
} }
function addTagElement(tagValue, resetFilter) {
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
tagElement.style.display = 'inline-block';
const humanFriendlyTagValue = humanFriendlyTitle(tagValue);
tagElement.innerHTML = `${humanFriendlyTagValue}`;
const tagMessage = `Stop filtering by tag "${humanFriendlyTagValue}"`;
tagElement.setAttribute('aria-label', tagMessage);
tagElement.setAttribute('title', tagMessage);
if (resetFilter)
resetAndFilter('tag', tagValue);
}
function refreshTagLinks() {
const tagLinks = document.getElementsByClassName('tag__link');
[...tagLinks].forEach(elem => {
if (!elem.getAttribute('click-listener')) {
elem.addEventListener('click', () => addTagElement(elem.dataset.tag, true));
elem.setAttribute('click-listener', 'true');
}
});
}
function removeTagElement(resetFilter) {
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
tagElement.style.display = 'none';
if (resetFilter)
resetAndFilter('tag', '');
}
function updateVisibleParams() { function updateVisibleParams() {
document.querySelectorAll('.filter').forEach(filter => {filter.value = params.get(filter.name) || ''}); document.querySelectorAll('.filter').forEach(filter => {filter.value = params.get(filter.name) || ''});
updateFilterColors(); updateFilterColors();
const tagKey = params.get('tag');
if (tagKey !== null && tagKey.trim() !== '') {
addTagElement(tagKey, false);
} else {
removeTagElement(false);
}
} }
window.addEventListener('resize', (event) => { window.addEventListener('resize', (event) => {
@ -417,6 +464,8 @@
document.querySelectorAll('.filter').forEach(filter => { document.querySelectorAll('.filter').forEach(filter => {
filter.addEventListener('change', () => {resetAndFilter(filter.name, filter.value)}); filter.addEventListener('change', () => {resetAndFilter(filter.name, filter.value)});
}); });
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
tagElement.addEventListener('click', () => removeTagElement(true));
if (filters) { if (filters) {
const currentLink = window.location.search; const currentLink = window.location.search;
const newLink = `?${params.toString()}`; const newLink = `?${params.toString()}`;

View File

@ -58,6 +58,7 @@
</div> </div>
<form id='kiwixSearchForm' class='kiwixNav__SearchForm'> <form id='kiwixSearchForm' class='kiwixNav__SearchForm'>
<input type="text" name="q" placeholder="Search" id="searchFilter" class='kiwixSearch filter'> <input type="text" name="q" placeholder="Search" id="searchFilter" class='kiwixSearch filter'>
<span class="kiwixButton tagFilterLabel"></span>
<input type="submit" class="kiwixButton kiwixButtonHover" value="Search"/> <input type="submit" class="kiwixButton kiwixButtonHover" value="Search"/>
</form> </form>
</div> </div>

View File

@ -179,12 +179,12 @@ R"EXPECTEDRESULT( src="/ROOT/skin/jquery-ui/external/jquery/jquery.js?cache
src="/ROOT/skin/jquery-ui/jquery-ui.min.js?cacheid=d927c2ff" src="/ROOT/skin/jquery-ui/jquery-ui.min.js?cacheid=d927c2ff"
href="/ROOT/skin/jquery-ui/jquery-ui.min.css?cacheid=e1de77b3" href="/ROOT/skin/jquery-ui/jquery-ui.min.css?cacheid=e1de77b3"
href="/ROOT/skin/jquery-ui/jquery-ui.theme.min.css?cacheid=2a5841f9" href="/ROOT/skin/jquery-ui/jquery-ui.theme.min.css?cacheid=2a5841f9"
href="/ROOT/skin/index.css?cacheid=1aca980a" href="/ROOT/skin/index.css?cacheid=a1acc52f"
src: url("/ROOT/skin/fonts/Poppins.ttf?cacheid=af705837") format("truetype"); src: url("/ROOT/skin/fonts/Poppins.ttf?cacheid=af705837") format("truetype");
src: url("/ROOT/skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype"); src: url("/ROOT/skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype");
<script src="/ROOT/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script> <script src="/ROOT/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
<script src="/ROOT/skin/iso6391To3.js?cacheid=ecde2bb3"></script> <script src="/ROOT/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
<script type="text/javascript" src="/ROOT/skin/index.js?cacheid=5f0f7683" defer></script> <script type="text/javascript" src="/ROOT/skin/index.js?cacheid=e99ed2dd" defer></script>
)EXPECTEDRESULT" )EXPECTEDRESULT"
}, },
{ {