diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2020-10-19 10:04:15 -0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-19 10:04:15 -0100 |
commit | 37fa47804839e12f8b29e23106fedf2b82b0d9a7 (patch) | |
tree | ce050bc128b43503228631bcc04ec20448d018cc | |
parent | e659931aab6ce6a96792c8642ac74c95f353e54b (diff) | |
parent | ccb6196a71a2fc2bc4db877a53331db1ec919885 (diff) |
Merge pull request #748 from nextcloud/timeline-to-grid
👌 IMPROVE: move timeline to css grid layout and restructure code
-rw-r--r-- | appinfo/routes.php | 2 | ||||
-rw-r--r-- | src/components/MessageContent.js | 3 | ||||
-rw-r--r-- | src/components/TimelineAvatar.vue | 44 | ||||
-rw-r--r-- | src/components/TimelineEntry.vue | 112 | ||||
-rw-r--r-- | src/components/TimelinePost.vue | 107 | ||||
-rw-r--r-- | webpack.common.js | 6 |
6 files changed, 177 insertions, 97 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php index 077c37ee..f88528b8 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -50,7 +50,7 @@ return [ ['name' => 'Navigation#resizedGetPublic', 'url' => '/document/public/resized', '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#getInbox', 'url' => '/@{username}/inbox', 'verb' => 'GET'], ['name' => 'ActivityPub#sharedInbox', 'url' => '/inbox', 'verb' => 'POST'], diff --git a/src/components/MessageContent.js b/src/components/MessageContent.js index ff4e23cf..d8fe78a8 100644 --- a/src/components/MessageContent.js +++ b/src/components/MessageContent.js @@ -24,6 +24,9 @@ export default Vue.component('MessageContent', { * All attributes other than `href` for links are stripped from the source */ export function formatMessage(createElement, source) { + if (!source.tag) { + source.tag = [] + } let mentions = source.tag.filter(tag => tag.type === 'Mention') let hashtags = source.tag.filter(tag => tag.type === 'Hashtag') diff --git a/src/components/TimelineAvatar.vue b/src/components/TimelineAvatar.vue new file mode 100644 index 00000000..25104274 --- /dev/null +++ b/src/components/TimelineAvatar.vue @@ -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> diff --git a/src/components/TimelineEntry.vue b/src/components/TimelineEntry.vue index 61833343..97f714c6 100644 --- a/src/components/TimelineEntry.vue +++ b/src/components/TimelineEntry.vue @@ -1,41 +1,50 @@ <template> - <div class="timeline-entry"> - <div v-if="item.type === 'SocialAppNotification'"> - {{ actionSummary }} - </div> - <div v-if="item.type === 'Announce'" class="boost"> - <div class="container-icon-boost"> + <div :class="['timeline-entry', hasHeader ? 'with-header' : '']"> + <template v-if="item.type === 'SocialAppNotification'"> + <div class="notification-icon" :class="notificationIcon" /> + <span class="notification-action"> + {{ actionSummary }} + </span> + </template> + <template v-else-if="item.type === 'Announce'"> + <div class="container-icon-boost boost"> <span class="icon-boost" /> </div> - <router-link v-if="item.actor_info" :to="{ name: 'profile', params: { account: item.local ? item.actor_info.preferredUsername : item.actor_info.account }}"> - <span v-tooltip.bottom="item.actor_info.account" class="post-author"> - {{ userDisplayName(item.actor_info) }} - </span> - </router-link> - <a v-else :href="item.attributedTo"> - <span class="post-author-id"> - {{ item.attributedTo }} - </span> - </a> - {{ boosted }} - </div> - <timeline-post - v-if="item.type === 'SocialAppNotification' && item.details.post" - :item="item.details.post" /> - <timeline-post - v-else - :item="entryContent" - :parent-announce="isBoost" /> + <div class="boost"> + <router-link v-if="item.actor_info" :to="{ name: 'profile', params: { account: item.local ? item.actor_info.preferredUsername : item.actor_info.account }}"> + <span v-tooltip.bottom="item.actor_info.account" class="post-author"> + {{ userDisplayName(item.actor_info) }} + </span> + </router-link> + <a v-else :href="item.attributedTo"> + <span class="post-author-id"> + {{ item.attributedTo }} + </span> + </a> + {{ boosted }} + </div> + </template> + <user-entry v-if="item.type === 'SocialAppNotification' && item.details.actor" :key="item.details.actor.id" :item="item.details.actor" /> + <template v-else> + <timeline-avatar :item="entryContent" /> + <timeline-post + :item="entryContent" + :parent-announce="isBoost" /> + </template> </div> </template> <script> import TimelinePost from './TimelinePost.vue' +import TimelineAvatar from './TimelineAvatar.vue' +import UserEntry from './UserEntry.vue' export default { name: 'TimelineEntry', components: { - TimelinePost + TimelinePost, + TimelineAvatar, + UserEntry }, props: { item: { type: Object, default: () => {} } @@ -48,6 +57,8 @@ export default { entryContent() { if (this.item.type === 'Announce') { return this.item.cache[this.item.object].object + } else if (this.item.type === 'SocialAppNotification') { + return this.item.details.post } else { return this.item } @@ -58,6 +69,9 @@ export default { } return {} }, + hasHeader() { + return this.item.type === 'Announce' || this.item.type === 'SocialAppNotification' + }, boosted() { return t('social', 'boosted') }, @@ -101,13 +115,59 @@ export default { } </script> <style scoped lang="scss"> + .timeline-entry.with-header { + grid-template-rows: 30px 1fr; + } .timeline-entry { + display: grid; + grid-template-columns: 44px 1fr; + grid-template-rows: 1fr; padding: 10px; margin-bottom: 10px; &: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 { display: inline-block; diff --git a/src/components/TimelinePost.vue b/src/components/TimelinePost.vue index 411dc5ff..2cfe98c4 100644 --- a/src/components/TimelinePost.vue +++ b/src/components/TimelinePost.vue @@ -1,55 +1,47 @@ <template> - <div class="entry-content"> - <div v-if="item.actor_info" class="post-avatar"> - <avatar v-if="item.local && item.type!=='SocialAppNotification'" :size="32" :user="item.actor_info.preferredUsername" - :display-name="item.actor_info.account" :disable-tooltip="true" /> - <avatar v-else :size="32" :url="avatarUrl" - :disable-tooltip="true" /> - </div> - <div class="post-content"> - <div class="post-header"> - <div class="post-author-wrapper"> - <router-link v-if="item.actor_info" - :to="{ name: 'profile', - params: { account: (item.local && item.type!=='SocialAppNotification') ? item.actor_info.preferredUsername : item.actor_info.account } - }"> - <span class="post-author"> - {{ userDisplayName(item.actor_info) }} - </span> - <span class="post-author-id"> - @{{ 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 }} + <div class="post-content"> + <div class="post-header"> + <div class="post-author-wrapper"> + <router-link v-if="item.actor_info" + :to="{ name: 'profile', + params: { account: (item.local && item.type!=='SocialAppNotification') ? item.actor_info.preferredUsername : item.actor_info.account } + }"> + <span class="post-author"> + {{ userDisplayName(item.actor_info) }} + </span> + <span class="post-author-id"> + @{{ item.actor_info.account }} + </span> + </router-link> + <a v-else :href="item.attributedTo"> + <span class="post-author-id"> + {{ item.attributedTo }} + </span> </a> </div> - <!-- eslint-disable-next-line vue/no-v-html --> - <div v-if="item.content" class="post-message"> - <MessageContent :source="source" /> - </div> - <!-- eslint-disable-next-line vue/no-v-html --> - <div v-else class="post-message" v-html="item.actor_info.summary" /> - <div v-if="hasAttachments" class="post-attachments"> - <post-attachment :attachments="item.attachment" /> - </div> - <div v-if="this.$route.params.type!=='notifications' && !serverData.public" v-click-outside="hidePopoverMenu" class="post-actions"> - <a v-tooltip.bottom="t('social', 'Reply')" class="icon-reply" @click.prevent="reply" /> - <a v-if="item.actor_info.account !== cloudId" v-tooltip.bottom="t('social', 'Boost')" - :class="(isBoosted) ? 'icon-boosted' : 'icon-boost'" - @click.prevent="boost" /> - <a v-tooltip.bottom="t('social', 'Like')" :class="(isLiked) ? 'icon-starred' : 'icon-favorite'" @click.prevent="like" /> - <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> + <a :data-timestamp="timestamp" class="post-timestamp live-relative-timestamp" @click="getSinglePostTimeline"> + {{ relativeTimestamp }} + </a> + </div> + <!-- eslint-disable-next-line vue/no-v-html --> + <div v-if="item.content" class="post-message"> + <MessageContent :source="source" /> + </div> + <!-- eslint-disable-next-line vue/no-v-html --> + <div v-else class="post-message" v-html="item.actor_info.summary" /> + <div v-if="hasAttachments" class="post-attachments"> + <post-attachment :attachments="item.attachment" /> + </div> + <div v-if="this.$route.params.type!=='notifications' && !serverData.public" v-click-outside="hidePopoverMenu" class="post-actions"> + <a v-tooltip.bottom="t('social', 'Reply')" class="icon-reply" @click.prevent="reply" /> + <a v-if="item.actor_info.account !== cloudId" v-tooltip.bottom="t('social', 'Boost')" + :class="(isBoosted) ? 'icon-boosted' : 'icon-boost'" + @click.prevent="boost" /> + <a v-tooltip.bottom="t('social', 'Like')" :class="(isLiked) ? 'icon-starred' : 'icon-favorite'" @click.prevent="like" /> + <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> @@ -57,7 +49,6 @@ </template> <script> -import Avatar from '@nextcloud/vue/dist/Components/Avatar' import * as linkify from 'linkifyjs' import pluginMention from 'linkifyjs/plugins/mention' import 'linkifyjs/string' @@ -74,7 +65,6 @@ pluginMention(linkify) export default { name: 'TimelinePost', components: { - Avatar, PostAttachment, MessageContent }, @@ -207,17 +197,6 @@ export default { 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 { width: 120px; text-align: right; @@ -260,10 +239,6 @@ export default { display: flex; } - .post-content { - flex-grow: 1; - } - .post-header { display: flex; flex-direction: row; diff --git a/webpack.common.js b/webpack.common.js index db2fadb7..3f7ba387 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -50,9 +50,7 @@ module.exports = { }, plugins: [new VueLoaderPlugin()], resolve: { - alias: { - vue$: 'vue/dist/vue.esm.js' - }, - extensions: ['*', '.js', '.vue', '.json'] + extensions: ['*', '.js', '.vue'], + symlinks: false } }; |