mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +00:00
feat: Creates components for Article Search in Reply [CW-2285] (#7957)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
f77db4d814
commit
b28721e10b
@@ -1,31 +1,35 @@
|
||||
<template>
|
||||
<div class="article-item">
|
||||
<h4 class="text-block-title margin-bottom-0">{{ title }}</h4>
|
||||
<p class="margin-bottom-0 overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{{ body }}
|
||||
</p>
|
||||
<div class="footer">
|
||||
<p class="text-small meta">
|
||||
<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 @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>
|
||||
|
||||
<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">
|
||||
{{ locale }}
|
||||
{{ ` / ` }}
|
||||
{{
|
||||
category ||
|
||||
$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.ARTICLE_SEARCH_RESULT')
|
||||
}}
|
||||
{{ category || $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.UNCATEGORIZED') }}
|
||||
</p>
|
||||
<div class="action-buttons">
|
||||
<div class="flex gap-0.5">
|
||||
<woot-button
|
||||
:title="$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.COPY_LINK')"
|
||||
variant="hollow"
|
||||
color-scheme="secondary"
|
||||
size="tiny"
|
||||
icon="copy"
|
||||
class="invisible group-hover:visible"
|
||||
@click="handleCopy"
|
||||
/>
|
||||
|
||||
<a
|
||||
:href="url"
|
||||
class="button hollow button--only-icon tiny secondary"
|
||||
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')"
|
||||
@@ -38,6 +42,7 @@
|
||||
color-scheme="secondary"
|
||||
size="tiny"
|
||||
icon="preview-link"
|
||||
class="invisible group-hover:visible"
|
||||
:title="$t('HELP_CENTER.ARTICLE_SEARCH_RESULT.PREVIEW_LINK')"
|
||||
@click="handlePreview"
|
||||
/>
|
||||
@@ -46,7 +51,7 @@
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
size="tiny"
|
||||
@click="handleClick"
|
||||
@click="handleInsert"
|
||||
>
|
||||
{{ $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.INSERT_ARTICLE') }}
|
||||
</woot-button>
|
||||
@@ -61,6 +66,10 @@ import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
export default {
|
||||
name: 'ArticleSearchResultItem',
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Untitled',
|
||||
@@ -79,15 +88,15 @@ export default {
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'en-US',
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit('click');
|
||||
handleInsert() {
|
||||
this.$emit('insert', this.id);
|
||||
},
|
||||
handlePreview() {
|
||||
this.$emit('preview');
|
||||
this.$emit('preview', this.id);
|
||||
},
|
||||
async handleCopy() {
|
||||
await copyTextToClipboard(this.url);
|
||||
@@ -96,37 +105,3 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.article-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-micro);
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: var(--space-micro);
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: var(--s-600);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: var(--space-micro);
|
||||
}
|
||||
.action-buttons .button:not(.insert-button) {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.1s ease-in;
|
||||
}
|
||||
|
||||
.article-item:hover .action-buttons .button:not(.insert-button) {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="h-full w-full flex flex-col flex-1 overflow-hidden">
|
||||
<div class="py-1">
|
||||
<woot-button
|
||||
variant="link"
|
||||
size="small"
|
||||
icon="chevron-left"
|
||||
@click="onBack"
|
||||
>
|
||||
{{ $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>
|
||||
|
||||
<div class="flex justify-end gap-2 py-2">
|
||||
<woot-button
|
||||
variant="hollow"
|
||||
size="small"
|
||||
is-expanded
|
||||
color-scheme="secondary"
|
||||
icon="chevron-left"
|
||||
@click="onBack"
|
||||
>
|
||||
{{ $t('HELP_CENTER.ARTICLE_SEARCH.BACK') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
size="small"
|
||||
is-expanded
|
||||
icon="arrow-download"
|
||||
@click="onInsert"
|
||||
>
|
||||
{{ $t('HELP_CENTER.ARTICLE_SEARCH.INSERT_ARTICLE') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IframeLoader from 'shared/components/IframeLoader.vue';
|
||||
|
||||
export default {
|
||||
name: 'ChatwootSearch',
|
||||
components: {
|
||||
IframeLoader,
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onBack() {
|
||||
this.$emit('back');
|
||||
},
|
||||
onInsert() {
|
||||
this.$emit('insert');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="flex flex-col py-1">
|
||||
<div class="flex-container align-middle align-justify py-2">
|
||||
<h3 class="sub-block-title text-slate-900 dark:text-slate-25">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<woot-button
|
||||
variant="clear"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
icon="dismiss"
|
||||
@click="onClose"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute left-0 w-8 h-8 flex justify-center items-center">
|
||||
<fluent-icon icon="search" class="" size="16" />
|
||||
</div>
|
||||
<input
|
||||
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"
|
||||
:value="searchQuery"
|
||||
@input="onInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChatwootSearch',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Chatwoot',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchQuery: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onInput(e) {
|
||||
this.$emit('search', e.target.value);
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="flex justify-end gap-1 py-4 bg-white dark:bg-slate-900">
|
||||
<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">
|
||||
{{ $t('HELP_CENTER.ARTICLE_SEARCH_RESULT.NO_RESULT') }}
|
||||
</div>
|
||||
<search-result-item
|
||||
v-for="article in articles"
|
||||
v-else
|
||||
:id="article.id"
|
||||
:key="article.id"
|
||||
:title="article.title"
|
||||
:body="article.content"
|
||||
:url="article.url"
|
||||
:category="article.category.name"
|
||||
:locale="article.category.locale"
|
||||
@preview="handlePreview"
|
||||
@insert="handleInsert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import SearchResultItem from 'dashboard/routes/dashboard/helpcenter/components/ArticleSearch/ArticleSearchResultItem.vue';
|
||||
|
||||
export default {
|
||||
name: 'SearchResults',
|
||||
components: {
|
||||
Spinner,
|
||||
SearchResultItem,
|
||||
},
|
||||
props: {
|
||||
articles: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
searchQuery: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
portalSlug: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showNoResults() {
|
||||
return this.searchQuery && this.articles.length === 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handlePreview(id) {
|
||||
this.$emit('preview', id);
|
||||
},
|
||||
handleInsert(id) {
|
||||
this.$emit('insert', id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.empty-state-message {
|
||||
@apply text-center flex items-center justify-center px-4 py-8 text-slate-500 text-sm;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
import ArticleSearchArticleView from '../ArticleSearch/ArticleView';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/ArticleSearch/ArticleView',
|
||||
component: ArticleSearchArticleView,
|
||||
argTypes: {
|
||||
title: {
|
||||
defaultValue: 'Insert article',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
selectedValue: {
|
||||
defaultValue: 'Status',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleSearchArticleView },
|
||||
template:
|
||||
'<article-search-article-view v-bind="$props"></article-search-article-view>',
|
||||
});
|
||||
|
||||
export const ArticleHeaderView = Template.bind({});
|
||||
ArticleHeaderView.args = {
|
||||
title: 'Insert article',
|
||||
url: 'https://staging.chatwoot.com/hc/home-rental-app/articles/filter-your-housing-search-results-0',
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import ArticleSearchHeader from '../ArticleSearch/Header';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/ArticleSearch/Header',
|
||||
component: ArticleSearchHeader,
|
||||
argTypes: {
|
||||
title: {
|
||||
defaultValue: 'Insert article',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
selectedValue: {
|
||||
defaultValue: 'Status',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleSearchHeader },
|
||||
template: '<article-search-header v-bind="$props"></article-search-header>',
|
||||
});
|
||||
|
||||
export const ArticleHeaderView = Template.bind({});
|
||||
ArticleHeaderView.args = {
|
||||
title: 'Insert article',
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import ArticleSearchResultItem from '../ArticleSearchResultItem.vue';
|
||||
import ArticleSearchResultItem from '../ArticleSearch/ArticleSearchResultItem.vue';
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center/ArticleSearchResultItem',
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<div class="p-4 space-y-6 bg-white">
|
||||
<div class="space-y-2 animate-pulse">
|
||||
<div class="h-6 bg-slate-100 rounded w-1/5" />
|
||||
<div class="h-10 bg-slate-100 rounded w-3/5" />
|
||||
<div class="p-4 space-y-6 bg-white dark:bg-slate-900">
|
||||
<div class="space-y-2 animate-loader-pulse">
|
||||
<div class="h-6 bg-slate-100 dark:bg-slate-700 rounded w-1/5" />
|
||||
<div class="h-10 bg-slate-100 dark:bg-slate-700 rounded w-3/5" />
|
||||
</div>
|
||||
<div class="space-y-2 animate-pulse">
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="h-5 bg-slate-100 rounded w-4/5" />
|
||||
<div class="space-y-2 animate-loader-pulse">
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded w-4/5" />
|
||||
</div>
|
||||
<div class="space-y-2 animate-pulse">
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="h-5 bg-slate-100 rounded w-3/5" />
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="space-y-2 animate-loader-pulse">
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded w-3/5" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
</div>
|
||||
<div class="space-y-2 animate-pulse">
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="h-5 bg-slate-100 rounded" />
|
||||
<div class="h-5 bg-slate-100 rounded w-3/5" />
|
||||
<div class="space-y-2 animate-loader-pulse">
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded" />
|
||||
<div class="h-5 bg-slate-100 dark:bg-slate-700 rounded w-3/5" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -138,10 +138,16 @@ module.exports = {
|
||||
'90%': { transform: 'translateX(-0.375rem)' },
|
||||
'100%': { transform: 'translateX(0)' },
|
||||
},
|
||||
'loader-pulse': {
|
||||
'0%': { opacity: 0.4 },
|
||||
'50%': { opacity: 1 },
|
||||
'100%': { opacity: 0.4 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
...defaultTheme.animation,
|
||||
wiggle: 'wiggle 0.5s ease-in-out',
|
||||
'loader-pulse': 'loader-pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
|
||||
Reference in New Issue
Block a user