# Pull Request Template
## Description
Implemented search results page functionality. Now you can press "Enter"
to search by term and display results in a results page. Also now you
can link to /hc/{account}/en/search?query=XXXXXX to view search results
for XXXXXX query.
fixes: https://github.com/chatwoot/chatwoot/issues/10945
## Screenshots
Classic layout search results:
<img width="3840" height="2160" alt="classic-results"
src="https://github.com/user-attachments/assets/3bbb3272-33ca-4eb4-b80a-76ed77442088"
/>
Classic layout pagination:
<img width="3840" height="2160" alt="classic-page-two"
src="https://github.com/user-attachments/assets/062b09d3-7c58-4d3b-8611-b94375e7db51"
/>
Classic layout empty search:
<img width="3840" height="2160" alt="no-results"
src="https://github.com/user-attachments/assets/c5e3f47a-cd9a-4e14-ae92-ccba00c89e98"
/>
Documentation layout search results:
<img width="3840" height="2160" alt="documentation-results"
src="https://github.com/user-attachments/assets/9e45d8d9-c975-4589-b6c6-3bc7bb3c588e"
/>
Documentation layout dark theme:
<img width="3840" height="2160" alt="documentation-dark"
src="https://github.com/user-attachments/assets/cdb6ed63-4241-4b32-9f79-7d92ed479fc8"
/>
Plain embedded dark layout:
<img width="3840" height="2160" alt="plain-embedded-dark"
src="https://github.com/user-attachments/assets/7deb02b9-9f24-48fb-8979-a2ecd7002c05"
/>
---------
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com>
195 lines
6.4 KiB
JavaScript
195 lines
6.4 KiB
JavaScript
import { createApp } from 'vue';
|
|
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
|
import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer';
|
|
import { directive as onClickaway } from 'vue3-click-away';
|
|
import { isSameHost } from '@chatwoot/utils';
|
|
|
|
import slugifyWithCounter from '@sindresorhus/slugify';
|
|
import PublicArticleSearch from './components/PublicArticleSearch.vue';
|
|
import TableOfContents from './components/TableOfContents.vue';
|
|
import SidebarThemeToggle from './components/SidebarThemeToggle.vue';
|
|
import { initializeTheme } from './portalThemeHelper.js';
|
|
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages.js';
|
|
|
|
export const getHeadingsfromTheArticle = () => {
|
|
const rows = [];
|
|
const articleElement = document.getElementById('cw-article-content');
|
|
articleElement.querySelectorAll('h1, h2, h3').forEach(element => {
|
|
const headingText = element.innerText;
|
|
const slug = slugifyWithCounter(headingText);
|
|
element.id = slug;
|
|
element.className = 'scroll-mt-24 heading';
|
|
|
|
const permalink = document.createElement('a');
|
|
permalink.className = 'permalink text-slate-600 ml-3';
|
|
permalink.href = `#${slug}`;
|
|
permalink.title = headingText;
|
|
permalink.dataset.turbo = 'false';
|
|
permalink.textContent = '#';
|
|
element.appendChild(permalink);
|
|
rows.push({
|
|
slug,
|
|
title: headingText,
|
|
tag: element.tagName.toLowerCase(),
|
|
});
|
|
});
|
|
return rows;
|
|
};
|
|
|
|
export const openExternalLinksInNewTab = () => {
|
|
const { customDomain, hostURL } = window.portalConfig;
|
|
const isOnArticlePage =
|
|
document.querySelector('#cw-article-content') !== null;
|
|
|
|
document.addEventListener('click', event => {
|
|
if (!isOnArticlePage) return;
|
|
|
|
const link = event.target.closest('a');
|
|
|
|
if (link) {
|
|
const currentLocation = window.location.href;
|
|
const linkHref = link.href;
|
|
|
|
// Check against current location and custom domains
|
|
const isInternalLink =
|
|
isSameHost(linkHref, currentLocation) ||
|
|
(customDomain && isSameHost(linkHref, customDomain)) ||
|
|
(hostURL && isSameHost(linkHref, hostURL));
|
|
|
|
if (!isInternalLink) {
|
|
link.target = '_blank';
|
|
link.rel = 'noopener noreferrer'; // Security and performance benefits
|
|
// Prevent default if you want to stop the link from opening in the current tab
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
export const InitializationHelpers = {
|
|
navigateToLocalePage: () => {
|
|
document.addEventListener('change', e => {
|
|
const localeSwitcher = e.target.closest('.locale-switcher');
|
|
if (!localeSwitcher) return;
|
|
|
|
const { portalSlug } = localeSwitcher.dataset;
|
|
window.location.href = `/hc/${encodeURIComponent(portalSlug)}/${encodeURIComponent(localeSwitcher.value)}/`;
|
|
});
|
|
},
|
|
|
|
initializeSearch: () => {
|
|
['#search-wrap', '#search-wrap-hero'].forEach(selector => {
|
|
const mountPoint = document.querySelector(selector);
|
|
if (!mountPoint) return;
|
|
const size = mountPoint.dataset.size || 'default';
|
|
const showKbd = !!mountPoint.dataset.kbd;
|
|
// eslint-disable-next-line vue/one-component-per-file
|
|
const app = createApp({
|
|
components: { PublicArticleSearch },
|
|
data() {
|
|
return { size, showKbd };
|
|
},
|
|
template: '<PublicArticleSearch :size="size" :show-kbd="showKbd" />',
|
|
});
|
|
app.use(VueDOMPurifyHTML, domPurifyConfig);
|
|
app.directive('on-clickaway', onClickaway);
|
|
app.mount(selector);
|
|
});
|
|
},
|
|
|
|
initializeTableOfContents: () => {
|
|
const isOnArticlePage = document.querySelector('#cw-hc-toc');
|
|
if (isOnArticlePage) {
|
|
// eslint-disable-next-line vue/one-component-per-file
|
|
const app = createApp({
|
|
components: { TableOfContents },
|
|
data() {
|
|
return { rows: getHeadingsfromTheArticle() };
|
|
},
|
|
template: '<table-of-contents :rows="rows" />',
|
|
});
|
|
|
|
app.use(VueDOMPurifyHTML, domPurifyConfig);
|
|
app.mount('#cw-hc-toc');
|
|
}
|
|
},
|
|
|
|
initializeSidebarThemeToggle: () => {
|
|
const mountPoint = document.querySelector('#sidebar-theme-toggle');
|
|
if (mountPoint) {
|
|
// eslint-disable-next-line vue/one-component-per-file
|
|
const app = createApp({
|
|
components: { SidebarThemeToggle },
|
|
template: '<sidebar-theme-toggle />',
|
|
});
|
|
app.directive('on-clickaway', onClickaway);
|
|
app.mount('#sidebar-theme-toggle');
|
|
}
|
|
},
|
|
|
|
initializeDetailsClickAway: () => {
|
|
document.addEventListener('click', event => {
|
|
document
|
|
.querySelectorAll('details[data-close-on-clickaway][open]')
|
|
.forEach(details => {
|
|
if (!details.contains(event.target)) {
|
|
details.removeAttribute('open');
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
appendPlainParamToURLs: () => {
|
|
[...document.getElementsByTagName('a')].forEach(aTagElement => {
|
|
if (aTagElement.href && aTagElement.href.includes('/hc/')) {
|
|
const url = new URL(aTagElement.href);
|
|
url.searchParams.set('show_plain_layout', 'true');
|
|
|
|
aTagElement.setAttribute('href', url);
|
|
}
|
|
});
|
|
},
|
|
|
|
setDirectionAttribute: () => {
|
|
const htmlElement = document.querySelector('html');
|
|
// If direction is already applied through props, do not apply again (iframe case)
|
|
const hasDirApplied = htmlElement.getAttribute('data-dir-applied');
|
|
if (!htmlElement || hasDirApplied) return;
|
|
|
|
const localeFromHtml = htmlElement.lang;
|
|
htmlElement.dir =
|
|
localeFromHtml && getLanguageDirection(localeFromHtml) ? 'rtl' : 'ltr';
|
|
},
|
|
|
|
initializeThemesInPortal: initializeTheme,
|
|
|
|
initialize: () => {
|
|
openExternalLinksInNewTab();
|
|
InitializationHelpers.setDirectionAttribute();
|
|
if (window.portalConfig.isPlainLayoutEnabled === 'true') {
|
|
InitializationHelpers.appendPlainParamToURLs();
|
|
} else {
|
|
InitializationHelpers.initializeThemesInPortal();
|
|
InitializationHelpers.navigateToLocalePage();
|
|
InitializationHelpers.initializeSearch();
|
|
InitializationHelpers.initializeTableOfContents();
|
|
InitializationHelpers.initializeSidebarThemeToggle();
|
|
InitializationHelpers.initializeDetailsClickAway();
|
|
}
|
|
},
|
|
|
|
onLoad: () => {
|
|
InitializationHelpers.initialize();
|
|
if (window.location.hash) {
|
|
if ('scrollRestoration' in window.history) {
|
|
window.history.scrollRestoration = 'manual';
|
|
}
|
|
|
|
const a = document.createElement('a');
|
|
a.href = window.location.hash;
|
|
a['data-turbo'] = false;
|
|
a.click();
|
|
}
|
|
},
|
|
};
|