diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f87efdf --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/vendor +composer.phar +.DS_Store +Thumbs.db +node_modules +bower_components +.idea/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4105477 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 ReFlar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b254c47 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Gamification by ReFlar + +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ReFlar/gamification/blob/master/LICENSE) [![Latest Stable Version](https://img.shields.io/packagist/v/reflar/gamification.svg)](https://github.com/ReFlar/gamification) + +A [Flarum](http://flarum.org) extension that adds upvotes, downvotes, and ranks to your Flarum Community! + +Upvote and downvote posts anonymously, and reward active users with ranks, and sort posts by hotness. + +### Usage + +- Just click upvote or downvote +- Posts can be sorted by "Hotness" + +### Installation + +Install it with composer: + +```bash +composer require reflar/gamification +``` + +Then login and enable the extension. + +You can optionally convert your likes into upvotes, as well as calculate the hotness of all previous discussions. + +### Developer Guide + +You have 2 events to listen for "PostWasUpvoted" as well as "PostWasDownvoted" which both contain the post, post's user, and the upvoter/downvoter. + +### To Do + +- Add ranking page +- Add notifications +- Requests? + +### Issues + +- [Open an issue on Github](https://github.com/ReFlar/gamification/issues) + +### Links + +- [on github](https://github.com/ReFlar/gamification) +- [on packagist](https://packagist.org/packages/ReFlar/gamification) diff --git a/bootstrap.php b/bootstrap.php new file mode 100644 index 0000000..5dd6058 --- /dev/null +++ b/bootstrap.php @@ -0,0 +1,26 @@ +subscribe(Listeners\AddApiAttributes::class); + $events->subscribe(Listeners\AddRelationships::class); + $events->subscribe(Listeners\EventHandlers::class); + $events->subscribe(Listeners\AddClientAssets::class); + $events->subscribe(Listeners\SaveVotesToDatabase::class); + $events->subscribe(Listeners\FilterDiscussionListByHotness::class); +}; \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..148ca3f --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "reflar/gamification", + "description": "Manage your users with style", + "keywords": [ + "misc", + "settings", + "flarum", + "reflar", + "points", + "gamification" + ], + "type": "flarum-extension", + "license": "MIT", + "authors": [ + { + "name": "Christian Lopez", + "email": "ralkage@reflar.email", + "homepage": "https://reflar.io" + }, + + { + "name": "Charlie K", + "email": "issyrocks12@reflar.email", + "homepage": "https://reflar.io" + } + ], + "support": { + "issues": "https://github.com/ReFlar/gamification/issues", + "source": "https://github.com/ReFlar/gamification" + }, + "require": { + "flarum/core": "^0.1.0-beta.6" + }, + "autoload": { + "psr-4": { + "Reflar\\gamification\\": "src/" + } + }, + "extra": { + "flarum-extension": { + "title": "ReFlar Gamification", + "icon": { + "name": "thumbs-up", + "backgroundColor": "#263238", + "color": "#fff" + } + } + } +} \ No newline at end of file diff --git a/js/admin/Gulpfile.js b/js/admin/Gulpfile.js new file mode 100644 index 0000000..d940a88 --- /dev/null +++ b/js/admin/Gulpfile.js @@ -0,0 +1,9 @@ +var gulp = require('flarum-gulp'); + +gulp({ + modules: { + 'Reflar/gamification': [ + 'src/**/*.js' + ] + } +}); \ No newline at end of file diff --git a/js/admin/dist/extension.js b/js/admin/dist/extension.js new file mode 100644 index 0000000..c735010 --- /dev/null +++ b/js/admin/dist/extension.js @@ -0,0 +1,259 @@ +"use strict"; + +System.register("Reflar/gamification/addSettingsPage", ["flarum/extend", "flarum/components/AdminNav", "flarum/components/AdminLinkButton", "Reflar/gamification/components/SettingsPage"], function (_export, _context) { + "use strict"; + + var extend, AdminNav, AdminLinkButton, SettingsPage; + + _export("default", function () { + app.routes['reflar-gamification'] = { path: '/reflar/gamification', component: SettingsPage.component() }; + + app.extensionSettings['reflar-gamification'] = function () { + return m.route(app.route('reflar-gamification')); + }; + + extend(AdminNav.prototype, 'items', function (items) { + items.add('reflar-gamification', AdminLinkButton.component({ + href: app.route('reflar-gamification'), + icon: 'thumbs-up', + children: 'Gamification', + description: app.translator.trans('reflar-gamification.admin.nav.desc') + })); + }); + }); + + return { + setters: [function (_flarumExtend) { + extend = _flarumExtend.extend; + }, function (_flarumComponentsAdminNav) { + AdminNav = _flarumComponentsAdminNav.default; + }, function (_flarumComponentsAdminLinkButton) { + AdminLinkButton = _flarumComponentsAdminLinkButton.default; + }, function (_ReflarGamificationComponentsSettingsPage) { + SettingsPage = _ReflarGamificationComponentsSettingsPage.default; + }], + execute: function () {} + }; +});; +"use strict"; + +System.register("Reflar/gamification/components/SettingsPage", ["flarum/Component", "flarum/components/Button", "flarum/utils/saveSettings", "flarum/components/Alert"], function (_export, _context) { + "use strict"; + + var Component, Button, saveSettings, Alert, SettingsPage; + return { + setters: [function (_flarumComponent) { + Component = _flarumComponent.default; + }, function (_flarumComponentsButton) { + Button = _flarumComponentsButton.default; + }, function (_flarumUtilsSaveSettings) { + saveSettings = _flarumUtilsSaveSettings.default; + }, function (_flarumComponentsAlert) { + Alert = _flarumComponentsAlert.default; + }], + execute: function () { + SettingsPage = function (_Component) { + babelHelpers.inherits(SettingsPage, _Component); + + function SettingsPage() { + babelHelpers.classCallCheck(this, SettingsPage); + return babelHelpers.possibleConstructorReturn(this, (SettingsPage.__proto__ || Object.getPrototypeOf(SettingsPage)).apply(this, arguments)); + } + + babelHelpers.createClass(SettingsPage, [{ + key: "init", + value: function init() { + var _this2 = this; + + this.loading = false; + + this.fields = ['convertedLikes', 'defaultRank', 'amountPerPost', 'amountPerDiscussion', 'postStartAmount']; + + // fields that are objects + this.objects = ['ranks']; + + this.values = {}; + + this.settingsPrefix = 'reflar.gamification'; + + var settings = app.data.settings; + + this.fields.forEach(function (key) { + return _this2.values[key] = m.prop(settings[_this2.addPrefix(key)]); + }); + + this.objects.forEach(function (key) { + return _this2.values[key] = settings[_this2.addPrefix(key)] ? m.prop(JSON.parse(settings[_this2.addPrefix(key)])) : m.prop(''); + }); + + this.values.ranks() || (this.values.ranks = m.prop({ + '50': 'Helper: #000' + })); + + this.newRank = { + 'points': m.prop(''), + 'name': m.prop('') + }; + } + }, { + key: "view", + value: function view() { + var _this3 = this; + + return [m('div', { className: 'SettingsPage' }, [m('div', { className: 'container' }, [m('form', { onsubmit: this.onsubmit.bind(this) }, [m('div', { className: 'helpText' }, app.translator.trans('reflar-gamification.admin.page.convert.help')), this.values.convertedLikes() === undefined ? Button.component({ + type: 'button', + className: 'Button Button--warning Ranks-button', + children: app.translator.trans('reflar-gamification.admin.page.convert.button'), + onclick: function onclick() { + app.request({ + url: app.forum.attribute('apiUrl') + '/reflar/gamification/convert', + method: 'POST' + }).then(_this3.values.convertedLikes('converting')); + } + }) : this.values.convertedLikes() === 'converting' ? m('label', {}, app.translator.trans('reflar-gamification.admin.page.convert.converting')) : m('label', {}, app.translator.trans('reflar-gamification.admin.page.convert.converted', { number: this.values.convertedLikes() })), m('fieldset', { className: 'SettingsPage-ranks' }, [m('legend', {}, app.translator.trans('reflar-gamification.admin.page.ranks.title')), m('label', {}, app.translator.trans('reflar-gamification.admin.page.ranks.ranks')), m('div', { className: 'Ranks--Container' }, Object.keys(this.values.ranks()).map(function (rank) { + return m('div', {}, [m('input', { + className: 'FormControl Ranks-number', + type: 'number', + value: rank, + oninput: m.withAttr('value', _this3.updateRankPoints.bind(_this3, rank)) + }), m('input', { + className: 'FormControl Ranks-name', + value: _this3.values.ranks()[rank], + oninput: m.withAttr('value', _this3.updateRankName.bind(_this3, rank)) + }), Button.component({ + type: 'button', + className: 'Button Button--warning Ranks-button', + icon: 'times', + onclick: _this3.deleteRank.bind(_this3, rank) + })]); + }), m('br'), m('div', {}, [m('input', { + className: 'FormControl Ranks-number', + value: this.newRank.points(), + type: 'number', + oninput: m.withAttr('value', this.newRank.points) + }), m('input', { + className: 'FormControl Ranks-name', + value: this.newRank.name(), + oninput: m.withAttr('value', this.newRank.name) + }), Button.component({ + type: 'button', + className: 'Button Button--warning Ranks-button', + icon: 'plus', + onclick: this.addRank.bind(this) + })])), m('div', { className: 'helpText' }, app.translator.trans('reflar-gamification.admin.page.ranks.help')), m('label', {}, app.translator.trans('reflar-gamification.admin.page.ranks.default')), m('input', { + className: 'FormControl Ranks-default', + value: this.values.defaultRank() || '', + placeholder: 'Newbie', + oninput: m.withAttr('value', this.values.defaultRank) + })]), m('div', { className: 'helpText' }, app.translator.trans('reflar-gamification.admin.page.ranks.default_help')), Button.component({ + type: 'submit', + className: 'Button Button--primary Ranks-save', + children: app.translator.trans('reflar-gamification.admin.page.save_settings'), + loading: this.loading, + disabled: !this.changed() + })])])])]; + } + }, { + key: "updateRankPoints", + value: function updateRankPoints(rank, value) { + this.values.ranks()[value] = this.values.ranks()[rank]; + + this.deleteRank(rank); + } + }, { + key: "updateRankName", + value: function updateRankName(rank, value) { + this.values.ranks()[rank] = value; + } + }, { + key: "deleteRank", + value: function deleteRank(rank) { + delete this.values.ranks()[rank]; + } + }, { + key: "addRank", + value: function addRank() { + this.values.ranks()[this.newRank.points()] = this.newRank.name(); + + this.newRank.points(''); + this.newRank.name(''); + } + }, { + key: "changed", + value: function changed() { + var _this4 = this; + + var fieldsCheck = this.fields.some(function (key) { + return _this4.values[key]() !== app.data.settings[_this4.addPrefix(key)]; + }); + var objectsCheck = this.objects.some(function (key) { + return JSON.stringify(_this4.values[key]()) !== app.data.settings[_this4.addPrefix(key)]; + }); + return fieldsCheck || objectsCheck; + } + }, { + key: "onsubmit", + value: function onsubmit(e) { + var _this5 = this; + + e.preventDefault(); + + if (this.loading) return; + + this.loading = true; + + app.alerts.dismiss(this.successAlert); + + var settings = {}; + + this.fields.forEach(function (key) { + return settings[_this5.addPrefix(key)] = _this5.values[key](); + }); + this.objects.forEach(function (key) { + return settings[_this5.addPrefix(key)] = JSON.stringify(_this5.values[key]()); + }); + + saveSettings(settings).then(function () { + app.alerts.show(_this5.successAlert = new Alert({ + type: 'success', + children: app.translator.trans('core.admin.basics.saved_message') + })); + }).catch(function () {}).then(function () { + _this5.loading = false; + window.location.reload(); + }); + } + }, { + key: "addPrefix", + value: function addPrefix(key) { + return this.settingsPrefix + '.' + key; + } + }]); + return SettingsPage; + }(Component); + + _export("default", SettingsPage); + } + }; +});; +'use strict'; + +System.register('Reflar/gamification/main', ['flarum/app', 'Reflar/gamification/addSettingsPage'], function (_export, _context) { + "use strict"; + + var app, addSettingsPage; + return { + setters: [function (_flarumApp) { + app = _flarumApp.default; + }, function (_ReflarGamificationAddSettingsPage) { + addSettingsPage = _ReflarGamificationAddSettingsPage.default; + }], + execute: function () { + + app.initializers.add('reflar-gamification', function () { + + addSettingsPage(); + }); + } + }; +}); \ No newline at end of file diff --git a/js/admin/package.json b/js/admin/package.json new file mode 100644 index 0000000..f4c413b --- /dev/null +++ b/js/admin/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "devDependencies": { + "gulp": "^3.9.1", + "flarum-gulp": "^0.2.0" + } +} \ No newline at end of file diff --git a/js/admin/src/addSettingsPage.js b/js/admin/src/addSettingsPage.js new file mode 100644 index 0000000..1c1a2cf --- /dev/null +++ b/js/admin/src/addSettingsPage.js @@ -0,0 +1,19 @@ +import {extend} from "flarum/extend"; +import AdminNav from "flarum/components/AdminNav"; +import AdminLinkButton from "flarum/components/AdminLinkButton"; +import SettingsPage from "Reflar/gamification/components/SettingsPage"; + +export default function () { + app.routes['reflar-gamification'] = {path: '/reflar/gamification', component: SettingsPage.component()}; + + app.extensionSettings['reflar-gamification'] = () => m.route(app.route('reflar-gamification')); + + extend(AdminNav.prototype, 'items', items => { + items.add('reflar-gamification', AdminLinkButton.component({ + href: app.route('reflar-gamification'), + icon: 'thumbs-up', + children: 'Gamification', + description: app.translator.trans('reflar-gamification.admin.nav.desc') + })); + }); +} diff --git a/js/admin/src/components/SettingsPage.js b/js/admin/src/components/SettingsPage.js new file mode 100644 index 0000000..71e2b9d --- /dev/null +++ b/js/admin/src/components/SettingsPage.js @@ -0,0 +1,217 @@ +import Component from "flarum/Component"; +import Button from "flarum/components/Button"; +import saveSettings from "flarum/utils/saveSettings"; +import Alert from "flarum/components/Alert"; + +export default class SettingsPage extends Component { + + init() { + this.loading = false; + + this.fields = [ + 'convertedLikes', + 'defaultRank', + 'amountPerPost', + 'amountPerDiscussion', + 'postStartAmount' + ]; + + + // fields that are objects + this.objects = [ + 'ranks' + ]; + + + this.values = {}; + + this.settingsPrefix = 'reflar.gamification'; + + const settings = app.data.settings; + + this.fields.forEach(key => + this.values[key] = m.prop(settings[this.addPrefix(key)]) + ); + + this.objects.forEach(key => + this.values[key] = settings[this.addPrefix(key)] ? m.prop(JSON.parse(settings[this.addPrefix(key)])) : m.prop('') + ); + + + this.values.ranks() || (this.values.ranks = m.prop({ + '50': 'Helper: #000' + })); + + this.newRank = { + 'points': m.prop(''), + 'name': m.prop('') + }; + } + + + /** + * @returns {*} + */ + view() { + return [ + m('div', {className: 'SettingsPage'}, [ + m('div', {className: 'container'}, [ + m('form', {onsubmit: this.onsubmit.bind(this)}, [ + m('div', {className: 'helpText'}, app.translator.trans('reflar-gamification.admin.page.convert.help')), + (this.values.convertedLikes() === undefined ? ( + Button.component({ + type: 'button', + className: 'Button Button--warning Ranks-button', + children: app.translator.trans('reflar-gamification.admin.page.convert.button'), + onclick: () => { + app.request({ + url: app.forum.attribute('apiUrl') + '/reflar/gamification/convert', + method: 'POST' + }).then(this.values.convertedLikes('converting')); + } + }) + ) : (this.values.convertedLikes() === 'converting' ? ( + m('label', {}, app.translator.trans('reflar-gamification.admin.page.convert.converting')) + ) : (m('label', {}, app.translator.trans('reflar-gamification.admin.page.convert.converted', {number: this.values.convertedLikes()}))))), + + m('fieldset', {className: 'SettingsPage-ranks'}, [ + m('legend', {}, app.translator.trans('reflar-gamification.admin.page.ranks.title')), + m('label', {}, app.translator.trans('reflar-gamification.admin.page.ranks.ranks')), + m('div', {className: 'Ranks--Container'}, + Object.keys(this.values.ranks()).map(rank => { + return m('div', {}, [ + m('input', { + className: 'FormControl Ranks-number', + type: 'number', + value: rank, + oninput: m.withAttr('value', this.updateRankPoints.bind(this, rank)) + }), + m('input', { + className: 'FormControl Ranks-name', + value: this.values.ranks()[rank], + oninput: m.withAttr('value', this.updateRankName.bind(this, rank)) + }), + Button.component({ + type: 'button', + className: 'Button Button--warning Ranks-button', + icon: 'times', + onclick: this.deleteRank.bind(this, rank) + }), + ]) + }), + m('br'), + m('div', {}, [ + m('input', { + className: 'FormControl Ranks-number', + value: this.newRank.points(), + type: 'number', + oninput: m.withAttr('value', this.newRank.points) + }), + m('input', { + className: 'FormControl Ranks-name', + value: this.newRank.name(), + oninput: m.withAttr('value', this.newRank.name) + }), + Button.component({ + type: 'button', + className: 'Button Button--warning Ranks-button', + icon: 'plus', + onclick: this.addRank.bind(this) + }), + ]) + ), + m('div', {className: 'helpText'}, app.translator.trans('reflar-gamification.admin.page.ranks.help')), + m('label', {}, app.translator.trans('reflar-gamification.admin.page.ranks.default')), + m('input', { + className: 'FormControl Ranks-default', + value: this.values.defaultRank() || '', + placeholder: 'Newbie', + oninput: m.withAttr('value', this.values.defaultRank) + }), + ]), + m('div', {className: 'helpText'}, app.translator.trans('reflar-gamification.admin.page.ranks.default_help')), + Button.component({ + type: 'submit', + className: 'Button Button--primary Ranks-save', + children: app.translator.trans('reflar-gamification.admin.page.save_settings'), + loading: this.loading, + disabled: !this.changed() + }) + ]) + ]) + ]) + ]; + } + + + updateRankPoints(rank, value) { + this.values.ranks()[value] = this.values.ranks()[rank]; + + this.deleteRank(rank); + } + + updateRankName(rank, value) { + this.values.ranks()[rank] = value; + } + + deleteRank(rank) { + delete this.values.ranks()[rank]; + } + + addRank() { + this.values.ranks()[this.newRank.points()] = this.newRank.name(); + + this.newRank.points(''); + this.newRank.name(''); + } + + + /** + * + * @returns boolean + */ + changed() { + var fieldsCheck = this.fields.some(key => this.values[key]() !== app.data.settings[this.addPrefix(key)]); + var objectsCheck = this.objects.some(key => JSON.stringify(this.values[key]()) !== (app.data.settings[this.addPrefix(key)])); + return fieldsCheck || objectsCheck; + } + + /** + * @param e + */ + onsubmit(e) { + e.preventDefault(); + + if (this.loading) return; + + this.loading = true; + + app.alerts.dismiss(this.successAlert); + + const settings = {}; + + this.fields.forEach(key => settings[this.addPrefix(key)] = this.values[key]()); + this.objects.forEach(key => settings[this.addPrefix(key)] = JSON.stringify(this.values[key]())); + + saveSettings(settings) + .then(() => { + app.alerts.show(this.successAlert = new Alert({ + type: 'success', + children: app.translator.trans('core.admin.basics.saved_message') + })); + }) + .catch(() => { + }) + .then(() => { + this.loading = false; + window.location.reload(); + }); + } + + /** + * @returns string + */ + addPrefix(key) { + return this.settingsPrefix + '.' + key; + } +} diff --git a/js/admin/src/main.js b/js/admin/src/main.js new file mode 100644 index 0000000..651eb6a --- /dev/null +++ b/js/admin/src/main.js @@ -0,0 +1,9 @@ +import app from 'flarum/app'; + +import addSettingsPage from 'Reflar/gamification/addSettingsPage'; + +app.initializers.add('reflar-gamification', () => { + + addSettingsPage(); + +}); diff --git a/js/forum/Gulpfile.js b/js/forum/Gulpfile.js new file mode 100644 index 0000000..d940a88 --- /dev/null +++ b/js/forum/Gulpfile.js @@ -0,0 +1,9 @@ +var gulp = require('flarum-gulp'); + +gulp({ + modules: { + 'Reflar/gamification': [ + 'src/**/*.js' + ] + } +}); \ No newline at end of file diff --git a/js/forum/dist/extension.js b/js/forum/dist/extension.js new file mode 100644 index 0000000..7c842fc --- /dev/null +++ b/js/forum/dist/extension.js @@ -0,0 +1,602 @@ +'use strict'; + +System.register('Reflar/gamification/components/AddAttributes', ['flarum/helpers/avatar', 'flarum/helpers/username', 'flarum/extend', 'flarum/Model', 'flarum/models/Post', 'flarum/components/PostUser', 'flarum/models/User', 'flarum/components/UserCard', 'flarum/helpers/userOnline', 'flarum/helpers/listItems'], function (_export, _context) { + "use strict"; + + var avatar, username, extend, Model, Post, PostUser, User, UserCard, userOnline, listItems; + + _export('default', function () { + User.prototype.points = Model.attribute('points'); + User.prototype.Rank = Model.attribute('Rank'); + + Post.prototype.upvotes = Model.hasMany('upvotes'); + Post.prototype.downvotes = Model.hasMany('downvotes'); + + extend(UserCard.prototype, 'infoItems', function (items, user) { + var rank = this.props.user.data.attributes.Rank.split(': '); + if (rank[0] == '') { + rank[0] = app.forum.attribute('DefaultRank'); + } + items.add('points', app.translator.trans('reflar-gamification.forum.user.points', { points: this.props.user.data.attributes.Points })); + + items.add('rank', app.translator.trans('reflar-gamification.forum.user.rank', { rank: rank[0] })); + }); + + PostUser.prototype.view = function () { + var post = this.props.post; + var user = post.user(); + + var rank = user.Rank().split(': '); + + if (rank[0] == '') { + rank[0] = app.forum.attribute('DefaultRank'); + } + + if (!user) { + return m( + 'div', + { className: 'PostUser' }, + m( + 'h3', + null, + avatar(user, { className: 'PostUser-avatar' }), + ' ', + username(user), + ' ', + rank[0] + ) + ); + } + + var card = ''; + + if (!post.isHidden() && this.cardVisible) { + card = UserCard.component({ + user: user, + className: 'UserCard--popover', + controlsButtonClassName: 'Button Button--icon Button--flat' + }); + } + + return m( + 'div', + { className: 'PostUser' }, + userOnline(user), + m( + 'h3', + null, + m( + 'a', + { href: app.route.user(user), config: m.route }, + avatar(user, { className: 'PostUser-avatar' }), + ' ', + username(user) + ), + m( + 'span', + { className: 'Post-Rank', style: "color: " + rank[1] }, + rank[0] + ) + ), + m( + 'ul', + { className: 'PostUser-badges badges' }, + listItems(user.badges().toArray()) + ), + card + ); + }; + }); + + return { + setters: [function (_flarumHelpersAvatar) { + avatar = _flarumHelpersAvatar.default; + }, function (_flarumHelpersUsername) { + username = _flarumHelpersUsername.default; + }, function (_flarumExtend) { + extend = _flarumExtend.extend; + }, function (_flarumModel) { + Model = _flarumModel.default; + }, function (_flarumModelsPost) { + Post = _flarumModelsPost.default; + }, function (_flarumComponentsPostUser) { + PostUser = _flarumComponentsPostUser.default; + }, function (_flarumModelsUser) { + User = _flarumModelsUser.default; + }, function (_flarumComponentsUserCard) { + UserCard = _flarumComponentsUserCard.default; + }, function (_flarumHelpersUserOnline) { + userOnline = _flarumHelpersUserOnline.default; + }, function (_flarumHelpersListItems) { + listItems = _flarumHelpersListItems.default; + }], + execute: function () {} + }; +});; +'use strict'; + +System.register('Reflar/gamification/components/AddHotnessSort', ['flarum/extend', 'flarum/components/IndexPage', 'flarum/utils/ItemList', 'flarum/components/DiscussionList', 'flarum/components/Select'], function (_export, _context) { + "use strict"; + + var extend, IndexPage, ItemList, DiscussionList, Select; + + _export('default', function () { + IndexPage.prototype.viewItems = function () { + var items = new ItemList(); + var sortMap = app.cache.discussionList.sortMap(); + + var sortOptions = {}; + for (var i in sortMap) { + sortOptions[i] = app.translator.trans('core.forum.index_sort.' + i + '_button'); + } + + var sort = this.params().sort; + + if (this.props.routeName == 'index.filter') { + sort = 'hot'; + } + + items.add('sort', Select.component({ + options: sortOptions, + value: sort || Object.keys(sortMap)[0], + onchange: this.changeSort.bind(this) + })); + + return items; + }; + + IndexPage.prototype.changeSort = function (sort) { + var params = this.params(); + + if (sort === 'hot') { + m.route('/hot'); + } else { + if (sort === Object.keys(app.cache.discussionList.sortMap())[0]) { + delete params.sort; + } else { + params.sort = sort; + } + if (params.filter = 'hot') { + delete params.filter; + } + m.route(app.route('index', params)); + } + }; + + extend(DiscussionList.prototype, 'sortMap', function (map) { + map.hot = 'hot'; + }); + + extend(DiscussionList.prototype, 'requestParams', function (params) { + if (this.props.params.filter === 'hot') { + params.filter.q = ' is:hot'; + } + }); + }); + + return { + setters: [function (_flarumExtend) { + extend = _flarumExtend.extend; + }, function (_flarumComponentsIndexPage) { + IndexPage = _flarumComponentsIndexPage.default; + }, function (_flarumUtilsItemList) { + ItemList = _flarumUtilsItemList.default; + }, function (_flarumComponentsDiscussionList) { + DiscussionList = _flarumComponentsDiscussionList.default; + }, function (_flarumComponentsSelect) { + Select = _flarumComponentsSelect.default; + }], + execute: function () {} + }; +});; +'use strict'; + +System.register('Reflar/gamification/components/AddVoteButtons', ['flarum/extend', 'flarum/app', 'flarum/components/Button', 'flarum/components/CommentPost'], function (_export, _context) { + "use strict"; + + var extend, app, Button, CommentPost; + + _export('default', function () { + extend(CommentPost.prototype, 'actionItems', function (items) { + var post = this.props.post; + + if (post.isHidden()) return; + + var isUpvoted = app.session.user && post.upvotes().some(function (user) { + return user === app.session.user; + }); + var isDownvoted = app.session.user && post.downvotes().some(function (user) { + return user === app.session.user; + }); + + items.add('upvote', Button.component({ + icon: 'thumbs-up', + className: 'Post-vote Post-upvote', + style: isUpvoted !== false ? 'color:' + app.forum.attribute('themePrimaryColor') : 'color:', + onclick: function onclick() { + var upData = post.data.relationships.upvotes.data; + var downData = post.data.relationships.downvotes.data; + + isUpvoted = !isUpvoted; + + isDownvoted = false; + + post.save({ isUpvoted: isUpvoted, isDownvoted: isDownvoted }); + + upData.some(function (upvote, i) { + if (upvote.id === app.session.user.id()) { + upData.splice(i, 1); + return true; + } + }); + + downData.some(function (downvote, i) { + if (downvote.id === app.session.user.id()) { + downData.splice(i, 1); + return true; + } + }); + + if (isUpvoted) { + upData.unshift({ type: 'users', id: app.session.user.id() }); + } + } + })); + + items.add('points', m( + 'div', + { className: 'Post-points' }, + post.data.relationships.upvotes.data.length - post.data.relationships.downvotes.data.length + )); + + items.add('downvote', Button.component({ + icon: 'thumbs-down', + className: 'Post-vote Post-downvote', + style: isDownvoted !== false ? 'color:' + app.forum.attribute('themePrimaryColor') : '', + onclick: function onclick() { + var upData = post.data.relationships.upvotes.data; + var downData = post.data.relationships.downvotes.data; + + isDownvoted = !isDownvoted; + + isUpvoted = false; + + post.save({ isUpvoted: isUpvoted, isDownvoted: isDownvoted }); + + upData.some(function (upvote, i) { + if (upvote.id === app.session.user.id()) { + upData.splice(i, 1); + return true; + } + }); + + downData.some(function (downvote, i) { + if (downvote.id === app.session.user.id()) { + downData.splice(i, 1); + return true; + } + }); + + if (isDownvoted) { + downData.unshift({ type: 'users', id: app.session.user.id() }); + } + } + })); + }); + }); + + return { + setters: [function (_flarumExtend) { + extend = _flarumExtend.extend; + }, function (_flarumApp) { + app = _flarumApp.default; + }, function (_flarumComponentsButton) { + Button = _flarumComponentsButton.default; + }, function (_flarumComponentsCommentPost) { + CommentPost = _flarumComponentsCommentPost.default; + }], + execute: function () {} + }; +});; +'use strict'; + +System.register('Reflar/gamification/components/RankingsPage', ['flarum/helpers/avatar', 'flarum/Component', 'flarum/components/IndexPage', 'flarum/helpers/listItems', 'flarum/helpers/icon', 'flarum/helpers/username', 'flarum/components/UserCard'], function (_export, _context) { + "use strict"; + + var avatar, Component, IndexPage, listItems, icon, username, UserCard, RankingsPage; + return { + setters: [function (_flarumHelpersAvatar) { + avatar = _flarumHelpersAvatar.default; + }, function (_flarumComponent) { + Component = _flarumComponent.default; + }, function (_flarumComponentsIndexPage) { + IndexPage = _flarumComponentsIndexPage.default; + }, function (_flarumHelpersListItems) { + listItems = _flarumHelpersListItems.default; + }, function (_flarumHelpersIcon) { + icon = _flarumHelpersIcon.default; + }, function (_flarumHelpersUsername) { + username = _flarumHelpersUsername.default; + }, function (_flarumComponentsUserCard) { + UserCard = _flarumComponentsUserCard.default; + }], + execute: function () { + RankingsPage = function (_Component) { + babelHelpers.inherits(RankingsPage, _Component); + + function RankingsPage() { + babelHelpers.classCallCheck(this, RankingsPage); + return babelHelpers.possibleConstructorReturn(this, (RankingsPage.__proto__ || Object.getPrototypeOf(RankingsPage)).apply(this, arguments)); + } + + babelHelpers.createClass(RankingsPage, [{ + key: 'init', + value: function init() { + var _this2 = this; + + app.current = this; + this.cardVisible = false; + + app.request({ + method: 'GET', + url: app.forum.attribute('apiUrl') + '/rankings' + }).then(function (response) { + _this2.data = response.data; + _this2.users = []; + for (i = 0; i < _this2.data.length; i++) { + _this2.users[i] = []; + _this2.users[i]['user'] = _this2.findRecipient(_this2.data[i].id); + _this2.users[i]['class'] = i + 1; + } + console.log(_this2.users); + console.log(_this2.users[1]); + _this2.loading = false; + m.redraw(); + }); + } + }, { + key: 'view', + value: function view() { + return m( + 'div', + { className: 'RankingPage' }, + IndexPage.prototype.hero(), + m( + 'div', + { className: 'container' }, + m( + 'nav', + { className: 'IndexPage-nav sideNav', config: IndexPage.prototype.affixSidebar }, + m( + 'ul', + null, + listItems(IndexPage.prototype.sidebarItems().toArray()) + ) + ), + m( + 'div', + { className: 'sideNavOffset' }, + m( + 'table', + { 'class': 'rankings' }, + m( + 'tr', + null, + m( + 'th', + null, + app.translator.trans('reflar-gamification.forum.ranking.rank') + ), + m( + 'th', + null, + app.translator.trans('reflar-gamification.forum.ranking.name') + ), + m( + 'th', + null, + app.translator.trans('reflar-gamification.forum.ranking.amount') + ) + ), + this.users.map(function (user) { + + user['user'].then(function (user) { + + var card = ''; + + return [m( + 'tr', + null, + m( + 'td', + { 'class': "rankings-" + user['class'] }, + icon("trophy") + ), + m( + 'td', + null, + m( + 'div', + { className: 'PostUser' }, + m( + 'h3', + { className: 'rankings-info' }, + m( + 'a', + { href: app.route.user(user), config: m.route }, + avatar(user, { className: 'info-avatar rankings-' + user + '-avatar' }) + ) + ), + card + ) + ), + m( + 'td', + null, + user.data.attributes['antoinefr-money.money'] + ) + )]; + }); + }) + ) + ) + ) + ); + } + }, { + key: 'findRecipient', + value: function findRecipient(id) { + return app.store.find('users', id); + } + }, { + key: 'config', + value: function config(isInitialized) { + var _this3 = this; + + if (isInitialized) return; + + var timeout = void 0; + + this.$().on('mouseover', 'h3 a, .UserCard', function () { + clearTimeout(timeout); + timeout = setTimeout(_this3.showCard.bind(_this3), 500); + }).on('mouseout', 'h3 a, .UserCard', function () { + clearTimeout(timeout); + timeout = setTimeout(_this3.hideCard.bind(_this3), 250); + }); + } + }, { + key: 'showCard', + value: function showCard() { + var _this4 = this; + + this.cardVisible = true; + + m.redraw(); + + setTimeout(function () { + return _this4.$('.UserCard').addClass('in'); + }); + } + }, { + key: 'hideCard', + value: function hideCard() { + var _this5 = this; + + this.$('.UserCard').removeClass('in').one('transitionend webkitTransitionEnd oTransitionEnd', function () { + _this5.cardVisible = false; + m.redraw(); + }); + } + }]); + return RankingsPage; + }(Component); + + _export('default', RankingsPage); + } + }; +});; +'use strict'; + +System.register('Reflar/gamification/components/UserPromotedNotification', ['flarum/components/Notification', 'flarum/helpers/username', 'flarum/helpers/punctuateSeries'], function (_export, _context) { + "use strict"; + + var Notification, username, punctuateSeries, UserPromotedNotification; + return { + setters: [function (_flarumComponentsNotification) { + Notification = _flarumComponentsNotification.default; + }, function (_flarumHelpersUsername) { + username = _flarumHelpersUsername.default; + }, function (_flarumHelpersPunctuateSeries) { + punctuateSeries = _flarumHelpersPunctuateSeries.default; + }], + execute: function () { + UserPromotedNotification = function (_Notification) { + babelHelpers.inherits(UserPromotedNotification, _Notification); + + function UserPromotedNotification() { + babelHelpers.classCallCheck(this, UserPromotedNotification); + return babelHelpers.possibleConstructorReturn(this, (UserPromotedNotification.__proto__ || Object.getPrototypeOf(UserPromotedNotification)).apply(this, arguments)); + } + + babelHelpers.createClass(UserPromotedNotification, [{ + key: 'icon', + value: function icon() { + return 'thumbs-o-up'; + } + }, { + key: 'href', + value: function href() { + return app.route.post(this.props.notification.subject()); + } + }, { + key: 'content', + value: function content() { + var notification = this.props.notification; + var user = notification.sender(); + var auc = notification.additionalUnreadCount(); + + return app.translator.transChoice('flarum-likes.forum.notifications.post_liked_text', auc + 1, { + user: user, + username: auc ? punctuateSeries([username(user), app.translator.transChoice('flarum-likes.forum.notifications.others_text', auc, { count: auc })]) : undefined + }); + } + }, { + key: 'excerpt', + value: function excerpt() { + return this.props.notification.subject().contentPlain(); + } + }]); + return UserPromotedNotification; + }(Notification); + + _export('default', UserPromotedNotification); + } + }; +});; +'use strict'; + +System.register('Reflar/gamification/main', ['flarum/extend', 'flarum/app', 'Reflar/gamification/components/AddAttributes', 'Reflar/gamification/components/AddHotnessSort', 'Reflar/gamification/components/AddVoteButtons'], function (_export, _context) { + "use strict"; + + var extend, app, AddAttributes, AddHotnessFilter, AddVoteButtons; + return { + setters: [function (_flarumExtend) { + extend = _flarumExtend.extend; + }, function (_flarumApp) { + app = _flarumApp.default; + }, function (_ReflarGamificationComponentsAddAttributes) { + AddAttributes = _ReflarGamificationComponentsAddAttributes.default; + }, function (_ReflarGamificationComponentsAddHotnessSort) { + AddHotnessFilter = _ReflarGamificationComponentsAddHotnessSort.default; + }, function (_ReflarGamificationComponentsAddVoteButtons) { + AddVoteButtons = _ReflarGamificationComponentsAddVoteButtons.default; + }], + execute: function () { + // import UserPromotedNotification from 'Reflar/gamification/components/UserPromotedNotification'; + // import RankingsPage from 'Reflar/gamification/components/RankingsPage'; + + + app.initializers.add('Reflar-gamification', function () { + + // app.notificationComponents.userPromoted = UserPromotedNotification; + + // app.routes.page = {path: '/rankings', component: RankingsPage.component()}; + + AddVoteButtons(); + AddHotnessFilter(); + AddAttributes(); + + /** + extend(NotificationGrid.prototype, 'notificationTypes', function (items) { + items.add('userPromoted', { + name: 'userPromoted', + icon: 'arrow-up', + label: ['hi'] + }); + });*/ + }); + // import NotificationGrid from 'flarum/components/NotificationGrid'; + } + }; +}); \ No newline at end of file diff --git a/js/forum/package.json b/js/forum/package.json new file mode 100644 index 0000000..f4c413b --- /dev/null +++ b/js/forum/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "devDependencies": { + "gulp": "^3.9.1", + "flarum-gulp": "^0.2.0" + } +} \ No newline at end of file diff --git a/js/forum/src/components/AddAttributes.js b/js/forum/src/components/AddAttributes.js new file mode 100644 index 0000000..39f3151 --- /dev/null +++ b/js/forum/src/components/AddAttributes.js @@ -0,0 +1,79 @@ +import avatar from 'flarum/helpers/avatar'; +import username from 'flarum/helpers/username'; +import { extend } from 'flarum/extend'; +import Model from 'flarum/Model'; +import Post from 'flarum/models/Post'; +import PostUser from 'flarum/components/PostUser'; +import User from 'flarum/models/User'; +import UserCard from 'flarum/components/UserCard'; +import userOnline from 'flarum/helpers/userOnline'; +import listItems from 'flarum/helpers/listItems'; + +export default function () { + User.prototype.points = Model.attribute('points'); + User.prototype.Rank = Model.attribute('Rank'); + + Post.prototype.upvotes = Model.hasMany('upvotes'); + Post.prototype.downvotes = Model.hasMany('downvotes'); + + extend(UserCard.prototype, 'infoItems', function (items, user) { + let rank = this.props.user.data.attributes.Rank.split(': '); + if (rank[0] == '') { + rank[0] = app.forum.attribute('DefaultRank'); + } + items.add('points', + app.translator.trans('reflar-gamification.forum.user.points', {points: this.props.user.data.attributes.Points}) + ); + + items.add('rank', + app.translator.trans('reflar-gamification.forum.user.rank', {rank: rank[0]}) + ); + }); + + PostUser.prototype.view = function () { + const post = this.props.post; + const user = post.user(); + + const rank = user.Rank().split(': '); + + if (rank[0] == '') { + rank[0] = app.forum.attribute('DefaultRank'); + } + + if (!user) { + return ( +
{app.translator.trans('reflar-gamification.forum.ranking.rank')} | +{app.translator.trans('reflar-gamification.forum.ranking.name')} | +{app.translator.trans('reflar-gamification.forum.ranking.amount')} | +
---|---|---|
{icon("trophy")} | ++ + | +{user.data.attributes['antoinefr-money.money']} | +