Add search and timeline for hashtags

Signed-off-by: Julius Härtl <jus@bitgrid.net>
pull/295/head
Julius Härtl 2019-01-11 09:36:49 +01:00 zatwierdzone przez Maxence Lange
rodzic 8c0c0bc5a3
commit 9c6c164b89
9 zmienionych plików z 12045 dodań i 15503 usunięć

27391
package-lock.json wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -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": [

Wyświetl plik

@ -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
}
},

Wyświetl plik

@ -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))
}

Wyświetl plik

@ -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))
}
}
})

Wyświetl plik

@ -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]
}

Wyświetl plik

@ -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',

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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)
}