Commit e96a1f95 authored by Klaus-Dieter Quibeldey-Cirkel's avatar Klaus-Dieter Quibeldey-Cirkel 🏀

Merge branch...

Merge branch '1134-implementations-are-now-required-for-editing-a-repetitorium-and-joining-one-for-learning' into 'staging'

Resolve "Implementations are now required for editing a repetitorium and joining one for learning"

Closes #1134, #1132, and #1120

See merge request arsnova/flashcards!1363
parents 39787cef 87ebe275
......@@ -4,14 +4,13 @@
@import '../imports/ui/welcome/welcome.scss';
@import '../imports/ui/cardsets/cardsets.scss';
@import '../imports/ui/cardset/cardset.scss';
@import '../imports/ui/cardsetCourseIterations/results.scss';
@import '../imports/ui/cardsets/resultItem';
@import '../imports/ui/card/card.scss';
@import '../imports/ui/card/navigation/navigation.scss';
@import '../imports/ui/markdeepEditor/content/content';
@import '../imports/ui/markdeepEditor/navigation/navigation';
@import '../imports/ui/courseIterations/coursesIterations.scss';
@import '../imports/ui/filter/filter.scss';
@import '../imports/ui/forms/cardsetCourseIterationForm.scss';
@import '../imports/ui/forms/cardsetForm.scss';
@import '../imports/ui/profile/profile.scss';
@import '../imports/ui/learn/learn.scss';
@import '../imports/ui/admin/admin.scss';
......
......@@ -2044,7 +2044,7 @@ $themes: (
.navigationFilterIcon {
color: $filter_navigation_icon !important;
}
//forms - cardsetCourseIterationForm
//forms - cardsetForm
#setDifficulty .btn, #cardType .btn {
background-color: $button_difficulty_inactive !important;
}
......@@ -2085,7 +2085,7 @@ $themes: (
background-color: $button_difficulty3 !important;
}
#setCardsetCourseIterationFormModal, #showSelectLearningUnitModal {
#setCardsetFormModal, #showSelectLearningUnitModal {
ul > li {
background-color: $cardset_form_list_background !important;
border-bottom-color: $cardset_form_border !important;
......
......@@ -516,7 +516,8 @@
"license": "Lizenz",
"activeLearners": "Aktive Lerner/innen",
"dateUpdated": "Aktualisiert",
"selectCardToLearn": "Mit einem Klick auf das Thema einer Kartei nimmst du sie in dein Lernpensum auf."
"selectCardToLearn": "Mit einem Klick auf das Thema einer Kartei nimmst du sie in dein Lernpensum auf.",
"selectShuffledCardsetToLearn": "Mit einem Klick auf das Thema eines Repetitorium nimmst du sie in dein Lernpensum auf."
},
"cardset": {
"info": {
......
......@@ -524,7 +524,8 @@
"license": "License",
"dateUpdated": "Last updated",
"activeLearners": "Active learners",
"selectCardToLearn": "Add cardset to Workload"
"selectCardToLearn": "Add cardset to Workload",
"selectShuffledCardsetToLearn": "Add shuffled cardset to Workload"
},
"cardset": {
"info": {
......
import {Meteor} from "meteor/meteor";
import {Mongo} from "meteor/mongo";
import {SimpleSchema} from "meteor/aldeed:simple-schema";
import {check} from "meteor/check";
import {TargetAudience} from "./targetAudience";
export const CourseIterations = new Mongo.Collection("courseIterations");
if (Meteor.isServer) {
let universityFilter = {$ne: null};
if (Meteor.settings.public.university.singleUniversity) {
universityFilter = Meteor.settings.public.university.default;
}
Meteor.publish("courseIterations", function () {
if (Roles.userIsInRole(this.userId, [
'admin',
'editor'
])) {
return CourseIterations.find({college: universityFilter});
} else if (this.userId && !Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
return CourseIterations.find(
{
college: universityFilter,
$or: [
{visible: true},
{owner: this.userId}
]
});
}
});
}
const CourseIterationsSchema = new SimpleSchema({
name: {
type: String
},
description: {
type: String,
optional: true
},
date: {
type: Date
},
dateUpdated: {
type: Date
},
editors: {
type: [String]
},
owner: {
type: String
},
visible: {
type: Boolean
},
ratings: {
type: Boolean
},
kind: {
type: String
},
quantity: {
type: Number
},
userDeleted: {
type: Boolean
},
module: {
type: String,
optional: true
},
moduleToken: {
type: String,
optional: true
},
moduleNum: {
type: String,
optional: true
},
moduleLink: {
type: String,
optional: true
},
college: {
type: String,
optional: true
},
course: {
type: String,
optional: true
},
cardsetsGroup: {
type: [String],
optional: true
},
semester: {
type: Number
},
price: {
type: Number,
decimal: true,
optional: true
},
targetAudience: {
type: Number
},
noModule: {
type: Boolean
},
noSemester: {
type: Boolean
}
});
CourseIterations.attachSchema(CourseIterationsSchema);
Meteor.methods({
/**
* Adds a course
* @param {String} name - Title of the course
* @param {String} description - Description for the content of the course
* @param {Boolean} visible - Visibility of the course
* @param {Boolean} ratings - Rating of the course
* @param {String} kind - Type of cards
* @param {String} module - Modulename
* @param {String} moduleShort - Abbreviation for the module
* @param {String} moduleNum - Number of the module
* @param {String} moduleLink - Link to the module description
* @param {String} college - Assigned university
* @param {String} course - Assigned university course
* @param {String} semester - Semester that the course belongs to
* @param {String} price - Price for the course iteration
* @param {String} targetAudience - The target audience that this course iteration is for
*/
addCourseIteration: function (name, description, visible, ratings, kind, module, moduleShort, moduleNum, moduleLink, college, course, semester, price, targetAudience) {
if (Meteor.settings.public.university.singleUniversity || college === "") {
college = Meteor.settings.public.university.default;
}
check(name, String);
check(description, String);
check(visible, Boolean);
check(ratings, Boolean);
check(kind, String);
check(module, String);
check(moduleShort, String);
check(moduleNum, String);
check(moduleLink, String);
check(college, String);
check(course, String);
check(semester, Number);
check(targetAudience, Number);
// Make sure the user is logged in before inserting a cardset
if (!Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
throw new Meteor.Error("not-authorized");
}
if (!Roles.userIsInRole(this.userId, ["admin", "editor", "lecturer"])) {
kind = "personal";
price = Number(0);
}
if (!TargetAudience.gotAccessControl(targetAudience)) {
visible = false;
kind = "personal";
}
CourseIterations.insert({
name: name.trim(),
description: description,
date: new Date(),
dateUpdated: new Date(),
editors: [],
owner: Meteor.userId(),
visible: visible,
ratings: ratings,
kind: kind,
relevance: 0,
raterCount: 0,
quantity: 0,
userDeleted: false,
module: module.trim(),
moduleToken: moduleShort.trim(),
moduleNum: moduleNum.trim(),
moduleLink: moduleLink.trim(),
college: college.trim(),
course: course.trim(),
price: price.toString().replace(",", "."),
semester: semester,
targetAudience: targetAudience,
noModule: !TargetAudience.gotModule(targetAudience),
noSemester: !TargetAudience.gotModule(targetAudience)
}, {trimStrings: false});
},
/**
* Updates the selected course
* @param {String} id - ID of the cardset to be updated
* @param {String} name - Title of the cardset
* @param {String} description - Description for the content of the cardset
* @param {String} module - Module name
* @param {String} moduleShort - Abbreviation for the module
* @param {String} moduleNum - Number of the module
* @param {String} moduleLink - Link to the module description
* @param {String} college - Assigned university
* @param {String} course - Assigned university course
*/
updateCourseIteration: function (id, name, description, module, moduleShort, moduleNum, moduleLink, college, course) {
if (Meteor.settings.public.university.singleUniversity) {
college = Meteor.settings.public.university.default;
}
check(id, String);
check(name, String);
check(description, String);
check(module, String);
check(moduleShort, String);
check(moduleNum, String);
check(moduleLink, String);
check(college, String);
check(course, String);
// Make sure only the task owner can make a task private
let courseIteration = CourseIterations.findOne(id);
if (!Roles.userIsInRole(this.userId, [
'admin',
'editor'
])) {
if (!Meteor.userId() || courseIteration.owner !== Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
throw new Meteor.Error("not-authorized");
}
}
CourseIterations.update(id, {
$set: {
name: name.trim(),
description: description,
dateUpdated: new Date(),
module: module.trim(),
moduleToken: moduleShort.trim(),
moduleNum: moduleNum.trim(),
moduleLink: moduleLink.trim(),
college: college.trim(),
course: course.trim()
}
}, {trimStrings: false});
},
/**
* Delete selected course (and associated decks) from database if user is auhorized.
* @param {String} id - Database id of the course to be deleted
* @param {Boolean} andCardDecks - true, if associated card decks should be deleted too
*/
deleteCourseIteration: function (id, andCardDecks) {
check(id, String);
// Make sure only the task owner can make a task private
let courseIteration = CourseIterations.findOne(id);
if (!Roles.userIsInRole(this.userId, [
'admin',
'editor'
])) {
if (!Meteor.userId() || courseIteration.owner !== Meteor.userId() || Roles.userIsInRole(this.userId, ["firstLogin", "blocked"])) {
throw new Meteor.Error("not-authorized");
}
}
if (andCardDecks) {
throw new Meteor.Error("not-implemented");
} else {
CourseIterations.remove(id);
}
}
});
......@@ -2,7 +2,6 @@ import {Meteor} from "meteor/meteor";
import {FilterNavigation} from "./filterNavigation";
import {Session} from "meteor/session";
import {Cardsets} from "./cardsets";
import {CourseIterations} from "./courseIterations";
import {Route} from "./route";
import {Leitner, Wozniak} from "./learned";
......@@ -21,8 +20,14 @@ let freeKindTag = "free";
let proKindTag = "pro";
export let Filter = class Filter {
static getActiveFilter () {
switch (FilterNavigation.getRouteId()) {
static getActiveFilter (_id = undefined) {
let route;
if (_id === undefined) {
route = FilterNavigation.getRouteId();
} else {
route = _id;
}
switch (route) {
case 0:
if (Session.get('poolFilter') === undefined) {
this.setDefaultFilter(FilterNavigation.getRouteId());
......@@ -73,21 +78,9 @@ export let Filter = class Filter {
case "author":
filter.owner = content;
break;
case "course":
filter.course = content;
break;
case "college":
filter.college = content;
break;
case "module":
filter.module = content;
break;
case "noDifficulty":
filter.noDifficulty = content;
break;
case "noModule":
filter.noModule = content;
break;
case "wordcloud":
filter.wordcloud = content;
break;
......@@ -97,14 +90,8 @@ export let Filter = class Filter {
case "kind":
filter.kind = content;
break;
case "targetAudience":
filter.targetAudience = content;
break;
case "semester":
filter.semester = content;
break;
case "noSemester":
filter.noSemester = content;
case "_id":
filter._id = content;
break;
}
}
......@@ -131,30 +118,38 @@ export let Filter = class Filter {
this.resetInfiniteBar();
}
static workloadFilter () {
let learnCardsets = [];
let leitnerCards = Leitner.find({
user_id: Meteor.userId()
});
let wozniakCards = Wozniak.find({
user_id: Meteor.userId()
});
leitnerCards.forEach(function (leitnerCard) {
if ($.inArray(leitnerCard.cardset_id, learnCardsets) === -1) {
learnCardsets.push(leitnerCard.cardset_id);
}
});
wozniakCards.forEach(function (wozniakCard) {
if ($.inArray(wozniakCard.cardset_id, learnCardsets) === -1) {
learnCardsets.push(wozniakCard.cardset_id);
}
});
return learnCardsets;
}
static updateWorkloadFilter () {
this.setActiveFilter(this.workloadFilter(), "_id");
}
static setDefaultFilter (filterType) {
let filter = {};
filter.topic = undefined;
if (Route.isWorkload()) {
let leitnerCards = Leitner.find({
user_id: Meteor.userId()
});
let wozniakCards = Wozniak.find({
user_id: Meteor.userId()
});
let learnCardsets = [];
leitnerCards.forEach(function (leitnerCard) {
if ($.inArray(leitnerCard.cardset_id, learnCardsets) === -1) {
learnCardsets.push(leitnerCard.cardset_id);
}
});
wozniakCards.forEach(function (wozniakCard) {
if ($.inArray(wozniakCard.cardset_id, learnCardsets) === -1) {
learnCardsets.push(wozniakCard.cardset_id);
}
});
filter._id = {$in: learnCardsets};
filter._id = this.workloadFilter();
}
if (Route.isMyCardsets() || FilterNavigation.gotAuthorFilter(filterType)) {
if (Route.isMyCardsets()) {
......@@ -166,25 +161,6 @@ export let Filter = class Filter {
if (FilterNavigation.gotCardTypeFilter(filterType)) {
filter.cardType = undefined;
}
if (FilterNavigation.gotTargetAudienceFilter(filterType)) {
filter.targetAudience = undefined;
}
if (FilterNavigation.gotCollegeFilter(filterType)) {
filter.college = undefined;
filter.noCollege = undefined;
}
if (FilterNavigation.gotCourseFilter(filterType)) {
filter.course = undefined;
filter.noCourse = undefined;
}
if (FilterNavigation.gotSemesterFilter(filterType)) {
filter.semester = undefined;
filter.noSemester = undefined;
}
if (FilterNavigation.gotModuleFilter(filterType)) {
filter.module = undefined;
filter.noModule = undefined;
}
if (FilterNavigation.gotDifficultyFilter(filterType)) {
filter.difficulty = undefined;
filter.noDifficulty = undefined;
......@@ -230,7 +206,12 @@ export let Filter = class Filter {
let query = {};
let activeFilter = this.getActiveFilter();
if (Route.isWorkload()) {
query._id = activeFilter._id;
query._id = {$in: activeFilter._id};
} else {
if (Session.get("selectingCardsetToLearn")) {
let learnFilter = this.getActiveFilter(3);
query._id = {$nin: learnFilter._id};
}
}
if (Route.isMyCardsets() || FilterNavigation.gotAuthorFilter(FilterNavigation.getRouteId()) && activeFilter.owner !== undefined) {
query.owner = activeFilter.owner;
......@@ -238,27 +219,6 @@ export let Filter = class Filter {
if (FilterNavigation.gotCardTypeFilter(FilterNavigation.getRouteId()) && activeFilter.cardType !== undefined) {
query.cardType = activeFilter.cardType;
}
if (FilterNavigation.gotTargetAudienceFilter(FilterNavigation.getRouteId()) && activeFilter.targetAudience !== undefined) {
query.targetAudience = activeFilter.targetAudience;
}
if (FilterNavigation.gotCollegeFilter(FilterNavigation.getRouteId()) && activeFilter.college !== undefined) {
query.college = activeFilter.college;
}
if (FilterNavigation.gotCourseFilter(FilterNavigation.getRouteId()) && activeFilter.course !== undefined) {
query.course = activeFilter.course;
}
if (FilterNavigation.gotSemesterFilter(FilterNavigation.getRouteId()) && activeFilter.semester !== undefined) {
query.semester = activeFilter.semester;
}
if (FilterNavigation.gotSemesterFilter(FilterNavigation.getRouteId()) && activeFilter.noSemester !== undefined) {
query.noSemester = activeFilter.noSemester;
}
if (FilterNavigation.gotModuleFilter(FilterNavigation.getRouteId()) && activeFilter.module !== undefined) {
query.module = activeFilter.module;
}
if (FilterNavigation.gotModuleFilter(FilterNavigation.getRouteId()) && activeFilter.noModule !== undefined) {
query.noModule = activeFilter.noModule;
}
if (FilterNavigation.gotDifficultyFilter(FilterNavigation.getRouteId()) && activeFilter.difficulty !== undefined) {
query.difficulty = activeFilter.difficulty;
}
......@@ -366,12 +326,7 @@ export let Filter = class Filter {
if (Route.isWorkload() && Session.get('cardsetIdFilter') !== undefined) {
query._id = {$in: Session.get('cardsetIdFilter')};
}
let totalResults;
if (Route.isCourseIteration()) {
totalResults = CourseIterations.find(query).count();
} else {
totalResults = Cardsets.find(query).count();
}
let totalResults = Cardsets.find(query).count();
if (totalResults > Session.get("itemsLimit")) {
$(".showMoreResults").data("visible", true);
Session.set("totalResults", totalResults);
......
......@@ -2,7 +2,6 @@ import {AdminSettings} from "./adminSettings.js";
import {Cards} from "./cards.js";
import {Cardsets} from "./cardsets.js";
import {CollegesCourses} from "./colleges_courses.js";
import {Courses} from "./courseIterations.js";
import {Leitner, Wozniak} from "./learned.js";
import {Notifications} from "./notifications.js";
import {Paid} from "./paid.js";
......@@ -14,7 +13,6 @@ Ground.Collection(AdminSettings);
Ground.Collection(Cards);
Ground.Collection(Cardsets);
Ground.Collection(CollegesCourses);
Ground.Collection(Courses);
Ground.Collection(Leitner);
Ground.Collection(Wozniak);
Ground.Collection(Notifications);
......
......@@ -154,6 +154,7 @@ if (Meteor.isServer) {
user_id: Meteor.userId()
}, {multi: true});
Meteor.call("updateLearnerCount", cardset_id);
return true;
},
deleteWozniak: function (cardset_id) {
check(cardset_id, String);
......@@ -167,6 +168,7 @@ if (Meteor.isServer) {
user_id: Meteor.userId()
}, {multi: true});
Meteor.call("updateLearnerCount", cardset_id);
return true;
},
updateWozniak: function (cardset_id, card_id, grade) {
check(cardset_id, String);
......
......@@ -7,7 +7,7 @@ export let Route = class Route {
* @return {Boolean} Return true, when route is a Cardset.
*/
static isCardset () {
return Router.current().route.getName() === "cardsetdetailsid" || Router.current().route.getName() === "cardsetcard";
return Router.current().route.getName() === 'cardsetlistid' || Router.current().route.getName() === 'cardsetdetailsid' || Router.current().route.getName() === "cardsetcard" || Router.current().route.getName() === 'admin_cardset';
}
/**
* Function checks if route is a card edit Mode
......@@ -89,10 +89,6 @@ export let Route = class Route {
return Router.current().route.getName() === "pool";
}
static isCourseIteration () {
return Router.current().route.getName() === "courseIterations";
}
static isFirstTimeVisit () {
if (localStorage.getItem(firstTimeVisit) === undefined || localStorage.getItem(firstTimeVisit) === null) {
localStorage.setItem(firstTimeVisit, "true");
......
//1: Privat / Private
//2: Vorkurs / Pre-course
//3: Brückenkurs / Bridge Course
//4: Bachelorkurs / Bachelor
//5: Masterkurs / Master
//6: Weiterbildungskurs / Advanced Training
let targetAudienceWithAccessControl = [2, 3, 4, 5, 6];
let targetAudienceWithSemester = [3, 4, 5];
let targetAudienceWithModule = [2, 3, 4, 5, 6];
let targetAudienceOrder = [{targetAudience: 1}, {targetAudience: 2}, {targetAudience: 3}, {targetAudience: 4}, {targetAudience: 5}, {targetAudience: 6}];
export let TargetAudience = class TargetAudience {
static gotAccessControl (targetAudience) {
return targetAudienceWithAccessControl.includes(targetAudience);
}
static gotSemester (targetAudience) {
return targetAudienceWithSemester.includes(targetAudience);
}
static getTargetAudienceWithModule () {
return targetAudienceWithModule;
}
static getTargetAudienceWithSemester () {
return targetAudienceWithSemester;
}
static gotModule (targetAudience) {
return targetAudienceWithModule.includes(targetAudience);
}
static getTargetAudienceName (targetAudience) {
return TAPi18n.__('courseIteration.targetAudience' + targetAudience + '.name');
}
static getTargetAudienceAbbreviation (targetAudience) {
return TAPi18n.__('courseIteration.targetAudience' + targetAudience + '.abbreviation');
}
static getTargetAudienceOrder () {
return targetAudienceOrder;
}
};
......@@ -12,9 +12,14 @@ import DOMPurify from 'dompurify';
import {DOMPurifyConfig} from "../../api/dompurify.js";
import "/client/markdeep.min.js";
import {getAuthorName} from "../../api/userdata";
import {Route} from "../../api/route";
Meteor.subscribe("collegesCourses");
Template.registerHelper('isSelectingCardsetToLearn', function () {
return Session.get("selectingCardsetToLearn");
});
Template.registerHelper('getFirstAppTitle', function () {
return Meteor.settings.public.welcome.title.first;
});
......@@ -114,6 +119,9 @@ Template.registerHelper("getKindText", function (kind, displayType = 0) {
});
Template.registerHelper("getShuffleLabel", function (shuffled = false) {
if (Route.isRepetitorium()) {
shuffled = false;
}
if (shuffled) {
return '<span class="label label-shuffled" data-id="shuffled" title="' + TAPi18n.__('cardset.shuffled.long') + '">' + TAPi18n.__('cardset.shuffled.short') + '</span>';
}
......
......@@ -116,6 +116,7 @@ Router.route('/shuffle', {
name: 'shuffle',
template: 'cardsets',
data: function () {
Session.set('isNewCardset', true);
Filter.resetMaxItemCounter();
}
});
......@@ -128,6 +129,7 @@ Router.route('/cardset/:_id', {
name: 'cardsetdetailsid',
template: 'cardsetAccess',
data: function () {
Session.set('isNewCardset', false);
return Cardsets.findOne({_id: this.params._id});
}
});
......@@ -136,6 +138,7 @@ Router.route('/cardset/:_id/card/:card_id', {
name: 'cardsetcard',
template: 'cardsetAccess',
data: function () {
Session.set('isNewCardset', false);
Session.set('activeCard', this.params.card_id);
return Cardsets.findOne({_id: this.params._id});
}
......@@ -174,6 +177,7 @@ Router.route('/cardsetlist/:_id', {
name: 'cardsetlistid',
template: 'cardsetAccess',
data: function () {
Session.set('isNewCardset', false);
return Cardsets.findOne({_id: this.params._id});
}
});
......@@ -278,6 +282,7 @@ Router.route('/admin/cardset/:_id', {
template: 'admin_cardset',
layoutTemplate: 'admin_main',
data: function () {
Session.set('isNewCardset', false);
return Cardsets.findOne({_id: this.params._id});
}
});
......
import {Meteor} from "meteor/meteor";
import {Cards} from "../../api/cards.js";
import {Cardsets} from "../../api/cardsets.js";
import {CourseIterations} from "../../api/courseIterations.js";
import {ColorThemes} from "../../api/theme.js";
import {Learned, Leitner, Wozniak} from "../../api/learned.js";
import {AdminSettings} from "../../api/adminSettings";
import {CronScheduler} from "../../../server/cronjob.js";
import {Ratings} from "../../api/ratings";