Skip to content
Closed
Prev Previous commit
Next Next commit
Router activity list and activities
Signed-off-by: Joas Schilling <[email protected]>
  • Loading branch information
nickvergessen committed Apr 16, 2018
commit ece48dc26091ace3f5cd65c5e166f141513a614a
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ npm-update:
npm update

build-js:
npm run dev
npm run watch

build-js-production:
npm run build
Expand Down
2 changes: 1 addition & 1 deletion appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
['name' => 'Settings#personal', 'url' => '/settings', 'verb' => 'POST'],
['name' => 'Settings#admin', 'url' => '/settings/admin', 'verb' => 'POST'],
['name' => 'Settings#feed', 'url' => '/settings/feed', 'verb' => 'POST'],
['name' => 'Activities#showList', 'url' => '/', 'verb' => 'GET'],
['name' => 'Feed#show', 'url' => '/rss.php', 'verb' => 'GET'],
['name' => 'Activities#showList', 'url' => '/{filter}', 'verb' => 'GET', 'defaults' => ['filter' => '']],
],
];
14 changes: 12 additions & 2 deletions js-src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default {
id: filter.id,
text: filter.name,
iconUrl: filter.icon,
href: OC.generateUrl('apps/activity/' + filter.id)
router: OC.generateUrl('apps/activity/' + filter.id)
});
});
this.menu.items = menuItems;
Expand All @@ -70,7 +70,13 @@ export default {
enable: !this.feedLink
})
.then((response) => this.$store.commit('setFeedLink', response.data.data.rsslink));
}
},

goBack () {
window.history.length > 1
? this.$router.go(-1)
: this.$router.push('/')
}
},
components: {
appNavigation
Expand All @@ -89,6 +95,10 @@ export default {
mounted: function () {
this.loadFilters();

if (!this.filter) {
this.filter = 'all';
}

// FIXME Clipboard is missing…

// this.loadActivities();
Expand Down
126 changes: 126 additions & 0 deletions js-src/components/activity.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<template>
<div class="section activity-section" :class="{ 'followup-section': sameDayAsSibling }">
<h2 v-if="!sameDayAsSibling"><span class="has-tooltip" :title="dateOfDay">{{displayDate}}</span></h2>

<div class="box" :data-activity-id="activity_id">
<div class="messagecontainer">
<div class="activity-icon">
<img v-if="icon" :src="icon" alt="" />
</div>
<div v-if="useLink" class="activitysubject">
<a :href="link" v-html="parsedSubject"></a>
</div>
<div v-else class="activitysubject" v-html="parsedSubject" />

<span class="activitytime has-tooltip live-relative-timestamp" :data-timestamp="timestamp" :title="formatDate">
{{relativeDate}}
</span>

<div class="activitymessage" v-if="parsedMessage" v-html="parsedMessage"></div>

<div class="activity-previews" v-if="previews.length">
<template v-for="preview in previews">
<a :href="preview.link" v-if="preview.link">
<img class="preview" :class="{ 'preview-mimetype-icon' : preview.isMimeTypeIcon }" :src="preview.source" :alt="t('activity', 'Open file')" />
</a>
<template v-else><img class="preview" :class="{ 'preview-mimetype-icon' : preview.isMimeTypeIcon }" :src="preview.source" :alt="t('activity', 'Open file')" /></template>
</template>
</div>
</div>
</div>
</div>
</template>

<script>
export default {
name: 'activity',

props: [
'index',
'activities',
'activity_id',
'icon',
'link',
'subject',
'subject_rich',
'message',
'message_rich',
'datetime',
'previews'
],

computed: {
timestamp: function () {
return moment(this.datetime).valueOf();
},
formatDate: function () {
return OC.Util.formatDate(this.timestamp);
},
relativeDate: function () {
return OC.Util.relativeModifiedDate(this.timestamp);
},
dateOfDay: function () {
return OC.Util.formatDate(this.timestamp, 'LL');
},
dayOfYear: function () {
return OC.Util.formatDate(this.timestamp, 'YYYY-DDD');
},
sameDayAsSibling: function () {
return this.index > 0 && this.dayOfYear === OC.Util.formatDate(moment(this.activities[this.index - 1].datetime).valueOf(), 'YYYY-DDD');
},
displayDate: function () {
var displayDate = this.dateOfDay;

if (this.dayOfYear === OC.Util.formatDate(moment(), 'YYYY-DDD')) {
displayDate = t('activity', 'Today');
} else {
if (this.dayOfYear === OC.Util.formatDate(moment().subtract(1, 'd'), 'YYYY-DDD')) {
displayDate = t('activity', 'Yesterday');
}
}

return displayDate;
},
parsedSubject: function () {
if (this.subject_rich[0].length > 1) {
return OCA.Activity.RichObjectStringParser.parseMessage(this.subject_rich[0], this.subject_rich[1]);
}
return this.subject;
},
parsedMessage: function () {
if (this.message_rich[0].length > 1) {
return OCA.Activity.RichObjectStringParser.parseMessage(this.message_rich[0], this.message_rich[1]);
}
return this.message;
},
useLink: function () {
return this.parsedSubject.indexOf('<a') === -1 && this.link.length !== 0;
}
},

mounted: function () {
this._$el = $(this.$el);

this._$el.find('.avatar').each(function() {
var $avatar = $(this);
if ($avatar.data('user-display-name')) {
$avatar.avatar($avatar.data('user'), 21, undefined, false, undefined, $avatar.data('user-display-name'));
} else {
$avatar.avatar($avatar.data('user'), 21);
}
});

this._$el.find('.avatar-name-wrapper').each(function() {
var element = $(this),
avatar = element.find('.avatar'),
label = element.find('strong');

$.merge(avatar, label).contactsMenu(element.data('user'), 0, element);
});

this._$el.find('.has-tooltip').tooltip({
placement: 'bottom'
});
}
}
</script>
8 changes: 7 additions & 1 deletion js-src/components/appNavigation/navigationItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
<div v-if="item.bullet" class="app-navigation-entry-bullet" :style="{ backgroundColor: item.bullet }"></div>

<!-- Main link -->
<a :href="(item.href) ? item.href : '#' " @click="toggleCollapse" :class="item.icon" >
<a v-if="item.href" :href="(item.href) ? item.href : '#' " @click="toggleCollapse" :class="item.icon" >
<img v-if="item.iconUrl" :alt="item.text" :src="item.iconUrl">
{{item.text}}
</a>

<!-- Router link if specified. href OR router -->
<router-link :to="item.router" v-else-if="item.router" :class="item.icon" >
<img v-if="item.iconUrl" :alt="item.text" :src="item.iconUrl">
{{item.text}}
</router-link>

<!-- Popover, counter and button(s) -->
<div v-if="item.utils" class="app-navigation-entry-utils">
<ul>
Expand Down
9 changes: 6 additions & 3 deletions js-src/router.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Vue from 'vue';
import Router from 'vue-router';
import Users from './views/Users';
import ActivityList from './views/ActivityList';

Vue.use(Router);

Expand All @@ -17,7 +17,10 @@ export default new Router({
mode: 'history',
base: window.location.pathName,
routes: [{
path: '/apps/activity',
component: Users
path: '/index.php/apps/activity/',
component: ActivityList
},{
path: '/index.php/apps/activity/:filter',
component: ActivityList
}]
});
122 changes: 122 additions & 0 deletions js-src/views/ActivityList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<template>
<div id="app-content" @scroll="onScroll">
<div id="emptycontent" v-if="!loading && !activities.length">
<div class="icon-activity"></div>
<h2>{{ t('activity', 'No activity yet') }}</h2>
<p>{{ t('activity', 'This stream will show events like additions, changes & shares') }}</p>
</div>

<div id="container">
<activity v-for="(a, index) in activities" v-bind="a" :index="index" :key="a.activity_id" :activities="activities"></activity>
</div>

<div v-if="loading" id="loading_activities" class="icon-loading"></div>

<div v-if="reachedEnd && activities.length" id="no_more_activities">{{ t('activity', 'No more events to load') }}</div>
</div>
</template>

<script>
import api from '../store/api';

export default {
name: 'ActivityList',

data() {
return {
/** @type {boolean} */
loading: true,

/** @type {boolean} */
reachedEnd: false,

/** @type {int} */
lastGivenId: 0,

/** @type {int} */
firstKnownId: 0,

/** @type {array} */
activities: []
};
},

computed: {
filter() {
return this.$route.params.filter || 'all';
}
},

methods: {

onScroll() {
if (!this.reachedEnd && this.ignoreScroll <= 0
) { // TODO && this.$content.scrollTop() + this.$content.height() > this.$container.height() - 100) {
this.loadActivities(false, false);
}
},

/**
* Navigation data is only loaded once on mount
* @param {boolean} lookAHead
* @param {boolean} isReset
*/
loadActivities(lookAHead, isReset) {
this.ignoreScroll = 1;
var url = OC.linkToOCS('apps/activity/api/v2/activity', 2) + this.filter + '?previews=true';
if (lookAHead) {
url += '&since=' + this.firstKnownId;
url += '&sort=asc';
} else {
url += '&since=' + this.lastGivenId;
}

api.get(url).then((response) => {
this._saveHeaders(response.headers, isReset, lookAHead);
this.loading = false;
this.ignoreScroll--;

if (response.data.ocs.data) {
this.activities = this.activities.concat(response.data.ocs.data);
}
}).catch((err) => {
this._saveHeaders(err.response.headers, isReset, lookAHead);
this.loading = false;
this.ignoreScroll--;

if (err.response.status === 304 && !lookAHead) {
this.reachedEnd = true;
}
});
},

/**
* Read the X-Activity-First-Known and X-Activity-Last-Given headers
* @param {string[]} headers
* @param {boolean} reset
* @param {boolean} lookAHead
*/
_saveHeaders(headers, reset, lookAHead) {
if (reset && typeof headers['x-activity-first-known'] !== 'undefined') {
this.firstKnownId = parseInt(headers['x-activity-first-known'], 10);
}

if (typeof headers['x-activity-last-given'] !== 'undefined') {
if (lookAHead) {
this.firstKnownId = parseInt(headers['x-activity-last-given'], 10);
} else {
this.lastGivenId = parseInt(headers['x-activity-last-given'], 10);
}
}
}
},

components: {
'activity': require('../components/activity.vue')
},

mounted() {
this.loadActivities();
}
}
</script>
Loading