mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 10:42:38 +00:00
- Lets users insert connected portal articles into replies https://linear.app/chatwoot/issue/CW-2282/list-all-the-top-articles-from-the-connected-help-center https://linear.app/chatwoot/issue/CW-1453/container-view-for-showing-search-input-and-result-items
170 lines
4.4 KiB
Vue
170 lines
4.4 KiB
Vue
<template>
|
|
<div
|
|
class="fixed flex items-center justify-center w-screen h-screen bg-white/70 top-0 left-0 z-50"
|
|
>
|
|
<div
|
|
v-on-clickaway="onClose"
|
|
class="flex flex-col px-4 pb-4 rounded-md shadow-md border border-solid border-slate-50 dark:border-slate-800 bg-white dark:bg-slate-900 z-[1000] max-w-[720px] md:w-[20rem] lg:w-[24rem] xl:w-[28rem] 2xl:w-[32rem] h-[calc(100vh-20rem)] max-h-[40rem]"
|
|
>
|
|
<search-header
|
|
:title="$t('HELP_CENTER.ARTICLE_SEARCH.TITLE')"
|
|
class="w-full sticky top-0 bg-[inherit]"
|
|
@close="onClose"
|
|
@search="onSearch"
|
|
/>
|
|
|
|
<article-view
|
|
v-if="activeId"
|
|
:url="articleViewerUrl"
|
|
@back="onBack"
|
|
@insert="onInsert"
|
|
/>
|
|
<search-results
|
|
v-else
|
|
:search-query="searchQuery"
|
|
:is-loading="isLoading"
|
|
:portal-slug="selectedPortalSlug"
|
|
:articles="searchResultsWithUrl"
|
|
@preview="handlePreview"
|
|
@insert="onInsert"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { debounce } from '@chatwoot/utils';
|
|
import { mixin as clickaway } from 'vue-clickaway';
|
|
import {
|
|
isEscape,
|
|
isActiveElementTypeable,
|
|
} from 'shared/helpers/KeyboardHelpers';
|
|
|
|
import SearchHeader from './Header.vue';
|
|
import SearchResults from './SearchResults.vue';
|
|
import ArticleView from './ArticleView.vue';
|
|
import ArticlesAPI from 'dashboard/api/helpCenter/articles';
|
|
import { buildPortalArticleURL } from 'dashboard/helper/portalHelper';
|
|
import portalMixin from '../../mixins/portalMixin';
|
|
import alertMixin from 'shared/mixins/alertMixin';
|
|
|
|
export default {
|
|
name: 'ArticleSearchPopover',
|
|
components: {
|
|
SearchHeader,
|
|
SearchResults,
|
|
ArticleView,
|
|
},
|
|
mixins: [clickaway, portalMixin, alertMixin],
|
|
props: {
|
|
selectedPortalSlug: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
searchQuery: '',
|
|
isLoading: false,
|
|
searchResults: [],
|
|
activeId: '',
|
|
debounceSearch: () => {},
|
|
};
|
|
},
|
|
computed: {
|
|
articleViewerUrl() {
|
|
const article = this.activeArticle(this.activeId);
|
|
if (!article) return '';
|
|
const isDark = document.body.classList.contains('dark');
|
|
|
|
const url = new URL(article.url);
|
|
url.searchParams.set('show_plain_layout', 'true');
|
|
|
|
if (isDark) {
|
|
url.searchParams.set('theme', 'dark');
|
|
}
|
|
|
|
return `${url}`;
|
|
},
|
|
searchResultsWithUrl() {
|
|
return this.searchResults.map(article => ({
|
|
...article,
|
|
localeName: this.localeName(article.category.locale || 'en'),
|
|
url: this.generateArticleUrl(article),
|
|
}));
|
|
},
|
|
},
|
|
mounted() {
|
|
this.fetchArticlesByQuery(this.searchQuery);
|
|
this.debounceSearch = debounce(this.fetchArticlesByQuery, 500, false);
|
|
document.body.addEventListener('keydown', this.closeOnEsc);
|
|
},
|
|
beforeDestroy() {
|
|
document.body.removeEventListener('keydown', this.closeOnEsc);
|
|
},
|
|
methods: {
|
|
generateArticleUrl(article) {
|
|
return buildPortalArticleURL(
|
|
this.selectedPortalSlug,
|
|
'',
|
|
'',
|
|
article.slug
|
|
);
|
|
},
|
|
activeArticle(id) {
|
|
return this.searchResultsWithUrl.find(article => article.id === id);
|
|
},
|
|
onSearch(query) {
|
|
this.searchQuery = query;
|
|
this.activeId = '';
|
|
this.debounceSearch(query);
|
|
},
|
|
onClose() {
|
|
this.$emit('close');
|
|
this.searchQuery = '';
|
|
this.activeId = '';
|
|
this.searchResults = [];
|
|
},
|
|
async fetchArticlesByQuery(query) {
|
|
try {
|
|
const sort = query ? '' : 'views';
|
|
this.isLoading = true;
|
|
this.searchResults = [];
|
|
const { data } = await ArticlesAPI.searchArticles({
|
|
portalSlug: this.selectedPortalSlug,
|
|
query,
|
|
sort,
|
|
});
|
|
this.searchResults = data.payload;
|
|
this.isLoading = true;
|
|
} catch (error) {
|
|
// Show something wrong message
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
},
|
|
handlePreview(id) {
|
|
this.activeId = id;
|
|
},
|
|
onBack() {
|
|
this.activeId = '';
|
|
},
|
|
onInsert(id) {
|
|
const article = this.activeArticle(id || this.activeId);
|
|
|
|
this.$emit('insert', article);
|
|
this.showAlert(
|
|
this.$t('HELP_CENTER.ARTICLE_SEARCH.SUCCESS_ARTICLE_INSERTED')
|
|
);
|
|
this.onClose();
|
|
},
|
|
closeOnEsc(e) {
|
|
if (isEscape(e) && !isActiveElementTypeable(e)) {
|
|
e.preventDefault();
|
|
this.onClose();
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|