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

Merge branch 'staging' of git.thm.de:arsnova/flashcards into staging

parents 5e800617 12db96da
Pipeline #20473 failed with stages
in 0 seconds
export let CardsetNavigation = class CardsetNavigation {
/**
* Creates a web push subscription for the current device.
* The Browser ask the user for permissions and creates the subscription.
* Afterwards the subscription will be saved for the current user via the
* Meteor-method addWebPushSubscription.
*/
static subscribeForPushNotification () {
try {
navigator.serviceWorker.getRegistration()
.then(function (registration) {
return registration.pushManager.getSubscription()
.then(function (subscription) {
if (!subscription) {
return registration.pushManager.subscribe({userVisibleOnly: true});
}
});
})
.then(function (subscription) {
if (subscription) {
var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
const key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
const authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
const endpoint = subscription.endpoint;
const sub = {
endpoint: endpoint,
key: key,
authSecret: authSecret
};
Meteor.call("addWebPushSubscription", sub, function (error) {
if (error) {
throw new Meteor.Error(error.statusCode, 'Error subscription failed');
}
});
}
});
} catch (error) {
console.log(error);
}
}
/**
* Add the current user to the leitner algorithm.
*/
static addToLeitner (cardset_id) {
this.subscribeForPushNotification();
Meteor.call('addToLeitner', cardset_id);
}
};
......@@ -7,4 +7,14 @@ export let CardsetVisuals = class CardsetVisuals {
$('.markdeepCardsetContent').css('height', 'auto');
}
}
static changeCollapseIcon (iconId) {
if ($(iconId).hasClass("glyphicon-collapse-down")) {
$(iconId).removeClass("glyphicon-collapse-down");
$(iconId).addClass("glyphicon-collapse-up");
} else {
$(iconId).removeClass("glyphicon-collapse-up");
$(iconId).addClass("glyphicon-collapse-down");
}
}
};
......@@ -51,61 +51,59 @@ function importCards(data, cardset, importType) {
}
if (importType === 1) {
for (let i = 0; i < data.length; i++) {
let item = data[i];
let subject, front, back, hint, lecture, top, bottom;
try {
// If the string is UTF-8, this will work and not throw an error.
subject = decodeURIComponent(encodeURIComponent(item.subject));
front = decodeURIComponent(encodeURIComponent(item.front));
back = decodeURIComponent(encodeURIComponent(item.back));
hint = decodeURIComponent(encodeURIComponent(item.hint));
lecture = decodeURIComponent(encodeURIComponent(item.lecture));
top = decodeURIComponent(encodeURIComponent(item.top));
bottom = decodeURIComponent(encodeURIComponent(item.bottom));
} catch (e) {
// If it isn't, an error will be thrown, and we can assume that we have an ISO string.
subject = item.subject;
front = item.front;
back = item.back;
hint = item.hint;
lecture = item.lecture;
top = item.top;
bottom = item.bottom;
}
let item = data[i];
let subject, front, back, hint, lecture, top, bottom;
try {
// If the string is UTF-8, this will work and not throw an error.
subject = decodeURIComponent(encodeURIComponent(item.subject));
front = decodeURIComponent(encodeURIComponent(item.front));
back = decodeURIComponent(encodeURIComponent(item.back));
hint = decodeURIComponent(encodeURIComponent(item.hint));
lecture = decodeURIComponent(encodeURIComponent(item.lecture));
top = decodeURIComponent(encodeURIComponent(item.top));
bottom = decodeURIComponent(encodeURIComponent(item.bottom));
} catch (e) {
// If it isn't, an error will be thrown, and we can assume that we have an ISO string.
subject = item.subject;
front = item.front;
back = item.back;
hint = item.hint;
lecture = item.lecture;
top = item.top;
bottom = item.bottom;
}
let hlcodeReplacement = "\n```\n";
let regex = /<hlcode>|<\/hlcode>/g;
front = front.replace(regex, hlcodeReplacement);
back = back.replace(regex, hlcodeReplacement);
let originalAuthorName;
if (item.originalAuthor !== undefined) {
originalAuthorName = {
legacyName: item.originalAuthor
};
} else {
originalAuthorName = item.originalAuthorName;
}
Cards.insert({
subject: subject.trim(),
front: front,
back: back,
hint: hint,
cardset_id: cardset._id,
cardGroup: -1,
lecture: lecture,
top: top,
bottom: bottom,
centerTextElement: item.centerTextElement,
alignType: item.alignType,
learningGoalLevel: item.learningGoalLevel,
backgroundStyle: item.backgroundStyle,
learningUnit: item.learningUnit,
date: item.date,
dateUpdated: item.dateUpdated,
originalAuthorName: originalAuthorName
}, {trimStrings: false});
let hlcodeReplacement = "\n```\n";
let regex = /<hlcode>|<\/hlcode>/g;
front = front.replace(regex, hlcodeReplacement);
back = back.replace(regex, hlcodeReplacement);
let originalAuthorName;
if (item.originalAuthor !== undefined) {
originalAuthorName = {
legacyName: item.originalAuthor
};
} else {
originalAuthorName = item.originalAuthorName;
}
Cards.insert({
subject: subject.trim(),
front: front,
back: back,
hint: hint,
cardset_id: cardset._id,
cardGroup: -1,
lecture: lecture,
top: top,
bottom: bottom,
centerTextElement: item.centerTextElement,
alignType: item.alignType,
learningGoalLevel: item.learningGoalLevel,
backgroundStyle: item.backgroundStyle,
learningUnit: item.learningUnit,
date: item.date,
dateUpdated: item.dateUpdated,
originalAuthorName: originalAuthorName
}, {trimStrings: false});
} else {
Cards.insert({
subject: item.subject,
......
This diff is collapsed.
This diff is collapsed.
<template name="cardsetLearnActivityStatistic">
{{#if isInRole 'firstLogin'}}
{{> first_login_content_only}}
{{else}}
{{#if isInRole 'blocked'}}
{{> access_denied_content_only}}
{{else}}
{{#unless isEditor}}
{{> access_denied_content_only}}
{{else}}
<div class="container">
<div id="cardsetInfoDetail">
<div class="panel panel-default cardsetInfo">
<div class="panel-body">
<h4>{{this.name}}</h4>
<hr>
<div class="row">
<span class="col-sm-6 collapseCardsetInfoContainer">
{{> cardsetInfoBox}}
</span>
<span class="col-sm-6 collapseCardsetInfoContainer">
{{> bonusInfoBox}}
</span>
</div>
</div>
<div class="panel-footer">
<button id="exportCSV" class="btn btn-success btn-raised btn-default">{{_
"box_export"}}</button>
</div>
</div>
</div>
<div style="overflow-x:auto;">
<table class="table table-striped table-hover table-user-list">
<tbody>
<tr class="active">
<th>{{_ "panel-body.birthname"}}</th>
<th>{{_ "panel-body.givenname"}}</th>
<th>{{_ "panel-body.email"}}
</th>
<th>{{_ "leitnerProgress.box" number=1}} [{{this.learningInterval.[0]}}]
</th>
<th>{{_ "leitnerProgress.box" number=2}} [{{this.learningInterval.[1]}}]
</th>
<th>{{_ "leitnerProgress.box" number=3}} [{{this.learningInterval.[2]}}]
</th>
<th>{{_ "leitnerProgress.box" number=4}} [{{this.learningInterval.[3]}}]
</th>
<th>{{_ "leitnerProgress.box" number=5}} [{{this.learningInterval.[4]}}]
</th>
<th>{{_ "leitnerProgress.learned"}}</th>
<th>{{_ "details"}}</th>
</tr>
{{#each getCardsetStats}}
<tr>
<td>{{this.birthname}}</td>
<td>{{this.givenname}}</td>
<td><a href="mailto:{{this.email}}">{{this.email}}</a></td>
<td>{{this.box1}}</td>
<td>{{this.box2}}</td>
<td>{{this.box3}}</td>
<td>{{this.box4}}</td>
<td>{{this.box5}}</td>
<td>
{{#if earnedTrophy}}
<span class="green-learning-text">
{{this.box6}}{{{getPercentage this.box6}}}
</span>
<i class="fa fa-trophy pull-right"></i>
{{else}}
<span class="red-learning-text">
{{this.box6}}{{{getPercentage this.box6}}}
</span>
{{/if}}
</td>
<td>
<button data-id="{{this.user_id}}" class="btn btn-block detailed-stats"><i
class="glyphicon glyphicon glyphicon-stats"
data-id="{{this.user_id}}"></i></button>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
<button class="btn btn-block btn-raised btn-danger" id="backButton">
<span class="pull-left"><i class="fa fa-arrow-left"></i> {{_ "backbutton"}}</span>
</button>
</div>
{{/unless}}
{{/if}}
{{/if}}
</template>
//------------------------ IMPORTS
import {Meteor} from "meteor/meteor";
import {Session} from "meteor/session";
import {Template} from "meteor/templating";
import {Cardsets} from "../../../../api/cardsets";
import "./bonus.html";
/*
* ############################################################################
* cardsetLearnActivityStatistic
* ############################################################################
*/
Template.cardsetLearnActivityStatistic.onRendered(function () {
Session.set('activeCardset', Cardsets.findOne({_id: Router.current().params._id}));
});
Template.cardsetLearnActivityStatistic.helpers({
getCardsetStats: function () {
return Session.get("learnerStats");
},
getPercentage: function (count) {
let percentage = Math.round(count / Session.get('activeCardset').quantity * 100);
if (percentage > 0) {
return '<span class="cardPercentage">[' + percentage + ' %]</span>';
}
},
earnedTrophy: function () {
let totalCards = this.box1 + this.box2 + this.box3 + this.box4 + this.box5 + this.box6;
let box6Percentage = (this.box6 / totalCards) * 100;
return box6Percentage >= 95;
}
});
Template.cardsetLearnActivityStatistic.events({
"click #exportCSV": function () {
var cardset = Cardsets.findOne({_id: this._id});
var hiddenElement = document.createElement('a');
var header = [];
header[0] = TAPi18n.__('leitnerProgress.box', {number: 1});
header[1] = TAPi18n.__('leitnerProgress.box', {number: 2});
header[2] = TAPi18n.__('leitnerProgress.box', {number: 3});
header[3] = TAPi18n.__('leitnerProgress.box', {number: 4});
header[4] = TAPi18n.__('leitnerProgress.box', {number: 5});
header[5] = TAPi18n.__('leitnerProgress.learned');
header[6] = TAPi18n.__('box_export_birth_name');
header[7] = TAPi18n.__('box_export_given_name');
header[8] = TAPi18n.__('box_export_mail');
header[9] = TAPi18n.__('leitnerProgress.percentage');
Meteor.call("getCSVExport", cardset._id, header, function (error, result) {
if (error) {
throw new Meteor.Error(error.statusCode, 'Error could not receive content for .csv');
}
if (result) {
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.name + "_" + statistics + "_" + new Date() + ".csv");
hiddenElement.download = str.replace(/ /g, "_").replace(/:/g, "_");
document.body.appendChild(hiddenElement);
hiddenElement.click();
}
});
},
"click #backButton": function () {
Router.go('cardsetdetailsid', {_id: this._id});
},
"click .detailed-stats": function (event) {
Router.go('progress', {
_id: Router.current().params._id,
user_id: $(event.target).data('id')
});
},
"click #showIntervalHelp": function (event) {
event.stopPropagation();
Session.set('helpFilter', "leitner");
Router.go('help');
}
});
Template.cardsetLearnActivityStatistic.created = function () {
Session.set("learnerStats", "");
Meteor.call("getLearningData", Router.current().params._id, function (error, result) {
if (error) {
throw new Meteor.Error(error.statusCode, 'Error could not receive content for stats');
}
if (result) {
Session.set("learnerStats", result);
}
});
};
<template name="cardsetList">
<div id="cardset-list">
{{#if gotCards}}
{{#each cardsetList}}
{{#if isShuffledCardset this._id}}
<div class="cardListName btn-raised {{getColors}}">
{{this.name}}
{{#unless isActiveRoute regex='demo|demolist|making|makinglist'}}
<span class="cardListOwner hidden-xs">
{{getAuthorName this.owner}}
</span>
{{/unless}}
<br>
<span class="badge" title="{{_ "cardset.info.quantity"}}">
{{this.quantity}}
</span>
{{{getCardTypeLabel this.cardType}}}
{{{getDifficultyLabel this.cardType this.difficulty}}}
</div>
{{/if}}
{{#each cardSubject}}
<div class="cardListSubject">
<table class="table table-hover">
<thead>
<tr>
<th>
<span>{{this.subject}}</span>
<span class="badge cardset-list-badge">{{cardList true}}</span>
</th>
</tr>
</thead>
<tbody>
{{#each cardList false}}
<tr class="cardListRow">
<td class="{{getCardsetBackground this.difficulty this.cardType 1}}"
data-id="{{this._id}}" data-card-type="{{this.cardType}}">
<div class="cardListNumber" data-id="{{this._id}}">
{{getPriority @index}}
</div>
<div class="col-xs-11 listCard" data-id="{{this._id}}"
data-card-type="{{this.cardType}}">
{{cleanContent getText}}
</div>
<i class="glyphicon glyphicon-menu-right pull-right" data-id="{{this._id}}"
data-card-type="{{this.cardType}}"></i>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
{{/each}}
{{/each}}
{{else}}
<table class="table table-bordered table-hover">
<tbody>
<tr class="empty-listitem">
<td colspan="3" align="center">
<span>
{{#if isActiveRoute 'cardsetlistid'}}{{_ "nocards"}}{{else}}{{_
"learningUnit.noUnitsFound"}}{{/if}}</span>
</td>
</tr>
</tbody>
</table>
{{/if}}
</div>
</template>
//------------------------ IMPORTS
import {Session} from "meteor/session";
import {Template} from "meteor/templating";
import {Cards} from "../../../../api/cards";
import {CardVisuals} from "../../../../api/cardVisuals";
import {CardType} from "../../../../api/cardTypes";
import {Cardsets} from "../../../../api/cardsets";
import {Route} from "../../../../api/route";
import {CardNavigation} from "../../../../api/cardNavigation";
import "./cards.html";
/*
* ############################################################################
* cardsetList
* ############################################################################
*/
Template.cardsetList.helpers({
isShuffledCardset: function () {
if (Router.current().route.getName() === "demolist") {
return Cardsets.findOne({kind: 'demo', name: "DemoCardset", shuffled: true}).shuffled;
} else if (Router.current().route.getName() === "makinglist") {
return Cardsets.findOne({kind: 'demo', name: "MakingOfCardset", shuffled: true}).shuffled;
} else {
return Cardsets.findOne({_id: Router.current().params._id}).shuffled;
}
},
cardsetList: function () {
let isDemo = (Router.current().route.getName() === "demolist" || Router.current().route.getName() === "makinglist");
if (Router.current().route.getName() === "cardsetlistid" || Router.current().route.getName() === "presentationlist" || isDemo) {
let cardsetId = Router.current().params._id;
if (isDemo) {
if (Route.isDemo()) {
cardsetId = Cardsets.findOne({kind: 'demo', name: "DemoCardset", shuffled: true})._id;
} else {
cardsetId = Cardsets.findOne({kind: 'demo', name: "MakingOfCardset", shuffled: true})._id;
}
}
if (this.shuffled) {
let cardsetFilter = [];
let sortCardsets = Cardsets.find({_id: {$in: this.cardGroups}}, {
sort: {name: 1}, fields: {_id: 1, name: 1, kind: 1, owner: 1, quantity: 1, difficulty: 1, cardType: 1}
}).fetch();
sortCardsets.forEach(function (cardset) {
if (cardset._id !== cardsetId) {
cardsetFilter.push(cardset);
}
});
return cardsetFilter;
} else {
return Cardsets.find({_id: this._id}).fetch();
}
} else {
return Cardsets.find({_id: Session.get('tempLearningIndex')}).fetch();
}
},
getPriority: function (index) {
return index + 1;
},
cleanContent: function (text) {
return CardVisuals.removeMarkdeepTags(text);
},
gotCards: function () {
if (Router.current().route.getName() === "cardsetlistid" || Router.current().route.getName() === "presentationlist" || Router.current().route.getName() === "demolist" || Router.current().route.getName() === "makinglist") {
if (this.shuffled) {
return Cards.find({cardset_id: {$in: this.cardGroups}}).count();
} else {
return Cards.find({cardset_id: this._id}).count();
}
} else {
return Cards.find({cardset_id: Session.get('tempLearningIndex'), cardType: 0}).count();
}
},
cardSubject: function () {
if (Router.current().route.getName() === "cardsetlistid" || Router.current().route.getName() === "presentationlist" || Router.current().route.getName() === "demolist" || Router.current().route.getName() === "makinglist") {
return _.uniq(Cards.find({
cardset_id: this._id
}, {
cardset_id: 1,
subject: 1,
sort: {subject: 1}
}).fetch(), function (card) {
return card.subject;
});
} else {
return _.uniq(Cards.find({
cardset_id: this._id
}, {
cardset_id: 1,
subject: 1,
sort: {subject: 1}
}).fetch(), function (card) {
return card.subject;
});
}
},
cardList: function (countCards) {
let sortQuery;
sortQuery = CardType.getSortQuery(Cardsets.findOne({_id: this.cardset_id}).cardType);
if (countCards) {
return Cards.find({
cardset_id: this.cardset_id,
subject: this.subject
}, {
fields: {
_id: 1,
front: 1,
back: 1,
hint: 1,
lecture: 1,
top: 1,
bottom: 1,
cardset_id: 1
},
sort: sortQuery
}).count();
}
let cards = Cards.find({
cardset_id: this.cardset_id,
subject: this.subject
}, {
fields: {
_id: 1,
front: 1,
back: 1,
hint: 1,
lecture: 1,
top: 1,
bottom: 1,
cardset_id: 1
},
sort: sortQuery
}).fetch();
return CardVisuals.setTypeAndDifficulty(cards);
},
getColors: function () {
switch (this.kind) {
case "personal":
return "btn-warning";
case "free":
return "btn-info";
case "edu":
return "btn-success";
case "pro":
return "btn-danger";
case "demo":
return "btn-demo";
}
},
gotReferences: function () {
return Cardsets.findOne({_id: Router.current().params._id}).cardGroups !== [""];
},
getText: function () {
let cubeSides = CardType.getCardTypeCubeSides(this.cardType);
switch (cubeSides[0].contentId) {
case 1:
return this.front;
case 2:
return this.back;
case 3:
return this.hint;
case 4:
return this.lecture;
case 5:
return this.top;
case 6:
return this.bottom;
}
}
});
Template.cardsetList.events({
'click .cardListRow': function (evt) {
let cubeSides = CardType.getCardTypeCubeSides($(evt.target).data('card-type'));
Session.set('cardType', $(evt.target).data('card-type'));
Session.set('activeCardContentId', cubeSides[0].contentId);
if (Router.current().route.getName() === "cardsetlistid" || Router.current().route.getName() === "presentationlist" || Router.current().route.getName() === "demolist" || Router.current().route.getName() === "makinglist") {
CardNavigation.setActiveCardData($(evt.target).data('id'));
if (Router.current().route.getName() === "presentationlist") {
Router.go('presentation', {
_id: Router.current().params._id
});
} else if (Router.current().route.getName() === "demolist") {
Router.go('demo');
} else if (Router.current().route.getName() === "makinglist") {
Router.go('making');
} else {
Router.go('cardsetcard', {
_id: Router.current().params._id,
card_id: $(evt.target).data('id')
});
}
} else {
let learningUnit = $(evt.target).data('id');
Session.set('learningIndex', Session.get('tempLearningIndex'));
Session.set('learningUnit', learningUnit);
Session.set('subject', Cards.findOne({_id: learningUnit}).subject);
$('#showSelectLearningUnitModal').modal('hide');
}
}
});
<template name="cardsetManageEditors">