kopia lustrzana https://github.com/nextcloud/social
Add search and timeline for hashtags
Signed-off-by: Julius Härtl <jus@bitgrid.net>pull/295/head
rodzic
8c0c0bc5a3
commit
9c6c164b89
Plik diff jest za duży
Load Diff
|
@ -43,6 +43,7 @@
|
|||
"vue-tribute": "^1.0.1",
|
||||
"vue-twemoji": "^1.0.1",
|
||||
"vuex": "^3.1.0",
|
||||
"vuetrend": "^0.3.2",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
|
|
|
@ -492,19 +492,31 @@ export default {
|
|||
var em = document.createTextNode(emoji.getAttribute('alt'))
|
||||
emoji.replaceWith(em)
|
||||
})
|
||||
let content = element.innerText.trim()
|
||||
let to = []
|
||||
const re = /@(([\w-_.]+)(@[\w-.]+)?)/g
|
||||
let hashtags = []
|
||||
const mentionRegex = /@(([\w-_.]+)(@[\w-.]+)?)/g
|
||||
let match = null
|
||||
do {
|
||||
match = re.exec(element.innerText)
|
||||
match = mentionRegex.exec(content)
|
||||
if (match) {
|
||||
to.push(match[1])
|
||||
}
|
||||
} while (match)
|
||||
let content = element.innerText.trim()
|
||||
|
||||
const hashtagRegex = /#([\w-_.]+)/g
|
||||
match = null
|
||||
do {
|
||||
match = hashtagRegex.exec(content)
|
||||
if (match) {
|
||||
hashtags.push(match[1])
|
||||
}
|
||||
} while (match)
|
||||
|
||||
return {
|
||||
content: content,
|
||||
to: to,
|
||||
hashtags: hashtags,
|
||||
type: this.type
|
||||
}
|
||||
},
|
||||
|
|
|
@ -22,23 +22,34 @@
|
|||
|
||||
<template>
|
||||
<div class="social__wrapper">
|
||||
<div v-if="allResults.length < 1" id="emptycontent" :class="{'icon-loading': loading || remoteLoading}">
|
||||
<div v-if="allResults.length < 1 && hashtags.length < 1" id="emptycontent" :class="{'icon-loading': loading || remoteLoading}">
|
||||
<div v-if="!loading" class="icon-search" />
|
||||
<h2 v-if="!loading">
|
||||
{{ t('social', 'No accounts found') }}
|
||||
{{ t('social', 'No results found') }}
|
||||
</h2>
|
||||
<p v-if="!loading">
|
||||
No accounts found for {{ term }}
|
||||
{{ t('social', 'There were no results for your search:') }} {{ term }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="allResults.length > 0">
|
||||
<div v-else>
|
||||
<h3>{{ t('social', 'Searching for') }} {{ term }}</h3>
|
||||
<user-entry v-for="result in allResults" :key="result.id" :item="result" />
|
||||
<div v-if="hashtags.length > 0">
|
||||
<li v-for="tag in hashtags" :key="tag.hashtag" class="tag">
|
||||
<router-link :to="{ name: 'tags', params: {tag: tag.hashtag } }">
|
||||
<span>#{{ tag.hashtag }}</span>
|
||||
<trend
|
||||
:data="trendData(tag.trend)"
|
||||
:gradient="['#17adff', '#0082c9']" :smooth="true" :width="150"
|
||||
:height="44" stroke-width="2" />
|
||||
</router-link>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.user-entry {
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -47,17 +58,35 @@
|
|||
margin-top: -3px;
|
||||
margin-left: 47px;
|
||||
}
|
||||
.tag {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid var(--color-background-dark);
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 12px;
|
||||
font-weight: 300;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import UserEntry from './UserEntry'
|
||||
import axios from 'nextcloud-axios'
|
||||
import Trend from 'vuetrend'
|
||||
|
||||
export default {
|
||||
name: 'Search',
|
||||
components: {
|
||||
UserEntry
|
||||
UserEntry,
|
||||
Trend
|
||||
},
|
||||
props: {
|
||||
term: {
|
||||
|
@ -67,18 +96,19 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
results: {},
|
||||
loading: false,
|
||||
remoteLoading: false,
|
||||
match: null
|
||||
match: null,
|
||||
hashtags: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
allResults() {
|
||||
if (!this.match) {
|
||||
return this.results
|
||||
if (this.results.accounts) {
|
||||
return this.results.accounts.result
|
||||
}
|
||||
return [this.match, ...this.results.filter((item) => item.id !== this.match.id)]
|
||||
return []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -90,36 +120,39 @@ export default {
|
|||
this.search(this.term)
|
||||
},
|
||||
methods: {
|
||||
trendData(trend) {
|
||||
const data = [
|
||||
Math.max(0, trend['10d'] - trend['3d']),
|
||||
Math.max(0, trend['3d'] - trend['1d']),
|
||||
Math.max(0, trend['1d'] - trend['12h']),
|
||||
Math.max(0, trend['12h'] - trend['1h']),
|
||||
Math.max(0, trend['1h'])
|
||||
]
|
||||
return data
|
||||
},
|
||||
search(val) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
const re = /((\w+)(@[\w.]+)+)/g
|
||||
if (val.match(re) && !this.remoteLoading) {
|
||||
this.remoteLoading = true
|
||||
this.remoteSearch(val).then((response) => {
|
||||
this.match = response.data.result.account
|
||||
this.$store.commit('addAccount', { actorId: this.match.id, data: this.match })
|
||||
this.remoteLoading = false
|
||||
}).catch((e) => {
|
||||
this.remoteLoading = false
|
||||
this.match = null
|
||||
})
|
||||
}
|
||||
this.accountSearch(val).then((response) => {
|
||||
this.results = response.data.result.accounts
|
||||
this.searchQuery(val).then((response) => {
|
||||
this.results = response.data.result
|
||||
this.loading = false
|
||||
this.results.forEach((account) => {
|
||||
|
||||
this.results.accounts.result.forEach((account) => {
|
||||
this.$store.commit('addAccount', { actorId: account.id, data: account })
|
||||
})
|
||||
this.hashtags = this.results.hashtags.result
|
||||
})
|
||||
|
||||
},
|
||||
accountSearch(term) {
|
||||
this.loading = true
|
||||
return axios.get(OC.generateUrl('apps/social/api/v1/global/accounts/search?search=' + term))
|
||||
},
|
||||
searchQuery(term) {
|
||||
this.loading = true
|
||||
return axios.get(OC.generateUrl('apps/social/api/v1/search?search=' + term))
|
||||
},
|
||||
remoteSearch(term) {
|
||||
return axios.get(OC.generateUrl('apps/social/api/v1/global/account/info?account=' + term))
|
||||
}
|
||||
|
|
|
@ -35,8 +35,14 @@
|
|||
|
||||
<script>
|
||||
import { Avatar } from 'nextcloud-vue'
|
||||
import * as linkify from 'linkifyjs'
|
||||
import pluginTag from 'linkifyjs/plugins/hashtag'
|
||||
import pluginMention from 'linkifyjs/plugins/mention'
|
||||
import 'linkifyjs/string'
|
||||
|
||||
pluginTag(linkify)
|
||||
pluginMention(linkify)
|
||||
|
||||
export default {
|
||||
name: 'TimelineEntry',
|
||||
components: {
|
||||
|
@ -62,8 +68,11 @@ export default {
|
|||
message = message.replace(/(?:\r\n|\r|\n)/g, '<br />')
|
||||
message = message.linkify({
|
||||
formatHref: {
|
||||
email: function(href) {
|
||||
return OC.generateUrl('/apps/social/@' + (href.indexOf('mailto:') === 0 ? href.substring(7) : href))
|
||||
hashtag: function(href) {
|
||||
return OC.generateUrl('/apps/social/timeline/tags/' + href.substring(1))
|
||||
},
|
||||
mention: function(href) {
|
||||
return OC.generateUrl('/apps/social/@' + href.substring(1))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -75,12 +75,19 @@ export default {
|
|||
image: 'img/undraw/global.svg',
|
||||
title: t('social', 'No global posts found'),
|
||||
description: t('social', 'Posts from federated instances will show up here')
|
||||
},
|
||||
tags: {
|
||||
image: 'img/undraw/profile.svg',
|
||||
title: t('social', 'No posts found for this tag')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
emptyContentData() {
|
||||
if (typeof this.emptyContent[this.$route.name] !== 'undefined') {
|
||||
return this.emptyContent[this.$route.name]
|
||||
}
|
||||
if (typeof this.emptyContent[this.$route.params.type] !== 'undefined') {
|
||||
return this.emptyContent[this.$route.params.type]
|
||||
}
|
||||
|
|
|
@ -49,7 +49,13 @@ export default new Router({
|
|||
default: Timeline
|
||||
},
|
||||
props: true,
|
||||
name: 'timeline'
|
||||
name: 'timeline',
|
||||
children: [
|
||||
{
|
||||
path: 'tags/:tag',
|
||||
name: 'tags'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/:index(index.php/)?apps/social/@:account',
|
||||
|
|
|
@ -27,6 +27,7 @@ const state = {
|
|||
timeline: {},
|
||||
since: Math.floor(Date.now() / 1000) + 1,
|
||||
type: 'home',
|
||||
params: {},
|
||||
account: ''
|
||||
}
|
||||
const mutations = {
|
||||
|
@ -43,6 +44,9 @@ const mutations = {
|
|||
setTimelineType(state, type) {
|
||||
state.type = type
|
||||
},
|
||||
setTimelineParams(state, params) {
|
||||
state.params = params
|
||||
},
|
||||
setAccount(state, account) {
|
||||
state.account = account
|
||||
}
|
||||
|
@ -55,9 +59,10 @@ const getters = {
|
|||
}
|
||||
}
|
||||
const actions = {
|
||||
changeTimelineType(context, type) {
|
||||
changeTimelineType(context, { type, params }) {
|
||||
context.commit('resetTimeline')
|
||||
context.commit('setTimelineType', type)
|
||||
context.commit('setTimelineParams', params)
|
||||
context.commit('setAccount', '')
|
||||
},
|
||||
changeTimelineTypeAccount(context, account) {
|
||||
|
@ -88,6 +93,8 @@ const actions = {
|
|||
let url
|
||||
if (state.type === 'account') {
|
||||
url = OC.generateUrl(`apps/social/api/v1/account/${state.account}/stream?limit=25&since=` + sinceTimestamp)
|
||||
} else if (state.type === 'tags') {
|
||||
url = OC.generateUrl(`apps/social/api/v1/stream/tag/${state.params.tag}?limit=25&since=` + sinceTimestamp)
|
||||
} else {
|
||||
url = OC.generateUrl(`apps/social/api/v1/stream/${state.type}?limit=25&since=` + sinceTimestamp)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
</div>
|
||||
</transition>
|
||||
<composer />
|
||||
<h2 v-if="type === 'tags'">
|
||||
#{{ this.$route.params.tag }}
|
||||
</h2>
|
||||
<timeline-list />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -111,7 +114,16 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
params() {
|
||||
if (this.$route.name === 'tags') {
|
||||
return { tag: this.$route.params.tag }
|
||||
}
|
||||
return {}
|
||||
},
|
||||
type() {
|
||||
if (this.$route.name === 'tags') {
|
||||
return 'tags'
|
||||
}
|
||||
if (this.$route.params.type) {
|
||||
return this.$route.params.type
|
||||
}
|
||||
|
@ -128,7 +140,7 @@ export default {
|
|||
}
|
||||
},
|
||||
beforeMount: function() {
|
||||
this.$store.dispatch('changeTimelineType', this.type)
|
||||
this.$store.dispatch('changeTimelineType', { type: this.type, params: this.params })
|
||||
if (this.showInfo) {
|
||||
this.$store.dispatch('fetchAccountInfo', this.nextcloudAccount)
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue