kopia lustrzana https://github.com/nextcloud/social
Merge pull request #748 from nextcloud/timeline-to-grid
👌 IMPROVE: move timeline to css grid layout and restructure code
pull/1068/head
commit
37fa478048
|
@ -50,7 +50,7 @@ return [
|
||||||
['name' => 'Navigation#resizedGetPublic', 'url' => '/document/public/resized', 'verb' => 'GET'],
|
['name' => 'Navigation#resizedGetPublic', 'url' => '/document/public/resized', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'ActivityPub#actor', 'url' => '/users/{username}', 'verb' => 'GET'],
|
['name' => 'ActivityPub#actor', 'url' => '/users/{username}', 'verb' => 'GET'],
|
||||||
['name' => 'ActivityPub#actorAlias', 'url' => '/@{username}', 'verb' => 'GET'],
|
['name' => 'ActivityPub#actorAlias', 'url' => '/@{username}/', 'verb' => 'GET'],
|
||||||
['name' => 'ActivityPub#inbox', 'url' => '/@{username}/inbox', 'verb' => 'POST'],
|
['name' => 'ActivityPub#inbox', 'url' => '/@{username}/inbox', 'verb' => 'POST'],
|
||||||
['name' => 'ActivityPub#getInbox', 'url' => '/@{username}/inbox', 'verb' => 'GET'],
|
['name' => 'ActivityPub#getInbox', 'url' => '/@{username}/inbox', 'verb' => 'GET'],
|
||||||
['name' => 'ActivityPub#sharedInbox', 'url' => '/inbox', 'verb' => 'POST'],
|
['name' => 'ActivityPub#sharedInbox', 'url' => '/inbox', 'verb' => 'POST'],
|
||||||
|
|
|
@ -24,6 +24,9 @@ export default Vue.component('MessageContent', {
|
||||||
* All attributes other than `href` for links are stripped from the source
|
* All attributes other than `href` for links are stripped from the source
|
||||||
*/
|
*/
|
||||||
export function formatMessage(createElement, source) {
|
export function formatMessage(createElement, source) {
|
||||||
|
if (!source.tag) {
|
||||||
|
source.tag = []
|
||||||
|
}
|
||||||
let mentions = source.tag.filter(tag => tag.type === 'Mention')
|
let mentions = source.tag.filter(tag => tag.type === 'Mention')
|
||||||
let hashtags = source.tag.filter(tag => tag.type === 'Hashtag')
|
let hashtags = source.tag.filter(tag => tag.type === 'Hashtag')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="item.actor_info" class="post-avatar">
|
||||||
|
<avatar v-if="item.local" :size="32" :user="userTest"
|
||||||
|
:display-name="item.actor_info.account" :disable-tooltip="true" />
|
||||||
|
<avatar v-else :size="32" :url="avatarUrl"
|
||||||
|
:disable-tooltip="true" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TimelineAvatar',
|
||||||
|
components: {
|
||||||
|
Avatar
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
item: { type: Object, default: () => {} }
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
userTest() {
|
||||||
|
return this.item.actor_info.preferredUsername
|
||||||
|
},
|
||||||
|
avatarUrl() {
|
||||||
|
return OC.generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.item.attributedTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.post-avatar {
|
||||||
|
margin: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
min-width: 32px;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,41 +1,50 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="timeline-entry">
|
<div :class="['timeline-entry', hasHeader ? 'with-header' : '']">
|
||||||
<div v-if="item.type === 'SocialAppNotification'">
|
<template v-if="item.type === 'SocialAppNotification'">
|
||||||
{{ actionSummary }}
|
<div class="notification-icon" :class="notificationIcon" />
|
||||||
</div>
|
<span class="notification-action">
|
||||||
<div v-if="item.type === 'Announce'" class="boost">
|
{{ actionSummary }}
|
||||||
<div class="container-icon-boost">
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.type === 'Announce'">
|
||||||
|
<div class="container-icon-boost boost">
|
||||||
<span class="icon-boost" />
|
<span class="icon-boost" />
|
||||||
</div>
|
</div>
|
||||||
<router-link v-if="item.actor_info" :to="{ name: 'profile', params: { account: item.local ? item.actor_info.preferredUsername : item.actor_info.account }}">
|
<div class="boost">
|
||||||
<span v-tooltip.bottom="item.actor_info.account" class="post-author">
|
<router-link v-if="item.actor_info" :to="{ name: 'profile', params: { account: item.local ? item.actor_info.preferredUsername : item.actor_info.account }}">
|
||||||
{{ userDisplayName(item.actor_info) }}
|
<span v-tooltip.bottom="item.actor_info.account" class="post-author">
|
||||||
</span>
|
{{ userDisplayName(item.actor_info) }}
|
||||||
</router-link>
|
</span>
|
||||||
<a v-else :href="item.attributedTo">
|
</router-link>
|
||||||
<span class="post-author-id">
|
<a v-else :href="item.attributedTo">
|
||||||
{{ item.attributedTo }}
|
<span class="post-author-id">
|
||||||
</span>
|
{{ item.attributedTo }}
|
||||||
</a>
|
</span>
|
||||||
{{ boosted }}
|
</a>
|
||||||
</div>
|
{{ boosted }}
|
||||||
<timeline-post
|
</div>
|
||||||
v-if="item.type === 'SocialAppNotification' && item.details.post"
|
</template>
|
||||||
:item="item.details.post" />
|
<user-entry v-if="item.type === 'SocialAppNotification' && item.details.actor" :key="item.details.actor.id" :item="item.details.actor" />
|
||||||
<timeline-post
|
<template v-else>
|
||||||
v-else
|
<timeline-avatar :item="entryContent" />
|
||||||
:item="entryContent"
|
<timeline-post
|
||||||
:parent-announce="isBoost" />
|
:item="entryContent"
|
||||||
|
:parent-announce="isBoost" />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TimelinePost from './TimelinePost.vue'
|
import TimelinePost from './TimelinePost.vue'
|
||||||
|
import TimelineAvatar from './TimelineAvatar.vue'
|
||||||
|
import UserEntry from './UserEntry.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimelineEntry',
|
name: 'TimelineEntry',
|
||||||
components: {
|
components: {
|
||||||
TimelinePost
|
TimelinePost,
|
||||||
|
TimelineAvatar,
|
||||||
|
UserEntry
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
item: { type: Object, default: () => {} }
|
item: { type: Object, default: () => {} }
|
||||||
|
@ -48,6 +57,8 @@ export default {
|
||||||
entryContent() {
|
entryContent() {
|
||||||
if (this.item.type === 'Announce') {
|
if (this.item.type === 'Announce') {
|
||||||
return this.item.cache[this.item.object].object
|
return this.item.cache[this.item.object].object
|
||||||
|
} else if (this.item.type === 'SocialAppNotification') {
|
||||||
|
return this.item.details.post
|
||||||
} else {
|
} else {
|
||||||
return this.item
|
return this.item
|
||||||
}
|
}
|
||||||
|
@ -58,6 +69,9 @@ export default {
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
|
hasHeader() {
|
||||||
|
return this.item.type === 'Announce' || this.item.type === 'SocialAppNotification'
|
||||||
|
},
|
||||||
boosted() {
|
boosted() {
|
||||||
return t('social', 'boosted')
|
return t('social', 'boosted')
|
||||||
},
|
},
|
||||||
|
@ -101,13 +115,59 @@ export default {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.timeline-entry.with-header {
|
||||||
|
grid-template-rows: 30px 1fr;
|
||||||
|
}
|
||||||
.timeline-entry {
|
.timeline-entry {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 44px 1fr;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-background-hover);
|
background-color: var(--color-background-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.notification-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-action {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: inline-block;
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon {
|
||||||
|
opacity: .5;
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 20px;
|
||||||
|
min-width: 32px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-boost {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-favorite {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-user {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
.container-icon-boost {
|
.container-icon-boost {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -1,55 +1,47 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="entry-content">
|
<div class="post-content">
|
||||||
<div v-if="item.actor_info" class="post-avatar">
|
<div class="post-header">
|
||||||
<avatar v-if="item.local && item.type!=='SocialAppNotification'" :size="32" :user="item.actor_info.preferredUsername"
|
<div class="post-author-wrapper">
|
||||||
:display-name="item.actor_info.account" :disable-tooltip="true" />
|
<router-link v-if="item.actor_info"
|
||||||
<avatar v-else :size="32" :url="avatarUrl"
|
:to="{ name: 'profile',
|
||||||
:disable-tooltip="true" />
|
params: { account: (item.local && item.type!=='SocialAppNotification') ? item.actor_info.preferredUsername : item.actor_info.account }
|
||||||
</div>
|
}">
|
||||||
<div class="post-content">
|
<span class="post-author">
|
||||||
<div class="post-header">
|
{{ userDisplayName(item.actor_info) }}
|
||||||
<div class="post-author-wrapper">
|
</span>
|
||||||
<router-link v-if="item.actor_info"
|
<span class="post-author-id">
|
||||||
:to="{ name: 'profile',
|
@{{ item.actor_info.account }}
|
||||||
params: { account: (item.local && item.type!=='SocialAppNotification') ? item.actor_info.preferredUsername : item.actor_info.account }
|
</span>
|
||||||
}">
|
</router-link>
|
||||||
<span class="post-author">
|
<a v-else :href="item.attributedTo">
|
||||||
{{ userDisplayName(item.actor_info) }}
|
<span class="post-author-id">
|
||||||
</span>
|
{{ item.attributedTo }}
|
||||||
<span class="post-author-id">
|
</span>
|
||||||
@{{ item.actor_info.account }}
|
|
||||||
</span>
|
|
||||||
</router-link>
|
|
||||||
<a v-else :href="item.attributedTo">
|
|
||||||
<span class="post-author-id">
|
|
||||||
{{ item.attributedTo }}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<a :data-timestamp="timestamp" class="post-timestamp live-relative-timestamp" @click="getSinglePostTimeline">
|
|
||||||
{{ relativeTimestamp }}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<a :data-timestamp="timestamp" class="post-timestamp live-relative-timestamp" @click="getSinglePostTimeline">
|
||||||
<div v-if="item.content" class="post-message">
|
{{ relativeTimestamp }}
|
||||||
<MessageContent :source="source" />
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<div v-else class="post-message" v-html="item.actor_info.summary" />
|
<div v-if="item.content" class="post-message">
|
||||||
<div v-if="hasAttachments" class="post-attachments">
|
<MessageContent :source="source" />
|
||||||
<post-attachment :attachments="item.attachment" />
|
</div>
|
||||||
</div>
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<div v-if="this.$route.params.type!=='notifications' && !serverData.public" v-click-outside="hidePopoverMenu" class="post-actions">
|
<div v-else class="post-message" v-html="item.actor_info.summary" />
|
||||||
<a v-tooltip.bottom="t('social', 'Reply')" class="icon-reply" @click.prevent="reply" />
|
<div v-if="hasAttachments" class="post-attachments">
|
||||||
<a v-if="item.actor_info.account !== cloudId" v-tooltip.bottom="t('social', 'Boost')"
|
<post-attachment :attachments="item.attachment" />
|
||||||
:class="(isBoosted) ? 'icon-boosted' : 'icon-boost'"
|
</div>
|
||||||
@click.prevent="boost" />
|
<div v-if="this.$route.params.type!=='notifications' && !serverData.public" v-click-outside="hidePopoverMenu" class="post-actions">
|
||||||
<a v-tooltip.bottom="t('social', 'Like')" :class="(isLiked) ? 'icon-starred' : 'icon-favorite'" @click.prevent="like" />
|
<a v-tooltip.bottom="t('social', 'Reply')" class="icon-reply" @click.prevent="reply" />
|
||||||
<div v-if="popoverMenu.length > 0" v-tooltip.bottom="menuOpened ? '' : t('social', 'More actions')" class="post-actions-more">
|
<a v-if="item.actor_info.account !== cloudId" v-tooltip.bottom="t('social', 'Boost')"
|
||||||
<a class="icon-more" @click.prevent="togglePopoverMenu" />
|
:class="(isBoosted) ? 'icon-boosted' : 'icon-boost'"
|
||||||
<div :class="{open: menuOpened}" class="popovermenu menu-center">
|
@click.prevent="boost" />
|
||||||
<popover-menu :menu="popoverMenu" />
|
<a v-tooltip.bottom="t('social', 'Like')" :class="(isLiked) ? 'icon-starred' : 'icon-favorite'" @click.prevent="like" />
|
||||||
</div>
|
<div v-if="popoverMenu.length > 0" v-tooltip.bottom="menuOpened ? '' : t('social', 'More actions')" class="post-actions-more">
|
||||||
|
<a class="icon-more" @click.prevent="togglePopoverMenu" />
|
||||||
|
<div :class="{open: menuOpened}" class="popovermenu menu-center">
|
||||||
|
<popover-menu :menu="popoverMenu" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,7 +49,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
|
|
||||||
import * as linkify from 'linkifyjs'
|
import * as linkify from 'linkifyjs'
|
||||||
import pluginMention from 'linkifyjs/plugins/mention'
|
import pluginMention from 'linkifyjs/plugins/mention'
|
||||||
import 'linkifyjs/string'
|
import 'linkifyjs/string'
|
||||||
|
@ -74,7 +65,6 @@ pluginMention(linkify)
|
||||||
export default {
|
export default {
|
||||||
name: 'TimelinePost',
|
name: 'TimelinePost',
|
||||||
components: {
|
components: {
|
||||||
Avatar,
|
|
||||||
PostAttachment,
|
PostAttachment,
|
||||||
MessageContent
|
MessageContent
|
||||||
},
|
},
|
||||||
|
@ -207,17 +197,6 @@ export default {
|
||||||
opacity: .7;
|
opacity: .7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-avatar {
|
|
||||||
margin: 5px;
|
|
||||||
margin-right: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
min-width: 32px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-timestamp {
|
.post-timestamp {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
@ -260,10 +239,6 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-header {
|
.post-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -50,9 +50,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
plugins: [new VueLoaderPlugin()],
|
plugins: [new VueLoaderPlugin()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
extensions: ['*', '.js', '.vue'],
|
||||||
vue$: 'vue/dist/vue.esm.js'
|
symlinks: false
|
||||||
},
|
|
||||||
extensions: ['*', '.js', '.vue', '.json']
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Ładowanie…
Reference in New Issue