Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"presets": [
["env", { "modules": false }],
"stage-3"
]
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ nbproject
.well-known
/.buildpath

# WebPack
/node_modules
/js/compiled

# Tests - auto-generated files
/PhantomJS_*
/tests/coverage*
Expand Down
47 changes: 47 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
app_name=activity

project_dir=$(CURDIR)/../$(app_name)
build_dir=$(CURDIR)/build
source_dir=$(build_dir)/$(app_name)
sign_dir=$(build_dir)/sign

all: package

dev-setup: clean npm-update build-js

npm-update:
rm -rf node_modules
npm update

build-js:
npm run watch

build-js-production:
npm run build

clean:
rm -rf $(build_dir)

package: clean build-js-production
mkdir -p $(source_dir)
rsync -a \
--exclude=/build \
--exclude=/docs \
--exclude=/js-src \
--exclude=/.tx \
--exclude=/tests \
--exclude=/.git \
--exclude=/.github \
--exclude=/CONTRIBUTING.md \
--exclude=/issue_template.md \
--exclude=/README.md \
--exclude=/.gitignore \
--exclude=/.scrutinizer.yml \
--exclude=/.travis.yml \
--exclude=/.drone.yml \
--exclude=/node_modules \
--exclude=/npm-debug.log \
--exclude=/package.json \
--exclude=/package-lock.json \
--exclude=/Makefile \
$(project_dir)/ $(source_dir)
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' => '']],
],
];
16 changes: 6 additions & 10 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@
margin-left: 24px;
}

#no_more_activities .head {
font-size: 1.5em;
color: #333;
font-weight: bold;
text-shadow: #aaa 0 1px 0;
}

#no_more_activities {
position: relative;
top: 50px;
Expand All @@ -52,9 +45,12 @@
border-radius: 5px;
}

.group {
clear: both;
padding-bottom: 10px;
.activity-section {
padding-bottom: 0;
padding-top: 0;
}
.section:not(.followup-section) {
padding-top: 40px;
}

.box {
Expand Down
119 changes: 119 additions & 0 deletions js-src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<template>
<div id="app-content" class="app-activity" role="main">
<app-navigation :menu="menu">
<template slot="settings-content">
<input type="checkbox" :checked="feedLink" @click="toggleFeedSetting" id="enable_rss" class="checkbox" />
<label for="enable_rss">{{ t('activity', 'Enable RSS feed') }}</label>

<span id="rssurl" :class="{hidden: !feedLink}">
<label for="feed-link" class="hidden-visually">{{ t('activity', 'RSS feed') }}</label>
<input id="feed-link" class="feed-link" type="text" readonly="readonly" :value="feedLink" />
<!--a class="icon-clippy" data-clipboard-target="#rssurl input"></a-->
</span>
</template>
</app-navigation>

<router-view></router-view>
</div>
</template>

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

export default {
name: 'App',

data: function () {
return {
/** @type {Object} */
menu: {
id: 'filters',
loading: true,
items: []
}
};
},

computed: {
feedLink() {
return this.$store.getters.getFeedLink;
}
},


watch: {
'$route' (to, from) {
this.menu.items.forEach((filter, index) => {
if (to.params.filter === filter.id) {
filter.classes = 'active';
} else if (from.params.filter === filter.id) {
filter.classes = '';
}
});
}
},

methods: {

/**
* Navigation data is only loaded once on mount
*/
loadFilters() {
api.get(OC.linkToOCS('apps/activity/api/v2/activity', 2) + 'filters').then((response) => {
let menuItems = [];
response.data.ocs.data.forEach((filter, index) => {
menuItems.push({
id: filter.id,
text: filter.name,
iconUrl: filter.icon,
router: OC.generateUrl('apps/activity/' + filter.id),
classes: (this.$route.params.filter || 'all') === filter.id ? 'active' : ''
});
});
this.menu.items = menuItems;
this.menu.loading = false;
});
},

/**
* Enable/disable the RSS Feed
*/
toggleFeedSetting() {
api.post(OC.generateUrl('/apps/activity/settings/feed'), {
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
},

beforeMount() {
// importing server data into the store
const appContentElmt = document.getElementById('app-content');

if (appContentElmt !== null) {
// this.$store.commit('setActiveFilter', appContentElmt.dataset['activityFilter']);
this.$store.commit('setFeedLink', appContentElmt.dataset['feedLink']);
}
},

mounted() {
this.loadFilters();

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

// FIXME Clipboard is missing…
}
}
</script>
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>
32 changes: 32 additions & 0 deletions js-src/components/appNavigation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<div id="app-navigation" :class="{'icon-loading': menu.loading}">
<div class="app-navigation-new" v-if="menu.new">
<button type="button" :id="menu.new.id" :class="menu.new.icon" @click="menu.new.action">{{menu.new.text}}</button>
</div>
<ul :id="menu.id">
<navigation-item v-for="(item, key) in menu.items" :item="item" :key="key" />
</ul>
<div id="app-settings">
<div id="app-settings-header">
<button class="settings-button"
data-apps-slide-toggle="#app-settings-content"
>{{t('settings', 'Settings')}}</button>
</div>
<div id="app-settings-content">
<slot name="settings-content"></slot>
</div>
</div>
</div>
</template>

<script>
import navigationItem from './appNavigation/navigationItem';

export default {
name: 'appNavigation',
props: ['menu'],
components: {
navigationItem
}
}
</script>
Loading