├── .gitignore ├── .npmrc ├── .streerc ├── .rubocop.yml ├── .prettierrc.cjs ├── .template-lintrc.cjs ├── stylelint.config.mjs ├── eslint.config.mjs ├── Gemfile ├── spec ├── system │ └── core_features_spec.rb └── requests │ ├── teambuild_controller_spec.rb │ └── targets_controller_spec.rb ├── config ├── locales │ ├── server.be.yml │ ├── server.bg.yml │ ├── server.ca.yml │ ├── server.cs.yml │ ├── server.da.yml │ ├── server.el.yml │ ├── server.et.yml │ ├── server.gl.yml │ ├── server.hr.yml │ ├── server.hu.yml │ ├── server.hy.yml │ ├── server.id.yml │ ├── server.ko.yml │ ├── server.lt.yml │ ├── server.lv.yml │ ├── server.ro.yml │ ├── server.sk.yml │ ├── server.sl.yml │ ├── server.sq.yml │ ├── server.sr.yml │ ├── server.sw.yml │ ├── server.te.yml │ ├── server.th.yml │ ├── server.ug.yml │ ├── server.uk.yml │ ├── server.ur.yml │ ├── server.vi.yml │ ├── client.en_GB.yml │ ├── server.bs_BA.yml │ ├── server.en_GB.yml │ ├── server.fa_IR.yml │ ├── server.nb_NO.yml │ ├── server.pl_PL.yml │ ├── server.zh_TW.yml │ ├── server.en.yml │ ├── server.zh_CN.yml │ ├── server.ja.yml │ ├── server.he.yml │ ├── client.sq.yml │ ├── client.sr.yml │ ├── server.ar.yml │ ├── client.be.yml │ ├── server.sv.yml │ ├── client.ko.yml │ ├── client.sw.yml │ ├── client.zh_TW.yml │ ├── server.fi.yml │ ├── server.nl.yml │ ├── server.de.yml │ ├── server.tr_TR.yml │ ├── client.fa_IR.yml │ ├── client.id.yml │ ├── client.th.yml │ ├── server.pt.yml │ ├── client.bs_BA.yml │ ├── client.vi.yml │ ├── client.cs.yml │ ├── client.da.yml │ ├── client.et.yml │ ├── client.sk.yml │ ├── client.ug.yml │ ├── client.ur.yml │ ├── server.pt_BR.yml │ ├── client.bg.yml │ ├── client.ca.yml │ ├── client.gl.yml │ ├── client.hr.yml │ ├── client.hy.yml │ ├── client.lv.yml │ ├── client.nb_NO.yml │ ├── client.sl.yml │ ├── client.te.yml │ ├── client.hu.yml │ ├── client.pl_PL.yml │ ├── client.uk.yml │ ├── server.fr.yml │ ├── server.ru.yml │ ├── client.lt.yml │ ├── client.el.yml │ ├── client.ro.yml │ ├── server.it.yml │ ├── server.es.yml │ ├── client.en.yml │ ├── client.zh_CN.yml │ ├── client.ja.yml │ ├── client.he.yml │ ├── client.ar.yml │ ├── client.sv.yml │ ├── client.tr_TR.yml │ ├── client.ru.yml │ ├── client.nl.yml │ ├── client.pt_BR.yml │ ├── client.fi.yml │ ├── client.pt.yml │ ├── client.fr.yml │ ├── client.es.yml │ ├── client.it.yml │ └── client.de.yml ├── settings.yml └── routes.rb ├── translator.yml ├── .github └── workflows │ └── discourse-plugin.yml ├── assets ├── javascripts │ ├── discourse │ │ ├── templates │ │ │ ├── team-build │ │ │ │ ├── loading.gjs │ │ │ │ ├── manage.gjs │ │ │ │ ├── index.gjs │ │ │ │ └── progress.gjs │ │ │ └── team-build.gjs │ │ ├── controllers │ │ │ └── team-build │ │ │ │ ├── progress.js │ │ │ │ └── manage.js │ │ ├── adapters │ │ │ ├── teambuild-progress.js │ │ │ └── teambuild-target.js │ │ ├── routes │ │ │ └── team-build │ │ │ │ ├── progress.js │ │ │ │ ├── index.js │ │ │ │ ├── manage.js │ │ │ │ └── show.js │ │ ├── models │ │ │ ├── teambuild-target.js │ │ │ └── teambuild-progress.js │ │ └── components │ │ │ ├── teambuild-choice.gjs │ │ │ └── teambuild-target.gjs │ ├── discourse-teambuild-route-map.js │ └── initializers │ │ └── setup-teambuilding.js └── stylesheets │ └── team-build.scss ├── lib └── discourse_teambuild │ └── engine.rb ├── db └── migrate │ ├── 20241009162518_alter_teambuild_target_id_to_bigint.rb │ ├── 20191112145430_create_teambuild_targets.rb │ └── 20191112145431_create_teambuild_target_users.rb ├── package.json ├── .discourse-compatibility ├── .eslintignore ├── app ├── serializers │ ├── teambuild_target_serializer.rb │ └── teambuild_progress_serializer.rb ├── models │ ├── teambuild_target_user.rb │ └── teambuild_target.rb └── controllers │ └── discourse_teambuild │ ├── targets_controller.rb │ └── teambuild_controller.rb ├── plugin.rb ├── LICENSE ├── README.md ├── Gemfile.lock └── test └── javascripts └── acceptance └── manage-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /gems 3 | /auto_generated 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict = true 2 | auto-install-peers = false 3 | -------------------------------------------------------------------------------- /.streerc: -------------------------------------------------------------------------------- 1 | --print-width=100 2 | --plugins=plugin/trailing_comma 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-discourse: stree-compat.yml 3 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require("@discourse/lint-configs/prettier"); 2 | -------------------------------------------------------------------------------- /.template-lintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require("@discourse/lint-configs/template-lint"); 2 | -------------------------------------------------------------------------------- /stylelint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ["@discourse/lint-configs/stylelint"], 3 | }; 4 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import DiscourseRecommended from "@discourse/lint-configs/eslint"; 2 | 3 | export default [...DiscourseRecommended]; 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | group :development do 6 | gem "rubocop-discourse" 7 | gem "syntax_tree" 8 | end 9 | -------------------------------------------------------------------------------- /spec/system/core_features_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Core features", type: :system do 4 | before { enable_current_plugin } 5 | 6 | it_behaves_like "having working core features" 7 | end 8 | -------------------------------------------------------------------------------- /config/locales/server.be.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | be: 8 | -------------------------------------------------------------------------------- /config/locales/server.bg.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | bg: 8 | -------------------------------------------------------------------------------- /config/locales/server.ca.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ca: 8 | -------------------------------------------------------------------------------- /config/locales/server.cs.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | cs: 8 | -------------------------------------------------------------------------------- /config/locales/server.da.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | da: 8 | -------------------------------------------------------------------------------- /config/locales/server.el.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | el: 8 | -------------------------------------------------------------------------------- /config/locales/server.et.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | et: 8 | -------------------------------------------------------------------------------- /config/locales/server.gl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | gl: 8 | -------------------------------------------------------------------------------- /config/locales/server.hr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hr: 8 | -------------------------------------------------------------------------------- /config/locales/server.hu.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hu: 8 | -------------------------------------------------------------------------------- /config/locales/server.hy.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hy: 8 | -------------------------------------------------------------------------------- /config/locales/server.id.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | id: 8 | -------------------------------------------------------------------------------- /config/locales/server.ko.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ko: 8 | -------------------------------------------------------------------------------- /config/locales/server.lt.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | lt: 8 | -------------------------------------------------------------------------------- /config/locales/server.lv.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | lv: 8 | -------------------------------------------------------------------------------- /config/locales/server.ro.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ro: 8 | -------------------------------------------------------------------------------- /config/locales/server.sk.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sk: 8 | -------------------------------------------------------------------------------- /config/locales/server.sl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sl: 8 | -------------------------------------------------------------------------------- /config/locales/server.sq.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sq: 8 | -------------------------------------------------------------------------------- /config/locales/server.sr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sr: 8 | -------------------------------------------------------------------------------- /config/locales/server.sw.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sw: 8 | -------------------------------------------------------------------------------- /config/locales/server.te.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | te: 8 | -------------------------------------------------------------------------------- /config/locales/server.th.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | th: 8 | -------------------------------------------------------------------------------- /config/locales/server.ug.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ug: 8 | -------------------------------------------------------------------------------- /config/locales/server.uk.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | uk: 8 | -------------------------------------------------------------------------------- /config/locales/server.ur.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ur: 8 | -------------------------------------------------------------------------------- /config/locales/server.vi.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | vi: 8 | -------------------------------------------------------------------------------- /translator.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for discourse-translator-bot 2 | 3 | files: 4 | - source_path: config/locales/client.en.yml 5 | destination_path: client.yml 6 | - source_path: config/locales/server.en.yml 7 | destination_path: server.yml 8 | -------------------------------------------------------------------------------- /.github/workflows/discourse-plugin.yml: -------------------------------------------------------------------------------- 1 | name: Discourse Plugin 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | ci: 11 | uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1 12 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/team-build/loading.gjs: -------------------------------------------------------------------------------- 1 | import RouteTemplate from "ember-route-template"; 2 | import loadingSpinner from "discourse/helpers/loading-spinner"; 3 | 4 | export default RouteTemplate(); 5 | -------------------------------------------------------------------------------- /config/locales/client.en_GB.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | en_GB: 8 | -------------------------------------------------------------------------------- /config/locales/server.bs_BA.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | bs_BA: 8 | -------------------------------------------------------------------------------- /config/locales/server.en_GB.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | en_GB: 8 | -------------------------------------------------------------------------------- /config/locales/server.fa_IR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fa_IR: 8 | -------------------------------------------------------------------------------- /config/locales/server.nb_NO.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | nb_NO: 8 | -------------------------------------------------------------------------------- /config/locales/server.pl_PL.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pl_PL: 8 | -------------------------------------------------------------------------------- /config/locales/server.zh_TW.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | zh_TW: 8 | -------------------------------------------------------------------------------- /lib/discourse_teambuild/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ::DiscourseTeambuild 4 | PLUGIN_NAME = "discourse-teambuild" 5 | 6 | class Engine < ::Rails::Engine 7 | engine_name PLUGIN_NAME 8 | isolate_namespace DiscourseTeambuild 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/controllers/team-build/progress.js: -------------------------------------------------------------------------------- 1 | import Controller from "@ember/controller"; 2 | import { propertyNotEqual } from "discourse/lib/computed"; 3 | 4 | export default class TeamBuildProgressController extends Controller { 5 | @propertyNotEqual("currentUser.id", "progress.user.id") readOnly; 6 | } 7 | -------------------------------------------------------------------------------- /assets/javascripts/discourse-teambuild-route-map.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | this.route("teamBuild", { path: "/team-build" }, function () { 3 | this.route("index", { path: "/" }); 4 | this.route("progress"); 5 | this.route("manage"); 6 | this.route("show", { path: "/progress/:username" }); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/adapters/teambuild-progress.js: -------------------------------------------------------------------------------- 1 | import RestAdapter from "discourse/adapters/rest"; 2 | 3 | export default class TeambuildProgressAdapter extends RestAdapter { 4 | jsonMode = true; 5 | 6 | pathFor(store, type, username) { 7 | return `/team-build/progress/${username}.json`; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /config/settings.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | teambuild_name: 3 | default: "Team Building" 4 | client: true 5 | teambuild_enabled: 6 | default: false 7 | client: true 8 | teambuild_access_group: 9 | default: "" 10 | type: group 11 | teambuild_description: 12 | default: "" 13 | client: true 14 | textarea: true 15 | -------------------------------------------------------------------------------- /config/locales/server.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | site_settings: 3 | teambuild_enabled: Enable the Team Building Exercise 4 | teambuild_access_group: Group that can access the Team Building Exercise 5 | teambuild_name: The name of your team building game 6 | teambuild_description: An optional message to be displayed at the top of the team building interface 7 | -------------------------------------------------------------------------------- /db/migrate/20241009162518_alter_teambuild_target_id_to_bigint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AlterTeambuildTargetIdToBigint < ActiveRecord::Migration[7.1] 4 | def up 5 | change_column :teambuild_target_users, :teambuild_target_id, :bigint 6 | end 7 | 8 | def down 9 | raise ActiveRecord::IrreversibleMigration 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/adapters/teambuild-target.js: -------------------------------------------------------------------------------- 1 | import RestAdapter from "discourse/adapters/rest"; 2 | 3 | export default class TeambuildTargetAdapter extends RestAdapter { 4 | jsonMode = true; 5 | 6 | pathFor(store, type, id) { 7 | if (id) { 8 | return `/team-build/targets/${id}.json`; 9 | } 10 | return `/team-build/targets.json`; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/routes/team-build/progress.js: -------------------------------------------------------------------------------- 1 | import Route from "@ember/routing/route"; 2 | 3 | export default class TeamBuildProgressRoute extends Route { 4 | model() { 5 | return this.store.find("teambuild-progress", this.currentUser.username); 6 | } 7 | 8 | setupController(controller, progress) { 9 | controller.setProperties({ progress }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/routes/team-build/index.js: -------------------------------------------------------------------------------- 1 | import Route from "@ember/routing/route"; 2 | import { ajax } from "discourse/lib/ajax"; 3 | 4 | export default class TeamBuildIndexRoute extends Route { 5 | model() { 6 | return ajax("/team-build/scores.json"); 7 | } 8 | 9 | setupController(controller, model) { 10 | controller.set("scores", model.scores); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/routes/team-build/manage.js: -------------------------------------------------------------------------------- 1 | import Route from "@ember/routing/route"; 2 | 3 | export default class TeamBuildManageRoute extends Route { 4 | model() { 5 | return this.store.findAll("teambuild-target"); 6 | } 7 | 8 | setupController(controller, targets) { 9 | controller.setProperties({ 10 | targets: targets.content, 11 | groups: targets.extras.groups, 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /config/locales/server.zh_CN.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | zh_CN: 8 | site_settings: 9 | teambuild_enabled: 启用团建活动 10 | teambuild_access_group: 可以参加团建活动的群组 11 | teambuild_name: 您的团建游戏的名称 12 | teambuild_description: 在团建界面顶部显示的可选消息 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "@discourse/lint-configs": "2.32.0", 5 | "ember-template-lint": "7.9.1", 6 | "eslint": "9.37.0", 7 | "prettier": "3.6.2", 8 | "stylelint": "16.25.0" 9 | }, 10 | "engines": { 11 | "node": ">= 22", 12 | "npm": "please-use-pnpm", 13 | "yarn": "please-use-pnpm", 14 | "pnpm": "9.x" 15 | }, 16 | "packageManager": "pnpm@9.15.5" 17 | } 18 | -------------------------------------------------------------------------------- /config/locales/server.ja.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ja: 8 | site_settings: 9 | teambuild_enabled: チームビルディング演習を有効にする 10 | teambuild_access_group: チームビルディング演習にアクセスできるグループ 11 | teambuild_name: チームビルディングゲームの名前 12 | teambuild_description: チームビルディングインターフェースの上部に表示されるオプションのメッセージ 13 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/routes/team-build/show.js: -------------------------------------------------------------------------------- 1 | import Route from "@ember/routing/route"; 2 | 3 | export default class TeamBuildShowRoute extends Route { 4 | model(params) { 5 | return this.store.find("teambuild-progress", params.username); 6 | } 7 | 8 | setupController(controller, progress) { 9 | this.controllerFor("teamBuild.progress").setProperties({ progress }); 10 | } 11 | 12 | renderTemplate() { 13 | this.render("teamBuild.progress"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.discourse-compatibility: -------------------------------------------------------------------------------- 1 | < 3.6.0.beta2-dev: 5680141120fdf50f8fa399294b0f98c10b359007 2 | < 3.6.0.beta1-dev: 5c8a1a7d7de6d61b0da811966b4fda190d9062cb 3 | < 3.5.0.beta5-dev: e71a243cfca147d8ac24b504044614ce5fd09cef 4 | < 3.5.0.beta1-dev: 54a7970fd9ae1609e453199aeba5fe582b5e3672 5 | < 3.4.0.beta2-dev: 1eef24bc58166e366a66676cae1990f9308b95e7 6 | < 3.4.0.beta1-dev: 0beedd0add742911ac8fa2969d74ca050c8d7417 7 | < 3.3.0.beta1-dev: 5a8b188613b41e5c0064528d2aefa3d6e32853d6 8 | 3.1.999: abcfdd92acd46e11436aae96055f3951d6194794 9 | -------------------------------------------------------------------------------- /config/locales/server.he.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | he: 8 | site_settings: 9 | teambuild_enabled: הפעלת תרגיל גיבוש הצוות 10 | teambuild_access_group: קבוצה שיכולה לגשת לתרגיל גיבוש הצוות 11 | teambuild_name: שם משחק גיבוש הצוות שלך 12 | teambuild_description: הודעה כרשות שתוצג בראש מנשק גיבוש הקבוצה 13 | -------------------------------------------------------------------------------- /config/locales/client.sq.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sq: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "I zakonshëm" 13 | save: "ruaj" 14 | cancel: "anulo" 15 | edit: "redakto" 16 | delete: "fshij" 17 | scores: 18 | user: "User" 19 | -------------------------------------------------------------------------------- /config/locales/client.sr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sr: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Stalni član" 13 | save: "sačuvaj" 14 | cancel: "otkaži" 15 | edit: "izmeni" 16 | delete: "obriši" 17 | scores: 18 | user: "Korisnik" 19 | -------------------------------------------------------------------------------- /config/locales/server.ar.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ar: 8 | site_settings: 9 | teambuild_enabled: تفعيل تمرين بناء الفريق 10 | teambuild_access_group: المجموعة التي يمكنها الوصول إلى "تمرين بناء الفريق" 11 | teambuild_name: اسم لعبة بناء الفريق 12 | teambuild_description: رسالة اختيارية يتم عرضها في أعلى واجهة بناء الفريق 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | app/assets/javascripts/env.js 2 | app/assets/javascripts/main_include_admin.js 3 | app/assets/javascripts/vendor.js 4 | app/assets/javascripts/locales/i18n.js 5 | app/assets/javascripts/ember-addons/ 6 | app/assets/javascripts/discourse/lib/autosize.js.es6 7 | lib/javascripts/locale/ 8 | lib/javascripts/messageformat.js 9 | lib/highlight_js/ 10 | plugins/**/lib/javascripts/locale 11 | public/javascripts/ 12 | vendor/ 13 | test/javascripts/test_helper.js 14 | test/javascripts/fixtures 15 | test/javascripts/helpers/assertions.js 16 | -------------------------------------------------------------------------------- /config/locales/client.be.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | be: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "рэгулярнае" 13 | save: "стварыць..." 14 | cancel: "адмена" 15 | edit: "правіць" 16 | delete: "выдаляць" 17 | scores: 18 | user: "карыстальнік" 19 | score: "кошт" 20 | -------------------------------------------------------------------------------- /config/locales/server.sv.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sv: 8 | site_settings: 9 | teambuild_enabled: Aktivera teambuilding-övningen 10 | teambuild_access_group: Grupp som kan komma åt teambuilding-övningen 11 | teambuild_name: Namnet på ditt teambuilding-spel 12 | teambuild_description: Ett valfritt meddelande som visas överst i teambuilding-gränssnittet 13 | -------------------------------------------------------------------------------- /config/locales/client.ko.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ko: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "정회원" 13 | save: "저장" 14 | cancel: "취소" 15 | edit: "편집" 16 | delete: "삭제하기" 17 | manage: 18 | title: "관리" 19 | scores: 20 | user: "사용자" 21 | score: "점수" 22 | -------------------------------------------------------------------------------- /config/locales/client.sw.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sw: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Kawaida" 13 | save: "hifadhi" 14 | cancel: "ghairi" 15 | edit: "hariri" 16 | delete: "futa" 17 | manage: 18 | title: "Simamia" 19 | scores: 20 | user: "Mtumiaji" 21 | -------------------------------------------------------------------------------- /config/locales/client.zh_TW.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | zh_TW: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "活躍使用者" 13 | save: "保存" 14 | cancel: "取消" 15 | edit: "編輯" 16 | delete: "刪除" 17 | manage: 18 | title: "管理" 19 | scores: 20 | user: "使用者" 21 | score: "分數" 22 | -------------------------------------------------------------------------------- /config/locales/server.fi.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fi: 8 | site_settings: 9 | teambuild_enabled: Ota tiiminrakennusharjoitus käyttöön 10 | teambuild_access_group: Ryhmä, jolla on pääsy tiiminrakennusharjoitukseen 11 | teambuild_name: Tiiminrakennuspelisi nimi 12 | teambuild_description: Valinnainen viesti, joka näytetään tiiminrakennuskäyttöliittymän yläosassa 13 | -------------------------------------------------------------------------------- /config/locales/server.nl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | nl: 8 | site_settings: 9 | teambuild_enabled: Schakel de teambuildingoefening in 10 | teambuild_access_group: Groep die toegang heeft tot de teambuildingoefening 11 | teambuild_name: De naam van je teambuildingspel 12 | teambuild_description: Een optioneel bericht om weer te geven bovenaan de teambuildinginterface 13 | -------------------------------------------------------------------------------- /config/locales/server.de.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | de: 8 | site_settings: 9 | teambuild_enabled: Teambuilding-Übung aktivieren 10 | teambuild_access_group: Gruppe, die auf die Teambuilding-Übung zugreifen kann 11 | teambuild_name: Der Name deines Teambuilding-Spiels 12 | teambuild_description: Eine optionale Nachricht, die oben auf der Teambuilding-Oberfläche angezeigt wird 13 | -------------------------------------------------------------------------------- /config/locales/server.tr_TR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | tr_TR: 8 | site_settings: 9 | teambuild_enabled: Ekip Geliştirme Egzersizini Etkinleştirin 10 | teambuild_access_group: Ekip Geliştirme Egzersizine erişebilen grup 11 | teambuild_name: Ekip geliştirme oyununuzun adı 12 | teambuild_description: Ekip geliştirme arayüzünün üst kısmında görüntülenecek isteğe bağlı bir mesaj 13 | -------------------------------------------------------------------------------- /db/migrate/20191112145430_create_teambuild_targets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateTeambuildTargets < ActiveRecord::Migration[5.2] 4 | def change 5 | create_table :teambuild_targets do |t| 6 | t.string :name, null: false 7 | t.integer :target_type_id, null: false 8 | t.integer :group_id, null: true 9 | t.integer :position, null: false 10 | t.timestamps null: false 11 | end 12 | 13 | add_index :teambuild_targets, :name, unique: true 14 | add_index :teambuild_targets, :position, unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config/locales/client.fa_IR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fa_IR: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "معمولی" 13 | save: "ذخیره" 14 | cancel: "لغو" 15 | edit: "ویرایش" 16 | delete: "حذف" 17 | manage: 18 | title: "مدیریت" 19 | scores: 20 | user: "کاربر" 21 | score: "امتیاز" 22 | -------------------------------------------------------------------------------- /config/locales/client.id.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | id: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Umum" 13 | save: "simpan" 14 | cancel: "batal" 15 | edit: "ubah" 16 | delete: "hapus" 17 | manage: 18 | title: "Mengelola" 19 | scores: 20 | user: "Pengguna" 21 | score: "Skor" 22 | -------------------------------------------------------------------------------- /config/locales/client.th.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | th: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "ทั่วไป" 13 | save: "บันทึก" 14 | cancel: "ยกเลิก" 15 | edit: "แก้ไข" 16 | delete: "ลบ" 17 | manage: 18 | title: "จัดการ" 19 | scores: 20 | user: "ผู้ใช้" 21 | score: "คะแนน" 22 | -------------------------------------------------------------------------------- /config/locales/server.pt.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pt: 8 | site_settings: 9 | teambuild_enabled: Ativar o Team Building Exercise 10 | teambuild_access_group: Grupo que pode acerder a Team Building Exercise 11 | teambuild_name: O nome do seu jogo de criação de equipa 12 | teambuild_description: Uma mensagem opcional para ser exibida na parte superior da interface da criação de equipa. 13 | -------------------------------------------------------------------------------- /config/locales/client.bs_BA.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | bs_BA: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Normalno" 13 | save: "save" 14 | cancel: "otkaži" 15 | edit: "uredi" 16 | delete: "delete" 17 | manage: 18 | title: "Uredi" 19 | scores: 20 | user: "User" 21 | score: "bodovi" 22 | -------------------------------------------------------------------------------- /config/locales/client.vi.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | vi: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Đều đặn" 13 | save: "lưu lại" 14 | cancel: "hủy" 15 | edit: "sửa" 16 | delete: "xóa" 17 | manage: 18 | title: "Quản lý" 19 | scores: 20 | user: "Người dùng" 21 | score: "Điểm số" 22 | -------------------------------------------------------------------------------- /app/serializers/teambuild_target_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TeambuildTargetSerializer < ApplicationSerializer 4 | attributes(:id, :target_type_id, :name, :group_id, :group_name, :position) 5 | 6 | has_many :users, serializer: BasicUserSerializer 7 | 8 | def group_name 9 | object.group.name 10 | end 11 | 12 | def include_group_name? 13 | object.group_id.present? 14 | end 15 | 16 | def users 17 | object.group.users 18 | end 19 | 20 | def include_users? 21 | !!@options[:include_users] && object.group_id.present? 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/locales/client.cs.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | cs: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Běžný" 13 | save: "uložit" 14 | cancel: "zrušit" 15 | edit: "upravit" 16 | delete: "smazat" 17 | manage: 18 | title: "Spravovat" 19 | scores: 20 | user: "Uživatel" 21 | score: "Skóre" 22 | -------------------------------------------------------------------------------- /config/locales/client.da.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | da: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Almindelig" 13 | save: "gem" 14 | cancel: "annuller" 15 | edit: "rediger" 16 | delete: "slet" 17 | manage: 18 | title: "Administrér" 19 | scores: 20 | user: "Bruger" 21 | score: "Score" 22 | -------------------------------------------------------------------------------- /config/locales/client.et.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | et: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Püsiliige" 13 | save: "salvesta" 14 | cancel: "tühista" 15 | edit: "muuda" 16 | delete: "kustuta" 17 | manage: 18 | title: "Halda" 19 | scores: 20 | user: "Kasutaja" 21 | score: "Skoor" 22 | -------------------------------------------------------------------------------- /config/locales/client.sk.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sk: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Bežné" 13 | save: "uložiť" 14 | cancel: "zrušiť" 15 | edit: "uprav" 16 | delete: "odstrániť" 17 | manage: 18 | title: "Spravovať" 19 | scores: 20 | user: "Používateľ" 21 | score: "Skóre" 22 | -------------------------------------------------------------------------------- /config/locales/client.ug.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ug: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "ئادەتتىكى" 13 | save: "ساقلا" 14 | cancel: "ۋاز كەچ" 15 | edit: "تەھرىر" 16 | delete: "ئۆچۈر" 17 | manage: 18 | title: "باشقۇرۇش" 19 | scores: 20 | user: "ئىشلەتكۈچى" 21 | score: "نومۇر" 22 | -------------------------------------------------------------------------------- /config/locales/client.ur.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ur: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "رَیگولر" 13 | save: "محفوظ کریں" 14 | cancel: "منسوخ" 15 | edit: "ترمیم کریں" 16 | delete: "مٹائیں" 17 | manage: 18 | title: "مَینَیج" 19 | scores: 20 | user: "صارف" 21 | score: "اسکور" 22 | -------------------------------------------------------------------------------- /config/locales/server.pt_BR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pt_BR: 8 | site_settings: 9 | teambuild_enabled: Ative o exercício de formação de equipes 10 | teambuild_access_group: Grupo que pode acessar o exercício de formação de equipes 11 | teambuild_name: O nome o seu jogo de formar equipes 12 | teambuild_description: Uma mensagem opcional para exibir no topo da interface da formação de equipe 13 | -------------------------------------------------------------------------------- /config/locales/client.bg.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | bg: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Редовен" 13 | save: "запази " 14 | cancel: "прекрати" 15 | edit: "промени" 16 | delete: "изтрий" 17 | manage: 18 | title: "Управление" 19 | scores: 20 | user: "Потребител" 21 | score: "Точки" 22 | -------------------------------------------------------------------------------- /config/locales/client.ca.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ca: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Habitual" 13 | save: "desa" 14 | cancel: "cancel·la" 15 | edit: "edita" 16 | delete: "suprimeix" 17 | manage: 18 | title: "Gestiona" 19 | scores: 20 | user: "Usuari" 21 | score: "Puntuació" 22 | -------------------------------------------------------------------------------- /config/locales/client.gl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | gl: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Normal" 13 | save: "gardar" 14 | cancel: "cancelar" 15 | edit: "editar" 16 | delete: "eliminar" 17 | manage: 18 | title: "Xestionar" 19 | scores: 20 | user: "Usuario" 21 | score: "Puntuación" 22 | -------------------------------------------------------------------------------- /config/locales/client.hr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hr: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Stalni član" 13 | save: "spremi" 14 | cancel: "otkaži" 15 | edit: "uredi" 16 | delete: "pobriši" 17 | manage: 18 | title: "Upravljanje" 19 | scores: 20 | user: "Korisnik" 21 | score: "Ocjena" 22 | -------------------------------------------------------------------------------- /config/locales/client.hy.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hy: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Սովորական" 13 | save: "պահպանել" 14 | cancel: "չեղարկել" 15 | edit: "խմբագրել" 16 | delete: "ջնջել" 17 | manage: 18 | title: "Կառավարել" 19 | scores: 20 | user: "Օգտատեր" 21 | score: "Քանակ" 22 | -------------------------------------------------------------------------------- /config/locales/client.lv.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | lv: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Regulārs" 13 | save: "saglabāt" 14 | cancel: "atcelt" 15 | edit: "rediģēt" 16 | delete: "dzēst" 17 | manage: 18 | title: "Pārvaldīt" 19 | scores: 20 | user: "Lietotājs" 21 | score: "Rezultāts" 22 | -------------------------------------------------------------------------------- /config/locales/client.nb_NO.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | nb_NO: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Aktivt medlem" 13 | save: "lagre" 14 | cancel: "avbryt" 15 | edit: "endre" 16 | delete: "slett" 17 | manage: 18 | title: "Behandle" 19 | scores: 20 | user: "Bruker" 21 | score: "Poeng" 22 | -------------------------------------------------------------------------------- /config/locales/client.sl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sl: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Običajne" 13 | save: "shrani" 14 | cancel: "prekliči" 15 | edit: "uredi" 16 | delete: "izbriši" 17 | manage: 18 | title: "Upravljaj" 19 | scores: 20 | user: "Uporabnik" 21 | score: "Ocena" 22 | -------------------------------------------------------------------------------- /config/locales/client.te.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | te: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "రెగ్యులరు" 13 | save: "భద్రపరుచు" 14 | cancel: "రద్దు" 15 | edit: "సవరణ" 16 | delete: "తొలగించు" 17 | manage: 18 | title: "నిర్వహించండి" 19 | scores: 20 | user: "సభ్యుడు" 21 | score: "స్కోర్" 22 | -------------------------------------------------------------------------------- /config/locales/client.hu.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hu: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Rendes tag" 13 | save: "mentés" 14 | cancel: "mégse" 15 | edit: "szerkesztés" 16 | delete: "törlés" 17 | manage: 18 | title: "Kezelés" 19 | scores: 20 | user: "Felhasználó" 21 | score: "Pontszám" 22 | -------------------------------------------------------------------------------- /config/locales/client.pl_PL.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pl_PL: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Stały bywalec" 13 | save: "zapisz" 14 | cancel: "anuluj" 15 | edit: "edytuj" 16 | delete: "usuń" 17 | manage: 18 | title: "Zarządzaj" 19 | scores: 20 | user: "Użytkownik" 21 | score: "Wynik" 22 | -------------------------------------------------------------------------------- /config/locales/client.uk.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | uk: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Звичайний" 13 | save: "зберегти" 14 | cancel: "скасувати" 15 | edit: "редагувати" 16 | delete: "видалити" 17 | manage: 18 | title: "Керувати" 19 | scores: 20 | user: "Користувач" 21 | score: "Бал" 22 | -------------------------------------------------------------------------------- /config/locales/server.fr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fr: 8 | site_settings: 9 | teambuild_enabled: Activer l'exercice de renforcement d'équipe 10 | teambuild_access_group: Groupe pouvant accéder à l'exercice de renforcement d'équipe 11 | teambuild_name: Le nom de votre jeu de renforcement d'équipe 12 | teambuild_description: Un message facultatif à afficher en haut de l'interface de renforcement d'équipe 13 | -------------------------------------------------------------------------------- /config/locales/server.ru.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ru: 8 | site_settings: 9 | teambuild_enabled: Включить обучение по созданию команды 10 | teambuild_access_group: Группа, которая может получить доступ к обучению по созданию команды 11 | teambuild_name: Название вашей командной игры 12 | teambuild_description: Необязательное сообщение, которое будет отображаться в верхней части интерфейса командной игры 13 | -------------------------------------------------------------------------------- /config/locales/client.lt.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | lt: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Nuolatinės" 13 | save: "išsaugoti" 14 | cancel: "atšaukti" 15 | edit: "redaguoti" 16 | delete: "pašalinti" 17 | manage: 18 | title: "Redaguoti" 19 | scores: 20 | user: "Narys" 21 | score: "Rezultatas" 22 | -------------------------------------------------------------------------------- /config/locales/client.el.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | el: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Τακτικός" 13 | save: "αποθήκευση" 14 | cancel: "ακύρωση" 15 | edit: "επεξεργασία" 16 | delete: "σβήσιμο" 17 | manage: 18 | title: "Διαχειριστείτε" 19 | scores: 20 | user: "Χρήστης" 21 | score: "Βαθμολογία" 22 | -------------------------------------------------------------------------------- /config/locales/client.ro.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ro: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | types: 12 | regular: "Utilizator frecvent" 13 | save: "salvare" 14 | cancel: "Anulează" 15 | edit: "Editează" 16 | delete: "șterge" 17 | manage: 18 | title: "Gestionează" 19 | scores: 20 | user: "Utilizatori" 21 | score: "Scor" 22 | -------------------------------------------------------------------------------- /config/locales/server.it.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | it: 8 | site_settings: 9 | teambuild_enabled: Abilita l'Esercitazione di Team Building 10 | teambuild_access_group: Gruppo che può accedere all'Esercitazione di Team Building 11 | teambuild_name: Il nome della tua attività di team building 12 | teambuild_description: Un messaggio opzionale da visualizzare nella parte superiore dell'interfaccia di team building 13 | -------------------------------------------------------------------------------- /config/locales/server.es.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | es: 8 | site_settings: 9 | teambuild_enabled: Habilitar el Ejercicio de formación de equipos 10 | teambuild_access_group: Grupo que puede acceder al Ejercicio de formación de equipos 11 | teambuild_name: El nombre de tu juego de construcción de equipos 12 | teambuild_description: Un mensaje opcional que se mostrará en la parte superior de la interfaz de creación de equipos 13 | -------------------------------------------------------------------------------- /db/migrate/20191112145431_create_teambuild_target_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateTeambuildTargetUsers < ActiveRecord::Migration[5.2] 4 | def change 5 | create_table :teambuild_target_users do |t| 6 | t.integer :user_id, null: false 7 | t.integer :teambuild_target_id, null: false 8 | t.integer :target_user_id, null: false 9 | t.timestamps null: false 10 | end 11 | 12 | add_index :teambuild_target_users, 13 | %i[user_id teambuild_target_id target_user_id], 14 | name: :teambuild_unique_choice, 15 | unique: true 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/serializers/teambuild_progress_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TeambuildProgressSerializer < ApplicationSerializer 4 | attributes :id, :completed, :total 5 | has_many :teambuild_targets 6 | has_one :user, serializer: BasicUserSerializer 7 | 8 | def id 9 | user.id 10 | end 11 | 12 | def teambuild_targets 13 | object[:teambuild_targets] 14 | end 15 | 16 | def user 17 | object[:user] 18 | end 19 | 20 | def completed 21 | object[:completed] 22 | end 23 | 24 | def total 25 | teambuild_targets.inject(0) { |total, t| total + (t.group.present? ? t.group.users.size : 1) } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | DiscourseTeambuild::Engine.routes.draw do 4 | get "/" => "teambuild#index" 5 | get "/scores" => "teambuild#scores" 6 | get "/manage" => "teambuild#index", :constraints => StaffConstraint.new 7 | get "/progress" => "teambuild#progress" 8 | get "/progress/:username" => "teambuild#progress", 9 | :constraints => { 10 | username: RouteFormat.username, 11 | } 12 | put "/complete/:target_id/:user_id" => "teambuild#complete" 13 | delete "/undo/:target_id/:user_id" => "teambuild#undo" 14 | 15 | resources :targets, constraints: StaffConstraint.new do 16 | put "/swap-position" => "targets#swap_position" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /assets/javascripts/initializers/setup-teambuilding.js: -------------------------------------------------------------------------------- 1 | import { withPluginApi } from "discourse/lib/plugin-api"; 2 | 3 | export default { 4 | name: "setup-teambuilding", 5 | initialize() { 6 | withPluginApi((api) => { 7 | const currentUser = api.getCurrentUser(); 8 | if (currentUser?.can_access_teambuild) { 9 | api.addCommunitySectionLink((baseSectionLink) => { 10 | return class TeambuildSectionLink extends baseSectionLink { 11 | name = "team-building"; 12 | route = "teamBuild.progress"; 13 | text = this.siteSettings.teambuild_name; 14 | title = this.siteSettings.teambuild_name; 15 | }; 16 | }); 17 | } 18 | }); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /app/models/teambuild_target_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TeambuildTargetUser < ActiveRecord::Base 4 | belongs_to :user 5 | belongs_to :teambuild_target 6 | belongs_to :teambuild_target_user, class_name: "User" 7 | end 8 | 9 | # == Schema Information 10 | # 11 | # Table name: teambuild_target_users 12 | # 13 | # id :bigint not null, primary key 14 | # user_id :integer not null 15 | # teambuild_target_id :bigint not null 16 | # target_user_id :integer not null 17 | # created_at :datetime not null 18 | # updated_at :datetime not null 19 | # 20 | # Indexes 21 | # 22 | # teambuild_unique_choice (user_id,teambuild_target_id,target_user_id) UNIQUE 23 | # 24 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/models/teambuild-target.js: -------------------------------------------------------------------------------- 1 | import { ajax } from "discourse/lib/ajax"; 2 | import RestModel from "discourse/models/rest"; 3 | 4 | export const Types = { 5 | REGULAR: 1, 6 | USER_GROUP: 2, 7 | }; 8 | 9 | export default class TeambuildTarget extends RestModel { 10 | swapPosition(other) { 11 | let tmp = this.position; 12 | this.set("position", other.position); 13 | other.set("position", tmp); 14 | 15 | return ajax(`/team-build/targets/${this.id}/swap-position`, { 16 | method: "PUT", 17 | data: { other_id: other.id }, 18 | }); 19 | } 20 | 21 | complete(userId) { 22 | return ajax(`/team-build/complete/${this.id}/${userId}`, { 23 | method: "PUT", 24 | }); 25 | } 26 | 27 | undo(userId) { 28 | return ajax(`/team-build/undo/${this.id}/${userId}`, { 29 | method: "DELETE", 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/team-build.gjs: -------------------------------------------------------------------------------- 1 | import RouteTemplate from "ember-route-template"; 2 | import NavItem from "discourse/components/nav-item"; 3 | 4 | export default RouteTemplate( 5 | 30 | ); 31 | -------------------------------------------------------------------------------- /config/locales/client.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | js: 3 | discourse_teambuild: 4 | targets: 5 | create: "Create Target" 6 | types: 7 | regular: "Regular" 8 | user_group: "User Group" 9 | name: "Target Name" 10 | save: "save" 11 | cancel: "cancel" 12 | edit: "edit" 13 | delete: "delete" 14 | choose_group: "(choose a group)" 15 | manage: 16 | title: "Manage" 17 | get_started: "Create a target to get started." 18 | scores: 19 | title: "High Scores" 20 | none: "Nobody has participated yet." 21 | rank: "Rank" 22 | user: "User" 23 | score: "Score" 24 | progress: 25 | title: "Progress" 26 | completed: "Completed" 27 | none: "There is nothing to do right now. Things likely haven't been configured yet." 28 | mark_complete: "Mark complete" 29 | mark_incomplete: "Mark incomplete" 30 | complete: "Complete" 31 | -------------------------------------------------------------------------------- /config/locales/client.zh_CN.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | zh_CN: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "创建目标" 12 | types: 13 | regular: "常规" 14 | user_group: "用户群组" 15 | name: "目标名称" 16 | save: "保存" 17 | cancel: "取消" 18 | edit: "编辑" 19 | delete: "删除" 20 | choose_group: "(选择群组)" 21 | manage: 22 | title: "管理" 23 | get_started: "创建一个目标以开始。" 24 | scores: 25 | title: "最高分" 26 | none: "还没有人参与。" 27 | rank: "排名" 28 | user: "用户" 29 | score: "分数" 30 | progress: 31 | title: "进度" 32 | completed: "已完成" 33 | none: "现在没有什么可做的。活动可能还没有配置好。" 34 | mark_complete: "标记完成" 35 | mark_incomplete: "标记未完成" 36 | complete: "完成" 37 | -------------------------------------------------------------------------------- /plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # name: discourse-teambuild 4 | # about: Allows admins to run team building scavenger hunt and checklist activities. 5 | # meta_topic_id: 134907 6 | # version: 0.0.1 7 | # authors: Robin Ward 8 | # url: https://github.com/discourse/discourse-teambuild 9 | 10 | require_relative "lib/discourse_teambuild/engine" 11 | 12 | enabled_site_setting :teambuild_enabled 13 | 14 | register_svg_icon "campground" if respond_to?(:register_svg_icon) 15 | register_asset "stylesheets/team-build.scss" 16 | 17 | Discourse::Application.routes.append { mount ::DiscourseTeambuild::Engine, at: "/team-build" } 18 | 19 | after_initialize do 20 | add_to_class(:guardian, :has_teambuild_access?) do 21 | return false unless SiteSetting.teambuild_enabled? 22 | return true if is_admin? 23 | @user.groups.where(name: SiteSetting.teambuild_access_group).exists? 24 | end 25 | 26 | add_to_serializer(:current_user, :can_access_teambuild) { object.guardian.has_teambuild_access? } 27 | end 28 | -------------------------------------------------------------------------------- /config/locales/client.ja.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ja: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "ターゲットを作成" 12 | types: 13 | regular: "レギュラー" 14 | user_group: "ユーザーグループ" 15 | name: "ターゲット名" 16 | save: "保存" 17 | cancel: "キャンセル" 18 | edit: "編集" 19 | delete: "削除" 20 | choose_group: "(グループを選択)" 21 | manage: 22 | title: "管理" 23 | get_started: "開始するにはターゲットを作成してください。" 24 | scores: 25 | title: "ハイスコア" 26 | none: "まだ誰も参加していません。" 27 | rank: "ランク" 28 | user: "ユーザー" 29 | score: "スコア" 30 | progress: 31 | title: "進捗" 32 | completed: "完了" 33 | none: "現在、何も行うことがありません。まだ設定されていない可能性があります。" 34 | mark_complete: "完了としてマークする" 35 | mark_incomplete: "未完了としてマークする" 36 | complete: "完了" 37 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/models/teambuild-progress.js: -------------------------------------------------------------------------------- 1 | import { popupAjaxError } from "discourse/lib/ajax-error"; 2 | import { 3 | addUniqueValueToArray, 4 | removeValueFromArray, 5 | } from "discourse/lib/array-tools"; 6 | import { trackedArray } from "discourse/lib/tracked-tools"; 7 | import RestModel from "discourse/models/rest"; 8 | 9 | function choiceKey(target, userId) { 10 | return `${target.id}:${userId}`; 11 | } 12 | 13 | export default class TeambuildProgress extends RestModel { 14 | @trackedArray completed = []; 15 | 16 | isComplete(target, userId) { 17 | return this.completed.includes(choiceKey(target, userId)); 18 | } 19 | 20 | complete(target, userId) { 21 | target 22 | .complete(userId) 23 | .then(() => { 24 | addUniqueValueToArray(this.completed, choiceKey(target, userId)); 25 | }) 26 | .catch(popupAjaxError); 27 | } 28 | 29 | undo(target, userId) { 30 | target 31 | .undo(userId) 32 | .then(() => { 33 | removeValueFromArray(this.completed, choiceKey(target, userId)); 34 | }) 35 | .catch(popupAjaxError); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/locales/client.he.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | he: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "יצירת יעד" 12 | types: 13 | regular: "רגיל" 14 | user_group: "קבוצת משתמשים" 15 | name: "שם יעד" 16 | save: "שמירה" 17 | cancel: "ביטול" 18 | edit: "עריכה" 19 | delete: "מחיקה" 20 | choose_group: "(נא לבחור קבוצה)" 21 | manage: 22 | title: "ניהול" 23 | get_started: "יש ליצור יעד כדי להתחיל." 24 | scores: 25 | title: "ציונים גבוהים" 26 | none: "עוד אין משתתפים." 27 | rank: "דירוג" 28 | user: "משתמש" 29 | score: "ניקוד" 30 | progress: 31 | title: "התקדמות" 32 | completed: "הושלמו" 33 | none: "כרגע אין מה לעשות. כנראה שעוד חסרות הגדרות." 34 | mark_complete: "סימון שהושלם" 35 | mark_incomplete: "סימון שלא הושלם" 36 | complete: "הושלם" 37 | -------------------------------------------------------------------------------- /config/locales/client.ar.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ar: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "إنشاء هدف" 12 | types: 13 | regular: "منتظم" 14 | user_group: "مجموعة المستخدم" 15 | name: "اسم الهدف" 16 | save: "حفظ" 17 | cancel: "إلغاء" 18 | edit: "تعديل" 19 | delete: "حذف" 20 | choose_group: "(اختيار مجموعة)" 21 | manage: 22 | title: "إدارة" 23 | get_started: "أنشئ هدفًا للبدء." 24 | scores: 25 | title: "نقاط عالية" 26 | none: "لم يشارك أحد بعد." 27 | rank: "المرتبة" 28 | user: "المستخدم" 29 | score: "النقاط" 30 | progress: 31 | title: "التقدُّم" 32 | completed: "اكتمل" 33 | none: "لا يوجد شيء لفعله الآن. من المرجَّح أنه لم يتم الإعداد بعد." 34 | mark_complete: "وضع علامة كمكتمل" 35 | mark_incomplete: "وضع علامة كغير مكتمل" 36 | complete: "مكتمل" 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Civilized Discourse Construction Kit, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/locales/client.sv.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sv: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Skapa mål" 12 | types: 13 | regular: "Vanlig" 14 | user_group: "Användargrupp" 15 | name: "Målnamn" 16 | save: "spara" 17 | cancel: "avbryt" 18 | edit: "redigera" 19 | delete: "radera" 20 | choose_group: "(välj en grupp)" 21 | manage: 22 | title: "Hantera" 23 | get_started: "Skapa ett mål för att komma igång." 24 | scores: 25 | title: "Högsta poäng" 26 | none: "Ingen har deltagit än." 27 | rank: "Rank" 28 | user: "Användare" 29 | score: "Poäng" 30 | progress: 31 | title: "Framsteg" 32 | completed: "Slutförd" 33 | none: "Det finns inget att göra just nu. Saker har troligen inte konfigurerats än." 34 | mark_complete: "Markera som klar" 35 | mark_incomplete: "Markera som ofullständig" 36 | complete: "Slutför" 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # discourse-teambuild 2 | 3 | https://meta.discourse.org/t/discourse-teambuild-run-your-own-team-building-activity/134907 4 | 5 | This plugin was extracted from a game the Discourse team played while meeting up in Montreal 6 | in 2019. The game is similar to a scavenger hunt, in that each player can check off an item 7 | that they did while on the trip. The player with the most items checked off wins the game. 8 | 9 | Our items were based on experiences and team bonding. We also had a special item, which 10 | was to encourage team members to share a meal together. In that case, they could get one 11 | point per employee they spent time with. 12 | 13 | ### Getting Started 14 | 15 | After installing the plugin: 16 | 17 | 1. Check the `teambuild enabled` site setting. 18 | 19 | 2. Grant access to the game by assigning a `teambuild access group`. All members in that group will be able to participate. 20 | 21 | 3. Create/Edit targets for your participants by visiting `/team-build/manage` 22 | 23 | - A regular target is a simple name (that supports emoji). For example: "Wear a hat :tophat:" 24 | 25 | - A user group target will list all the members in the group as individual goals. For example "Say good morning to" 26 | -------------------------------------------------------------------------------- /config/locales/client.tr_TR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | tr_TR: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Hedef Oluştur" 12 | types: 13 | regular: "Normal" 14 | user_group: "Kullanıcı Grubu" 15 | name: "Hedef İsmi" 16 | save: "kaydet" 17 | cancel: "iptal et" 18 | edit: "düzenle" 19 | delete: "sil" 20 | choose_group: "(bir grup seçin)" 21 | manage: 22 | title: "Yönet" 23 | get_started: "Başlamak için bir hedef oluşturun." 24 | scores: 25 | title: "Yüksek Puanlar" 26 | none: "Henüz kimse katılmadı." 27 | rank: "Rütbe" 28 | user: "Kullanıcı" 29 | score: "Puan" 30 | progress: 31 | title: "İlerleme" 32 | completed: "Tamamlandı" 33 | none: "Şu anda yapacak bir şey yok. İşler muhtemelen henüz yapılandırılmamıştır." 34 | mark_complete: "Tamamlandı olarak işaretle" 35 | mark_incomplete: "Tamamlanmadı olarak işaretle" 36 | complete: "Tamamlandı" 37 | -------------------------------------------------------------------------------- /config/locales/client.ru.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ru: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Создание цели" 12 | types: 13 | regular: "Обычная" 14 | user_group: "Группа пользователей" 15 | name: "Название цели" 16 | save: "сохранить" 17 | cancel: "отмена" 18 | edit: "изменить" 19 | delete: "удалить" 20 | choose_group: "(выбрать группу)" 21 | manage: 22 | title: "Управление" 23 | get_started: "Создайте цель, чтобы приступить к работе." 24 | scores: 25 | title: "Высшие показатели" 26 | none: "Никто ещё не участвовал." 27 | rank: "Ранг" 28 | user: "Пользователь" 29 | score: "Счёт" 30 | progress: 31 | title: "Прогресс" 32 | completed: "Завершено" 33 | none: "Пока что делать нечего. Скорее всего, настройка ещё не завершена." 34 | mark_complete: "Отметить завершённым" 35 | mark_incomplete: "Отметить неполным" 36 | complete: "Завершено" 37 | -------------------------------------------------------------------------------- /config/locales/client.nl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | nl: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Doel maken" 12 | types: 13 | regular: "Normaal" 14 | user_group: "Gebruikersgroep" 15 | name: "Doelnaam" 16 | save: "opslaan" 17 | cancel: "annuleren" 18 | edit: "bewerken" 19 | delete: "verwijderen" 20 | choose_group: "(kies een groep)" 21 | manage: 22 | title: "Beheren" 23 | get_started: "Maak een doel om te beginnen." 24 | scores: 25 | title: "Highscores" 26 | none: "Er heeft nog niemand deelgenomen." 27 | rank: "Rang" 28 | user: "Gebruiker" 29 | score: "Score" 30 | progress: 31 | title: "Voortgang" 32 | completed: "Voltooid" 33 | none: "Er is op dit moment niets te doen. Dingen zijn waarschijnlijk nog niet geconfigureerd." 34 | mark_complete: "Als voltooid markeren" 35 | mark_incomplete: "Als onvoltooid markeren" 36 | complete: "Voltooien" 37 | -------------------------------------------------------------------------------- /config/locales/client.pt_BR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pt_BR: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Criar destino" 12 | types: 13 | regular: "Normal" 14 | user_group: "Grupo de usuários(as)" 15 | name: "Nome do destino" 16 | save: "Salvar" 17 | cancel: "cancelar" 18 | edit: "editar" 19 | delete: "excluir" 20 | choose_group: "(escolha um grupo)" 21 | manage: 22 | title: "Gerenciar" 23 | get_started: "Crie um destino para começar." 24 | scores: 25 | title: "Pontuação alta" 26 | none: "Ninguém participou ainda." 27 | rank: "Ranque" 28 | user: "Usuário(a)" 29 | score: "Pontuação" 30 | progress: 31 | title: "Progresso" 32 | completed: "Concluído" 33 | none: "Não há nada a fazer agora. Talvez as coisas não tenham sido configuradas ainda." 34 | mark_complete: "Marca concluída" 35 | mark_incomplete: "Marca incompleta" 36 | complete: "Completar" 37 | -------------------------------------------------------------------------------- /config/locales/client.fi.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fi: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Luo tavoite" 12 | types: 13 | regular: "Tavallinen" 14 | user_group: "Käyttäjäryhmä" 15 | name: "Tavoitteen nimi" 16 | save: "tallenna" 17 | cancel: "peruuta" 18 | edit: "muokkaa" 19 | delete: "poista" 20 | choose_group: "(valitse ryhmä)" 21 | manage: 22 | title: "Hallitse" 23 | get_started: "Aloita luomalla tavoite." 24 | scores: 25 | title: "Korkeimmat pistemäärät" 26 | none: "Kukaan ei ole vielä osallistunut." 27 | rank: "Sijoitus" 28 | user: "Käyttäjä" 29 | score: "Pistemäärä" 30 | progress: 31 | title: "Edistyminen" 32 | completed: "Valmis" 33 | none: "Tällä hetkellä ei ole mitään tehtävänä, määrityksiä ei ole vielä luultavasti tehty." 34 | mark_complete: "Merkitse valmiiksi" 35 | mark_incomplete: "Merkitse keskeneräiseksi" 36 | complete: "Valmis" 37 | -------------------------------------------------------------------------------- /config/locales/client.pt.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pt: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Criar Objetivo" 12 | types: 13 | regular: "Regular" 14 | user_group: "Grupo de Utilizadores" 15 | name: "Nome de Objetivo" 16 | save: "guardar" 17 | cancel: "cancelar" 18 | edit: "editar" 19 | delete: "eliminar" 20 | choose_group: "(escolher um grupo)" 21 | manage: 22 | title: "Gerir" 23 | get_started: "Crie um objetivo para começar." 24 | scores: 25 | title: "Pontuações Elevadas" 26 | none: "Ainda ninguém participou." 27 | rank: "Posição" 28 | user: "Utilizador" 29 | score: "Pontuação" 30 | progress: 31 | title: "Progresso" 32 | completed: "Concluído" 33 | none: "Não há nada para fazer de momento. As coisas provavelmente ainda não foram configuradas." 34 | mark_complete: "Marcar concluído" 35 | mark_incomplete: "Marcar incompleto" 36 | complete: "Concluído" 37 | -------------------------------------------------------------------------------- /config/locales/client.fr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fr: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Créer une cible" 12 | types: 13 | regular: "Normal" 14 | user_group: "Groupe d'utilisateurs" 15 | name: "Nom de la cible" 16 | save: "enregistrer" 17 | cancel: "annuler" 18 | edit: "modifier" 19 | delete: "supprimer" 20 | choose_group: "(choisir un groupe)" 21 | manage: 22 | title: "Gérer" 23 | get_started: "Créez une cible pour commencer." 24 | scores: 25 | title: "Meilleurs scores" 26 | none: "Personne n'a encore participé." 27 | rank: "Rang" 28 | user: "Utilisateur" 29 | score: "Score" 30 | progress: 31 | title: "Progrès" 32 | completed: "Terminé" 33 | none: "Il n'y a rien à faire pour le moment. Il est probable que rien n'ait encore été configuré." 34 | mark_complete: "Marquer comme terminé" 35 | mark_incomplete: "Marquer comme incomplet" 36 | complete: "Terminer" 37 | -------------------------------------------------------------------------------- /config/locales/client.es.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | es: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Crear objetivo" 12 | types: 13 | regular: "Habitual" 14 | user_group: "Grupo de usuarios" 15 | name: "Nombre del objetivo" 16 | save: "guardar" 17 | cancel: "cancelar" 18 | edit: "editar" 19 | delete: "eliminar" 20 | choose_group: "(elige un grupo)" 21 | manage: 22 | title: "Gestionar" 23 | get_started: "Crea un objetivo para empezar." 24 | scores: 25 | title: "Puntuaciones altas" 26 | none: "Aún no ha participado nadie." 27 | rank: "Rango" 28 | user: "Usuario" 29 | score: "Puntuación" 30 | progress: 31 | title: "Progreso" 32 | completed: "Completado" 33 | none: "No hay nada que hacer en este momento. Es probable que las cosas aún no se hayan configurado." 34 | mark_complete: "Marcar como completado" 35 | mark_incomplete: "Marcar como incompleto" 36 | complete: "Completar" 37 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/controllers/team-build/manage.js: -------------------------------------------------------------------------------- 1 | import Controller from "@ember/controller"; 2 | import { action } from "@ember/object"; 3 | import { sort } from "@ember/object/computed"; 4 | import { removeValueFromArray } from "discourse/lib/array-tools"; 5 | import { Types } from "discourse/plugins/discourse-teambuild/discourse/models/teambuild-target"; 6 | 7 | export default class TeamBuildManageController extends Controller { 8 | targets = null; 9 | targetSort = ["position"]; 10 | 11 | @sort("targets", "targetSort") sortedTargets; 12 | 13 | @action 14 | move(idx, direction) { 15 | let item = this.sortedTargets[idx]; 16 | let other = this.sortedTargets[idx + direction]; 17 | if (item && other) { 18 | item.swapPosition(other); 19 | } 20 | } 21 | 22 | @action 23 | newTarget() { 24 | let maxPosition = 0; 25 | if (this.targets.length > 0) { 26 | maxPosition = Math.max(...this.targets.map((t) => t.position)); 27 | } 28 | this.targets.push( 29 | this.store.createRecord("teambuild-target", { 30 | target_type_id: Types.REGULAR, 31 | position: maxPosition + 1, 32 | }) 33 | ); 34 | } 35 | 36 | @action 37 | removeTarget(t) { 38 | removeValueFromArray(this.targets, t); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config/locales/client.it.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | it: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Crea destinazione" 12 | types: 13 | regular: "Abituale" 14 | user_group: "Gruppo di utenti" 15 | name: "Nome destinazione" 16 | save: "salva" 17 | cancel: "annulla" 18 | edit: "modifica" 19 | delete: "elimina" 20 | choose_group: "(scegli un gruppo)" 21 | manage: 22 | title: "Gestisci" 23 | get_started: "Crea una destinazione per iniziare." 24 | scores: 25 | title: "Punteggi più alti" 26 | none: "Nessuno ha ancora partecipato." 27 | rank: "Classifica" 28 | user: "Utente" 29 | score: "Punteggio" 30 | progress: 31 | title: "Avanzamento" 32 | completed: "Completato" 33 | none: "Al momento non c'è niente da fare. Forse non sono state ancora eseguite le configurazioni." 34 | mark_complete: "Contrassegna come completo" 35 | mark_incomplete: "Contrassegna come incompleto" 36 | complete: "Completo" 37 | -------------------------------------------------------------------------------- /config/locales/client.de.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | de: 8 | js: 9 | discourse_teambuild: 10 | targets: 11 | create: "Ziel erstellen" 12 | types: 13 | regular: "Regulär" 14 | user_group: "Benutzergruppe" 15 | name: "Zielname" 16 | save: "speichern" 17 | cancel: "abbrechen" 18 | edit: "bearbeiten" 19 | delete: "löschen" 20 | choose_group: "(wähle eine Gruppe)" 21 | manage: 22 | title: "Verwalten" 23 | get_started: "Erstelle ein Ziel, um loszulegen." 24 | scores: 25 | title: "Höchstpunktzahlen" 26 | none: "Bisher hat noch niemand teilgenommen." 27 | rank: "Rang" 28 | user: "Benutzer" 29 | score: "Punktzahl" 30 | progress: 31 | title: "Fortschritt" 32 | completed: "Abgeschlossen" 33 | none: "Im Moment gibt es nichts zu tun. Die Konfiguration wurde vermutlich noch nicht abgeschlossen." 34 | mark_complete: "Als abgeschlossen markieren" 35 | mark_incomplete: "Als unvollständig markieren" 36 | complete: "Abgeschlossen" 37 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/team-build/manage.gjs: -------------------------------------------------------------------------------- 1 | import { fn } from "@ember/helper"; 2 | import RouteTemplate from "ember-route-template"; 3 | import DButton from "discourse/components/d-button"; 4 | import { i18n } from "discourse-i18n"; 5 | import TeambuildTarget from "../../components/teambuild-target"; 6 | 7 | export default RouteTemplate( 8 | 38 | ); 39 | -------------------------------------------------------------------------------- /app/models/teambuild_target.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TeambuildTarget < ActiveRecord::Base 4 | belongs_to :user 5 | belongs_to :group 6 | has_many :teambuild_target_users, dependent: :destroy 7 | 8 | before_create :default_position 9 | validates :name, uniqueness: true 10 | default_scope { order(:position) } 11 | 12 | validates :group_id, 13 | presence: true, 14 | if: -> { target_type_id == TeambuildTarget.target_types[:user_group] } 15 | 16 | def self.target_types 17 | @target_types ||= Enum.new(regular: 1, user_group: 2) 18 | end 19 | 20 | # This is not safe under concurrency, but it's unlikely someone will be creating 21 | # targets that actively. 22 | def default_position 23 | self.position = (TeambuildTarget.maximum(:position) || 0) + 1 24 | end 25 | end 26 | 27 | # == Schema Information 28 | # 29 | # Table name: teambuild_targets 30 | # 31 | # id :bigint not null, primary key 32 | # name :string not null 33 | # target_type_id :integer not null 34 | # group_id :integer 35 | # position :integer not null 36 | # created_at :datetime not null 37 | # updated_at :datetime not null 38 | # 39 | # Indexes 40 | # 41 | # index_teambuild_targets_on_name (name) UNIQUE 42 | # index_teambuild_targets_on_position (position) UNIQUE 43 | # 44 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/teambuild-choice.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@ember/component"; 2 | import { action } from "@ember/object"; 3 | import { tagName } from "@ember-decorators/component"; 4 | import DButton from "discourse/components/d-button"; 5 | import icon from "discourse/helpers/d-icon"; 6 | 7 | @tagName("") 8 | export default class TeambuildChoice extends Component { 9 | get completed() { 10 | return this.progress.isComplete(this.target, this.userId); 11 | } 12 | 13 | @action 14 | complete() { 15 | this.progress.complete(this.target, this.userId); 16 | } 17 | 18 | @action 19 | undo() { 20 | this.progress.undo(this.target, this.userId); 21 | } 22 | 23 | 52 | } 53 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/team-build/index.gjs: -------------------------------------------------------------------------------- 1 | import { LinkTo } from "@ember/routing"; 2 | import RouteTemplate from "ember-route-template"; 3 | import avatar from "discourse/helpers/avatar"; 4 | import replaceEmoji from "discourse/helpers/replace-emoji"; 5 | import { i18n } from "discourse-i18n"; 6 | 7 | export default RouteTemplate( 8 | 49 | ); 50 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/templates/team-build/progress.gjs: -------------------------------------------------------------------------------- 1 | import RouteTemplate from "ember-route-template"; 2 | import replaceEmoji from "discourse/helpers/replace-emoji"; 3 | import { i18n } from "discourse-i18n"; 4 | import TeambuildChoice from "../../components/teambuild-choice"; 5 | 6 | export default RouteTemplate( 7 | 55 | ); 56 | -------------------------------------------------------------------------------- /app/controllers/discourse_teambuild/targets_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency "teambuild_target" 4 | require_dependency "teambuild_target_serializer" 5 | 6 | module DiscourseTeambuild 7 | class TargetsController < ApplicationController 8 | requires_plugin PLUGIN_NAME 9 | 10 | requires_login 11 | before_action :ensure_enabled 12 | 13 | def index 14 | targets = TeambuildTarget.all.includes(:group) 15 | render_serialized( 16 | targets, 17 | TeambuildTargetSerializer, 18 | rest_serializer: true, 19 | root: "teambuild_targets", 20 | extras: { 21 | groups: serialize_data(Group.all, BasicGroupSerializer), 22 | }, 23 | ) 24 | end 25 | 26 | def create 27 | target = 28 | TeambuildTarget.create!(params[:teambuild_target].permit(:name, :target_type_id, :group_id)) 29 | render_serialized(target, TeambuildTargetSerializer, rest_serializer: true) 30 | end 31 | 32 | def destroy 33 | TeambuildTarget.find_by(id: params[:id])&.destroy 34 | render json: success_json 35 | end 36 | 37 | def update 38 | target = TeambuildTarget.find_by(id: params[:id]) 39 | raise Discourse::NotFound if target.blank? 40 | 41 | target.update!(params[:teambuild_target].permit(:name, :target_type_id, :group_id)) 42 | render_serialized(target, TeambuildTargetSerializer, rest_serializer: true) 43 | end 44 | 45 | def swap_position 46 | target_position = TeambuildTarget.where(id: params[:target_id]).pick(:position) 47 | other_position = TeambuildTarget.where(id: params[:other_id]).pick(:position) 48 | raise Discourse::NotFound if target_position.nil? || other_position.nil? 49 | 50 | TeambuildTarget.transaction do 51 | TeambuildTarget.where(id: params[:target_id], position: target_position).update_all( 52 | position: other_position * -1, 53 | ) 54 | TeambuildTarget.where(id: params[:other_id], position: other_position).update_all( 55 | position: target_position, 56 | ) 57 | TeambuildTarget.where(id: params[:target_id], position: other_position * -1).update_all( 58 | position: other_position, 59 | ) 60 | end 61 | render json: success_json 62 | end 63 | 64 | protected 65 | 66 | def ensure_enabled 67 | raise Discourse::InvalidAccess.new unless SiteSetting.teambuild_enabled? 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /assets/stylesheets/team-build.scss: -------------------------------------------------------------------------------- 1 | .team-build { 2 | h1 { 3 | margin-bottom: 0.5em; 4 | } 5 | } 6 | 7 | .teambuild-description { 8 | font-size: 1.2em; 9 | margin-top: 1em; 10 | } 11 | 12 | .high-scores { 13 | min-width: 300px; 14 | 15 | td { 16 | padding: 0.25em; 17 | } 18 | 19 | .user { 20 | .avatar { 21 | margin-right: 0.5em; 22 | } 23 | 24 | a { 25 | font-weight: bold; 26 | color: var(--primary-high); 27 | } 28 | } 29 | 30 | .rank { 31 | padding-left: 1em; 32 | 33 | .emoji { 34 | margin-left: 0.5em; 35 | } 36 | } 37 | 38 | td.score { 39 | font-size: 1.5em; 40 | text-align: center; 41 | } 42 | 43 | th.score { 44 | text-align: center; 45 | } 46 | 47 | tr.me { 48 | background-color: var(--highlight-medium); 49 | } 50 | } 51 | 52 | .all-targets { 53 | margin-top: 1em; 54 | margin-bottom: 1em; 55 | 56 | .target-type .description { 57 | margin-bottom: 1em; 58 | font-size: 1.25em; 59 | } 60 | } 61 | 62 | .completed-score { 63 | margin-top: 1em; 64 | margin-bottom: 1em; 65 | font-size: 1.2em; 66 | font-weight: bold; 67 | display: flex; 68 | 69 | .username { 70 | color: var(--primary-medium); 71 | margin-right: 0.5em; 72 | } 73 | } 74 | 75 | .multi-choice { 76 | flex-wrap: wrap; 77 | 78 | .teambuild-choice { 79 | width: 200px; 80 | } 81 | margin-bottom: 1em; 82 | } 83 | 84 | .single-goal { 85 | flex-direction: column; 86 | } 87 | 88 | .teambuild-choice { 89 | display: flex; 90 | align-items: center; 91 | 92 | &.completed { 93 | font-weight: bold; 94 | } 95 | 96 | .controls, 97 | .display-done { 98 | margin-right: 0.5em; 99 | } 100 | margin-bottom: 1em; 101 | 102 | .choice-label { 103 | .emoji { 104 | margin-left: 0.25em; 105 | } 106 | } 107 | } 108 | 109 | .target-choices { 110 | display: flex; 111 | } 112 | 113 | .teambuild-manage { 114 | .get-started { 115 | margin: 2em 0 1em 0; 116 | } 117 | } 118 | 119 | .teambuild-target { 120 | display: flex; 121 | margin-top: 1em; 122 | margin-bottom: 1em; 123 | padding-bottom: 1em; 124 | border-bottom: 1px solid var(--primary-low); 125 | align-items: center; 126 | justify-content: space-between; 127 | 128 | .target-types { 129 | display: flex; 130 | 131 | label { 132 | margin-right: 1em; 133 | } 134 | } 135 | 136 | .target-group-name { 137 | font-weight: bold; 138 | } 139 | 140 | .target-name { 141 | label { 142 | display: block; 143 | } 144 | margin-top: 0.5em; 145 | 146 | input { 147 | width: 500px; 148 | } 149 | } 150 | 151 | .controls { 152 | margin-left: 1em; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (8.0.3) 5 | base64 6 | benchmark (>= 0.3) 7 | bigdecimal 8 | concurrent-ruby (~> 1.0, >= 1.3.1) 9 | connection_pool (>= 2.2.5) 10 | drb 11 | i18n (>= 1.6, < 2) 12 | logger (>= 1.4.2) 13 | minitest (>= 5.1) 14 | securerandom (>= 0.3) 15 | tzinfo (~> 2.0, >= 2.0.5) 16 | uri (>= 0.13.1) 17 | ast (2.4.3) 18 | base64 (0.3.0) 19 | benchmark (0.4.1) 20 | bigdecimal (3.3.0) 21 | concurrent-ruby (1.3.5) 22 | connection_pool (2.5.4) 23 | drb (2.2.3) 24 | i18n (1.14.7) 25 | concurrent-ruby (~> 1.0) 26 | json (2.15.1) 27 | language_server-protocol (3.17.0.5) 28 | lint_roller (1.1.0) 29 | logger (1.7.0) 30 | minitest (5.26.0) 31 | parallel (1.27.0) 32 | parser (3.3.9.0) 33 | ast (~> 2.4.1) 34 | racc 35 | prettier_print (1.2.1) 36 | prism (1.5.1) 37 | racc (1.8.1) 38 | rack (3.2.3) 39 | rainbow (3.1.1) 40 | regexp_parser (2.11.3) 41 | rubocop (1.81.1) 42 | json (~> 2.3) 43 | language_server-protocol (~> 3.17.0.2) 44 | lint_roller (~> 1.1.0) 45 | parallel (~> 1.10) 46 | parser (>= 3.3.0.2) 47 | rainbow (>= 2.2.2, < 4.0) 48 | regexp_parser (>= 2.9.3, < 3.0) 49 | rubocop-ast (>= 1.47.1, < 2.0) 50 | ruby-progressbar (~> 1.7) 51 | unicode-display_width (>= 2.4.0, < 4.0) 52 | rubocop-ast (1.47.1) 53 | parser (>= 3.3.7.2) 54 | prism (~> 1.4) 55 | rubocop-capybara (2.22.1) 56 | lint_roller (~> 1.1) 57 | rubocop (~> 1.72, >= 1.72.1) 58 | rubocop-discourse (3.13.3) 59 | activesupport (>= 6.1) 60 | lint_roller (>= 1.1.0) 61 | rubocop (>= 1.73.2) 62 | rubocop-capybara (>= 2.22.0) 63 | rubocop-factory_bot (>= 2.27.0) 64 | rubocop-rails (>= 2.30.3) 65 | rubocop-rspec (>= 3.0.1) 66 | rubocop-rspec_rails (>= 2.31.0) 67 | rubocop-factory_bot (2.27.1) 68 | lint_roller (~> 1.1) 69 | rubocop (~> 1.72, >= 1.72.1) 70 | rubocop-rails (2.33.4) 71 | activesupport (>= 4.2.0) 72 | lint_roller (~> 1.1) 73 | rack (>= 1.1) 74 | rubocop (>= 1.75.0, < 2.0) 75 | rubocop-ast (>= 1.44.0, < 2.0) 76 | rubocop-rspec (3.7.0) 77 | lint_roller (~> 1.1) 78 | rubocop (~> 1.72, >= 1.72.1) 79 | rubocop-rspec_rails (2.31.0) 80 | lint_roller (~> 1.1) 81 | rubocop (~> 1.72, >= 1.72.1) 82 | rubocop-rspec (~> 3.5) 83 | ruby-progressbar (1.13.0) 84 | securerandom (0.4.1) 85 | syntax_tree (6.3.0) 86 | prettier_print (>= 1.2.0) 87 | tzinfo (2.0.6) 88 | concurrent-ruby (~> 1.0) 89 | unicode-display_width (3.2.0) 90 | unicode-emoji (~> 4.1) 91 | unicode-emoji (4.1.0) 92 | uri (1.0.4) 93 | 94 | PLATFORMS 95 | ruby 96 | 97 | DEPENDENCIES 98 | rubocop-discourse 99 | syntax_tree 100 | 101 | BUNDLED WITH 102 | 2.7.2 103 | -------------------------------------------------------------------------------- /app/controllers/discourse_teambuild/teambuild_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_dependency "teambuild_target" 4 | require_dependency "teambuild_progress_serializer" 5 | 6 | module DiscourseTeambuild 7 | class TeambuildController < ApplicationController 8 | requires_plugin PLUGIN_NAME 9 | 10 | requires_login 11 | before_action :ensure_can_access 12 | 13 | def index 14 | render json: success_json 15 | end 16 | 17 | def progress 18 | user = params[:username].present? ? fetch_user_from_params : current_user 19 | targets = TeambuildTarget.all 20 | 21 | completed = [] 22 | 23 | progress = { user: user, teambuild_targets: TeambuildTarget.all, completed: completed } 24 | 25 | TeambuildTargetUser 26 | .where(user_id: user.id) 27 | .each { |t| completed << "#{t.teambuild_target_id}:#{t.target_user_id}" } 28 | 29 | render_serialized( 30 | progress, 31 | TeambuildProgressSerializer, 32 | rest_serializer: true, 33 | include_users: true, 34 | ) 35 | end 36 | 37 | def scores 38 | results = DB.query(<<~SQL, group_name: SiteSetting.teambuild_access_group) 39 | SELECT u.id, 40 | u.username, 41 | u.username_lower, 42 | u.uploaded_avatar_id, 43 | COUNT(ttu.id) AS score, 44 | RANK() OVER (ORDER BY COUNT(ttu.id) DESC) AS rank 45 | FROM users AS u 46 | LEFT OUTER JOIN teambuild_target_users AS ttu ON ttu.user_id = u.id 47 | INNER JOIN group_users AS gu ON gu.user_id = u.id 48 | INNER JOIN groups AS g ON gu.group_id = g.id 49 | WHERE g.name = :group_name 50 | GROUP BY u.id, u.name, u.username, u.username_lower, u.uploaded_avatar_id 51 | ORDER BY score DESC, u.username 52 | SQL 53 | 54 | scores = 55 | results.map do |r| 56 | r.as_json.tap do |result| 57 | result["trophy"] = true if r.rank == 1 58 | result["me"] = r.id == current_user.id 59 | result["avatar_template"] = User.avatar_template(r.username_lower, r.uploaded_avatar_id) 60 | result.delete("uploaded_avatar_id") 61 | end 62 | end 63 | 64 | render json: { scores: scores } 65 | end 66 | 67 | def complete 68 | begin 69 | TeambuildTargetUser.create!( 70 | user_id: current_user.id, 71 | teambuild_target_id: params[:target_id].to_i, 72 | target_user_id: params[:user_id].to_i, 73 | ) 74 | rescue StandardError 75 | ActiveRecord::RecordNotUnique 76 | end 77 | render json: success_json 78 | end 79 | 80 | def undo 81 | TeambuildTargetUser.where( 82 | user_id: current_user.id, 83 | teambuild_target_id: params[:target_id].to_i, 84 | target_user_id: params[:user_id].to_i, 85 | ).delete_all 86 | 87 | render json: success_json 88 | end 89 | 90 | protected 91 | 92 | def ensure_can_access 93 | raise Discourse::InvalidAccess.new unless guardian.has_teambuild_access? 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/requests/teambuild_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe DiscourseTeambuild::TeambuildController do 6 | it "returns 403 when anonymous" do 7 | SiteSetting.teambuild_enabled = true 8 | get "/team-build/scores.json" 9 | expect(response.code).to eq("403") 10 | end 11 | 12 | context "when logged in" do 13 | fab!(:user) 14 | fab!(:target) do 15 | TeambuildTarget.create!( 16 | name: "test target", 17 | target_type_id: TeambuildTarget.target_types[:regular], 18 | ) 19 | end 20 | fab!(:group) 21 | 22 | before do 23 | SiteSetting.teambuild_enabled = true 24 | SiteSetting.teambuild_access_group = group.name 25 | group.group_users.create!(user: user) 26 | sign_in(user) 27 | end 28 | 29 | context "when enabled/disabled" do 30 | it "returns 404 when disabled" do 31 | SiteSetting.teambuild_enabled = false 32 | get "/team-build/scores.json" 33 | expect(response.code).to eq("404") 34 | end 35 | 36 | it "returns 200 when enabled" do 37 | get "/team-build/scores.json" 38 | expect(response.code).to eq("200") 39 | end 40 | end 41 | 42 | context "with access group" do 43 | it "returns 403 when not in the group" do 44 | group.group_users.where(user: user).delete_all 45 | get "/team-build/scores.json" 46 | expect(response.code).to eq("403") 47 | end 48 | 49 | it "returns 200 if staff" do 50 | group.group_users.where(user: user).delete_all 51 | user.update!(admin: true) 52 | get "/team-build/scores.json" 53 | expect(response.code).to eq("200") 54 | end 55 | 56 | it "returns 200 when enabled" do 57 | get "/team-build/scores.json" 58 | expect(response.code).to eq("200") 59 | end 60 | end 61 | 62 | describe "progress" do 63 | it "returns json" do 64 | put "/team-build/complete/#{target.id}/#{user.id}.json" 65 | 66 | get "/team-build/progress.json" 67 | json = JSON.parse(response.body) 68 | expect(json).to be_present 69 | 70 | progress = json["teambuild_progress"] 71 | expect(progress["teambuild_target_ids"]).to include(target.id) 72 | 73 | expect(progress["completed"]).to include("#{target.id}:#{user.id}") 74 | 75 | targets = json["teambuild_targets"] 76 | expect(targets).to be_present 77 | json_target = targets.find { |t| t["id"] == target.id } 78 | expect(json_target).to be_present 79 | end 80 | 81 | it "returns completed" do 82 | get "/team-build/progress.json" 83 | json = JSON.parse(response.body) 84 | expect(json).to be_present 85 | 86 | progress = json["teambuild_progress"] 87 | expect(progress["teambuild_target_ids"]).to include(target.id) 88 | 89 | targets = json["teambuild_targets"] 90 | expect(targets).to be_present 91 | json_target = targets.find { |t| t["id"] == target.id } 92 | expect(json_target).to be_present 93 | end 94 | end 95 | 96 | describe "complete / undo " do 97 | it "will mark the target as completed" do 98 | put "/team-build/complete/#{target.id}/#{user.id}.json" 99 | expect(response.code).to eq("200") 100 | expect( 101 | TeambuildTargetUser.find_by(user_id: user.id, teambuild_target_id: target.id), 102 | ).to be_present 103 | 104 | # Test duplicate, should not error 105 | put "/team-build/complete/#{target.id}/#{user.id}.json" 106 | expect(response.code).to eq("200") 107 | 108 | delete "/team-build/undo/#{target.id}/#{user.id}.json" 109 | expect(response.code).to eq("200") 110 | expect( 111 | TeambuildTargetUser.find_by(user_id: user.id, teambuild_target_id: target.id), 112 | ).to be_blank 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /test/javascripts/acceptance/manage-test.js: -------------------------------------------------------------------------------- 1 | import { click, fillIn, visit } from "@ember/test-helpers"; 2 | import { test } from "qunit"; 3 | import { acceptance } from "discourse/tests/helpers/qunit-helpers"; 4 | 5 | acceptance("Team Building: Manage", function (needs) { 6 | needs.user({ can_access_teambuild: true }); 7 | needs.pretender((server, helper) => { 8 | server.get("/team-build/scores.json", () => { 9 | return helper.response(200, { 10 | scores: [ 11 | { 12 | id: 1, 13 | username: "user1", 14 | username_lower: "user1", 15 | score: 10, 16 | rank: 1, 17 | trophy: true, 18 | me: false, 19 | avatar_template: 20 | "/letter_avatar_proxy/v4/letter/u/3be4f8/{size}.png", 21 | }, 22 | { 23 | id: 2, 24 | username: "user2", 25 | username_lower: "user2", 26 | score: 5, 27 | rank: 2, 28 | trophy: false, 29 | me: true, 30 | avatar_template: 31 | "/letter_avatar_proxy/v4/letter/u/3be4f8/{size}.png", 32 | }, 33 | ], 34 | }); 35 | }); 36 | server.get("/team-build/targets.json", () => { 37 | return helper.response(200, { 38 | teambuild_targets: [ 39 | { 40 | id: 1, 41 | target_type_id: 1, 42 | name: "existing target", 43 | }, 44 | ], 45 | extras: { 46 | groups: [{ id: 1, name: "cool group" }], 47 | }, 48 | }); 49 | }); 50 | server.put("/team-build/targets/:id.json", (request) => { 51 | let data = JSON.parse(request.requestBody); 52 | return helper.response(200, data); 53 | }); 54 | server.post("/team-build/targets.json", () => { 55 | return helper.response(200, { teambuild_target: {} }); 56 | }); 57 | server.delete("/team-build/targets/:id.json", () => { 58 | return helper.response(200, { success: true }); 59 | }); 60 | }); 61 | 62 | test("can cancel creating", async function (assert) { 63 | await visit("/team-build/manage"); 64 | await click(".create-target"); 65 | assert.dom(".teambuild-target.editing").exists(); 66 | 67 | await click(".teambuild-target.editing .cancel"); 68 | assert.dom(".teambuild-target.editing").doesNotExist(); 69 | }); 70 | 71 | test("can create a new regular target", async function (assert) { 72 | await visit("/team-build/manage"); 73 | await click(".create-target"); 74 | assert.dom(".teambuild-target.editing").exists(); 75 | assert.dom(".teambuild-target.editing .save").isDisabled(); 76 | 77 | await click(".teambuild-target.editing .target-types input.regular"); 78 | await fillIn(".teambuild-target.editing .target-name input", "Cool target"); 79 | assert.dom(".teambuild-target.editing .save").isNotDisabled(); 80 | 81 | await click(".teambuild-target.editing .save"); 82 | assert.dom(".teambuild-target.editing").doesNotExist(); 83 | }); 84 | 85 | test("can delete", async function (assert) { 86 | await visit("/team-build/manage"); 87 | assert.dom(".teambuild-target").exists(); 88 | 89 | await click(".teambuild-target .destroy"); 90 | assert.dom(".teambuild-target").doesNotExist(); 91 | }); 92 | 93 | test("can cancel edit", async function (assert) { 94 | await visit("/team-build/manage"); 95 | await click(".teambuild-target .edit"); 96 | await fillIn(".teambuild-target.editing .target-name input", "New Name"); 97 | await click(".teambuild-target.editing .cancel"); 98 | assert.dom(".teambuild-target .target-name").hasText("existing target"); 99 | 100 | await click(".teambuild-target .edit"); 101 | assert 102 | .dom(".teambuild-target.editing .target-name input") 103 | .hasValue("existing target"); 104 | }); 105 | 106 | test("can update", async function (assert) { 107 | await visit("/team-build/manage"); 108 | await click(".teambuild-target .edit"); 109 | await fillIn(".teambuild-target.editing .target-name input", "New Name"); 110 | await click(".teambuild-target.editing .save"); 111 | assert.dom(".teambuild-target .target-name").hasText("New Name"); 112 | }); 113 | 114 | test("has links in sidebar", async (assert) => { 115 | await visit("/team-build/manage"); 116 | await click(".sidebar-more-section-links-details-summary"); 117 | 118 | assert 119 | .dom(".sidebar-section-link[data-link-name='team-building']") 120 | .hasText("Team Building"); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /spec/requests/targets_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe DiscourseTeambuild::TargetsController do 6 | it "returns 404 when anonymous" do 7 | SiteSetting.teambuild_enabled = true 8 | get "/team-build/targets.json" 9 | expect(response.code).to eq("404") 10 | end 11 | 12 | it "returns 404 when not staff" do 13 | SiteSetting.teambuild_enabled = true 14 | sign_in(Fabricate(:user)) 15 | get "/team-build/targets.json" 16 | expect(response.code).to eq("404") 17 | end 18 | 19 | context "when logged in" do 20 | fab!(:user, :moderator) 21 | 22 | before do 23 | SiteSetting.teambuild_enabled = true 24 | sign_in(user) 25 | end 26 | 27 | context "when enabled/disabled" do 28 | fab!(:target) do 29 | TeambuildTarget.create!( 30 | target_type_id: TeambuildTarget.target_types[:regular], 31 | name: "cool", 32 | ) 33 | end 34 | 35 | it "returns 404 when disabled" do 36 | SiteSetting.teambuild_enabled = false 37 | get "/team-build/targets.json" 38 | expect(response.code).to eq("404") 39 | end 40 | 41 | it "returns json" do 42 | get "/team-build/targets.json" 43 | expect(response.code).to eq("200") 44 | json = JSON.parse(response.body) 45 | expect(json).to be_present 46 | json_target = json["teambuild_targets"].find { |t| t["id"] == target.id } 47 | 48 | expect(json_target).to be_present 49 | expect(json_target["name"]).to eq(target.name) 50 | expect(json_target["target_type_id"]).to eq(target.target_type_id) 51 | end 52 | 53 | it "creates the object" do 54 | post "/team-build/targets.json", 55 | params: { 56 | teambuild_target: { 57 | name: "cool target name", 58 | target_type_id: TeambuildTarget.target_types[:regular], 59 | }, 60 | } 61 | expect(response.code).to eq("200") 62 | json = JSON.parse(response.body) 63 | expect(json).to be_present 64 | expect(json["teambuild_target"]).to be_present 65 | expect(json["teambuild_target"]["name"]).to eq("cool target name") 66 | t = TeambuildTarget.find_by(id: json["teambuild_target"]["id"]) 67 | expect(t).to be_present 68 | end 69 | 70 | it "returns an error if group is missing" do 71 | post "/team-build/targets.json", 72 | params: { 73 | teambuild_target: { 74 | name: "missing group", 75 | target_type_id: TeambuildTarget.target_types[:user_group], 76 | }, 77 | } 78 | expect(response.code).to eq("422") 79 | end 80 | 81 | it "creates the object with a group" do 82 | group_id = Group.pick(:id) 83 | post "/team-build/targets.json", 84 | params: { 85 | teambuild_target: { 86 | name: "cool target name", 87 | target_type_id: TeambuildTarget.target_types[:user_group], 88 | group_id: group_id, 89 | }, 90 | } 91 | expect(response.code).to eq("200") 92 | json = JSON.parse(response.body) 93 | expect(json).to be_present 94 | expect(json["teambuild_target"]).to be_present 95 | expect(json["teambuild_target"]["name"]).to eq("cool target name") 96 | expect(json["teambuild_target"]["group_id"]).to eq(group_id) 97 | end 98 | 99 | it "destroys the object" do 100 | id = target.id 101 | TeambuildTargetUser.create!( 102 | user_id: user.id, 103 | teambuild_target_id: id, 104 | target_user_id: user.id, 105 | ) 106 | delete "/team-build/targets/#{id}.json" 107 | expect(response.code).to eq("200") 108 | expect(TeambuildTarget.find_by(id: id)).to be_blank 109 | expect(TeambuildTargetUser.find_by(user_id: user.id)).to be_blank 110 | end 111 | 112 | it "updates the object" do 113 | put "/team-build/targets/#{target.id}.json", 114 | params: { 115 | teambuild_target: { 116 | name: "updated name", 117 | }, 118 | } 119 | expect(response.code).to eq("200") 120 | json = JSON.parse(response.body) 121 | expect(json).to be_present 122 | expect(json["teambuild_target"]).to be_present 123 | expect(json["teambuild_target"]["name"]).to eq("updated name") 124 | end 125 | 126 | it "can swap the position of targets" do 127 | other = 128 | TeambuildTarget.create!( 129 | target_type_id: TeambuildTarget.target_types[:regular], 130 | name: "another", 131 | ) 132 | other_position = other.position 133 | target_position = target.position 134 | 135 | put "/team-build/targets/#{target.id}/swap-position.json", params: { other_id: other.id } 136 | expect(response.code).to eq("200") 137 | expect(target.reload.position).to eq(other_position) 138 | expect(other.reload.position).to eq(target_position) 139 | end 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /assets/javascripts/discourse/components/teambuild-target.gjs: -------------------------------------------------------------------------------- 1 | import Component, { Input } from "@ember/component"; 2 | import { concat, fn } from "@ember/helper"; 3 | import { action, computed } from "@ember/object"; 4 | import { equal, or } from "@ember/object/computed"; 5 | import { underscore } from "@ember/string"; 6 | import { tagName } from "@ember-decorators/component"; 7 | import BufferedProxy from "ember-buffered-proxy/proxy"; 8 | import DButton from "discourse/components/d-button"; 9 | import RadioButton from "discourse/components/radio-button"; 10 | import replaceEmoji from "discourse/helpers/replace-emoji"; 11 | import { popupAjaxError } from "discourse/lib/ajax-error"; 12 | import { i18n } from "discourse-i18n"; 13 | import ComboBox from "select-kit/components/combo-box"; 14 | import { Types } from "discourse/plugins/discourse-teambuild/discourse/models/teambuild-target"; 15 | 16 | @tagName("") 17 | export default class TeambuildTarget extends Component { 18 | editSelected = false; 19 | 20 | @equal("buffered.target_type_id", Types.USER_GROUP) needsGroup; 21 | @or("editSelected", "target.isNew") editing; 22 | 23 | @computed("target") 24 | get buffered() { 25 | return BufferedProxy.create({ 26 | content: this.get("target"), 27 | }); 28 | } 29 | 30 | @computed("editing", "index") 31 | get canMoveUp() { 32 | return !this.editing && this.index > 0; 33 | } 34 | 35 | @computed("editing", "index", "length") 36 | get canMoveDown() { 37 | return !this.editing && this.index < this.length - 1; 38 | } 39 | 40 | get targetTypes() { 41 | return Object.keys(Types).map((key) => { 42 | return { id: Types[key], name: underscore(key) }; 43 | }); 44 | } 45 | 46 | @computed( 47 | "buffered.name", 48 | "target.isSaving", 49 | "needsGroup", 50 | "buffered.group_id" 51 | ) 52 | get saveDisabled() { 53 | if (this.target.isSaving) { 54 | return true; 55 | } 56 | 57 | let name = this.get("buffered.name"); 58 | if (!name || name.length === 0) { 59 | return true; 60 | } 61 | 62 | if (this.needsGroup && !this.get("buffered.group_id")) { 63 | return true; 64 | } 65 | 66 | return false; 67 | } 68 | 69 | @action 70 | save() { 71 | this.target 72 | .save(this.buffered.getProperties("name", "target_type_id", "group_id")) 73 | .then(() => { 74 | this.set("editSelected", false); 75 | }) 76 | .catch(popupAjaxError); 77 | } 78 | 79 | @action 80 | cancel() { 81 | if (this.target.isNew) { 82 | return this.removeTarget(); 83 | } else { 84 | this.set("editSelected", false); 85 | this.buffered.discardChanges(); 86 | } 87 | } 88 | 89 | @action 90 | destroyTarget() { 91 | this.target.destroyRecord().then(() => this.removeTarget()); 92 | } 93 | 94 | 182 | } 183 | --------------------------------------------------------------------------------