mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 23:58:31 -05:00
FEATURE: User Directory, with sorting and time period filter
This commit is contained in:
parent
6b85d5582c
commit
3d2d224312
47 changed files with 684 additions and 146 deletions
|
@ -1,31 +1,45 @@
|
||||||
const ADMIN_MODELS = ['plugin'];
|
const ADMIN_MODELS = ['plugin'];
|
||||||
|
|
||||||
export default Ember.Object.extend({
|
export default Ember.Object.extend({
|
||||||
pathFor(type, id) {
|
pathFor(store, type, findArgs) {
|
||||||
let path = "/" + Ember.String.underscore(type + 's');
|
let path = "/" + Ember.String.underscore(store.pluralize(type));
|
||||||
|
|
||||||
if (ADMIN_MODELS.indexOf(type) !== -1) { path = "/admin/" + path; }
|
if (ADMIN_MODELS.indexOf(type) !== -1) { path = "/admin/" + path; }
|
||||||
if (id) { path += "/" + id; }
|
|
||||||
|
if (findArgs) {
|
||||||
|
if (typeof findArgs === "object") {
|
||||||
|
const queryString = Object.keys(findArgs)
|
||||||
|
.reject(k => !findArgs[k])
|
||||||
|
.map(k => k + "=" + encodeURIComponent(findArgs[k]));
|
||||||
|
|
||||||
|
if (queryString.length) {
|
||||||
|
path += "?" + queryString.join('&');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// It's serializable as a string if not an object
|
||||||
|
path += "/" + findArgs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
|
|
||||||
findAll(store, type) {
|
findAll(store, type) {
|
||||||
return Discourse.ajax(this.pathFor(type));
|
return Discourse.ajax(this.pathFor(store, type));
|
||||||
},
|
},
|
||||||
|
|
||||||
find(store, type, id) {
|
find(store, type, findArgs) {
|
||||||
return Discourse.ajax(this.pathFor(type, id));
|
return Discourse.ajax(this.pathFor(store, type, findArgs));
|
||||||
},
|
},
|
||||||
|
|
||||||
update(store, type, id, attrs) {
|
update(store, type, id, attrs) {
|
||||||
const data = {};
|
const data = {};
|
||||||
data[Ember.String.underscore(type)] = attrs;
|
data[Ember.String.underscore(type)] = attrs;
|
||||||
return Discourse.ajax(this.pathFor(type, id), { method: 'PUT', data });
|
return Discourse.ajax(this.pathFor(store, type, id), { method: 'PUT', data });
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyRecord(store, type, record) {
|
destroyRecord(store, type, record) {
|
||||||
return Discourse.ajax(this.pathFor(type, record.get('id')), { method: 'DELETE' });
|
return Discourse.ajax(this.pathFor(store, type, record.get('id')), { method: 'DELETE' });
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||||
|
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||||
|
|
||||||
|
export default Ember.Component.extend(StringBuffer, {
|
||||||
|
tagName: 'th',
|
||||||
|
classNames: ['sortable'],
|
||||||
|
rerenderTriggers: ['order', 'asc'],
|
||||||
|
|
||||||
|
renderString(buffer) {
|
||||||
|
const field = this.get('field');
|
||||||
|
buffer.push(I18n.t('directory.' + field));
|
||||||
|
|
||||||
|
if (field === this.get('order')) {
|
||||||
|
buffer.push(iconHTML(this.get('asc') ? 'chevron-up' : 'chevron-down'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
click() {
|
||||||
|
const currentOrder = this.get('order'),
|
||||||
|
field = this.get('field');
|
||||||
|
|
||||||
|
if (currentOrder === field) {
|
||||||
|
this.set('asc', this.get('asc') ? null : true);
|
||||||
|
} else {
|
||||||
|
this.setProperties({ order: field, asc: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -10,9 +10,9 @@ export default Ember.Component.extend(CleansUp, {
|
||||||
},
|
},
|
||||||
|
|
||||||
_clickToClose: function() {
|
_clickToClose: function() {
|
||||||
var self = this;
|
const self = this;
|
||||||
$('html').off('mousedown.top-period').on('mousedown.top-period', function(e) {
|
$('html').off('mousedown.top-period').on('mousedown.top-period', function(e) {
|
||||||
var $target = $(e.target);
|
const $target = $(e.target);
|
||||||
if (($target.prop('id') === 'topic-entrance') || (self.$().has($target).length !== 0)) {
|
if (($target.prop('id') === 'topic-entrance') || (self.$().has($target).length !== 0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,23 @@ export default Ember.Component.extend(CleansUp, {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
click: function() {
|
click(e) {
|
||||||
|
if ($(e.target).closest('.period-popup').length) { return; }
|
||||||
|
|
||||||
if (!this.get('showPeriods')) {
|
if (!this.get('showPeriods')) {
|
||||||
var $chevron = this.$('i.fa-caret-down');
|
const $chevron = this.$('i.fa-caret-down');
|
||||||
this.$('#period-popup').css($chevron.position());
|
this.$('#period-popup').css($chevron.position());
|
||||||
this.set('showPeriods', true);
|
this.set('showPeriods', true);
|
||||||
this._clickToClose();
|
this._clickToClose();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
changePeriod(p) {
|
||||||
|
this.cleanUp();
|
||||||
|
this.set('period', p);
|
||||||
|
this.sendAction('action', p);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
|
@ -1,3 +1,14 @@
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
classNames: ['top-title-buttons']
|
classNames: ['top-title-buttons'],
|
||||||
|
|
||||||
|
periods: function() {
|
||||||
|
const period = this.get('period');
|
||||||
|
return this.site.get('periods').filter(p => p !== period);
|
||||||
|
}.property('period'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
changePeriod(p) {
|
||||||
|
this.sendAction('action', p);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import TopTitle from 'discourse/components/top-title';
|
|
||||||
|
|
||||||
export default TopTitle.extend({
|
|
||||||
tagName: 'button',
|
|
||||||
classNameBindings: [':btn', ':btn-default', 'unless:hidden'],
|
|
||||||
|
|
||||||
click: function() {
|
|
||||||
var url = this.get('period.showMoreUrl');
|
|
||||||
if (url) {
|
|
||||||
Discourse.URL.routeTo(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
|
||||||
|
|
||||||
export default Ember.Component.extend(StringBuffer, {
|
|
||||||
tagName: 'h2',
|
|
||||||
rerenderTriggers: ['period.title'],
|
|
||||||
|
|
||||||
renderString: function(buffer) {
|
|
||||||
buffer.push("<i class='fa fa-calendar-o'></i> " + this.get('period.title'));
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export default Ember.Controller.extend({
|
||||||
|
queryParams: ['order', 'asc'],
|
||||||
|
order: 'likes_received',
|
||||||
|
asc: null,
|
||||||
|
|
||||||
|
showTimeRead: Ember.computed.equal('period', 'all'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
loadMore() {
|
||||||
|
this.get('model').loadMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,5 +1,4 @@
|
||||||
import ObjectController from 'discourse/controllers/object';
|
import ObjectController from 'discourse/controllers/object';
|
||||||
import TopPeriod from 'discourse/models/top-period';
|
|
||||||
|
|
||||||
export default ObjectController.extend({
|
export default ObjectController.extend({
|
||||||
needs: ['navigation/category', 'discovery/topics', 'application'],
|
needs: ['navigation/category', 'discovery/topics', 'application'],
|
||||||
|
@ -15,7 +14,7 @@ export default ObjectController.extend({
|
||||||
}.observes("loadedAllItems"),
|
}.observes("loadedAllItems"),
|
||||||
|
|
||||||
showMoreUrl(period) {
|
showMoreUrl(period) {
|
||||||
var url = '', category = this.get('category');
|
let url = '', category = this.get('category');
|
||||||
if (category) {
|
if (category) {
|
||||||
url = '/c/' + Discourse.Category.slugFor(category) + (this.get('noSubcategories') ? '/none' : '') + '/l';
|
url = '/c/' + Discourse.Category.slugFor(category) + (this.get('noSubcategories') ? '/none' : '') + '/l';
|
||||||
}
|
}
|
||||||
|
@ -23,15 +22,10 @@ export default ObjectController.extend({
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
periods: function() {
|
actions: {
|
||||||
const self = this,
|
changePeriod(p) {
|
||||||
periods = [];
|
Discourse.URL.routeTo(this.showMoreUrl(p));
|
||||||
this.site.get('periods').forEach(function(p) {
|
}
|
||||||
periods.pushObject(TopPeriod.create({ id: p,
|
}
|
||||||
showMoreUrl: self.showMoreUrl(p),
|
|
||||||
periods }));
|
|
||||||
});
|
|
||||||
return periods;
|
|
||||||
}.property('category', 'noSubcategories'),
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
11
app/assets/javascripts/discourse/helpers/period-title.js.es6
Normal file
11
app/assets/javascripts/discourse/helpers/period-title.js.es6
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||||
|
|
||||||
|
const TITLE_SUBS = { yearly: 'this_year',
|
||||||
|
monthly: 'this_month',
|
||||||
|
daily: 'today',
|
||||||
|
all: 'all' };
|
||||||
|
|
||||||
|
export default Ember.Handlebars.makeBoundHelper(function (period) {
|
||||||
|
const title = I18n.t('filters.top.' + (TITLE_SUBS[period] || 'this_week'));
|
||||||
|
return new Handlebars.SafeString(iconHTML('calendar-o') + " " + title);
|
||||||
|
});
|
|
@ -1,33 +1,30 @@
|
||||||
export default Ember.Mixin.create({
|
export default Ember.Mixin.create({
|
||||||
|
|
||||||
_watchProps: function() {
|
_watchProps: function() {
|
||||||
var args = this.get('rerenderTriggers');
|
const args = this.get('rerenderTriggers');
|
||||||
if (!Ember.isNone(args)) {
|
if (!Ember.isNone(args)) {
|
||||||
var self = this;
|
args.forEach(k => this.addObserver(k, this.rerenderString));
|
||||||
args.forEach(function(k) {
|
|
||||||
self.addObserver(k, self.rerenderString);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}.on('init'),
|
}.on('init'),
|
||||||
|
|
||||||
render: function(buffer) {
|
render(buffer) {
|
||||||
this.renderString(buffer);
|
this.renderString(buffer);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderString: function(buffer){
|
renderString(buffer){
|
||||||
var template = Discourse.__container__.lookup('template:' + this.rawTemplate);
|
const template = Discourse.__container__.lookup('template:' + this.rawTemplate);
|
||||||
if (template) {
|
if (template) {
|
||||||
buffer.push(template(this));
|
buffer.push(template(this));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_rerenderString: function() {
|
_rerenderString() {
|
||||||
var buffer = [];
|
const buffer = [];
|
||||||
this.renderString(buffer);
|
this.renderString(buffer);
|
||||||
this.$().html(buffer.join(''));
|
this.$().html(buffer.join(''));
|
||||||
},
|
},
|
||||||
|
|
||||||
rerenderString: function() {
|
rerenderString() {
|
||||||
Ember.run.once(this, '_rerenderString');
|
Ember.run.once(this, '_rerenderString');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
app/assets/javascripts/discourse/models/rest.js.es6
Normal file
20
app/assets/javascripts/discourse/models/rest.js.es6
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
export default Ember.Object.extend({
|
||||||
|
update(attrs) {
|
||||||
|
const self = this,
|
||||||
|
type = this.get('__type');
|
||||||
|
return this.store.update(type, this.get('id'), attrs).then(function(result) {
|
||||||
|
if (result && result[type]) {
|
||||||
|
Object.keys(result).forEach(function(k) {
|
||||||
|
attrs[k] = result[k];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.setProperties(attrs);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyRecord() {
|
||||||
|
const type = this.get('__type');
|
||||||
|
return this.store.destroyRecord(type, this);
|
||||||
|
}
|
||||||
|
});
|
22
app/assets/javascripts/discourse/models/result-set.js.es6
Normal file
22
app/assets/javascripts/discourse/models/result-set.js.es6
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
export default Ember.ArrayProxy.extend({
|
||||||
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
|
totalRows: 0,
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
const loadMoreUrl = this.get('loadMoreUrl');
|
||||||
|
if (!loadMoreUrl) { return; }
|
||||||
|
|
||||||
|
const totalRows = this.get('totalRows');
|
||||||
|
if (this.get('length') < totalRows && !this.get('loadingMore')) {
|
||||||
|
this.set('loadingMore', true);
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
return this.store.appendResults(this, this.get('__type'), loadMoreUrl).then(function() {
|
||||||
|
self.set('loadingMore', false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ember.RSVP.resolve();
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,40 +1,49 @@
|
||||||
|
import RestModel from 'discourse/models/rest';
|
||||||
|
import ResultSet from 'discourse/models/result-set';
|
||||||
|
|
||||||
const _identityMap = {};
|
const _identityMap = {};
|
||||||
|
|
||||||
const RestModel = Ember.Object.extend({
|
export default Ember.Object.extend({
|
||||||
update(attrs) {
|
pluralize(thing) {
|
||||||
const self = this,
|
return thing + "s";
|
||||||
type = this.get('__type');
|
|
||||||
return this.store.update(type, this.get('id'), attrs).then(function(result) {
|
|
||||||
if (result && result[type]) {
|
|
||||||
Object.keys(result).forEach(function(k) {
|
|
||||||
attrs[k] = result[k];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
self.setProperties(attrs);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
destroyRecord() {
|
|
||||||
const type = this.get('__type');
|
|
||||||
return this.store.destroyRecord(type, this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Ember.Object.extend({
|
|
||||||
findAll(type) {
|
findAll(type) {
|
||||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||||
const self = this;
|
const self = this;
|
||||||
return adapter.findAll(this, type).then(function(result) {
|
return adapter.findAll(this, type).then(function(result) {
|
||||||
return result[Ember.String.underscore(type + 's')].map(obj => self._hydrate(type, obj));
|
return self._resultSet(type, result);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
find(type, id) {
|
find(type, findArgs) {
|
||||||
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
const adapter = this.container.lookup('adapter:' + type) || this.container.lookup('adapter:rest');
|
||||||
const self = this;
|
const self = this;
|
||||||
return adapter.find(this, type, id).then(function(result) {
|
return adapter.find(this, type, findArgs).then(function(result) {
|
||||||
|
if (typeof findArgs === "object") {
|
||||||
|
return self._resultSet(type, result);
|
||||||
|
} else {
|
||||||
return self._hydrate(type, result[Ember.String.underscore(type)]);
|
return self._hydrate(type, result[Ember.String.underscore(type)]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
appendResults(resultSet, type, url) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
return Discourse.ajax(url).then(function(result) {
|
||||||
|
const typeName = Ember.String.underscore(self.pluralize(type)),
|
||||||
|
totalRows = result["total_rows_" + typeName] || result.get('totalRows'),
|
||||||
|
loadMoreUrl = result["load_more_" + typeName],
|
||||||
|
content = result[typeName].map(obj => self._hydrate(type, obj));
|
||||||
|
|
||||||
|
resultSet.setProperties({ totalRows, loadMoreUrl });
|
||||||
|
resultSet.get('content').pushObjects(content);
|
||||||
|
|
||||||
|
// If we've loaded them all, clear the load more URL
|
||||||
|
if (resultSet.get('length') >= totalRows) {
|
||||||
|
resultSet.set('loadMoreUrl', null);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -63,6 +72,15 @@ export default Ember.Object.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_resultSet(type, result) {
|
||||||
|
const typeName = Ember.String.underscore(this.pluralize(type)),
|
||||||
|
content = result[typeName].map(obj => this._hydrate(type, obj)),
|
||||||
|
totalRows = result["total_rows_" + typeName] || content.length,
|
||||||
|
loadMoreUrl = result["load_more_" + typeName];
|
||||||
|
|
||||||
|
return ResultSet.create({ content, totalRows, loadMoreUrl, store: this, __type: type });
|
||||||
|
},
|
||||||
|
|
||||||
_hydrate(type, obj) {
|
_hydrate(type, obj) {
|
||||||
if (!obj) { throw "Can't hydrate " + type + " of `null`"; }
|
if (!obj) { throw "Can't hydrate " + type + " of `null`"; }
|
||||||
if (!obj.id) { throw "Can't hydrate " + type + " without an `id`"; }
|
if (!obj.id) { throw "Can't hydrate " + type + " without an `id`"; }
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
export default Ember.Object.extend({
|
|
||||||
title: null,
|
|
||||||
|
|
||||||
availablePeriods: function() {
|
|
||||||
var periods = this.get('periods');
|
|
||||||
if (!periods) { return; }
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
return periods.filter(function(p) {
|
|
||||||
return p !== self;
|
|
||||||
});
|
|
||||||
}.property('showMoreUrl'),
|
|
||||||
|
|
||||||
_createTitle: function() {
|
|
||||||
var id = this.get('id');
|
|
||||||
if (id) {
|
|
||||||
var title = "this_week";
|
|
||||||
if (id === "yearly") {
|
|
||||||
title = "this_year";
|
|
||||||
} else if (id === "monthly") {
|
|
||||||
title = "this_month";
|
|
||||||
} else if (id === "daily") {
|
|
||||||
title = "today";
|
|
||||||
} else if (id === "all") {
|
|
||||||
title = "all";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set('title', I18n.t("filters.top." + title));
|
|
||||||
}
|
|
||||||
}.on('init')
|
|
||||||
|
|
||||||
});
|
|
|
@ -11,6 +11,10 @@ export default function() {
|
||||||
});
|
});
|
||||||
this.resource('topicBySlug', { path: '/t/:slug' });
|
this.resource('topicBySlug', { path: '/t/:slug' });
|
||||||
|
|
||||||
|
this.resource('directory', function() {
|
||||||
|
this.route('show', {path: '/:period'});
|
||||||
|
});
|
||||||
|
|
||||||
this.resource('discovery', { path: '/' }, function() {
|
this.resource('discovery', { path: '/' }, function() {
|
||||||
// top
|
// top
|
||||||
this.route('top');
|
this.route('top');
|
||||||
|
|
|
@ -67,14 +67,13 @@ export default function(filter, params) {
|
||||||
|
|
||||||
setupController: function(controller, model) {
|
setupController: function(controller, model) {
|
||||||
var topics = this.get('topics'),
|
var topics = this.get('topics'),
|
||||||
periods = this.controllerFor('discovery').get('periods'),
|
|
||||||
periodId = topics.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : '');
|
periodId = topics.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : '');
|
||||||
|
|
||||||
this.controllerFor('navigation/category').set('canCreateTopic', topics.get('can_create_topic'));
|
this.controllerFor('navigation/category').set('canCreateTopic', topics.get('can_create_topic'));
|
||||||
this.controllerFor('discovery/topics').setProperties({
|
this.controllerFor('discovery/topics').setProperties({
|
||||||
model: topics,
|
model: topics,
|
||||||
category: model,
|
category: model,
|
||||||
period: periods.findBy('id', periodId),
|
period: periodId,
|
||||||
selected: [],
|
selected: [],
|
||||||
noSubcategories: params && !!params.no_subcategories,
|
noSubcategories: params && !!params.no_subcategories,
|
||||||
order: topics.get('params.order'),
|
order: topics.get('params.order'),
|
||||||
|
|
|
@ -45,13 +45,11 @@ export default function(filter, extras) {
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
const periods = this.controllerFor('discovery').get('periods'),
|
const period = model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : '');
|
||||||
periodId = model.get('for_period') || (filter.indexOf('/') > 0 ? filter.split('/')[1] : '');
|
|
||||||
|
|
||||||
const topicOpts = {
|
const topicOpts = {
|
||||||
model,
|
model,
|
||||||
category: null,
|
category: null,
|
||||||
period: periods.findBy('id', periodId),
|
period,
|
||||||
selected: [],
|
selected: [],
|
||||||
expandGloballyPinned: true
|
expandGloballyPinned: true
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default Discourse.Route.extend({
|
||||||
|
beforeModel: function() {
|
||||||
|
this.controllerFor('directory-show').setProperties({ sort: null, asc: null });
|
||||||
|
this.replaceWith('directory.show', 'all');
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,31 @@
|
||||||
|
export default Discourse.Route.extend({
|
||||||
|
queryParams: {
|
||||||
|
order: { refreshModel: true },
|
||||||
|
asc: { refreshModel: true },
|
||||||
|
},
|
||||||
|
|
||||||
|
model(params) {
|
||||||
|
// If we refresh via `refreshModel` set the old model to loading
|
||||||
|
const existing = this.modelFor('directory-show');
|
||||||
|
if (existing) {
|
||||||
|
existing.set('loading', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._period = params.period;
|
||||||
|
return this.store.find('directoryItem', {
|
||||||
|
id: params.period,
|
||||||
|
asc: params.asc,
|
||||||
|
order: params.order
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
controller.setProperties({ model, period: this._period });
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
changePeriod(period) {
|
||||||
|
this.transitionTo('directory.show', period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
<h2>{{period-title period}}</h2>
|
||||||
|
<button>{{fa-icon "caret-down"}}</button>
|
||||||
|
|
||||||
|
<div id='period-popup' {{bind-attr class="showPeriods::hidden :period-popup"}}>
|
||||||
|
<ul>
|
||||||
|
{{#each p in site.periods}}
|
||||||
|
<li><a href {{action "changePeriod" p}}>{{period-title p}}</a></li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class='clearfix'></div>
|
|
@ -1,3 +1,5 @@
|
||||||
{{#each p in period.availablePeriods}}
|
{{#each p in periods}}
|
||||||
{{top-title-button period=p}}
|
{{#d-button action="changePeriod" actionParam=p}}
|
||||||
|
{{period-title p}}
|
||||||
|
{{/d-button}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
{{top-title period=period}}
|
|
||||||
<button><i class='fa fa-caret-down'></i></button>
|
|
||||||
|
|
||||||
<div id='period-popup' {{bind-attr class="showPeriods::hidden"}}>
|
|
||||||
<ul>
|
|
||||||
{{#each p in period.availablePeriods}}
|
|
||||||
<li><a {{bind-attr href="p.showMoreUrl"}}>{{top-title tagName="span" period=p}}</a></li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class='clearfix'></div>
|
|
5
app/assets/javascripts/discourse/templates/directory.hbs
Normal file
5
app/assets/javascripts/discourse/templates/directory.hbs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="container">
|
||||||
|
<div class='directory'>
|
||||||
|
{{outlet}}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,50 @@
|
||||||
|
{{period-chooser period=period action="changePeriod"}}
|
||||||
|
|
||||||
|
{{#loading-spinner condition=model.loading}}
|
||||||
|
{{#if model.length}}
|
||||||
|
<span class='total-rows'>{{i18n "directory.total_rows" count=model.totalRows}}</span>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<th> </th>
|
||||||
|
{{directory-toggle field="likes_received" order=order asc=asc}}
|
||||||
|
{{directory-toggle field="likes_given" order=order asc=asc}}
|
||||||
|
{{directory-toggle field="topic_count" order=order asc=asc}}
|
||||||
|
{{directory-toggle field="post_count" order=order asc=asc}}
|
||||||
|
{{directory-toggle field="topics_entered" order=order asc=asc}}
|
||||||
|
{{#if showTimeRead}}
|
||||||
|
<th>{{i18n "directory.time_read"}}</th>
|
||||||
|
{{/if}}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each item in model}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{avatar item imageSize="tiny"}}
|
||||||
|
{{#link-to 'user' item.username}}{{unbound item.username}}{{/link-to}}
|
||||||
|
</td>
|
||||||
|
<td class="likes">
|
||||||
|
{{fa-icon "heart"}}
|
||||||
|
{{number item.likes_received}}
|
||||||
|
</td>
|
||||||
|
<td class="likes">
|
||||||
|
{{fa-icon "heart"}}
|
||||||
|
{{number item.likes_given}}
|
||||||
|
</td>
|
||||||
|
<td>{{number item.topic_count}}</td>
|
||||||
|
<td>{{number item.post_count}}</td>
|
||||||
|
<td>{{number item.topics_entered}}</td>
|
||||||
|
{{#if showTimeRead}}
|
||||||
|
<td>{{unbound item.time_read}}</td>
|
||||||
|
{{/if}}
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{loading-spinner condition=model.loadingMore}}
|
||||||
|
{{else}}
|
||||||
|
<div class='clearfix'></div>
|
||||||
|
<p>{{i18n "directory.no_results"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
{{/loading-spinner}}
|
|
@ -19,7 +19,7 @@
|
||||||
<div class='contents'>
|
<div class='contents'>
|
||||||
{{#if top}}
|
{{#if top}}
|
||||||
<div class='top-lists'>
|
<div class='top-lists'>
|
||||||
{{top-period-chooser period=period}}
|
{{period-chooser period=period action="changePeriod"}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if topicTrackingState.hasIncoming}}
|
{{#if topicTrackingState.hasIncoming}}
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
{{#if top}}
|
{{#if top}}
|
||||||
<h3>
|
<h3>
|
||||||
{{#link-to "discovery.categories"}}{{i18n 'topic.browse_all_categories'}}{{/link-to}}, {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}} {{i18n 'or'}} {{i18n 'filters.top.other_periods'}}
|
{{#link-to "discovery.categories"}}{{i18n 'topic.browse_all_categories'}}{{/link-to}}, {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}} {{i18n 'or'}} {{i18n 'filters.top.other_periods'}}
|
||||||
{{top-period-buttons period=period}}
|
{{top-period-buttons period=period action="changePeriod"}}
|
||||||
</h3>
|
</h3>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="education">
|
<div class="education">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class='contents'>
|
<div class='contents'>
|
||||||
{{#if top}}
|
{{#if top}}
|
||||||
<div class='top-lists'>
|
<div class='top-lists'>
|
||||||
{{top-period-chooser period=period}}
|
{{period-chooser period=period action="changePeriod"}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
<h3>
|
<h3>
|
||||||
{{#link-to "discovery.categories"}}{{i18n 'topic.browse_all_categories'}}{{/link-to}}, {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}} {{i18n 'or'}} {{i18n 'filters.top.other_periods'}}
|
{{#link-to "discovery.categories"}}{{i18n 'topic.browse_all_categories'}}{{/link-to}}, {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}} {{i18n 'or'}} {{i18n 'filters.top.other_periods'}}
|
||||||
<br/>
|
<br/>
|
||||||
{{top-period-buttons period=period}}
|
{{top-period-buttons period=period action="changePeriod"}}
|
||||||
</h3>
|
</h3>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="education">
|
<div class="education">
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<li>{{#link-to 'directory'}}{{i18n "directory.title"}}{{/link-to}}</li>
|
||||||
|
|
||||||
{{plugin-outlet "site-map-links"}}
|
{{plugin-outlet "site-map-links"}}
|
||||||
|
|
||||||
{{#if showKeyboardShortcuts}}
|
{{#if showKeyboardShortcuts}}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import LoadMore from 'discourse/mixins/load-more';
|
||||||
|
|
||||||
|
export default Discourse.View.extend(LoadMore, {
|
||||||
|
eyelineSelector: '.directory tbody tr'
|
||||||
|
});
|
|
@ -29,7 +29,6 @@
|
||||||
//= require ./discourse/models/post-stream
|
//= require ./discourse/models/post-stream
|
||||||
//= require ./discourse/models/topic-details
|
//= require ./discourse/models/topic-details
|
||||||
//= require ./discourse/models/topic
|
//= require ./discourse/models/topic
|
||||||
//= require ./discourse/models/top-period
|
|
||||||
//= require ./discourse/controllers/controller
|
//= require ./discourse/controllers/controller
|
||||||
//= require ./discourse/controllers/discovery-sortable
|
//= require ./discourse/controllers/discovery-sortable
|
||||||
//= require ./discourse/controllers/object
|
//= require ./discourse/controllers/object
|
||||||
|
@ -51,7 +50,6 @@
|
||||||
//= require ./discourse/routes/user-topic-list
|
//= require ./discourse/routes/user-topic-list
|
||||||
//= require ./discourse/routes/user-activity-stream
|
//= require ./discourse/routes/user-activity-stream
|
||||||
//= require ./discourse/routes/topic-from-params
|
//= require ./discourse/routes/topic-from-params
|
||||||
//= require ./discourse/components/top-title
|
|
||||||
//= require ./discourse/components/text-field
|
//= require ./discourse/components/text-field
|
||||||
//= require ./discourse/components/visible
|
//= require ./discourse/components/visible
|
||||||
//= require ./discourse/components/conditional-loading-spinner
|
//= require ./discourse/components/conditional-loading-spinner
|
||||||
|
|
|
@ -226,11 +226,12 @@ ol.category-breadcrumb {
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-chooser {
|
.period-chooser {
|
||||||
|
display: inline-block;
|
||||||
@include unselectable;
|
@include unselectable;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
float: left;
|
float: left;
|
||||||
|
margin: 5px 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@ -274,6 +275,10 @@ ol.category-breadcrumb {
|
||||||
|
|
||||||
.top-title-buttons {
|
.top-title-buttons {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.education {
|
div.education {
|
||||||
|
|
46
app/assets/stylesheets/common/base/directory.scss
Normal file
46
app/assets/stylesheets/common/base/directory.scss
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
.directory {
|
||||||
|
margin-bottom: 100px;
|
||||||
|
|
||||||
|
.period-chooser {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.total-rows {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
color: darken(scale-color-diff(), 20%);
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.spinner {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
padding: 0.5em;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid scale-color-diff();
|
||||||
|
}
|
||||||
|
|
||||||
|
th.sortable {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
width: 13%;
|
||||||
|
i.fa {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: scale-color-diff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td.likes {
|
||||||
|
i {
|
||||||
|
color: $love;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
app/controllers/directory_controller.rb
Normal file
8
app/controllers/directory_controller.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class DirectoryController < ApplicationController
|
||||||
|
# This controller just exists to avoid 404s and to have the ember app load up
|
||||||
|
def index
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
end
|
35
app/controllers/directory_items_controller.rb
Normal file
35
app/controllers/directory_items_controller.rb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
class DirectoryItemsController < ApplicationController
|
||||||
|
PAGE_SIZE = 50
|
||||||
|
|
||||||
|
def index
|
||||||
|
id = params.require(:id)
|
||||||
|
period_type = DirectoryItem.period_types[id.to_sym]
|
||||||
|
raise Discourse::InvalidAccess.new(:period_type) unless period_type
|
||||||
|
|
||||||
|
result = DirectoryItem.where(period_type: period_type).includes(:user)
|
||||||
|
|
||||||
|
order = params[:order] || DirectoryItem.headings.first
|
||||||
|
if DirectoryItem.headings.include?(order.to_sym)
|
||||||
|
dir = params[:asc] ? 'ASC' : 'DESC'
|
||||||
|
result = result.order("directory_items.#{order} #{dir}")
|
||||||
|
end
|
||||||
|
|
||||||
|
if period_type == DirectoryItem.period_types[:all]
|
||||||
|
result = result.includes(:user_stat)
|
||||||
|
end
|
||||||
|
page = params[:page].to_i
|
||||||
|
result = result.order('users.username')
|
||||||
|
result_count = result.dup.count
|
||||||
|
result = result.limit(PAGE_SIZE).offset(PAGE_SIZE * page)
|
||||||
|
|
||||||
|
serialized = serialize_data(result, DirectoryItemSerializer)
|
||||||
|
|
||||||
|
more_params = params.slice(:id, :order, :asc)
|
||||||
|
more_params[:page] = page + 1
|
||||||
|
|
||||||
|
render_json_dump directory_items: serialized,
|
||||||
|
total_rows_directory_items: result_count,
|
||||||
|
load_more_directory_items: directory_items_path(more_params)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
9
app/jobs/scheduled/directory_refresh.rb
Normal file
9
app/jobs/scheduled/directory_refresh.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module Jobs
|
||||||
|
class DirectoryRefresh < Jobs::Scheduled
|
||||||
|
every 1.hour
|
||||||
|
|
||||||
|
def execute(args)
|
||||||
|
DirectoryItem.refresh!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
57
app/models/directory_item.rb
Normal file
57
app/models/directory_item.rb
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
class DirectoryItem < ActiveRecord::Base
|
||||||
|
belongs_to :user
|
||||||
|
has_one :user_stat, foreign_key: :user_id, primary_key: :user_id
|
||||||
|
|
||||||
|
def self.headings
|
||||||
|
@headings ||= [:likes_received,
|
||||||
|
:likes_given,
|
||||||
|
:topics_entered,
|
||||||
|
:topic_count,
|
||||||
|
:post_count]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.period_types
|
||||||
|
@types ||= Enum.new(:all, :yearly, :monthly, :weekly, :daily)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.refresh!
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
exec_sql "TRUNCATE TABLE directory_items"
|
||||||
|
period_types.keys.each {|p| refresh_period!(p)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.refresh_period!(period_type)
|
||||||
|
since = case period_type
|
||||||
|
when :daily then 1.day.ago
|
||||||
|
when :weekly then 1.week.ago
|
||||||
|
when :monthly then 1.month.ago
|
||||||
|
when :yearly then 1.year.ago
|
||||||
|
else 1000.years.ago
|
||||||
|
end
|
||||||
|
|
||||||
|
exec_sql "INSERT INTO directory_items
|
||||||
|
(period_type, user_id, likes_received, likes_given, topics_entered, topic_count, post_count)
|
||||||
|
SELECT
|
||||||
|
:period_type,
|
||||||
|
u.id,
|
||||||
|
SUM(CASE WHEN ua.action_type = :was_liked_type THEN 1 ELSE 0 END),
|
||||||
|
SUM(CASE WHEN ua.action_type = :like_type THEN 1 ELSE 0 END),
|
||||||
|
(SELECT COUNT(topic_id) FROM topic_views AS v WHERE v.user_id = u.id AND v.viewed_at > :since),
|
||||||
|
SUM(CASE WHEN ua.action_type = :new_topic_type THEN 1 ELSE 0 END),
|
||||||
|
SUM(CASE WHEN ua.action_type = :reply_type THEN 1 ELSE 0 END)
|
||||||
|
FROM users AS u
|
||||||
|
LEFT OUTER JOIN user_actions AS ua ON ua.user_id = u.id
|
||||||
|
WHERE u.active
|
||||||
|
AND NOT u.blocked
|
||||||
|
AND COALESCE(ua.created_at, :since) >= :since
|
||||||
|
AND u.id > 0
|
||||||
|
GROUP BY u.id",
|
||||||
|
period_type: period_types[period_type],
|
||||||
|
since: since,
|
||||||
|
like_type: UserAction::LIKE,
|
||||||
|
was_liked_type: UserAction::WAS_LIKED,
|
||||||
|
new_topic_type: UserAction::NEW_TOPIC,
|
||||||
|
reply_type: UserAction::REPLY
|
||||||
|
end
|
||||||
|
end
|
35
app/serializers/directory_item_serializer.rb
Normal file
35
app/serializers/directory_item_serializer.rb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
class DirectoryItemSerializer < ApplicationSerializer
|
||||||
|
|
||||||
|
attributes :id,
|
||||||
|
:username,
|
||||||
|
:uploaded_avatar_id,
|
||||||
|
:avatar_template,
|
||||||
|
:time_read
|
||||||
|
|
||||||
|
attributes *DirectoryItem.headings
|
||||||
|
|
||||||
|
def id
|
||||||
|
object.user_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def username
|
||||||
|
object.user.username
|
||||||
|
end
|
||||||
|
|
||||||
|
def uploaded_avatar_id
|
||||||
|
object.user.uploaded_avatar_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def avatar_template
|
||||||
|
object.user.avatar_template
|
||||||
|
end
|
||||||
|
|
||||||
|
def time_read
|
||||||
|
AgeWords.age_words(object.user_stat.time_read)
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_time_read?
|
||||||
|
object.period_type == DirectoryItem.period_types[:all]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
9
app/serializers/directory_serializer.rb
Normal file
9
app/serializers/directory_serializer.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class DirectorySerializer < ApplicationSerializer
|
||||||
|
attributes :id
|
||||||
|
has_many :directory_items, serializer: DirectoryItemSerializer, embed: :objects
|
||||||
|
|
||||||
|
def id
|
||||||
|
object.filter
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -237,6 +237,19 @@ en:
|
||||||
sent_by_user: "Sent by <a href='{{userUrl}}'>{{user}}</a>"
|
sent_by_user: "Sent by <a href='{{userUrl}}'>{{user}}</a>"
|
||||||
sent_by_you: "Sent by <a href='{{userUrl}}'>you</a>"
|
sent_by_you: "Sent by <a href='{{userUrl}}'>you</a>"
|
||||||
|
|
||||||
|
directory:
|
||||||
|
title: "User Directory"
|
||||||
|
likes_given: "Likes Given"
|
||||||
|
likes_received: "Likes Received"
|
||||||
|
topics_entered: "Topics Entered"
|
||||||
|
time_read: "Time Read"
|
||||||
|
topic_count: "Topics"
|
||||||
|
post_count: "Replies"
|
||||||
|
no_results: "No results were found for this time period."
|
||||||
|
total_rows:
|
||||||
|
one: "1 user found"
|
||||||
|
other: "%{count} users found"
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
visible: "Group is visible to all users"
|
visible: "Group is visible to all users"
|
||||||
title:
|
title:
|
||||||
|
|
|
@ -25,6 +25,9 @@ Discourse::Application.routes.draw do
|
||||||
|
|
||||||
resources :about
|
resources :about
|
||||||
|
|
||||||
|
resources :directory
|
||||||
|
resources :directory_items
|
||||||
|
|
||||||
get "site" => "site#site"
|
get "site" => "site#site"
|
||||||
namespace :site do
|
namespace :site do
|
||||||
get "settings"
|
get "settings"
|
||||||
|
|
16
db/migrate/20150318143915_create_directory_items.rb
Normal file
16
db/migrate/20150318143915_create_directory_items.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
class CreateDirectoryItems < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :directory_items do |t|
|
||||||
|
t.integer :period_type, null: false
|
||||||
|
t.references :user, null: false
|
||||||
|
t.integer :likes_received, null: false
|
||||||
|
t.integer :likes_given, null: false
|
||||||
|
t.integer :topics_entered, null: false
|
||||||
|
t.integer :topic_count, null: false
|
||||||
|
t.integer :post_count, null: false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :directory_items, :period_type
|
||||||
|
end
|
||||||
|
end
|
31
spec/controllers/directory_items_controller_spec.rb
Normal file
31
spec/controllers/directory_items_controller_spec.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe DirectoryItemsController do
|
||||||
|
|
||||||
|
it "requires an `id` param" do
|
||||||
|
->{ xhr :get, :index }.should raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "requires a proper `id` param" do
|
||||||
|
xhr :get, :index, id: 'eviltrout'
|
||||||
|
response.should_not be_success
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with data" do
|
||||||
|
before do
|
||||||
|
Fabricate(:user)
|
||||||
|
DirectoryItem.refresh!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "succeeds with a valid value" do
|
||||||
|
xhr :get, :index, id: 'all'
|
||||||
|
response.should be_success
|
||||||
|
json = ::JSON.parse(response.body)
|
||||||
|
|
||||||
|
json.should be_present
|
||||||
|
json['directory_items'].should be_present
|
||||||
|
json['total_rows_directory_items'].should be_present
|
||||||
|
json['load_more_directory_items'].should be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
spec/models/directory_item_spec.rb
Normal file
15
spec/models/directory_item_spec.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe DirectoryItem do
|
||||||
|
context 'refresh' do
|
||||||
|
let!(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
it "creates the record for the user" do
|
||||||
|
DirectoryItem.refresh!
|
||||||
|
expect(DirectoryItem.where(period_type: DirectoryItem.period_types[:all])
|
||||||
|
.where(user_id: user.id)
|
||||||
|
.exists?).to be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
3
test/javascripts/fixtures/directory-fixtures.js.es6
Normal file
3
test/javascripts/fixtures/directory-fixtures.js.es6
Normal file
File diff suppressed because one or more lines are too long
|
@ -7,6 +7,10 @@ function parsePostData(query) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clone(obj) {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
function response(code, obj) {
|
function response(code, obj) {
|
||||||
if (typeof code === "object") {
|
if (typeof code === "object") {
|
||||||
obj = code;
|
obj = code;
|
||||||
|
@ -24,6 +28,11 @@ const _widgets = [
|
||||||
{id: 124, name: 'Evil Repellant'}
|
{id: 124, name: 'Evil Repellant'}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const _moreWidgets = [
|
||||||
|
{id: 223, name: 'Bass Lure'},
|
||||||
|
{id: 224, name: 'Good Repellant'}
|
||||||
|
];
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
const server = new Pretender(function() {
|
const server = new Pretender(function() {
|
||||||
|
|
||||||
|
@ -101,12 +110,23 @@ export default function() {
|
||||||
|
|
||||||
this.put('/widgets/:widget_id', function(request) {
|
this.put('/widgets/:widget_id', function(request) {
|
||||||
const w = _widgets.findBy('id', parseInt(request.params.widget_id));
|
const w = _widgets.findBy('id', parseInt(request.params.widget_id));
|
||||||
const cloned = JSON.parse(JSON.stringify(w));
|
return response({ widget: clone(w) });
|
||||||
return response({ widget: cloned });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.get('/widgets', function() {
|
this.get('/widgets', function(request) {
|
||||||
return response({ widgets: _widgets });
|
let result = _widgets;
|
||||||
|
|
||||||
|
const qp = request.queryParams;
|
||||||
|
if (qp) {
|
||||||
|
if (qp.name) { result = result.filterBy('name', qp.name); }
|
||||||
|
if (qp.id) { result = result.filterBy('id', parseInt(qp.id)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
return response({ widgets: result, total_rows_widgets: 4, load_more_widgets: '/load-more-widgets' });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.get('/load-more-widgets', function() {
|
||||||
|
return response({ widgets: _moreWidgets, total_rows_widgets: 4, load_more_widgets: '/load-more-widgets' });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.delete('/widgets/:widget_id', success);
|
this.delete('/widgets/:widget_id', success);
|
||||||
|
|
8
test/javascripts/integration/directory-test.js.es6
Normal file
8
test/javascripts/integration/directory-test.js.es6
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
integration("User Directory");
|
||||||
|
|
||||||
|
test("Visit Page", function() {
|
||||||
|
visit("/directory/all");
|
||||||
|
andThen(function() {
|
||||||
|
ok(exists('.directory table tr'), "has a list of users");
|
||||||
|
});
|
||||||
|
});
|
28
test/javascripts/models/result-set-test.js.es6
Normal file
28
test/javascripts/models/result-set-test.js.es6
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
module('result-set');
|
||||||
|
|
||||||
|
import ResultSet from 'discourse/models/result-set';
|
||||||
|
import createStore from 'helpers/create-store';
|
||||||
|
|
||||||
|
test('defaults', function() {
|
||||||
|
const rs = ResultSet.create({ content: [] });
|
||||||
|
equal(rs.get('length'), 0);
|
||||||
|
equal(rs.get('totalRows'), 0);
|
||||||
|
ok(!rs.get('loadMoreUrl'));
|
||||||
|
ok(!rs.get('loading'));
|
||||||
|
ok(!rs.get('loadingMore'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pagination support', function() {
|
||||||
|
const store = createStore();
|
||||||
|
store.findAll('widget').then(function(rs) {
|
||||||
|
equal(rs.get('length'), 2);
|
||||||
|
equal(rs.get('totalRows'), 4);
|
||||||
|
ok(rs.get('loadMoreUrl'), 'has a url to load more');
|
||||||
|
|
||||||
|
rs.loadMore().then(function() {
|
||||||
|
equal(rs.get('length'), 4);
|
||||||
|
ok(!rs.get('loadMoreUrl'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -19,7 +19,20 @@ test('find', function() {
|
||||||
store.find('widget', 123).then(function(w2) {
|
store.find('widget', 123).then(function(w2) {
|
||||||
equal(w, w2);
|
equal(w, w2);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('find with object id', function() {
|
||||||
|
const store = createStore();
|
||||||
|
store.find('widget', {id: 123}).then(function(w) {
|
||||||
|
equal(w.get('firstObject.name'), 'Trout Lure');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('find with query param', function() {
|
||||||
|
const store = createStore();
|
||||||
|
store.find('widget', {name: 'Trout Lure'}).then(function(w) {
|
||||||
|
equal(w.get('firstObject.id'), 123);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,7 +46,7 @@ test('update', function() {
|
||||||
test('findAll', function() {
|
test('findAll', function() {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
store.findAll('widget').then(function(result) {
|
store.findAll('widget').then(function(result) {
|
||||||
equal(result.length, 2);
|
equal(result.get('length'), 2);
|
||||||
const w = result.findBy('id', 124);
|
const w = result.findBy('id', 124);
|
||||||
equal(w.get('name'), 'Evil Repellant');
|
equal(w.get('name'), 'Evil Repellant');
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue