mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 02:32:29 +00:00
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"
|
||||
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>
|
||||
</button>
|
||||
|
||||
<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,9 +10,11 @@
|
||||
{{ $t('HELP_CENTER.ARTICLE_SEARCH.BACK_RESULTS') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<div class="w-full h-full overflow-auto min-h-0">
|
||||
<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">
|
||||
<woot-button
|
||||
@@ -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: {
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"dismiss-outline": "m4.397 4.554.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l6.47-6.47a.75.75 0 1 1 1.06 1.061L13.061 12l6.47 6.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-6.47 6.47a.75.75 0 0 1-1.06-1.061L10.939 12l-6.47-6.47a.75.75 0 0 1-.072-.976l.073-.084-.073.084Z",
|
||||
"document-outline": "M18.5 20a.5.5 0 0 1-.5.5H6a.5.5 0 0 1-.5-.5V4a.5.5 0 0 1 .5-.5h6V8a2 2 0 0 0 2 2h4.5v10Zm-5-15.379L17.378 8.5H14a.5.5 0 0 1-.5-.5V4.621Zm5.914 3.793-5.829-5.828c-.026-.026-.058-.046-.085-.07a2.072 2.072 0 0 0-.219-.18c-.04-.027-.086-.045-.128-.068-.071-.04-.141-.084-.216-.116a1.977 1.977 0 0 0-.624-.138C12.266 2.011 12.22 2 12.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9.828a2 2 0 0 0-.586-1.414Z",
|
||||
"document-error-outline": "M6 2a2 2 0 0 0-2 2v5.207a5.48 5.48 0 0 1 1-.185V4a1 1 0 0 1 1-1h4v3.5A1.5 1.5 0 0 0 11.5 8H15v8a1 1 0 0 1-1 1h-3.6a5.507 5.507 0 0 1-.657 1H14a2 2 0 0 0 2-2V7.414a1.5 1.5 0 0 0-.44-1.06l-3.914-3.915A1.5 1.5 0 0 0 10.586 2H6Zm8.793 5H11.5a.5.5 0 0 1-.5-.5V3.207L14.793 7ZM10 14.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0ZM5.5 12a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 1 0v-2a.5.5 0 0 0-.5-.5Zm0 5.125a.625.625 0 1 0 0-1.25a.625.625 0 0 0 0 1.25Z",
|
||||
"document-text-link-outline": "M18 20.5a.5.5 0 0 0 .5-.5V10H14a2 2 0 0 1-2-2V3.5H6a.5.5 0 0 0-.5.5v10h-.75c-.255 0-.506.02-.75.059V4a2 2 0 0 1 2-2h6.172c.028 0 .055.004.082.007.02.003.04.006.059.007.215.015.427.056.624.138.057.024.112.056.166.087l.05.029.047.024a.652.652 0 0 1 .081.044c.078.053.148.116.219.18a.63.63 0 0 0 .036.03.491.491 0 0 1 .049.04l5.829 5.828A2 2 0 0 1 20 9.828V20a2 2 0 0 1-2 2h-6.286c.406-.432.731-.94.953-1.5H18Zm-.622-12L13.5 4.621V8a.5.5 0 0 0 .5.5h3.378Zm-7.603 5.75c.854.29 1.6.815 2.158 1.5h3.317a.75.75 0 0 0 0-1.5H9.775ZM12.667 17c.186.468.3.973.326 1.5h2.257a.75.75 0 0 0 0-1.5h-2.583ZM8.75 11.5a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5ZM12 18.75A3.75 3.75 0 0 0 8.25 15l-.102.007A.75.75 0 0 0 8.25 16.5l.154.005A2.25 2.25 0 0 1 8.25 21l-.003.005-.102.007a.75.75 0 0 0 .108 1.493V22.5l.2-.005A3.75 3.75 0 0 0 12 18.75Zm-6.5-3a.75.75 0 0 0-.75-.75l-.2.005a3.75 3.75 0 0 0 .2 7.495l.102-.007A.75.75 0 0 0 4.75 21l-.154-.005A2.25 2.25 0 0 1 4.75 16.5l.102-.007a.75.75 0 0 0 .648-.743Zm3.5 3a.75.75 0 0 0-.75-.75h-3.5l-.102.007A.75.75 0 0 0 4.75 19.5h3.5l.102-.007A.75.75 0 0 0 9 18.75Z",
|
||||
"draft-outline": "m20.877 2.826.153.144.145.153a3.579 3.579 0 0 1-.145 4.908L9.062 19.999a2.25 2.25 0 0 1-1 .58l-5.115 1.395a.75.75 0 0 1-.92-.921l1.394-5.116a2.25 2.25 0 0 1 .58-.999L15.97 2.97a3.579 3.579 0 0 1 4.908-.144ZM15 6.06l-9.938 9.938a.75.75 0 0 0-.193.333l-1.05 3.85 3.85-1.05A.75.75 0 0 0 8 18.938L17.94 9 15 6.06ZM6.525 11l-1.5 1.5H2.75a.75.75 0 0 1 0-1.5h3.775Zm4-4-1.5 1.5H2.75a.75.75 0 1 1 0-1.5h7.775Zm6.505-2.97-.97.97 2.939 2.94.97-.97a2.078 2.078 0 1 0-2.939-2.94ZM14.525 3l-1.5 1.5H2.75a.75.75 0 1 1 0-1.5h11.775Z",
|
||||
"drag-outline": "M15 3.707V8.5a.5.5 0 0 0 1 0V3.707l1.146 1.147a.5.5 0 0 0 .708-.708l-2-2a.499.499 0 0 0-.708 0l-2 2a.5.5 0 0 0 .708.708L15 3.707ZM2 4.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5Zm0 5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm.5 4.5a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6ZM15 16.293V11.5a.5.5 0 0 1 1 0v4.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 .708-.708L15 16.293Z",
|
||||
"dual-screen-clock-outline": "M10.019 6.002a6.632 6.632 0 0 0 .058 1.5H3.75a.25.25 0 0 0-.25.25v12.494c0 .138.112.25.25.25h7.498l-.001-10.167c.416.57.924 1.07 1.5 1.479v8.69h7.498a.25.25 0 0 0 .25-.25v-8.62a6.535 6.535 0 0 0 1.501-1.656V20.25a1.75 1.75 0 0 1-1.75 1.75h-8.998l-.001-.003H3.75A1.75 1.75 0 0 1 2 20.246V7.751c0-.966.784-1.75 1.75-1.75h6.269Zm6.22 11.498a.75.75 0 0 1 .101 1.493L16.24 19h-1.5a.75.75 0 0 1-.102-1.493l.102-.007h1.5Zm-6.996 0a.75.75 0 0 1 .102 1.493L9.243 19H7.74a.75.75 0 0 1-.102-1.493l.102-.007h1.502ZM16.498 1a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm-1 2a.5.5 0 0 0-.5.5v4a.5.5 0 0 0 .5.5h3.001a.5.5 0 0 0 0-1h-2.501V3.5a.5.5 0 0 0-.5-.5Z",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
import ArticleSkeletonLoader from 'shared/components/ArticleSkeletonLoader.vue';
|
||||
|
||||
export default {
|
||||
name: 'IframeRenderer',
|
||||
name: 'IframeLoader',
|
||||
components: {
|
||||
ArticleSkeletonLoader,
|
||||
},
|
||||
|
||||
@@ -11,4 +11,5 @@ export const BUS_EVENTS = {
|
||||
TOGGLE_REPLY_TO_MESSAGE: 'TOGGLE_REPLY_TO_MESSAGE',
|
||||
SHOW_TOAST: 'newToastMessage',
|
||||
NEW_CONVERSATION_MODAL: 'newConversationModal',
|
||||
INSERT_INTO_RICH_EDITOR: 'insertIntoRichEditor',
|
||||
};
|
||||
|
||||
@@ -59,3 +59,5 @@
|
||||
enabled: false
|
||||
- name: message_reply_to
|
||||
enabled: false
|
||||
- name: insert_article_in_reply
|
||||
enabled: false
|
||||
|
||||
Reference in New Issue
Block a user