feat: Lets users insert connected portal article into replies [CW-2282] (#8117)
- 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
This commit is contained in:
committed by
GitHub
parent
b4d20689b7
commit
39d0748a5b
@@ -109,6 +109,16 @@
|
||||
</h4>
|
||||
</div>
|
||||
</transition>
|
||||
<woot-button
|
||||
v-if="enableInsertArticleInReply"
|
||||
v-tooltip.top-end="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
||||
icon="document-text-link"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
:title="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
|
||||
@click="toggleInsertArticle"
|
||||
/>
|
||||
</div>
|
||||
<div class="right-wrap">
|
||||
<woot-button
|
||||
@@ -233,6 +243,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
portalSlug: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@@ -307,6 +321,13 @@ export default {
|
||||
? this.$t('CONVERSATION.FOOTER.DISABLE_SIGN_TOOLTIP')
|
||||
: this.$t('CONVERSATION.FOOTER.ENABLE_SIGN_TOOLTIP');
|
||||
},
|
||||
enableInsertArticleInReply() {
|
||||
const isFeatEnabled = this.isFeatureEnabledonAccount(
|
||||
this.accountId,
|
||||
FEATURE_FLAGS.INSERT_ARTICLE_IN_REPLY
|
||||
);
|
||||
return isFeatEnabled && this.portalSlug;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
ActiveStorage.start();
|
||||
@@ -325,6 +346,9 @@ export default {
|
||||
replaceText(text) {
|
||||
this.$emit('replace-text', text);
|
||||
},
|
||||
toggleInsertArticle() {
|
||||
this.$emit('toggle-insert-article');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
:popout-reply-box="popoutReplyBox"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
<article-search-popover
|
||||
v-if="showArticleSearchPopover && connectedPortalSlug"
|
||||
:selected-portal-slug="connectedPortalSlug"
|
||||
@insert="handleInsert"
|
||||
@close="onSearchPopoverClose"
|
||||
/>
|
||||
<div class="reply-box__top">
|
||||
<reply-to-message
|
||||
v-if="shouldShowReplyToMessage"
|
||||
@@ -35,7 +41,7 @@
|
||||
v-if="showEmojiPicker"
|
||||
v-on-clickaway="hideEmojiPicker"
|
||||
:class="emojiDialogClassOnExpandedLayoutAndRTLView"
|
||||
:on-click="emojiOnClick"
|
||||
:on-click="addIntoEditor"
|
||||
/>
|
||||
<reply-email-head
|
||||
v-if="showReplyHead"
|
||||
@@ -121,10 +127,12 @@
|
||||
:toggle-audio-recorder="toggleAudioRecorder"
|
||||
:toggle-emoji-picker="toggleEmojiPicker"
|
||||
:message="message"
|
||||
:portal-slug="connectedPortalSlug"
|
||||
:new-conversation-modal-active="newConversationModalActive"
|
||||
@selectWhatsappTemplate="openWhatsappTemplateModal"
|
||||
@toggle-editor="toggleRichContentEditor"
|
||||
@replace-text="replaceText"
|
||||
@toggle-insert-article="toggleInsertArticle"
|
||||
/>
|
||||
<whatsapp-templates
|
||||
:inbox-id="inbox.id"
|
||||
@@ -154,6 +162,7 @@ import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.v
|
||||
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel.vue';
|
||||
import ReplyEmailHead from './ReplyEmailHead.vue';
|
||||
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
|
||||
import ArticleSearchPopover from 'dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.vue';
|
||||
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert';
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
||||
@@ -206,6 +215,7 @@ export default {
|
||||
Banner,
|
||||
WhatsappTemplates,
|
||||
MessageSignatureMissingAlert,
|
||||
ArticleSearchPopover,
|
||||
},
|
||||
mixins: [
|
||||
clickaway,
|
||||
@@ -248,6 +258,7 @@ export default {
|
||||
showCannedMenu: false,
|
||||
showVariablesMenu: false,
|
||||
newConversationModalActive: false,
|
||||
showArticleSearchPopover: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -506,6 +517,11 @@ export default {
|
||||
? this.messageSignature
|
||||
: extractTextFromMarkdown(this.messageSignature);
|
||||
},
|
||||
connectedPortalSlug() {
|
||||
const { help_center: portal = {} } = this.inbox;
|
||||
const { slug = '' } = portal;
|
||||
return slug;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentChat(conversation) {
|
||||
@@ -597,6 +613,23 @@ export default {
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
handleInsert(article) {
|
||||
const { url, title } = article;
|
||||
if (this.isRichEditorEnabled) {
|
||||
// Removing empty lines from the title
|
||||
const lines = title.split('\n');
|
||||
const nonEmptyLines = lines.filter(line => line.trim() !== '');
|
||||
const filteredMarkdown = nonEmptyLines.join(' ');
|
||||
bus.$emit(
|
||||
BUS_EVENTS.INSERT_INTO_RICH_EDITOR,
|
||||
`[${filteredMarkdown}](${url})`
|
||||
);
|
||||
} else {
|
||||
this.addIntoEditor(
|
||||
`${this.$t('CONVERSATION.REPLYBOX.INSERT_READ_MORE')} ${url}`
|
||||
);
|
||||
}
|
||||
},
|
||||
toggleRichContentEditor() {
|
||||
this.updateUISettings({
|
||||
display_rich_content_editor: !this.showRichContentEditor,
|
||||
@@ -862,22 +895,22 @@ export default {
|
||||
clearEditorSelection() {
|
||||
this.updateEditorSelectionWith = '';
|
||||
},
|
||||
insertEmoji(emoji, selectionStart, selectionEnd) {
|
||||
insertIntoTextEditor(text, selectionStart, selectionEnd) {
|
||||
const { message } = this;
|
||||
const newMessage =
|
||||
message.slice(0, selectionStart) +
|
||||
emoji +
|
||||
text +
|
||||
message.slice(selectionEnd, message.length);
|
||||
this.message = newMessage;
|
||||
},
|
||||
emojiOnClick(emoji) {
|
||||
addIntoEditor(content) {
|
||||
if (this.showRichContentEditor) {
|
||||
this.updateEditorSelectionWith = emoji;
|
||||
this.updateEditorSelectionWith = content;
|
||||
this.onFocus();
|
||||
}
|
||||
if (!this.showRichContentEditor) {
|
||||
const { selectionStart, selectionEnd } = this.$refs.messageInput.$el;
|
||||
this.insertEmoji(emoji, selectionStart, selectionEnd);
|
||||
this.insertIntoTextEditor(content, selectionStart, selectionEnd);
|
||||
}
|
||||
},
|
||||
clearMessage() {
|
||||
@@ -1136,6 +1169,12 @@ export default {
|
||||
// When new conversation modal is open
|
||||
this.newConversationModalActive = isActive;
|
||||
},
|
||||
onSearchPopoverClose() {
|
||||
this.showArticleSearchPopover = false;
|
||||
},
|
||||
toggleInsertArticle() {
|
||||
this.showArticleSearchPopover = !this.showArticleSearchPopover;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1154,7 +1193,7 @@ export default {
|
||||
}
|
||||
|
||||
.reply-box {
|
||||
@apply border-t border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-900;
|
||||
@apply relative border-t border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-900;
|
||||
|
||||
&.is-private {
|
||||
@apply bg-yellow-50 dark:bg-yellow-200;
|
||||
|
||||
@@ -17,4 +17,5 @@ export const FEATURE_FLAGS = {
|
||||
VOICE_RECORDER: 'voice_recorder',
|
||||
AUDIT_LOGS: 'audit_logs',
|
||||
MESSAGE_REPLY_TO: 'message_reply_to',
|
||||
INSERT_ARTICLE_IN_REPLY: 'insert_article_in_reply',
|
||||
};
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
"PRIVATE_NOTE": "Private Note",
|
||||
"SEND": "Send",
|
||||
"CREATE": "Add Note",
|
||||
"INSERT_READ_MORE": "Read more",
|
||||
"DISMISS_REPLY": "Dismiss reply",
|
||||
"REPLYING_TO": "Replying to:",
|
||||
"TIP_FORMAT_ICON": "Show rich text editor",
|
||||
|
||||
@@ -76,6 +76,9 @@
|
||||
},
|
||||
"ARTICLE_SEARCH_RESULT": {
|
||||
"UNCATEGORIZED": "Uncategorized",
|
||||
"SEARCH_RESULTS": "Search results for %{query}",
|
||||
"EMPTY_TEXT": "Search for articles to insert into replies.",
|
||||
"SEARCH_LOADER": "Searching...",
|
||||
"INSERT_ARTICLE": "Insert",
|
||||
"NO_RESULT": "No articles found",
|
||||
"COPY_LINK": "Copy article link to clipboard",
|
||||
@@ -426,6 +429,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ARTICLE_SEARCH": {
|
||||
"TITLE": "Search articles",
|
||||
"PLACEHOLDER": "Search articles",
|
||||
"NO_RESULT": "No articles found",
|
||||
"SEARCHING": "Searching...",
|
||||
"SEARCH_BUTTON": "Search",
|
||||
"INSERT_ARTICLE": "Insert link",
|
||||
"IFRAME_ERROR": "URL is empty or invalid. Unable to display content.",
|
||||
"OPEN_ARTICLE_SEARCH": "Insert article from Help Center",
|
||||
"SUCCESS_ARTICLE_INSERTED": "Article inserted successfully",
|
||||
"PREVIEW_LINK": "Preview article",
|
||||
"CANCEL": "Close",
|
||||
"BACK": "Back",
|
||||
"BACK_RESULTS": "Back to results"
|
||||
},
|
||||
"UPGRADE_PAGE": {
|
||||
"TITLE": "Help Center",
|
||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col gap-1 bg-white dark:bg-slate-900 hover:bg-slate-25 hover:dark:bg-slate-800 rounded-md py-1 px-2 w-full group"
|
||||
<button
|
||||
class="flex flex-col gap-1 bg-white dark:bg-slate-900 hover:bg-slate-25 hover:dark:bg-slate-800 rounded-md py-1 px-2 w-full group border border-transparent border-solid focus:outline-none focus:bg-slate-25 focus:border-slate-500 dark:focus:border-slate-400 dark:focus:bg-slate-800 cursor-pointer"
|
||||
@click="handlePreview"
|
||||
>
|
||||
<button @click="handlePreview">
|
||||
<h4
|
||||
class="text-block-title text-left mb-0 text-slate-900 dark:text-slate-25 px-1 -mx-1 rounded-sm hover:underline cursor-pointer width-auto"
|
||||
>
|
||||
{{ title }}
|
||||
</h4>
|
||||
</button>
|
||||
<h4
|
||||
class="text-block-title text-left mb-0 text-slate-900 dark:text-slate-25 px-1 -mx-1 rounded-sm width-auto hover:underline group-hover:underline"
|
||||
>
|
||||
{{ title }}
|
||||
</h4>
|
||||
|
||||
<div class="flex content-between items-center gap-0.5 w-full">
|
||||
<p class="text-sm text-slate-600 dark:text-slate-300 mb-0 w-full">
|
||||
<p
|
||||
class="text-sm text-left text-slate-600 dark:text-slate-300 mb-0 w-full"
|
||||
>
|
||||
{{ locale }}
|
||||
{{ ` / ` }}
|
||||
{{ category || $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.UNCATEGORIZED') }}
|
||||
@@ -26,26 +27,6 @@
|
||||
class="invisible group-hover:visible"
|
||||
@click="handleCopy"
|
||||
/>
|
||||
|
||||
<a
|
||||
:href="url"
|
||||
class="button hollow button--only-icon tiny secondary invisible group-hover:visible"
|
||||
rel="noopener noreferrer nofollow"
|
||||
target="_blank"
|
||||
:title="$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.OPEN_LINK')"
|
||||
>
|
||||
<fluent-icon size="12" icon="arrow-up-right" />
|
||||
<span class="show-for-sr">{{ url }}</span>
|
||||
</a>
|
||||
<woot-button
|
||||
variant="hollow"
|
||||
color-scheme="secondary"
|
||||
size="tiny"
|
||||
icon="preview-link"
|
||||
class="invisible group-hover:visible"
|
||||
:title="$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.PREVIEW_LINK')"
|
||||
@click="handlePreview"
|
||||
/>
|
||||
<woot-button
|
||||
class="insert-button"
|
||||
variant="smooth"
|
||||
@@ -57,14 +38,16 @@
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
export default {
|
||||
name: 'ArticleSearchResultItem',
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
@@ -92,13 +75,16 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleInsert() {
|
||||
handleInsert(e) {
|
||||
e.stopPropagation();
|
||||
this.$emit('insert', this.id);
|
||||
},
|
||||
handlePreview() {
|
||||
handlePreview(e) {
|
||||
e.stopPropagation();
|
||||
this.$emit('preview', this.id);
|
||||
},
|
||||
async handleCopy() {
|
||||
async handleCopy(e) {
|
||||
e.stopPropagation();
|
||||
await copyTextToClipboard(this.url);
|
||||
this.showAlert(this.$t('CONTACT_PANEL.COPY_SUCCESSFUL'));
|
||||
},
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
{{ $t('HELP_CENTER.ARTICLE_SEARCH.BACK_RESULTS') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<div class="w-full h-full overflow-auto min-h-0">
|
||||
<iframe-loader :url="url" />
|
||||
<div class="-ml-4 h-full overflow-y-auto">
|
||||
<div class="w-full h-full min-h-0">
|
||||
<iframe-loader :url="url" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 py-2">
|
||||
@@ -41,7 +43,7 @@
|
||||
import IframeLoader from 'shared/components/IframeLoader.vue';
|
||||
|
||||
export default {
|
||||
name: 'ChatwootSearch',
|
||||
name: 'ArticleView',
|
||||
components: {
|
||||
IframeLoader,
|
||||
},
|
||||
@@ -52,10 +54,12 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onBack() {
|
||||
onBack(e) {
|
||||
e.stopPropagation();
|
||||
this.$emit('back');
|
||||
},
|
||||
onInsert() {
|
||||
onInsert(e) {
|
||||
e.stopPropagation();
|
||||
this.$emit('insert');
|
||||
},
|
||||
},
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
<fluent-icon icon="search" class="" size="16" />
|
||||
</div>
|
||||
<input
|
||||
ref="searchInput"
|
||||
type="text"
|
||||
:placeholder="$t('HELP_CENTER.ARTICLE_SEARCH.PLACEHOLDER')"
|
||||
class="block w-full pl-8 h-8 text-sm dark:bg-slate-700 bg-slate-25 rounded-md leading-8 py-1 text-slate-700 shadow-sm ring-2 ring-transparent ring-slate-300 border border-solid border-slate-300 placeholder:text-slate-400 focus:border-woot-600 focus:ring-2 focus:ring-woot-100 mb-0 focus:bg-slate-25 dark:focus:bg-slate-700"
|
||||
class="block w-full pl-8 h-8 text-sm dark:bg-slate-700 bg-slate-25 rounded-md leading-8 py-1 text-slate-700 shadow-sm ring-2 ring-transparent ring-slate-300 border border-solid border-slate-300 placeholder:text-slate-400 focus:border-woot-600 focus:ring-woot-200 mb-0 focus:bg-slate-25 dark:focus:bg-slate-700 dark:focus:ring-woot-700"
|
||||
:value="searchQuery"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@input="onInput"
|
||||
/>
|
||||
</div>
|
||||
@@ -29,8 +32,15 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import {
|
||||
buildHotKeys,
|
||||
isActiveElementTypeable,
|
||||
} from 'shared/helpers/KeyboardHelpers';
|
||||
|
||||
export default {
|
||||
name: 'ChatwootSearch',
|
||||
mixins: [eventListenerMixins],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
@@ -40,8 +50,12 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
searchQuery: '',
|
||||
isInputFocused: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.searchInput.focus();
|
||||
},
|
||||
methods: {
|
||||
onInput(e) {
|
||||
this.$emit('search', e.target.value);
|
||||
@@ -49,6 +63,20 @@ export default {
|
||||
onClose() {
|
||||
this.$emit('close');
|
||||
},
|
||||
onFocus() {
|
||||
this.isInputFocused = true;
|
||||
},
|
||||
onBlur() {
|
||||
this.isInputFocused = false;
|
||||
},
|
||||
handleKeyEvents(e) {
|
||||
const keyPattern = buildHotKeys(e);
|
||||
|
||||
if (keyPattern === '/' && !isActiveElementTypeable(e)) {
|
||||
e.preventDefault();
|
||||
this.$refs.searchInput.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<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>
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div class="flex justify-end gap-1 py-4 bg-white dark:bg-slate-900">
|
||||
<div
|
||||
class="flex justify-end gap-1 py-4 bg-white dark:bg-slate-900 h-full overflow-y-auto"
|
||||
>
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<div v-if="isLoading" class="empty-state-message">
|
||||
<spinner />
|
||||
{{ $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.SEARCH_LOADER') }}
|
||||
</div>
|
||||
<div v-else-if="showNoResults" class="empty-state-message">
|
||||
@@ -17,7 +18,7 @@
|
||||
:body="article.content"
|
||||
:url="article.url"
|
||||
:category="article.category.name"
|
||||
:locale="article.category.locale"
|
||||
:locale="article.localeName"
|
||||
@preview="handlePreview"
|
||||
@insert="handleInsert"
|
||||
/>
|
||||
@@ -26,13 +27,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import SearchResultItem from 'dashboard/routes/dashboard/helpcenter/components/ArticleSearch/ArticleSearchResultItem.vue';
|
||||
import SearchResultItem from './ArticleSearchResultItem.vue';
|
||||
|
||||
export default {
|
||||
name: 'SearchResults',
|
||||
components: {
|
||||
Spinner,
|
||||
SearchResultItem,
|
||||
},
|
||||
props: {
|
||||
|
||||
Reference in New Issue
Block a user