Commit b00f693e authored by Curtis Adam's avatar Curtis Adam

Allow cardset owner to add multiple editors

parent a7270b65
......@@ -95,6 +95,11 @@
"title": "Aktive Lernphase verlassen",
"text": "Wollen Sie diese aktive Lernphase wirklich verlassen? Wenn Sie den Vorgang bestätigen, wird Ihr gesamter Lernfortschritt für diesen Kartensatz gelöscht."
},
"confirm-leave-editors": {
"btn-txt": "Editoren verlassen",
"title": "Editoren Gruppe verlassen",
"text": "Wollen Sie sich Ihre Editoren Berechtigung für den Kartensatz \"__name__\" entfernen?"
},
"complete-profile": {
"btn-txt": "Schließen",
"title": "Nutzerprofil unvollständig",
......@@ -185,6 +190,7 @@
"ratingoption1": "erlauben",
"ratingoption2": "abschalten",
"kind": "Art",
"close": "Schließen",
"cancel": "Abbrechen",
"createcarddeck": "Anlegen",
"delete": "Kartensatz löschen",
......@@ -218,6 +224,7 @@
"college_required": "Wähle eine Hochschule",
"course": "Studiengang",
"course_required": "Wähle einen Studiengang",
"manageEditors": "Bearbeiter verwalten für den Kartensatz ",
"module": "Modul",
"module_required": "Geben Sie den vollen Namen des Moduls an!",
"moduleShort": "Initialien",
......@@ -346,7 +353,8 @@
"nameToLong": "Kennung muss aus weniger als 26 Buchstaben bestehen!",
"nameAlreadyExists": "Kennung ist bereits vergeben!",
"birthnameEmpty": "Der Nachname darf nicht leer sein!",
"givennameEmpty": "Der Vorname darf nicht leer sein!"
"givennameEmpty": "Der Vorname darf nicht leer sein!",
"roles": "Berechtigungen"
},
"panel-heading-experience": {
"experience": "Erfahrungspunkte (XP)",
......@@ -412,6 +420,7 @@
"createcard": "Neue Karte",
"importcard": "Karten importieren",
"exportcard": "Karten exportieren",
"manageEditors": "Bearbeiter verwalten",
"publish": "Status",
"tooltip": "Erstelle mindestens 5 Karten in diesem Kartensatz und vervollständige dein Profil, um diesen zu veröffentlichen. Pro-Kartensätze können nicht wieder zurückgestuft werden!",
"published": "Veröffentlicht",
......@@ -420,7 +429,8 @@
"license": " Lizenz wählen",
"startLearning": "Lernphase starten",
"stopLearning": "Lernphase beenden",
"poolLicense": "Lizenz für "
"poolLicense": "Lizenz für ",
"leaveEditors": "Als Editor abmelden"
},
"pool": {
"relevance": "Relevanz",
......@@ -651,6 +661,7 @@
"front": "Vorderseite",
"back": "Rückseite",
"admin": "Admin",
"superAdmin": "Super Admin",
"pro": "Pro",
"university": "Edu",
"lecturer": "Dozent",
......
......@@ -95,6 +95,11 @@
"title": "Leave the active Learning-Phase",
"text": "Do you really want to leave this active Learning-Phase? If you accept this, then your entire learning progress will be deleted."
},
"confirm-leave-editors": {
"btn-txt": "Leave Editors",
"title": "Leave Editors group",
"text": "Do you want to remove your editor privilege for the cardset \"__name__\"?"
},
"complete-profile": {
"btn-txt": "Close",
"title": "User profile incomplete",
......@@ -187,6 +192,7 @@
"ratingoption1": "allow",
"ratingoption2": "disable",
"kind": "Kind",
"close": "Close",
"cancel": "Cancel",
"createcarddeck": "Create",
"delete": "Delete deck of cards",
......@@ -221,6 +227,7 @@
"college_required": "Choose a college",
"course": "Course",
"course_required": "Choose a course",
"manageEditors": "Manage Editors for cardset ",
"module": "Module",
"module_required": "The full name of the module is required!",
"moduleShort": "Initials",
......@@ -349,7 +356,8 @@
"nameToLong": "Username must consist less than 26 letters!",
"nameAlreadyExists": "Username is already taken!",
"birthnameEmpty": "Birth name required!",
"givennameEmpty": "Given name required!"
"givennameEmpty": "Given name required!",
"roles": "Roles"
},
"panel-heading-experience": {
"experience": "Experience Points (XP)",
......@@ -412,6 +420,7 @@
"createcard": "New card",
"importcard": "Import cards",
"exportcard": "Export cards",
"manageEditors": "Manage Editors",
"publish": "Status",
"tooltip": "Create at least 5 Cards in this set and complete your profile before publishing this set. Pro Cardsets can't be demoted!",
"published": "Published",
......@@ -420,7 +429,8 @@
"license": " Select license",
"startLearning": "Start Learning-Phase",
"stopLearning": "Stop Learning-Phase",
"poolLicense": "License for "
"poolLicense": "License for ",
"leaveEditors": "Remove Editor privilege"
},
"pool": {
"relevance": "Relevance",
......@@ -651,6 +661,7 @@
"manageUniversity": "College manager",
"manageCourse": "Course manager",
"admin": "Admin",
"superAdmin": "Super Admin",
"pro": "Pro",
"university": "Edu",
"lecturer": "Lecturer",
......
......@@ -335,7 +335,7 @@ Meteor.methods({
'editor'
])) {
// Make sure the user is logged in and is authorized
if (!Meteor.userId() || cardset.owner !== Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
if (!Meteor.userId() || cardset.owner !== Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"]) || cardset.editors.includes(Meteor.userId())) {
throw new Meteor.Error("not-authorized");
}
}
......
import {Meteor} from "meteor/meteor";
import {Match} from "meteor/check";
import {Learned} from "./learned.js";
import {Cardsets} from "./cardsets.js";
import {check} from "meteor/check";
......@@ -30,7 +29,7 @@ Meteor.methods({
check(header, [String]);
var cardset = Cardsets.findOne({_id: cardset_id});
if ((Roles.userIsInRole(Meteor.userId(), 'lecturer')) && Meteor.userId() === cardset.owner && (Match.test(header, [String]))) {
if ((Roles.userIsInRole(Meteor.userId(), ["admin", "editor", "lecturer"])) && (Meteor.userId() === cardset.owner || cardset.editors.includes(Meteor.userId()))) {
var content;
var colSep = ";"; // Separates columns
var newLine = "\r\n"; //Adds a new line
......@@ -52,10 +51,10 @@ Meteor.methods({
data = Learned.find({cardset_id: cardset_id, user_id: distinctData[k].user_id}).fetch();
for (var l = 1; l <= 6; l++) {
content += Learned.find({
cardset_id: cardset_id,
user_id: distinctData[k].user_id,
box: l
}).count() + colSep;
cardset_id: cardset_id,
user_id: distinctData[k].user_id,
box: l
}).count() + colSep;
}
content += newLine;
}
......@@ -66,7 +65,7 @@ Meteor.methods({
check(cardset_id, String);
var cardset = Cardsets.findOne({_id: cardset_id});
if ((Roles.userIsInRole(Meteor.userId(), 'lecturer')) && Meteor.userId() === cardset.owner) {
if ((Roles.userIsInRole(Meteor.userId(), ["admin", "editor", "lecturer"])) && (Meteor.userId() === cardset.owner || cardset.editors.includes(Meteor.userId()))) {
var learningDataArray = [];
var data = Learned.find({cardset_id: cardset_id}).fetch();
var distinctData = _.uniq(data, false, function (d) {
......@@ -101,5 +100,66 @@ Meteor.methods({
}
return learningDataArray;
}
},
getEditors: function (cardset_id) {
check(cardset_id, String);
let cardset = Cardsets.findOne({_id: cardset_id});
let editorsDataArray = [];
if (Meteor.userId() === cardset.owner) {
let editors = Meteor.users.find({
_id: {$ne: cardset.owner},
roles: {$in: ['admin', 'editor', 'lecturer']}
}).fetch();
for (let i = 0; i < editors.length; i++) {
editorsDataArray.push({
givenname: editors[i].profile.givenname,
birthname: editors[i].profile.birthname,
roles: editors[i].roles,
id: editors[i]._id
});
}
}
return editorsDataArray;
},
addEditor: function (cardset_id, user_id) {
check(cardset_id, String);
check(user_id, String);
let cardset = Cardsets.findOne({_id: cardset_id});
if (Meteor.userId() === cardset.owner && user_id !== cardset.owner) {
Cardsets.update(
{_id: cardset._id},
{
$push: {editors: user_id}
});
}
Learned.remove({
cardset_id: cardset._id,
user_id: user_id
});
Meteor.call("updateLearnerCount", cardset._id);
},
removeEditor: function (cardset_id, user_id) {
check(cardset_id, String);
check(user_id, String);
let cardset = Cardsets.findOne({_id: cardset_id});
if (Meteor.userId() === cardset.owner && user_id !== cardset.owner) {
Cardsets.update(
{_id: cardset._id},
{
$pull: {editors: user_id}
});
}
},
leaveEditors: function (cardset_id) {
check(cardset_id, String);
let cardset = Cardsets.findOne({_id: cardset_id});
if (cardset.editors.includes(Meteor.userId())) {
Cardsets.update(
{_id: cardset._id},
{
$pull: {editors: Meteor.userId()}
});
}
}
});
......@@ -77,6 +77,9 @@ const CardsetsSchema = new SimpleSchema({
dateUpdated: {
type: Date
},
editors: {
type: [String]
},
owner: {
type: String
},
......@@ -234,6 +237,7 @@ Meteor.methods({
description: description,
date: new Date(),
dateUpdated: new Date(),
editors: [],
owner: Meteor.userId(),
visible: visible,
ratings: ratings,
......@@ -629,6 +633,15 @@ Meteor.methods({
}
if (Cardsets.findOne(id) && Meteor.users.findOne(owner)) {
if (Cardsets.findOne({id: id, editors: {$in: {owner}}})) {
let oldOwner = Cardsets.findOne({id: id}).owner;
Cardsets.update(
{_id: id},
{
$pull: {editors: owner},
$push: {editors: oldOwner}
});
}
Cardsets.update(id, {
$set: {
owner: owner
......@@ -641,7 +654,7 @@ Meteor.methods({
},
/**
* Whitelist the cardset for the wordcloud
* * @param {String} id - ID of the cardset to be updated
* * @param {String} id - ID of the cardset to be updated
* @param {Boolean} status - Wordcloud status for the cardset: true = Add to wordcloud, false = remove from Wordcloud
*/
updateWordcloudStatus: function (id, status) {
......
......@@ -287,7 +287,7 @@ Meteor.methods({
throw new Meteor.Error("not-authorized");
}
var cardsets = Cardsets.find({
let cardsets = Cardsets.find({
owner: this.userId,
kind: 'personal'
});
......@@ -309,6 +309,10 @@ Meteor.methods({
kind: 'personal'
});
Cardsets.update({editors: {$in: [this.userId]}}, {
$pull: {editors: this.userId}
});
Meteor.users.update(this.userId, {
$set: {
"services.resume.loginTokens": []
......
......@@ -27,8 +27,9 @@ Template.registerHelper("isLecturer", function () {
}
});
Template.registerHelper("isProfileCompleted", function (owner_id, learningActive) {
if (owner_id === Meteor.userId() && learningActive) {
Template.registerHelper("isProfileCompleted", function (id, learningActive) {
let cardset = Cardsets.findOne({_id: id});
if ((cardset.owner === Meteor.userId() || cardset.editors.includes(Meteor.userId())) && learningActive) {
return true;
}
if ((Meteor.user().profile.birthname !== "" && Meteor.user().profile.birthname !== undefined) && (Meteor.user().profile.givenname !== "" && Meteor.user().profile.givenname !== undefined) && (Meteor.user().email !== "" && Meteor.user().email !== undefined)) {
......@@ -43,6 +44,25 @@ Template.registerHelper("isCardsetOwner", function (cardset_id) {
return owner === Meteor.userId();
});
Template.registerHelper("isCardsetEditor", function (user_id) {
return Cardsets.findOne({"_id": Router.current().params._id, "editors": {$in: [user_id]}});
});
Template.registerHelper("learningActiveAndNotEditor", function () {
let cardset = Cardsets.findOne({"_id": Router.current().params._id});
return (cardset.owner !== Meteor.userId() && !cardset.editors.includes(Meteor.userId())) && cardset.learningActive;
});
Template.registerHelper("learningActiveAndEditor", function () {
let cardset = Cardsets.findOne({"_id": Router.current().params._id});
return (cardset.owner === Meteor.userId() || cardset.editors.includes(Meteor.userId())) && cardset.learningActive;
});
Template.registerHelper("isEditor", function () {
let cardset = Cardsets.findOne({"_id": Router.current().params._id});
return (cardset.owner === Meteor.userId() || cardset.editors.includes(Meteor.userId()));
});
Template.registerHelper("isLecturerOrPro", function () {
this.owner = Cardsets.findOne(Router.current().params._id).owner;
if (Roles.userIsInRole(Meteor.userId(), 'lecturer') || Cardsets.findOne(Router.current().params._id).owner != Meteor.userId()) {
......@@ -50,6 +70,26 @@ Template.registerHelper("isLecturerOrPro", function () {
}
});
Template.registerHelper("getRoles", function (roles) {
roles.sort();
let translatedRoles = "";
for (let i = 0; i < roles.length; i++) {
switch (roles[i]) {
case 'admin':
translatedRoles += (TAPi18n.__('admin.superAdmin') + ", ");
break;
case 'editor':
translatedRoles += (TAPi18n.__('admin.admin') + ", ");
break;
case 'lecturer':
translatedRoles += (TAPi18n.__('admin.lecturer') + ", ");
break;
}
}
return translatedRoles.substring(0, translatedRoles.length - 2);
});
// Check if multiple universities are enabled
Template.registerHelper("singleUniversity", function () {
return Meteor.settings.public.university.singleUniversity;
......
......@@ -60,6 +60,11 @@ Router.route('/cardset/:_id', {
}
});
Router.route('/cardset/:_id/editors', {
name: 'cardseteditors',
template: 'cardsetManageEditors'
});
Router.route('/cardset/:_id/stats', {
name: 'cardsetstats',
template: 'cardsetLearnActivityStatistic'
......
......@@ -26,7 +26,7 @@
{{#if isInRole 'blocked'}}
{{> access_denied_content_only}}
{{else}}
{{#if isCardsetOwner this.cardset_id}}
{{#if isEditor}}
<div class="container">
<div class="col-md-12">
<h3 style="margin-bottom:20px">{{_ "editcard"}}</h3>
......@@ -135,13 +135,18 @@
<template name="btnCard">
<div class="col-md-12">
{{#if isEditMode}}
<button id="cardDelete" class="btn {{#if learningActive}} btn-warning {{else}} btn-default{{/if}} btn-raised col-xs-12 col-sm-3" {{isDisabled}}>{{_ "deletecard"}}</button>
{{#if isCardsetOwner this._id}}
{{#if isEditMode}}
<button id="cardDelete" class="btn {{#if
learningActive}} btn-warning {{else}} btn-default{{/if}} btn-raised col-xs-12 col-sm-3" {{isDisabled}}>{{_
"deletecard"}}</button>
<button id="cardDeleteConfirm" style="display: none;"
class="btn btn-danger btn-raised col-xs-12 col-sm-3">{{_
"confirmcard"}}</button>
"confirmcard"}}</button>
{{/if}}
{{/if}}
<button id="cardCancel" class="btn btn-default btn-raised cancel col-xs-12 col-sm-3">{{_ "declinecard"}}</button>
<button id="cardCancel" class="btn btn-default btn-raised cancel col-xs-12 col-sm-3">{{_
"declinecard"}}</button>
<button id="cardSave"
class="btn btn-success btn-raised save pull-right col-xs-12 col-sm-3" {{disableIfOffline}}>
{{_ "savecard"}}
......@@ -160,7 +165,7 @@
<div class="box flashcard">
<div class="innerBoxHeader {{getCardBackground difficulty}}-header">
<div class="leftHeader">
{{#if isCardsetOwner cardset_id}}
{{#if isEditor}}
{{#if isCardset}}
<a id="editCard"
class="btn-editCard btn btn-raised btn-default btn-xs"
......@@ -182,7 +187,7 @@
{{cardsIndex @index}}/{{countCards cardset_id}}<br>
{{/if}}
{{#if isBox}}
{{_ "leitner_status" count=countLeitner}}
{{_ "leitner_status" count=countLeitner}}
<br>
{{/if}}
<span class="cardfront">{{_ "frontside"}}</span><span
......@@ -190,11 +195,12 @@
</div>
<div id="clicktoflip" class="clicktoflip">
{{#unless isMemo}}
<img id="iconfront" class="cardside cardfront-symbol" src="/img/icon_front.png">
<img id="iconback" class="cardside cardback-symbol" src="/img/icon_back.png"
style="display: none;">
{{_ "turn"}}{{/unless}}</div>
{{#unless isMemo}}
<img id="iconfront" class="cardside cardfront-symbol"
src="/img/icon_front.png">
<img id="iconback" class="cardside cardback-symbol" src="/img/icon_back.png"
style="display: none;">
{{_ "turn"}}{{/unless}}</div>
<br>
<h3>{{subject}}</h3>
......
This diff is collapsed.
......@@ -134,9 +134,6 @@ Template.cardset.helpers({
Session.set('previousCollegeName', Cardsets.findOne(id).college);
Session.set('previousCourseName', Cardsets.findOne(id).course);
},
'learningActiveAndNotEditor': function () {
return this.owner !== Meteor.userId() && this.learningActive;
},
'hasCardsetPermission': function () {
var userId = Meteor.userId();
var cardsetKind = this.kind;
......@@ -150,7 +147,7 @@ Template.cardset.helpers({
hasRole = true;
}
return this.owner === Meteor.userId() || hasRole;
return (this.owner === Meteor.userId() || this.editors.includes(Meteor.userId())) || hasRole;
},
'isLecturerAndHasRequest': function () {
return (Roles.userIsInRole(Meteor.userId(), 'lecturer') && this.request === true && this.owner !== Meteor.userId());
......@@ -631,6 +628,23 @@ Template.leaveLearnPhaseForm.events({
}
});
/*
* ############################################################################
* leaveEditorsForm
* ############################################################################
*/
Template.leaveEditorsForm.events({
'click #leaveEditorsConfirm': function () {
$('#leaveEditorsModal').modal('hide');
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
$('#leaveEditorsModal').on('hidden.bs.modal', function () {
Meteor.call("leaveEditors", Router.current().params._id);
});
}
});
/*
* ############################################################################
* cardsetSidebar
......@@ -677,6 +691,17 @@ Template.cardsetSidebar.events({
Router.go('cardsetstats', {_id: Router.current().params._id});
}
});
},
"click #manageEditors": function () {
Meteor.call("getEditors", this._id, function (error, result) {
if (error) {
throw new Meteor.Error(error.statusCode, 'Error could not receive content for editors');
}
if (result) {
Session.set("cardsetEditors", result);
Router.go('cardseteditors', {_id: Router.current().params._id});
}
});
}
});
......@@ -684,9 +709,6 @@ Template.cardsetSidebar.helpers({
enableIfPublished: function () {
return this.kind !== 'personal';
},
'learningActiveAndNotEditor': function () {
return this.owner !== Meteor.userId() && this.learningActive;
},
'learningLeitner': function () {
return Learned.findOne({cardset_id: this._id, user_id: Meteor.userId(), box: {$ne: 1}});
},
......@@ -712,8 +734,7 @@ Template.cardsetLearnActivityStatistic.helpers({
Template.cardsetLearnActivityStatistic.events({
"click #exportCSV": function () {
var cardset_id = Template.parentData(1)._id;
var cardset = Cardsets.find({"_id": cardset_id}).fetch();
var cardset = Cardsets.findOne({_id: Router.current().params._id});
var hiddenElement = document.createElement('a');
var header = [];
header[0] = TAPi18n.__('subject1');
......@@ -725,7 +746,7 @@ Template.cardsetLearnActivityStatistic.events({
header[6] = TAPi18n.__('box_export_given_name');
header[7] = TAPi18n.__('box_export_birth_name');
header[8] = TAPi18n.__('box_export_mail');
Meteor.call("getCSVExport", cardset_id, header, function (error, result) {
Meteor.call("getCSVExport", cardset._id, header, function (error, result) {
if (error) {
throw new Meteor.Error(error.statusCode, 'Error could not receive content for .csv');
}
......@@ -733,7 +754,7 @@ Template.cardsetLearnActivityStatistic.events({
var statistics = TAPi18n.__('box_export_statistics');
hiddenElement.href = 'data:text/csv;charset=utf-8,%EF%BB%BF' + encodeURIComponent(result);
hiddenElement.target = '_blank';
var str = (cardset[0].name + "_" + statistics + "_" + new Date() + ".csv");
var str = (cardset.name + "_" + statistics + "_" + new Date() + ".csv");
hiddenElement.download = str.replace(/ /g, "_").replace(/:/g, "_");
document.body.appendChild(hiddenElement);
hiddenElement.click();
......@@ -745,6 +766,32 @@ Template.cardsetLearnActivityStatistic.events({
}
});
/*
* ############################################################################
* cardsetManageEditors
* ############################################################################
*/
Template.cardsetManageEditors.helpers({
getEditors: function () {
return Session.get("cardsetEditors");
},
getManageEditorsTitle: function () {
return "\"" + Cardsets.findOne({_id: Router.current().params._id}).name + "\"";
}
});
Template.cardsetManageEditors.events({
"click #backButton": function () {
Router.go('cardsetdetailsid', {_id: Router.current().params._id});
},
"click .addEditor": function (event) {
Meteor.call("addEditor", Router.current().params._id, $(event.target).data('id'));
},
"click .removeEditor": function (event) {
Meteor.call("removeEditor", Router.current().params._id, $(event.target).data('id'));
}
});
/*
* ############################################################################
* cardsetStartLearnForm
......@@ -1209,7 +1256,7 @@ Template.resetMemoForm.events({
Template.leitnerLearning.helpers({
addToLeitner: function () {
if (this.owner !== Meteor.userId()) {
if (this.owner !== Meteor.userId() && !this.editors.includes(Meteor.userId())) {
addToLeitner(this._id);
}
},
......
......@@ -301,3 +301,9 @@ $font_5: sans-serif;
}
}
.table {
td {
vertical-align: middle !important;
}
}
......@@ -283,7 +283,7 @@
<template name="poolCardsetRow">
{{#if this.learningActive}}
{{#if isProfileCompleted this.owner this.learningActive}}
{{#if isProfileCompleted this._id this.learningActive}}
{{#if isAlreadyLearning}}
<a href="/cardset/{{_id}}" class="panelUnitBorder field-tip"
title="{{_ "set-list.topic"}}: {{this.name}} ({{_ "pool.quantity"}}: {{countCards this._id}})">
......
......@@ -364,7 +364,7 @@ Template.showLicense.helpers({
Template.poolCardsetRow.helpers({
isAlreadyLearning: function () {
if (this.owner === Meteor.userId()) {
if (this.owner === Meteor.userId() || this.editors.includes(Meteor.userId())) {
return true;
}
let learnedCards = Learned.find({
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment