activity-sidebar.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. /**
  2. * @copyright (c) 2016 Joas Schilling <coding@schilljs.com>
  3. *
  4. * @author Joas Schilling <coding@schilljs.com>
  5. *
  6. * This file is licensed under the Affero General Public License version 3 or
  7. * later. See the COPYING file.
  8. */
  9. (function() {
  10. OCA.Activity = OCA.Activity || {};
  11. OCA.Activity.RichObjectStringParser = {
  12. avatarsEnabled: true,
  13. _fileTemplate: '<a class="filename has-tooltip" href="{{link}}" title="{{title}}">{{name}}</a>',
  14. _fileNoPathTemplate: '<a class="filename" href="{{link}}">{{name}}</a>',
  15. _systemTagTemplate: '<strong class="systemtag">{{name}}</strong>',
  16. _emailTemplate: '<a class="email" href="mailto:{{id}}">{{name}}</a>',
  17. _userLocalTemplate: '<span class="avatar-name-wrapper" data-user="{{id}}"><div class="avatar" data-user="{{id}}" data-user-display-name="{{name}}"></div><strong>{{name}}</strong></span>',
  18. _userRemoteTemplate: '<strong>{{name}}</strong>',
  19. _openGraphTemplate: '{{#if link}}<a href="{{link}}">{{/if}}<div id="opengraph-{{id}}" class="opengraph">' +
  20. '{{#if thumb}}<div class="opengraph-thumb" style="background-image: url(\'{{thumb}}\')"></div>{{/if}}' +
  21. '<div class="opengraph-name {{#if thumb}}opengraph-with-thumb{{/if}}">{{name}}</div>' +
  22. '<div class="opengraph-description {{#if thumb}}opengraph-with-thumb{{/if}}">{{description}}</div>' +
  23. '<span class="opengraph-website">{{website}}</span></div>{{#if link}}</a>{{/if}}',
  24. _unknownTemplate: '<strong>{{name}}</strong>',
  25. _unknownLinkTemplate: '<a href="{{link}}">{{name}}</a>',
  26. /**
  27. * @param {string} subject
  28. * @param {Object} parameters
  29. * @returns {string}
  30. */
  31. parseMessage: function(subject, parameters) {
  32. var self = this,
  33. regex = /\{([a-z0-9]+)\}/gi,
  34. matches = subject.match(regex);
  35. _.each(matches, function(parameter) {
  36. parameter = parameter.substring(1, parameter.length - 1);
  37. var parsed = self.parseParameter(parameters[parameter]);
  38. subject = subject.replace('{' + parameter + '}', parsed);
  39. });
  40. return subject;
  41. },
  42. /**
  43. * @param {Object} parameter
  44. * @param {string} parameter.type
  45. * @param {string} parameter.id
  46. * @param {string} parameter.name
  47. * @param {string} parameter.link
  48. */
  49. parseParameter: function(parameter) {
  50. switch (parameter.type) {
  51. case 'file':
  52. return this.parseFileParameter(parameter);
  53. case 'systemtag':
  54. if (!this.systemTagTemplate) {
  55. this.systemTagTemplate = Handlebars.compile(this._systemTagTemplate);
  56. }
  57. var name = parameter.name;
  58. if (parameter.visibility !== '1') {
  59. name = t('activity', '{name} (invisible)', parameter);
  60. } else if (parameter.assignable !== '1') {
  61. name = t('activity', '{name} (restricted)', parameter);
  62. }
  63. return this.systemTagTemplate({
  64. name: name
  65. });
  66. case 'email':
  67. if (!this.emailTemplate) {
  68. this.emailTemplate = Handlebars.compile(this._emailTemplate);
  69. }
  70. return this.emailTemplate(parameter);
  71. case 'open-graph':
  72. if (!this.openGraphTemplate) {
  73. this.openGraphTemplate = Handlebars.compile(this._openGraphTemplate);
  74. }
  75. return this.openGraphTemplate(parameter);
  76. case 'user':
  77. if (_.isUndefined(parameter.server)) {
  78. if (!this.userLocalTemplate) {
  79. this.userLocalTemplate = Handlebars.compile(this._userLocalTemplate);
  80. }
  81. return this.userLocalTemplate(parameter);
  82. }
  83. if (!this.userRemoteTemplate) {
  84. this.userRemoteTemplate = Handlebars.compile(this._userRemoteTemplate);
  85. }
  86. return this.userRemoteTemplate(parameter);
  87. default:
  88. if (!_.isUndefined(parameter.link)) {
  89. if (!this.unknownLinkTemplate) {
  90. this.unknownLinkTemplate = Handlebars.compile(this._unknownLinkTemplate);
  91. }
  92. return this.unknownLinkTemplate(parameter);
  93. }
  94. if (!this.unknownTemplate) {
  95. this.unknownTemplate = Handlebars.compile(this._unknownTemplate);
  96. }
  97. return this.unknownTemplate(parameter);
  98. }
  99. },
  100. /**
  101. * @param {Object} parameter
  102. * @param {string} parameter.type
  103. * @param {string} parameter.id
  104. * @param {string} parameter.name
  105. * @param {string} parameter.path
  106. * @param {string} parameter.link
  107. */
  108. parseFileParameter: function(parameter) {
  109. if (!this.fileTemplate) {
  110. this.fileTemplate = Handlebars.compile(this._fileTemplate);
  111. this.fileNoPathTemplate = Handlebars.compile(this._fileNoPathTemplate);
  112. }
  113. var lastSlashPosition = parameter.path.lastIndexOf('/'),
  114. firstSlashPosition = parameter.path.indexOf('/');
  115. parameter.path = parameter.path.substring(firstSlashPosition === 0 ? 1 : 0, lastSlashPosition);
  116. if (!parameter.link) {
  117. parameter.link = OC.generateUrl('/f/{fileId}', {fileId: parameter.id})
  118. }
  119. if (parameter.path === '' || parameter.path === '/') {
  120. return this.fileNoPathTemplate(parameter);
  121. }
  122. return this.fileTemplate(_.extend(parameter, {
  123. title: parameter.path.length === 0 ? '' : t('activity', 'in {path}', parameter)
  124. }));
  125. }
  126. };
  127. })();
  128. /*
  129. * Copyright (c) 2015
  130. *
  131. * This file is licensed under the Affero General Public License version 3
  132. * or later.
  133. *
  134. * See the COPYING-README file.
  135. *
  136. */
  137. (function() {
  138. /**
  139. * @class OCA.Activity.ActivityModel
  140. * @classdesc
  141. *
  142. * Displays activity information for a given file
  143. *
  144. */
  145. var ActivityModel = OC.Backbone.Model.extend(/** @lends OCA.Activity.ActivityModel.prototype */{
  146. /**
  147. *
  148. * @returns int UNIX milliseconds timestamp
  149. */
  150. getUnixMilliseconds: function() {
  151. if (_.isUndefined(this.unixMilliseconds)) {
  152. this.unixMilliseconds = moment(this.get('datetime')).valueOf();
  153. }
  154. return this.unixMilliseconds;
  155. },
  156. /**
  157. * @returns string E.g. "seconds ago"
  158. */
  159. getRelativeDate: function () {
  160. return OC.Util.relativeModifiedDate(this.getUnixMilliseconds());
  161. },
  162. /**
  163. * @returns string E.g. "April 26, 2016 10:53 AM"
  164. */
  165. getFullDate: function () {
  166. return OC.Util.formatDate(this.getUnixMilliseconds());
  167. }
  168. });
  169. OCA.Activity = OCA.Activity || {};
  170. OCA.Activity.ActivityModel = ActivityModel;
  171. })();
  172. /*
  173. * Copyright (c) 2015
  174. *
  175. * This file is licensed under the Affero General Public License version 3
  176. * or later.
  177. *
  178. * See the COPYING-README file.
  179. *
  180. */
  181. (function() {
  182. OCA.Activity = OCA.Activity || {};
  183. /**
  184. * @class OCA.Activity.ActivityCollection
  185. * @classdesc
  186. *
  187. * Displays activity information for a given file
  188. *
  189. */
  190. var ActivityCollection = OC.Backbone.Collection.extend(
  191. /** @lends OCA.Activity.ActivityCollection.prototype */ {
  192. firstKnownId: 0,
  193. lastGivenId: 0,
  194. hasMore: false,
  195. /**
  196. * Id of the file for which to filter activities by
  197. *
  198. * @var int
  199. */
  200. _objectId: null,
  201. /**
  202. * Type of the object to filter by
  203. *
  204. * @var string
  205. */
  206. _objectType: null,
  207. model: OCA.Activity.ActivityModel,
  208. /**
  209. * Sets the object id to filter by or null for all.
  210. *
  211. * @param {int} objectId file id or null
  212. */
  213. setObjectId: function(objectId) {
  214. this._objectId = objectId;
  215. this.firstKnownId = 0;
  216. this.lastGivenId = 0;
  217. this.hasMore = false;
  218. },
  219. /**
  220. * Sets the object type to filter by or null for all.
  221. *
  222. * @param {string} objectType string
  223. */
  224. setObjectType: function(objectType) {
  225. this._objectType = objectType;
  226. this.firstKnownId = 0;
  227. this.lastGivenId = 0;
  228. this.hasMore = false;
  229. },
  230. /**
  231. *
  232. * @param ocsResponse
  233. * @param response
  234. * @returns {Array}
  235. */
  236. parse: function(ocsResponse, response) {
  237. this._saveHeaders(response.xhr.getAllResponseHeaders());
  238. if (response.xhr.status === 304) {
  239. // No activities found
  240. return [];
  241. }
  242. return ocsResponse.ocs.data;
  243. },
  244. /**
  245. * Read the X-Activity-First-Known and X-Activity-Last-Given headers
  246. * @param headers
  247. */
  248. _saveHeaders: function(headers) {
  249. var self = this;
  250. this.hasMore = false;
  251. headers = headers.split("\n");
  252. _.each(headers, function (header) {
  253. var parts = header.split(':');
  254. if (parts[0].toLowerCase() === 'x-activity-first-known') {
  255. self.firstKnownId = parseInt(parts[1].trim(), 10);
  256. } else if (parts[0].toLowerCase() === 'x-activity-last-given') {
  257. self.lastGivenId = parseInt(parts[1].trim(), 10);
  258. } else if (parts[0].toLowerCase() === 'link') {
  259. self.hasMore = true;
  260. }
  261. });
  262. },
  263. url: function() {
  264. var query = {
  265. format: 'json'
  266. };
  267. var url = OC.linkToOCS('apps/activity/api/v2/activity', 2) + 'filter';
  268. if (this.lastGivenId) {
  269. query.since = this.lastGivenId;
  270. }
  271. if (this._objectId && this._objectType) {
  272. query.object_type = this._objectType;
  273. query.object_id = this._objectId;
  274. }
  275. url += '?' + OC.buildQueryString(query);
  276. return url;
  277. }
  278. });
  279. OCA.Activity.ActivityCollection = ActivityCollection;
  280. })();
  281. /*
  282. * Copyright (c) 2015
  283. *
  284. * This file is licensed under the Affero General Public License version 3
  285. * or later.
  286. *
  287. * See the COPYING-README file.
  288. *
  289. */
  290. (function() {
  291. var TEMPLATE =
  292. '<div class="activity-section">' +
  293. '<div class="loading hidden" style="height: 50px"></div>' +
  294. '<div class="emptycontent">' +
  295. ' <div class="icon-activity"></div>' +
  296. ' <p>{{emptyMessage}}</p>' +
  297. '</div>' +
  298. '<ul class="activities hidden">' +
  299. '</ul>' +
  300. '<input type="button" class="showMore" value="{{moreLabel}}"' +
  301. '</div>';
  302. var ACTIVITY_TEMPLATE =
  303. ' <li class="activity box">' +
  304. ' <div class="activity-icon">' +
  305. ' {{#if icon}}' +
  306. ' <img src="{{icon}}">' +
  307. ' {{/if}}' +
  308. ' </div>' +
  309. ' <div class="activitysubject">{{{subject}}}</div>' +
  310. ' <span class="activitytime has-tooltip live-relative-timestamp" data-timestamp="{{timestamp}}" title="{{formattedDateTooltip}}">{{formattedDate}}</span>' +
  311. ' <div class="activitymessage">{{{message}}}</div>' +
  312. ' </li>';
  313. /**
  314. * @class OCA.Activity.ActivityTabView
  315. * @classdesc
  316. *
  317. * Displays activity information for a given file
  318. *
  319. */
  320. var ActivityTabView = OCA.Files.DetailTabView.extend(/** @lends OCA.Activity.ActivityTabView.prototype */ {
  321. id: 'activityTabView',
  322. className: 'activityTabView tab',
  323. events: {
  324. 'click .showMore': '_onClickShowMore'
  325. },
  326. _loading: false,
  327. _plugins: [],
  328. initialize: function() {
  329. this.collection = new OCA.Activity.ActivityCollection();
  330. this.collection.setObjectType('files');
  331. this.collection.on('request', this._onRequest, this);
  332. this.collection.on('sync', this._onEndRequest, this);
  333. this.collection.on('error', this._onError, this);
  334. this.collection.on('add', this._onAddModel, this);
  335. this._plugins = OC.Plugins.getPlugins('OCA.Activity.RenderingPlugins');
  336. _.each(this._plugins, function(plugin) {
  337. if (_.isFunction(plugin.initialize)) {
  338. plugin.initialize();
  339. }
  340. });
  341. },
  342. template: function(data) {
  343. if (!this._template) {
  344. this._template = Handlebars.compile(TEMPLATE);
  345. }
  346. return this._template(data);
  347. },
  348. get$: function() {
  349. return this.$el;
  350. },
  351. getLabel: function() {
  352. return t('activity', 'Activities');
  353. },
  354. setFileInfo: function(fileInfo) {
  355. this._fileInfo = fileInfo;
  356. if (this._fileInfo) {
  357. this.collection.setObjectId(this._fileInfo.get('id'));
  358. this.collection.reset();
  359. this.collection.fetch();
  360. _.each(this._plugins, function(plugin) {
  361. if (_.isFunction(plugin.setFileInfo)) {
  362. plugin.setFileInfo('files', fileInfo.get('id'));
  363. }
  364. });
  365. } else {
  366. this.collection.reset();
  367. _.each(this._plugins, function(plugin) {
  368. if (_.isFunction(plugin.resetFileInfo)) {
  369. plugin.resetFileInfo();
  370. }
  371. });
  372. }
  373. },
  374. _onError: function() {
  375. var $emptyContent = this.$el.find('.emptycontent');
  376. $emptyContent.removeClass('hidden');
  377. $emptyContent.find('p').text(t('activity', 'An error occurred while loading activities'));
  378. },
  379. _onRequest: function() {
  380. if (this.collection.lastGivenId === 0) {
  381. this.render();
  382. }
  383. this.$el.find('.showMore').addClass('hidden');
  384. },
  385. _onEndRequest: function() {
  386. this.$container.removeClass('hidden');
  387. this.$el.find('.loading').addClass('hidden');
  388. if (this.collection.length) {
  389. this.$el.find('.emptycontent').addClass('hidden');
  390. }
  391. if (this.collection.hasMore) {
  392. this.$el.find('.showMore').removeClass('hidden');
  393. }
  394. },
  395. _onClickShowMore: function() {
  396. this.collection.fetch({
  397. reset: false
  398. });
  399. },
  400. /**
  401. * Format an activity model for display
  402. *
  403. * @param {OCA.Activity.ActivityModel} activity
  404. * @return {Object}
  405. */
  406. _formatItem: function(activity) {
  407. var subject = activity.get('subject'),
  408. subject_rich = activity.get('subject_rich');
  409. if (subject_rich[0].length > 1) {
  410. subject = OCA.Activity.RichObjectStringParser.parseMessage(subject_rich[0], subject_rich[1]);
  411. }
  412. var message = activity.get('message'),
  413. message_rich = activity.get('message_rich');
  414. if (message_rich[0].length > 1) {
  415. message = OCA.Activity.RichObjectStringParser.parseMessage(message_rich[0], message_rich[1]);
  416. }
  417. var output = {
  418. subject: subject,
  419. formattedDate: activity.getRelativeDate(),
  420. formattedDateTooltip: activity.getFullDate(),
  421. timestamp: moment(activity.get('datetime')).valueOf(),
  422. message: message,
  423. icon: activity.get('icon')
  424. };
  425. /**
  426. * Disable previews in the rightside bar,
  427. * it's always the same image anyway.
  428. if (activity.has('previews')) {
  429. output.previews = _.map(activity.get('previews'), function(data) {
  430. return {
  431. previewClass: data.isMimeTypeIcon ? 'preview-mimetype-icon': '',
  432. source: data.source
  433. };
  434. });
  435. }
  436. */
  437. return output;
  438. },
  439. activityTemplate: function(params) {
  440. if (!this._activityTemplate) {
  441. this._activityTemplate = Handlebars.compile(ACTIVITY_TEMPLATE);
  442. }
  443. return this._activityTemplate(params);
  444. },
  445. _onAddModel: function(model, collection, options) {
  446. var $el = $(this.activityTemplate(this._formatItem(model)));
  447. _.each(this._plugins, function(plugin) {
  448. if (_.isFunction(plugin.prepareModelForDisplay)) {
  449. plugin.prepareModelForDisplay(model, $el, 'ActivityTabView');
  450. }
  451. });
  452. if (!_.isUndefined(options.at) && collection.length > 1) {
  453. this.$container.find('li').eq(options.at).before($el);
  454. } else {
  455. this.$container.append($el);
  456. }
  457. this._postRenderItem($el);
  458. },
  459. _postRenderItem: function($el) {
  460. $el.find('.avatar').each(function() {
  461. var element = $(this);
  462. if (element.data('user-display-name')) {
  463. element.avatar(element.data('user'), 21, undefined, false, undefined, element.data('user-display-name'));
  464. } else {
  465. element.avatar(element.data('user'), 21);
  466. }
  467. });
  468. $el.find('.avatar-name-wrapper').each(function() {
  469. var element = $(this);
  470. var avatar = element.find('.avatar');
  471. var label = element.find('strong');
  472. $.merge(avatar, label).contactsMenu(element.data('user'), 0, element);
  473. });
  474. $el.find('.has-tooltip').tooltip({
  475. placement: 'bottom'
  476. });
  477. },
  478. /**
  479. * Renders this details view
  480. */
  481. render: function() {
  482. if (this._fileInfo) {
  483. this.$el.html(this.template({
  484. emptyMessage: t('activity', 'No activity yet'),
  485. moreLabel: t('activity', 'Load more activities')
  486. }));
  487. this.$container = this.$el.find('ul.activities');
  488. }
  489. }
  490. });
  491. OCA.Activity = OCA.Activity || {};
  492. OCA.Activity.ActivityTabView = ActivityTabView;
  493. })();
  494. /*
  495. * Copyright (c) 2015
  496. *
  497. * This file is licensed under the Affero General Public License version 3
  498. * or later.
  499. *
  500. * See the COPYING-README file.
  501. *
  502. */
  503. (function(OCA) {
  504. var FilesPlugin = {
  505. attach: function(fileList) {
  506. fileList.registerTabView(new OCA.Activity.ActivityTabView({order: -50}));
  507. }
  508. };
  509. OC.Plugins.register('OCA.Files.FileList', FilesPlugin);
  510. })(OCA);