Skip to content
3 changes: 2 additions & 1 deletion frontend/src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ const en = {
'issues-close': 'closed an issue',
'issues-closed': 'closed an issue',
'issue-comment': 'commented on an issue',
'pull_request-comment': 'commented on a pull request',
'pull_request-comment':
'commented on a pull request',
'discussion-started': 'started a discussion',
'discussion-comment': 'commented on a discussion',
contributed_to_community:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="text-gray-600 text-sm">
<div class="text-gray-600 text-sm" v-if="count > 0">
{{ count }}
{{ typeOfPostsFound }} posts
{{
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/premium/eagle-eye/components/eagle-eye-filter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,25 @@ export default {
return activeFilters
}
},
watch: {
filter: {
handler(newValue, oldValue) {
if (newValue.nDays !== oldValue.nDays) {
this.nDays = newValue.nDays
}
this.platforms = newValue.platforms
? newValue.platforms
: ['hacker_news', 'devto']
},
deep: true
},
'filter.platforms': {
handler(newValue) {
this.platforms = [...newValue]
},
deep: true
}
},
methods: {
...mapActions({
doReset: 'eagleEye/doReset',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="py-6">
<div class="pt-2 pb-6">
<div class="eagle-eye-list">
<div v-if="count > 0">
<transition-group name="fade" mode="out-in">
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/premium/eagle-eye/components/eagle-eye-page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<app-eagle-eye-search />
<div
v-if="shouldRenderEmptyState"
class="flex flex-col items-center justify-center w-full py-14"
class="flex flex-col items-center justify-center w-full py-10"
>
<img
src="/images/eagle-eye-empty-state.svg"
Expand All @@ -25,7 +25,7 @@
v-loading="loading"
></div>
<div v-else>
<div class="flex justify-between items-center pt-8">
<div class="flex justify-between items-center pt-4">
<app-eagle-eye-counter />
<app-eagle-eye-sorter
v-if="activeTab === 'inbox'"
Expand Down
56 changes: 21 additions & 35 deletions frontend/src/premium/eagle-eye/components/eagle-eye-search.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
<template>
<div class="eagle-eye-search">
<app-autocomplete-many-input
class="inline-input w-full mx-3"
:fetchFn="handleSearchAutocomplete"
v-model="selectedKeywords"
placeholder="Enter keywords, or topics..."
:triggerOnFocus="false"
:inMemoryFilter="false"
@remove-tag="handleRemoveKeyword"
>
</app-autocomplete-many-input>
<div class="flex-grow mx-3">
<app-keywords-input
v-model="selectedKeywords"
placeholder="Enter keywords, or topics..."
/>
</div>
<app-eagle-eye-filter />
<el-button
class="btn btn--primary mx-3"
Expand All @@ -21,53 +17,48 @@
</template>

<script>
import AppAutocompleteManyInput from '@/shared/form/autocomplete-many-input'
import AppEagleEyeFilter from './eagle-eye-filter'
import { mapGetters, mapActions } from 'vuex'

export default {
name: 'app-eagle-eye-search',
components: {
AppEagleEyeFilter,
AppAutocompleteManyInput
AppEagleEyeFilter
},
computed: {
...mapGetters({
filter: 'eagleEye/filter'
filter: 'eagleEye/filter',
activeTab: 'eagleEye/activeTab'
})
},
data() {
return {
selectedKeywords: []
}
},
watch: {
activeTab: {
handler(newValue, oldValue) {
if (newValue !== oldValue) {
this.selectedKeywords = []
}
}
}
},
methods: {
...mapActions({
doPopulate: 'eagleEye/doPopulate',
doFetch: 'eagleEye/doFetch'
}),
async handleSearchAutocomplete(query) {
return [
{
id: query,
label: query
}
]
},
async doSearch() {
const filtersToApply = {
...this.filter,
keywords: this.selectedKeywords
.map((k) => k.id)
.join(',')
keywords: this.selectedKeywords.join(',')
}
await this.doFetch({
rawFilter: filtersToApply,
filter: filtersToApply
})
},
handleRemoveKeyword() {
this.doSearch()
}
},
async created() {
Expand All @@ -76,12 +67,7 @@ export default {
)
this.selectedKeywords =
savedKeywords && savedKeywords !== ''
? savedKeywords.split(',').map((k) => {
return {
id: k,
label: k
}
})
? savedKeywords.split(',')
: []

if (savedKeywords) {
Expand All @@ -93,6 +79,6 @@ export default {

<style lang="scss">
.eagle-eye-search {
@apply -mx-2 flex items-center mt-6;
@apply -mx-3 flex items-start mt-6;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="eagle-eye-sorter" :style="computedWidth">
<div class="eagle-eye-sorter -mr-3" :style="computedWidth">
<el-select
:value="value"
popper-class="eagle-eye-popper-class"
Expand Down
8 changes: 3 additions & 5 deletions frontend/src/premium/eagle-eye/eagle-eye-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,14 +315,12 @@ export default {
})
},

doChangeActiveTab(
{ commit, dispatch, getters },
activeTab
) {
doChangeActiveTab({ commit, dispatch }, activeTab) {
commit('ACTIVE_TAB_CHANGED', activeTab)
commit('RESETED')
const filtersToApply = {
...getters.filter,
keywords: undefined,
nDays: 1,
status:
activeTab === 'inbox' ? undefined : activeTab
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/shared/form/autocomplete-many-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
multiple
:placeholder="placeholder || ''"
remote
reserve-keyword
:reserve-keyword="false"
:allow-create="allowCreate"
value-key="id"
:class="inputClass"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/shared/form/autocomplete-one-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
filterable
remote
:allow-create="allowCreate"
reserve-keyword
:reserve-keyword="false"
value-key="id"
:class="inputClass"
>
Expand Down
175 changes: 175 additions & 0 deletions frontend/src/shared/form/keywords-input.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<template>
<div>
<div
class="el-select el-keywords-input-wrapper"
@click="focusKeywordInput"
:class="focused ? 'is-focus' : ''"
>
<el-tag
v-for="(keyword, idx) in innerKeywords"
v-bind="$attrs"
:key="keyword"
size="small"
type="info"
effect="light"
:disable-transitions="true"
:closable="!readOnly"
@close="remove(idx)"
>
{{ keyword }}
</el-tag>
<input
v-if="!readOnly"
class="el-keywords-input"
:placeholder="placeholder"
@input="inputKeyword"
:value="newKeyword"
@keydown.delete.stop="removeLastKeyword"
@keydown="addNew"
@blur="addNew"
/>
</div>
<span
class="text-xs text-gray-400"
:class="focused ? 'opacity-100' : 'opacity-0'"
>Press ENTER or comma (,) to separate keywords</span
>
</div>
</template>

<script>
export default {
name: 'AppKeywordsInput',
props: {
value: {
type: Array,
default: () => []
},
addKeywordOnKeys: {
type: Array,
default: () => [13, 188, 9]
},
readOnly: {
type: Boolean,
default: false
},
placeholder: String
},
data() {
return {
newKeyword: '',
innerKeywords: [...this.value],
focused: false
}
},
watch: {
value() {
this.innerKeywords = [...this.value]
}
},
methods: {
focusKeywordInput() {
if (
this.readOnly ||
!this.$el.querySelector('.el-keywords-input')
) {
return
} else {
this.$el.querySelector('.el-keywords-input').focus()
this.focused = true
}
},
inputKeyword(ev) {
this.newKeyword = ev.target.value
},
addNew(e) {
if (
e &&
!this.addKeywordOnKeys.includes(e.keyCode) &&
e.type !== 'blur'
) {
return
}
if (e) {
e.stopPropagation()
e.preventDefault()
}
let addSuccess = false
if (this.newKeyword.includes(',')) {
this.newKeyword.split(',').forEach((item) => {
if (this.addKeyword(item.trim())) {
addSuccess = true
}
})
} else {
if (this.addKeyword(this.newKeyword.trim())) {
addSuccess = true
}
}
if (addSuccess) {
this.keywordChange()
this.newKeyword = ''
}
this.focused = false
},
addKeyword(keyword) {
keyword = keyword.trim()
if (
keyword &&
!this.innerKeywords.includes(keyword)
) {
this.innerKeywords.push(keyword)
return true
}
return false
},
remove(index) {
this.innerKeywords.splice(index, 1)
this.keywordChange()
},
removeLastKeyword() {
if (this.newKeyword) {
return
}
this.innerKeywords.pop()
this.keywordChange()
},
keywordChange() {
this.$emit('input', this.innerKeywords)
}
}
}
</script>

<style lang="scss">
.el-keywords-input-wrapper {
@apply relative text-sm bg-white rounded-md pr-2 pl-1 flex items-center flex-wrap;
background-image: none;
border: 1px solid #dcdfe6;
box-sizing: content-box;
color: #606266;
outline: none;
transition: border-color 0.2s
cubic-bezier(0.645, 0.045, 0.355, 1);
min-height: 38px;

&.el-select > .el-tag {
margin: 4px 0 4px 4px;
}
&.is-focus {
border: 1px solid #0068bd;
}
}

.el-keywords-input {
@apply bg-transparent border-none pl-0 ml-2 h-full;
font-size: inherit;
outline: none;
width: 200px;
min-height: 24px;
}

.el-keywords-input-list {
@apply flex items-center flex-wrap h-full;
}
</style>
Loading