Ajout choix groupe pour les questionnaire et prise en compte création fichiers HTML
This commit is contained in:
parent
9a4c4aa122
commit
e092c31e43
|
@ -15,6 +15,7 @@ module.exports =
|
|||
// -- groupes :
|
||||
groupRoutes: "/group",
|
||||
getGroupRoute: "/get/",
|
||||
previewGroupRoutes: "/preview",
|
||||
searchGroupsRoute : "/search",
|
||||
// -- questions & choices :
|
||||
questionsRoute: "/question/",
|
||||
|
@ -56,11 +57,11 @@ module.exports =
|
|||
dirCacheTags : "datas/questionnaires/tags",
|
||||
dirCacheUsersQuestionnaires : "datas/users/questionnaires",
|
||||
// Emplacement des fichiers HTML générés :
|
||||
dirHTMLGroups : "front/public/quizs/gp",
|
||||
dirHTMLGroups : "front/public/quiz/gp",
|
||||
dirHTMLQuestionnaires : "front/public/quiz",
|
||||
dirHTMLTags : "front/public/quizs",
|
||||
// Idem mais pour urls :
|
||||
dirWebGroups : "quizs/gp",
|
||||
dirWebGroups : "quiz/gp",
|
||||
dirWebQuestionnaires : "quiz",
|
||||
dirWebTags : "quizs/",
|
||||
// limite des résultat du moteur de recherche, quand demande de résultats au hasard :
|
||||
|
|
|
@ -9,7 +9,8 @@ const questionnaireCtrl = require("./questionnaire");
|
|||
const txt = require("../lang/"+config.adminLang+"/choice");
|
||||
const txtQuestion = require("../lang/"+config.adminLang+"/question");
|
||||
|
||||
// J'arrive aux deux contrôleurs suivants après être passé par les contrôleurs de "question" qui leur passe la main via next()
|
||||
// J'arrive aux deux contrôleurs suivants après être les contrôleurs de "question" qui leur passe la main via next()
|
||||
|
||||
exports.create = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
|
@ -51,8 +52,8 @@ exports.create = async (req, res, next) =>
|
|||
for(let i in choices)
|
||||
await db["Choice"].create(choices[i], { fields: ["text", "isCorrect", "QuestionId"] });
|
||||
question=await questionCtrl.creaQuestionJson(req.body.QuestionId);// besoin de ces données pour la réponse
|
||||
await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId,true);// pour le cache + HTML
|
||||
questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId,true);// nécessaire réaffichage après ajout
|
||||
await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId, true);// pour le cache + HTML
|
||||
questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId, true);// nécessaire au réaffichage après ajout
|
||||
res.status(201).json({ message: txtQuestion.addOkMessage , questionnaire: questionnaire });
|
||||
}
|
||||
next();
|
||||
|
@ -121,9 +122,9 @@ exports.modify = async (req, res, next) =>
|
|||
await db["Choice"].update(choicesUpdated[i], { where: { id: choicesUpdated[i].id } , fields: ["text", "isCorrect"], limit:1 });
|
||||
for(let i in choicesAdded)
|
||||
await db["Choice"].create(choicesAdded[i], { fields: ["text", "isCorrect", "QuestionId"] });
|
||||
question=await questionCtrl.creaQuestionJson(req.params.id);// attendre pour pouvoir tout retourner
|
||||
await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId,true);// pour le cache + HTML
|
||||
questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId, true);// nécessaire réaffichage après enregistrement
|
||||
question=await questionCtrl.creaQuestionJson(req.params.id);// nécessaire d'attendre pour pouvoir tout retourner ensuite
|
||||
await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId, true);// pour le cache + HTML
|
||||
questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId, true);// nécessaire au réaffichage après enregistrement
|
||||
res.status(200).json({ message: txtQuestion.updateOkMessage , questionnaire: questionnaire });
|
||||
}
|
||||
next();
|
||||
|
|
|
@ -157,6 +157,38 @@ exports.getOneById = async (req, res, next) =>
|
|||
}
|
||||
}
|
||||
|
||||
exports.showOneGroupById = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Seuls certains utilisateurs peuvent avoir accès à cette page
|
||||
const connectedUser=await userCtrl.checkTokenUser(req.params.token);
|
||||
if(connectedUser===false)
|
||||
res.status(403).json({ errors:txtGeneral.failAuthToken });
|
||||
else
|
||||
{
|
||||
if(["admin", "manager", "creator"].indexOf(connectedUser.User.status) === -1)
|
||||
res.status(403).json({ errors:txtGeneral.notAllowed });
|
||||
else
|
||||
{
|
||||
const HTML=await creaGroupHTML(req.params.id, true);
|
||||
if(HTML)
|
||||
{
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.send(HTML);
|
||||
}
|
||||
else
|
||||
res.status(404).json({ errors:txtGeneral.notFound.replace("#SEARCH", req.params.id) });
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Retourne les statistiques concernant les groupes de questionnaires
|
||||
exports.getStats = async(req, res, next) =>
|
||||
{
|
||||
|
@ -218,15 +250,18 @@ const creaGroupJson = async (id) =>
|
|||
if(Group)
|
||||
{
|
||||
let datas={ Group };
|
||||
const Questionnaires=await db["Questionnaire"].findAll({ where: { GroupId: Group.id }, order: [["rankInGroup", "ASC"], ["createdAt", "ASC"]], attributes: ["id"] });
|
||||
const Questionnaires=await db["Questionnaire"].findAll({ where: { GroupId: Group.id, isPublished : true }, order: [["rankInGroup", "ASC"], ["createdAt", "ASC"]], attributes: ["id"] });
|
||||
if(Questionnaires)
|
||||
datas.Questionnaires=Questionnaires;
|
||||
await toolFile.createJSON(configQuestionnaires.dirCacheGroups, id, datas);
|
||||
// si le groupe est publiable on génère/actualise la page HTML :
|
||||
// Si le groupe est publiable on génère/actualise la page HTML :
|
||||
if(checkGroupIsPublishable(datas))
|
||||
creaGroupHTML(id);
|
||||
await creaGroupHTML(id);// !!! await important car sinon bug, plusieurs fonctions accédant au même fichier (à revoir car pas clair !)
|
||||
else // dans le cas contraire, on supprime l'éventuel fichier préexistant
|
||||
toolFile.deleteFile(config.dirHTMLGroups, Group.slug+".html");
|
||||
// Dans certains cas les fichiers HTML des quizs du groupe peuvent aussi être impactés (notamment le dernier)
|
||||
for(let i in Questionnaires)
|
||||
await questionnaireCtrl.creaQuestionnaireHTML(Questionnaires[i].id, false);
|
||||
// + mise à jour des statistiques :
|
||||
creaStatsGroupsJson();
|
||||
return datas;
|
||||
|
@ -258,15 +293,15 @@ const checkGroupIsPublishable = (datas, checkDate=true) =>
|
|||
|
||||
const creaGroupHTML = async (id, preview = false) =>
|
||||
{
|
||||
// J'ai besoin de toutes les infos concernant le groupe et ses éléments pour les transmettre à la vue
|
||||
const group=await searchGroupById(id, true);
|
||||
if(!group)
|
||||
return false;
|
||||
if(checkGroupIsPublishable(group)===false && preview===false)
|
||||
if(group.isPublishable === false && preview === false)
|
||||
return false;
|
||||
const txtIllustration = require("../lang/"+config.adminLang+"/illustration");
|
||||
const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz-group.pug");
|
||||
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
|
||||
const pageDatas = // à revoir en regardant les besoins de la vue
|
||||
const pageDatas =
|
||||
{
|
||||
config: config,
|
||||
configQuestionnaires: configQuestionnaires,
|
||||
|
@ -274,18 +309,19 @@ const creaGroupHTML = async (id, preview = false) =>
|
|||
tool: tool,
|
||||
txtGeneral: txtGeneral,
|
||||
txtGroups: txtGroups,
|
||||
pageLang: group.language,
|
||||
metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(groupe.introduction.replace("<br>", " ").replace("</p>", " ")), 200),
|
||||
author: group.CreatorName,
|
||||
pageTitle: txtGroups.groupsName+" "+group.title,
|
||||
contentTitle: group.title+"("+txtGroups.groupsName+")",
|
||||
txtIllustration: txtIllustration,
|
||||
pageLang: group.Group.language,
|
||||
metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(group.Group.introduction.replace("<br>", " ").replace("</p>", " ")), 200),
|
||||
author: group.Group.CreatorName,
|
||||
pageTitle: txtGroups.groupsName+" "+group.Group.title,
|
||||
contentTitle: group.Group.title+"("+txtGroups.groupsName+")",
|
||||
group: group,
|
||||
linkCanonical: config.siteUrl+"/"+config.dirWebGroups+"/"+group.slug+".html"
|
||||
linkCanonical: config.siteUrl+"/"+config.dirWebGroups+"/"+group.Group.slug+".html"
|
||||
}
|
||||
const html=await compiledFunction(pageDatas);
|
||||
if(preview === false)
|
||||
{
|
||||
await toolFile.createHTML(configQuestionnaires.dirHTMLGroups, group.slug, html);
|
||||
await toolFile.createHTML(configQuestionnaires.dirHTMLGroups, group.Group.slug, html);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -300,6 +336,7 @@ const searchGroupById = async (id, reassemble=false) =>
|
|||
group=await creaGroupJson(id);
|
||||
if(!group)
|
||||
return false;
|
||||
group.Group.isPublishable=checkGroupIsPublishable(group);
|
||||
if(reassemble)
|
||||
{
|
||||
let questionnaire; Questionnaires=[];
|
||||
|
@ -308,7 +345,7 @@ const searchGroupById = async (id, reassemble=false) =>
|
|||
group.Group.CreatorName=author.User.name;
|
||||
for(let i in group.Questionnaires)
|
||||
{
|
||||
questionnaire=await questionnaireCtrl.searchQuestionnaireById(questionnaire.Questions[i].id, true);
|
||||
questionnaire=await questionnaireCtrl.searchQuestionnaireById(group.Questionnaires[i].id, true);
|
||||
if(questionnaire)
|
||||
Questionnaires.push(questionnaire);
|
||||
}
|
||||
|
|
|
@ -20,12 +20,12 @@ exports.create = async (req, res, next) =>
|
|||
const questionnaire=await questionnaireCtrl.searchQuestionnaireById(req.body.QuestionnaireId);
|
||||
if(!questionnaire)
|
||||
throw txt.needQuestionnaire;
|
||||
else if(config.nbQuestionsMax!==0 && questionnaire.Questions.length>=config.nbQuestionsMax)
|
||||
else if(config.nbQuestionsMax !== 0 && questionnaire.Questions.length >= config.nbQuestionsMax)
|
||||
res.status(400).json({ errors: [txt.needMaxQuestions+config.nbQuestionsMax] });
|
||||
else
|
||||
{
|
||||
const question=await db["Question"].create({ ...req.body }, { fields: ["text", "explanation", "rank", "QuestionnaireId"] });
|
||||
questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId);
|
||||
await questionnaireCtrl.creaQuestionnaireJson(req.body.QuestionnaireId);
|
||||
req.body.QuestionId=question.id;
|
||||
next();// je passe la main au contrôleur qui gère les réponses possibles saisies pour cette question
|
||||
return true;
|
||||
|
|
|
@ -15,6 +15,7 @@ const toolError = require("../tools/error");
|
|||
const toolFile = require("../tools/file");
|
||||
const toolMail = require("../tools/mail");
|
||||
|
||||
const groupCtrl = require("./group");
|
||||
const questionCtrl = require("./question");
|
||||
const illustrationCtrl = require("./illustration");
|
||||
const tagCtrl = require("./tag");
|
||||
|
@ -29,13 +30,39 @@ exports.create = async (req, res, next) =>
|
|||
{
|
||||
try
|
||||
{
|
||||
const db = require("../models/index");
|
||||
req.body.CreatorId=req.connectedUser.User.id;
|
||||
const questionnaire=await db["Questionnaire"].create({ ...req.body }, { fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime", "CreatorId"] });
|
||||
creaStatsQuestionnairesJson();
|
||||
//utile au middleware suivant (classement tags) qui s'occupe aussi de retourner une réponse si ok :
|
||||
req.body.QuestionnaireId=questionnaire.id;
|
||||
next();
|
||||
if(tool.isEmpty(req.body.GroupId) && !tool.isEmpty(req.body.rankInGroup))
|
||||
res.status(400).json({ errors : [txtQuestionnaire.needGroupIfRank] });
|
||||
else
|
||||
{
|
||||
const db = require("../models/index");
|
||||
req.body.CreatorId=req.connectedUser.User.id;
|
||||
if(!tool.isEmpty(req.body.GroupId) && tool.isEmpty(req.body.rankInGroup))
|
||||
req.body.rankInGroup=1;
|
||||
const questionnaire=await db["Questionnaire"].create({ ...req.body }, { fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime", "CreatorId", "GroupId", "rankInGroup"] });
|
||||
creaStatsQuestionnairesJson();
|
||||
// Si un groupe a été sélectionné, il faut mettre à jour les fichiers du groupe :
|
||||
if(!tool.isEmpty(req.body.GroupId))
|
||||
{
|
||||
const groupInfos = await groupCtrl.creaGroupJson(req.body.GroupId);
|
||||
// Si le nouveau quiz est publiable, le fichier HTML de l'éventuel quiz/élément précédent du groupe doit être actualisé
|
||||
// Si le nouveau n'est pas publiable, il ne sera pas listé dans groupInfos.Questionnaires donc pas d'incidence
|
||||
if(groupInfos !== false)
|
||||
{
|
||||
for(let i in groupInfos.Questionnaires)
|
||||
{
|
||||
if(groupInfos.Questionnaires[i].id === questionnaire.id && i != 0)
|
||||
{
|
||||
let j=parseInt(i)-1;// !! "i" n'est pas un nombre !
|
||||
if(groupInfos.Questionnaires[j] !== undefined)
|
||||
await creaQuestionnaireHTML(groupInfos.Questionnaires[j].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// id utile au middleware suivant (classement tags) qui s'occupe aussi de retourner une réponse si ok :
|
||||
req.body.QuestionnaireId=questionnaire.id;
|
||||
next();
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
|
@ -65,12 +92,27 @@ exports.modify = async (req, res, next) =>
|
|||
}
|
||||
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId)
|
||||
res.status(401).json({ errors: txtGeneral.notAllowed });
|
||||
else if(tool.isEmpty(req.body.GroupId) && !tool.isEmpty(req.body.rankInGroup))
|
||||
res.status(400).json({ errors : [txtQuestionnaire.needGroupIfRank] });
|
||||
else
|
||||
{
|
||||
await db["Questionnaire"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime"], limit:1 }); // voir si admin aura le droit de changer le créateur ? et ajouter une gestion des redirection si slug change ?
|
||||
if(!tool.isEmpty(req.body.GroupId) && tool.isEmpty(req.body.rankInGroup))
|
||||
req.body.rankInGroup=1;
|
||||
await db["Questionnaire"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["title", "slug", "introduction", "keywords", "publishingAt", "language", "estimatedTime", "GroupId", "rankInGroup"], limit:1 });
|
||||
creaStatsQuestionnairesJson();// le nombre de quizs publiés peut avoir changé
|
||||
// Si le questionnaire a été déplacé d'un groupe, il faut actualiser les fichiers de l'ancien groupe :
|
||||
if(req.body.GroupId != questionnaire.Questionnaire.GroupId && !tool.isEmpty(questionnaire.Questionnaire.GroupId))
|
||||
{ //(ne pas utiliser "!==" car types différents)
|
||||
const groupInfos = await groupCtrl.creaGroupJson(questionnaire.Questionnaire.GroupId);
|
||||
// Peut aussi avoir un impact sur les autres élement dans l'ancien groupe, notamment celui qui le précédait
|
||||
if(groupInfos !== false)
|
||||
{
|
||||
for(let i in groupInfos.Questionnaires)
|
||||
await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
//utile au middleware suivant (classement tags) qui s'occupe aussi de retourner une réponse si ok :
|
||||
// id utile au middleware suivant (classement tags) qui s'occupe aussi de retourner une réponse si ok :
|
||||
req.body.QuestionnaireId=req.params.id;
|
||||
next();
|
||||
}
|
||||
|
@ -125,9 +167,9 @@ exports.showOneQuestionnaireById = async (req, res, next) =>
|
|||
{
|
||||
try
|
||||
{
|
||||
// Seuls certains utilisateurs peuvent avoir accès à cette page
|
||||
// Seuls certains utilisateurs peuvent avoir accès à cette route :
|
||||
const connectedUser=await userCtrl.checkTokenUser(req.params.token);
|
||||
if(connectedUser===false)
|
||||
if(connectedUser === false)
|
||||
res.status(403).json({ errors:txtGeneral.failAuthToken });
|
||||
else
|
||||
{
|
||||
|
@ -301,16 +343,15 @@ exports.getListNextQuestionnaires = async(req, res, next) =>
|
|||
}
|
||||
}
|
||||
|
||||
// test si des questionnaires doivent être publiés
|
||||
// puis (re)génère tous les fichiers HTML des questionnaires + les pages accueil + news
|
||||
// la requête est ensuite passé aux tags qui font la même chose
|
||||
// Test si des questionnaires doivent être publiés puis (re)génère tous les fichiers HTML des questionnaires + les pages accueil + news
|
||||
// La requête est ensuite passé aux tags qui font la même chose
|
||||
exports.HTMLRegenerate= async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await checkQuestionnairesNeedToBePublished();
|
||||
const nb=await checkQuestionnairesPublishedHaveHTML(true);
|
||||
creaNewQuestionnairesJson();// provoque mise à jour du HTLM, RSS, etc.
|
||||
await creaNewQuestionnairesJson();// provoque mise à jour du HTLM, RSS, etc.
|
||||
res.messageToNext=txtQuestionnaire.haveBeenRegenerated.replace("#NB1", nb);
|
||||
next();
|
||||
}
|
||||
|
@ -368,7 +409,7 @@ exports.checkQuestionnairesNeedToBePublished= async (req, res, next) =>
|
|||
|
||||
const creaQuestionnaireJson = async (id) =>
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const db=require("../models/index");
|
||||
const Questionnaire=await db["Questionnaire"].findByPk(id);
|
||||
if(Questionnaire)
|
||||
{
|
||||
|
@ -387,24 +428,36 @@ const creaQuestionnaireJson = async (id) =>
|
|||
datas.Questions=Questions;
|
||||
const wasPublished=datas.Questionnaire.isPublished;
|
||||
datas.Questionnaire.isPublished=checkQuestionnaireIsPublishable(datas);
|
||||
// important d'écrire le fichier ici, car il est nécessaire aux autres fonctions appelées ci-dessous
|
||||
// Important d'écrire le fichier ici, car il est nécessaire aux fonctions appelées par la suite
|
||||
await toolFile.createJSON(config.dirCacheQuestionnaires, id, datas);
|
||||
if(datas.Questionnaire.isPublished!==wasPublished)
|
||||
if(datas.Questionnaire.isPublished !== wasPublished)
|
||||
{
|
||||
await db["Questionnaire"].update({ isPublished:datas.Questionnaire.isPublished }, { where: { id : id } , fields: ["isPublished"], limit:1 });
|
||||
// + liste des tags utilisés :
|
||||
// + Peut impacter la liste des tags utilisés :
|
||||
tagCtrl.creaUsedTagsJson();
|
||||
// si le quiz était publié jusqu'ici, il me faut supprimer son fichier HTML (revenir pour réactiver)
|
||||
// Et si le quiz était publié jusqu'ici, il me faut supprimer son fichier HTML.
|
||||
if(wasPublished)
|
||||
toolFile.deleteFile(config.dirHTMLQuestionnaires, Questionnaire.slug+".html");
|
||||
}
|
||||
// peut impacter la liste des derniers si des informations affichées ont changé
|
||||
creaNewQuestionnairesJson();// peut avoir été impacté
|
||||
// Peut impacter la liste des derniers quizs si des informations affichées ont changé
|
||||
creaNewQuestionnairesJson();
|
||||
// + les listes de quizs / tags :
|
||||
for(let i in Tags)
|
||||
tagCtrl.creaQuestionnairesTagJson(Tags[i].TagId) // ! Json + HTML, donc potentiellement long.
|
||||
if(datas.Questionnaire.isPublished)
|
||||
await creaQuestionnaireHTML(id);
|
||||
// Si le questionnaire est l'élément d'un groupe, il faut actualiser les fichiers du groupe
|
||||
// Il faut le faire ici et non dans le contrôleur de mise à jour, car des chgts des éléments annexes (liens, questions...) peuvent aussi impacter les fichiers du groupe ou avoir rendu le questionnaire publiable, etc.
|
||||
if(!tool.isEmpty(Questionnaire.GroupId))
|
||||
{
|
||||
const groupInfos = await groupCtrl.creaGroupJson(Questionnaire.GroupId);
|
||||
// Idem pour le HTML des éventuels autres quizs du groupe car title, slug, rank, etc. peuvent avoir changé
|
||||
if(groupInfos !== false)
|
||||
{
|
||||
for(let i in groupInfos.Questionnaires)
|
||||
await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id);
|
||||
}
|
||||
}
|
||||
return datas;
|
||||
}
|
||||
else
|
||||
|
@ -440,18 +493,29 @@ const deleteQuestionnaire = async (id, req) =>
|
|||
for(let i in questionnaire.Illustrations)
|
||||
await illustrationCtrl.deleteIllustrationById(questionnaire.Illustrations[i].id);
|
||||
const nb=await db["Questionnaire"].destroy( { where: { id : id }, limit:1 });
|
||||
if(nb===1)// = json existant, bien que sql déjà supprimé
|
||||
if(nb===1)// = json existant, bien que sql déjà supprimé ?
|
||||
{
|
||||
toolFile.deleteJSON(configQuestionnaires.dirCacheQuestionnaires, id);
|
||||
// + HTML :
|
||||
toolFile.deleteFile(configQuestionnaires.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug+".html");
|
||||
// Si ce questionnaire était l'élément d'un groupe, il faut actualiser ses fichiers :
|
||||
if(!tool.isEmpty(questionnaire.Questionnaire.GroupId))
|
||||
{
|
||||
const groupInfos=await groupCtrl.creaGroupJson(questionnaire.Questionnaire.GroupId);
|
||||
// Idem pour le HTML des éventuels autres quizs du groupe, notamment si un d'entre eux le précédait
|
||||
if(groupInfos !== false)
|
||||
{
|
||||
for(let i in groupInfos.Questionnaires)
|
||||
await creaQuestionnaireHTML(groupInfos.Questionnaires[i].id);
|
||||
}
|
||||
}
|
||||
// Actualisation de liste des questionnaires pour les tags concernés.
|
||||
// Ici au contraire, les enregistrements doivent être supprimés avant.
|
||||
for(let i in questionnaire.Tags)
|
||||
tagCtrl.creaQuestionnairesTagJson(questionnaire.Tags[i].TagId);
|
||||
// La suppression peut éventuellement concerner un des derniers questionnaires, donc :
|
||||
creaNewQuestionnairesJson();
|
||||
creaStatsQuestionnairesJson();
|
||||
await creaNewQuestionnairesJson();
|
||||
await creaStatsQuestionnairesJson();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -473,42 +537,101 @@ const checkQuestionnaireIsPublishable = (datas, checkDate=true) =>
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if(datas.Questions==undefined || datas.Questions.length < config.nbQuestionsMin)
|
||||
if(datas.Questions === undefined || datas.Questions.length < config.nbQuestionsMin)
|
||||
return false;// le nombre de réponses mini étant contrôlé au moment de l'enregistrement de la question
|
||||
if(datas.Tags==undefined || datas.Tags.length < config.nbTagsMin)
|
||||
if(datas.Tags === undefined || datas.Tags.length < config.nbTagsMin)
|
||||
return false;
|
||||
if(datas.Links==undefined || datas.Links.length < config.nbLinksMin)
|
||||
if(datas.Links === undefined || datas.Links.length < config.nbLinksMin)
|
||||
return false;
|
||||
if(datas.Illustrations==undefined || datas.Illustrations.length < config.nbIllustrationsMin)
|
||||
if(datas.Illustrations === undefined || datas.Illustrations.length < config.nbIllustrationsMin)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const creaQuestionnaireHTML = async (id, preview=false) =>
|
||||
{
|
||||
// besoin de toutes les infos concernant le questionnaire pour les transmettre à la vue
|
||||
// à terme : séparer la création de la partie body pouvant être retournée pour recharger page, de la génération complète pour créer le fichier html
|
||||
// deux possibilités :
|
||||
// -- si élément d'un groupe de quiz : juste le texte sans les questions
|
||||
// -- si quiz automone : toutes les infos
|
||||
const questionnaire=await searchQuestionnaireById(id, true);
|
||||
if(!questionnaire)
|
||||
return false;
|
||||
if(questionnaire.Questionnaire.isPublished===false && preview===false)
|
||||
return false;
|
||||
const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz.pug");
|
||||
if(!tool.isEmpty(questionnaire.Questionnaire.GroupId))
|
||||
return creaQuestionnaireInGroupHTML(questionnaire, preview);
|
||||
else
|
||||
{
|
||||
const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz.pug");
|
||||
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
|
||||
const pageDatas =
|
||||
{
|
||||
config: config,
|
||||
configTpl: configTpl,
|
||||
tool: tool,
|
||||
txtGeneral : txtGeneral,
|
||||
txtQuestionnaire: txtQuestionnaire,
|
||||
txtIllustration: txtIllustration,
|
||||
pageLang: questionnaire.Questionnaire.language,
|
||||
metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(questionnaire.Questionnaire.introduction.replace("<br>", " ").replace("</p>", " ")), 200),
|
||||
author: questionnaire.Questionnaire.CreatorName,
|
||||
pageTitle: questionnaire.Questionnaire.title+" ("+txtQuestionnaire.questionnairesName+")",
|
||||
contentTitle: questionnaire.Questionnaire.title,
|
||||
questionnaire: questionnaire,
|
||||
linkCanonical: config.siteUrl+"/"+config.dirWebQuestionnaires+"/"+questionnaire.Questionnaire.slug+".html"
|
||||
}
|
||||
const html=await compiledFunction(pageDatas);
|
||||
if(preview===false)
|
||||
{
|
||||
await toolFile.createHTML(config.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug, html);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return html;
|
||||
}
|
||||
}
|
||||
exports.creaQuestionnaireHTML = creaQuestionnaireHTML;
|
||||
|
||||
const creaQuestionnaireInGroupHTML = async (questionnaire, preview=false) =>
|
||||
{
|
||||
if(questionnaire === undefined)
|
||||
return false;
|
||||
if(questionnaire.Questionnaire.isPublished === false && preview === false)
|
||||
return false;
|
||||
const compiledFunction = pug.compileFile("./views/"+config.theme+"/quiz-element.pug");
|
||||
const configTpl = require("../views/"+config.theme+"/config/"+config.availableLangs[0]+".js");
|
||||
const txtUser = require("../lang/"+config.adminLang+"/user");
|
||||
// J'ai aussi besoin de certaines informations du groupe :
|
||||
const groupInfos=await groupCtrl.searchGroupById(questionnaire.Questionnaire.GroupId, false);
|
||||
if(!groupInfos)
|
||||
return false;
|
||||
// + Certaines infos de l'élément suivant du groupe, s'il existe :
|
||||
let nextQuestionnaire=null;
|
||||
for(let i in groupInfos.Questionnaires)
|
||||
if(groupInfos.Questionnaires[i].id === questionnaire.Questionnaire.id)
|
||||
{
|
||||
let j=parseInt(i)+1;// !! "i" n'est pas un nombre !
|
||||
if(groupInfos.Questionnaires[j] !== undefined)
|
||||
nextQuestionnaire=await searchQuestionnaireById(groupInfos.Questionnaires[j].id, false);
|
||||
}
|
||||
const pageDatas=
|
||||
{
|
||||
config: config,
|
||||
configQuestionnaires: configQuestionnaires,
|
||||
configTpl: configTpl,
|
||||
tool: tool,
|
||||
txtGeneral : txtGeneral,
|
||||
txtQuestionnaire: txtQuestionnaire,
|
||||
txtIllustration: txtIllustration,
|
||||
txtUser: txtUser,
|
||||
pageLang: questionnaire.Questionnaire.language,
|
||||
metaDescription: tool.shortenIfLongerThan(config.siteName+" : "+striptags(questionnaire.Questionnaire.introduction.replace("<br>", " ").replace("</p>", " ")), 200),
|
||||
author: questionnaire.Questionnaire.CreatorName,
|
||||
pageTitle: questionnaire.Questionnaire.title+" ("+txtQuestionnaire.questionnairesName+")",
|
||||
contentTitle: questionnaire.Questionnaire.title,
|
||||
questionnaire: questionnaire,
|
||||
group: groupInfos,
|
||||
nextQuestionnaire: nextQuestionnaire,
|
||||
linkCanonical: config.siteUrl+"/"+config.dirWebQuestionnaires+"/"+questionnaire.Questionnaire.slug+".html"
|
||||
}
|
||||
const html=await compiledFunction(pageDatas);
|
||||
|
@ -530,17 +653,19 @@ const searchQuestionnaireById = async (id, reassemble=false) =>
|
|||
return false;
|
||||
if(reassemble)
|
||||
{
|
||||
let question; Questions=[];
|
||||
const author=await userCtrl.searchUserById(questionnaire.Questionnaire.CreatorId);
|
||||
if(author)
|
||||
questionnaire.Questionnaire.CreatorName=author.User.name;
|
||||
for(i in questionnaire.Questions)
|
||||
if(!tool.isEmpty(questionnaire.Questionnaire.GroupId))
|
||||
questionnaire.Group=await groupCtrl.searchGroupById(questionnaire.Questionnaire.GroupId, false);// !! false, sinon on risque de tourner en rond !
|
||||
let questionDatas; questionsDatas=[];
|
||||
for(let i in questionnaire.Questions)
|
||||
{
|
||||
question=await questionCtrl.searchQuestionById(questionnaire.Questions[i].id);
|
||||
if(question)
|
||||
Questions.push(question);
|
||||
questionDatas=await questionCtrl.searchQuestionById(questionnaire.Questions[i].id);
|
||||
if(questionDatas)
|
||||
questionsDatas.push(questionDatas);
|
||||
}
|
||||
questionnaire.Questions=Questions;
|
||||
questionnaire.Questions=questionsDatas;
|
||||
const tags=await tagCtrl.getTagsQuestionnaire(id);
|
||||
if(tags)
|
||||
questionnaire.Tags=tags;
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
<input type="hidden" name="id" id="id" value="">
|
||||
<div class="input_wrapper"><input type="submit" value="Valider" class="cardboard" id="submitDatas" /></div>
|
||||
<div class="input_wrapper"><a href="#groups" class="button cardboard" id="wantNewGroup">Vider</a></div>
|
||||
<div class="input_wrapper"><a href="#groups" class="button cardboard" id="previewGroup" target="_blank">Voir le quiz</a></div>
|
||||
<div id="response"></div>
|
||||
</form>
|
||||
<div id="response"></div>
|
||||
|
|
|
@ -1,176 +1,198 @@
|
|||
// -- GESTION DU FORMULAIRE PERMETTANT DE CRÉER SON COMPTE
|
||||
// -- GESTION DU FORMULAIRE PERMETTANT D'AFFICHER ET RÉPONDRE À UN QUIZ
|
||||
|
||||
/// L'utilisateur peut avoir répondu à un quiz avant d'arriver sur la page d'inscription
|
||||
/// Des ce cas il faut enregistrer son résultat en même temps que les informations de son compte
|
||||
/// Il n'est pas nécessaire d'être connecté pour répondre au quiz et voir son résultat.
|
||||
/// Mais si pas connecté, on propose à l'internaute de se connecter ou de créer un compte pour sauvegarder son résultat.
|
||||
/// Dans ce but son résultat est stocké dans son navigateur.
|
||||
/// Si il est connecté, l'enregistrement de son résultat se fait automatiquement côté serveur et ses éventuels précédents résultats sont affichés.
|
||||
|
||||
// Fichier de configuration tirés du backend :
|
||||
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
|
||||
const lang=availableLangs[0];
|
||||
import { getPreviousAnswers, questionnaireRoutes, saveAnswersRoute } from "../../config/questionnaires.js";
|
||||
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");
|
||||
|
||||
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");// besoin de toutes les déclarations, car appel dynamique : configTemplate[homePage]
|
||||
const configUsers = require("../../config/users");// idem pour configurer formulaire
|
||||
|
||||
// Importation des fonctions utiles au script :
|
||||
import { getLocaly, removeLocaly, saveLocaly } from "./tools/clientstorage.js";
|
||||
import { checkAnswerOuput, saveAnswer } from "./tools/answers.js";
|
||||
import { addElement } from "./tools/dom.js";
|
||||
import { helloDev } from "./tools/everywhere.js";
|
||||
import { getDatasFromInputs, setAttributesToInputs } from "./tools/forms.js";
|
||||
import { helloDev, updateAccountLink } from "./tools/everywhere.js";
|
||||
import { getLocaly } from "./tools/clientstorage.js";
|
||||
import { getDatasFromInputs } from "./tools/forms.js";
|
||||
import { dateFormat, replaceAll } from "../../tools/main";
|
||||
import { loadMatomo } from "./tools/matomo.js";
|
||||
import { checkAnswerDatas, checkSession, getPassword, getTimeDifference } from "./tools/users.js";
|
||||
import { checkSession, getTimeDifference } from "./tools/users.js";
|
||||
|
||||
// Dictionnaires :
|
||||
const { notRequired, serverError } = require("../../lang/"+lang+"/general");
|
||||
const { alreadyConnected, godfatherFound, godfatherNotFound, needUniqueEmail, passwordCopied } = require("../../lang/"+lang+"/user");
|
||||
const { noPreviousAnswer, previousAnswersLine, previousAnswersStats, previousAnswersTitle, responseSavedError, wantToSaveResponses } = require("../../lang/"+lang+"/answer");
|
||||
const { serverError } = require("../../lang/"+lang+"/general");
|
||||
|
||||
// Principaux éléments du DOM manipulés :
|
||||
const myForm=document.getElementById("subscription");
|
||||
const divResponse=document.getElementById("response");
|
||||
const passwordInput=document.getElementById("password");
|
||||
const passwordLink=document.getElementById("getPassword");
|
||||
const passwordHelp=document.getElementById("passwordMessage");
|
||||
const emailInput=document.getElementById("email");
|
||||
const btnSubmit=document.getElementById("submitDatas");
|
||||
const codeGodfatherInput=document.getElementById("codeGodfather");
|
||||
const myForm = document.getElementById("questionnaire");
|
||||
const divResponse = document.getElementById("response");
|
||||
const btnShow = document.getElementById("showQuestionnaire");
|
||||
const btnSubmit = document.getElementById("checkResponses");
|
||||
const explanationsTitle = document.getElementById("explanationsTitle");
|
||||
const explanationsContent = document.getElementById("explanationsContent");
|
||||
|
||||
helloDev();
|
||||
|
||||
// Test de connexion de l'utilisateur + affichage formulaire d'inscription.
|
||||
let isConnected, user;
|
||||
const initialise = async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const isConnected=await checkSession();
|
||||
btnShow.style.display="inline";// bouton caché si JS inactif, car JS nécessaire pour vérifier les réponses
|
||||
isConnected=await checkSession(["user"]);// "user" car seuls les utilisateurs de base peuvent enregistrer leurs réponses aux quizs
|
||||
// Si l'utilisateur est connecté et a déjà répondu à ce quiz, on affiche ses précédentes réponses à la place du texte servant à expliquer le topo aux nouveaux
|
||||
if(isConnected)
|
||||
{
|
||||
saveLocaly("message", { message: alreadyConnected, color:"info" });// pour l'afficher sur la page suivante
|
||||
const user=getLocaly("user", true);
|
||||
const homePage=user.status+"HomePage";
|
||||
window.location.assign("/"+configTemplate[homePage]);
|
||||
user=getLocaly("user", true);
|
||||
updateAccountLink(user.status, configTemplate);// lien vers le compte adapté pour les utilisateurs connectés
|
||||
checkPreviousResponses(user);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadMatomo();
|
||||
setAttributesToInputs(configUsers, myForm);
|
||||
myForm.style.display="block";
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
initialise();
|
||||
helloDev();
|
||||
|
||||
// Affichage du questionnaire quand l'utilisateur clique sur le bouton ou si l'id du formulaire est passée par l'url.
|
||||
// Déclenche en même temps le chronomètre mesurant la durée de la réponse aux questions.
|
||||
const showQuestionnaire = () =>
|
||||
{
|
||||
chronoBegin=Date.now();
|
||||
myForm.style.display="block";
|
||||
btnShow.style.display="none";
|
||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview".
|
||||
if(window.location.hash!=="")
|
||||
{
|
||||
window.location.hash="";// ! le "#" reste
|
||||
window.location.assign(here+"questionnaire");
|
||||
}
|
||||
else
|
||||
window.location.assign(here+"#questionnaire");
|
||||
}
|
||||
let chronoBegin=0;
|
||||
btnShow.addEventListener("click", function(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.preventDefault();
|
||||
showQuestionnaire();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
initialise();
|
||||
});
|
||||
// Lien passé par mail pour voir directement le quiz
|
||||
if(location.hash!="" && location.hash==="#questionnaire")
|
||||
showQuestionnaire();
|
||||
|
||||
// Générateur de mot de passe "aléatoire"
|
||||
passwordLink.addEventListener("click", function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
passwordInput.type="text";
|
||||
passwordInput.value=getPassword(8, 12);
|
||||
// Copie du mot de passe généré dans le "presse-papier" de l'ordinateur :
|
||||
passwordInput.select();
|
||||
document.execCommand("copy");
|
||||
addElement(passwordHelp, "div", passwordCopied, "", ["success"]);
|
||||
});
|
||||
|
||||
// Test si l'e-mail saisi est déjà utilisé par un autre compte.
|
||||
// Si c'est le cas, la validation du formulaire est bloquée.
|
||||
emailInput.addEventListener("focus", function(e)
|
||||
{
|
||||
document.getElementById("emailMessage").innerHTML="";// pour supprimer l'éventuel message d'erreur déjà affiché
|
||||
});
|
||||
emailInput.addEventListener("blur", function(e)
|
||||
{
|
||||
const emailValue=emailInput.value.trim();
|
||||
if(emailValue!=="")
|
||||
{
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiUrl+configUsers.userRoutes+configUsers.checkIfIsEmailfreeRoute);
|
||||
xhr.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 200 && response.free!==undefined && response.free === false)
|
||||
{
|
||||
addElement(document.getElementById("emailMessage"), "div", needUniqueEmail.replace("#URL", configTemplate.connectionPage), "", ["error"]);
|
||||
btnSubmit.setAttribute("disabled", true);
|
||||
}
|
||||
else
|
||||
btnSubmit.removeAttribute("disabled");
|
||||
}
|
||||
}
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
const datas={ emailTest:emailValue };
|
||||
xhr.send(JSON.stringify(datas));
|
||||
}
|
||||
});
|
||||
|
||||
// Vérification que le code/e-mail de parrainage saisi est valide.
|
||||
codeGodfatherInput.addEventListener("focus", function(e)
|
||||
{ // on efface l'éventuel message d'erreur si on revient sur le champ pour tester un autre code.
|
||||
addElement(document.getElementById("codeGodfatherMessage"), "i", notRequired);
|
||||
});
|
||||
codeGodfatherInput.addEventListener("blur", function(e)
|
||||
{
|
||||
const codeValue=codeGodfatherInput.value.trim();
|
||||
if(codeValue!=="")
|
||||
{
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiUrl+configUsers.userRoutes+configUsers.getGodfatherRoute);
|
||||
xhr.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
if (this.status === 204)
|
||||
addElement(document.getElementById("codeGodfatherMessage"), "div", godfatherNotFound, "", ["error"]);
|
||||
else
|
||||
addElement(document.getElementById("codeGodfatherMessage"), "div", godfatherFound, "", ["success"]);
|
||||
}
|
||||
}
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
const datas={ codeTest:codeValue };
|
||||
xhr.send(JSON.stringify(datas));
|
||||
}
|
||||
});
|
||||
|
||||
// Traitement de l'envoi des données d'inscription :
|
||||
// Traitement de l'envoi de la réponse de l'utilisateur :
|
||||
let answer = {};
|
||||
myForm.addEventListener("submit", function(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.preventDefault();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiUrl+configUsers.userRoutes+configUsers.subscribeRoute);
|
||||
xhr.onreadystatechange = function()
|
||||
btnSubmit.style.display="none";// seulement un envoi à la fois, SVP :)
|
||||
divResponse.innerHTML="";// supprime les éventuels messages déjà affichés
|
||||
const userResponses=getDatasFromInputs(myForm);
|
||||
answer.duration=Math.round((Date.now()-chronoBegin)/1000);
|
||||
answer.nbQuestions=0;
|
||||
answer.nbCorrectAnswers=0;
|
||||
answer.QuestionnaireId=document.getElementById("questionnaireId").value;
|
||||
// Les réponses sont regroupées par question, donc quand idQuestion change, on connaît le résultat pour la question précédente.
|
||||
// Pour qu'une réponse soit bonne, il faut cocher toutes les bonnes réponses (si QCM) à la question ET cocher aucune des mauvaises.
|
||||
let idChoice, idQuestion="", goodResponse=false;
|
||||
for(let item in userResponses)
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
if(item.startsWith("isCorrect_response_"))// = Nouvelle réponse possible.
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 201)
|
||||
idChoice = item.substring(item.lastIndexOf("_") + 1);
|
||||
// si on change de queston
|
||||
if(userResponses["question_id_response_"+idChoice]!=idQuestion) // on commence à traiter une nouvelle question
|
||||
{
|
||||
myForm.style.display="none";
|
||||
addElement(divResponse, "p", response.message, "", ["success"]);
|
||||
removeLocaly("lastAnswer");// ! important pour ne pas enregister plusieurs fois le résultat.
|
||||
idQuestion=userResponses["question_id_response_"+idChoice];
|
||||
answer.nbQuestions++;
|
||||
if(goodResponse) // résultat de la question précédente
|
||||
answer.nbCorrectAnswers++;
|
||||
goodResponse=true;// réponse bonne jusqu'à la première erreur...
|
||||
}
|
||||
else if (response.errors)
|
||||
if(userResponses[item]=="true")
|
||||
{
|
||||
response.errors = response.errors.join("<br>");
|
||||
addElement(divResponse, "p", response.errors, "", ["error"]);
|
||||
document.getElementById("response_"+idChoice).parentNode.classList.add("isCorrect");
|
||||
if(userResponses["response_"+idChoice]===undefined)// une bonne réponse n'a pas été sélectionnée
|
||||
goodResponse=false;
|
||||
}
|
||||
else
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
{
|
||||
if(userResponses["response_"+idChoice]==="on")// réponse cochée ne faisant pas partie des bonnes
|
||||
{
|
||||
goodResponse=false;
|
||||
document.getElementById("response_"+idChoice).parentNode.classList.add("isNotCorrect");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
let datas=getDatasFromInputs(myForm);
|
||||
if(datas)
|
||||
// si j'ai bien répondu à la dernière question, il faut le compter ici, car je suis sorti de la boucle :
|
||||
if(goodResponse)
|
||||
answer.nbCorrectAnswers++;
|
||||
|
||||
// Affichage du résultat, suivant si l'utilisateur est connecté ou pas et son score :
|
||||
let getOuput=checkAnswerOuput(answer);
|
||||
if(isConnected)
|
||||
{
|
||||
datas.timeDifference=getTimeDifference(configUsers);
|
||||
// si l'utilisateur a précédement répondu à un quiz, j'ajoute les infos de son résultat :
|
||||
datas=checkAnswerDatas(datas);
|
||||
xhr.send(JSON.stringify(datas));
|
||||
// Si l'utilisateur est connecté, on enregistre son résultat sur le serveur.
|
||||
const xhrSaveAnswer = new XMLHttpRequest();
|
||||
xhrSaveAnswer.open("POST", apiUrl+questionnaireRoutes+saveAnswersRoute);
|
||||
xhrSaveAnswer.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let xhrResponse=JSON.parse(this.responseText);
|
||||
if (this.status === 201 && (xhrResponse.message))
|
||||
{
|
||||
getOuput+="<br>"+xhrResponse.message.replace("#URL", configTemplate.userHomePage);
|
||||
checkPreviousResponses(user);
|
||||
}
|
||||
else
|
||||
getOuput+="<br>"+responseSavedError.replace("#URL", configTemplate.userHomePage);
|
||||
// on redirige vers le résultat
|
||||
window.location.hash="";
|
||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
||||
window.location.assign(here+"explanations");
|
||||
}
|
||||
}
|
||||
xhrSaveAnswer.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
xhrSaveAnswer.setRequestHeader("Content-Type", "application/json");
|
||||
answer.timeDifference=getTimeDifference();// on en profite pour mettre les pendules à l'heure.
|
||||
xhrSaveAnswer.send(JSON.stringify(answer));
|
||||
}
|
||||
else
|
||||
{ // si pas connecté, on enregistre le résultat côté client pour permettre de le retrouver au moment de la création du compte ou de la connexion.
|
||||
if(saveAnswer(answer))
|
||||
{
|
||||
getOuput+="<br><br>"+wantToSaveResponses;
|
||||
addElement(divResponse, "p", getOuput, "", ["info"]);
|
||||
document.querySelector(".subscribeBtns").style.display="block";
|
||||
}
|
||||
else // inutile de proposer de créer un compte si le stockage local ne fonctionne pas
|
||||
addElement(divResponse, "p", getOuput, "", ["info"]);
|
||||
// on redirige vers le résultat
|
||||
window.location.hash="";
|
||||
const here=window.location;// window.location à ajouter pour ne pas quitter la page en mode "preview"...
|
||||
window.location.assign(here+"response");
|
||||
}
|
||||
// + affichage des textes d'explications pour chaque question
|
||||
const explanations=document.querySelectorAll(".help");
|
||||
for(let i in explanations)
|
||||
{
|
||||
if(explanations[i].style!=undefined) // sinon, la console affiche une erreur "TypeError: explanations[i].style is undefined", bien que tout fonctionne (?)
|
||||
explanations[i].style.display="block";
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
|
@ -178,4 +200,57 @@ myForm.addEventListener("submit", function(e)
|
|||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Fonction vérifiant les précédentes réponses de l'utilisateur
|
||||
// Utile si connecté lors du premier chargement de la page, puis après une nouvelle réponse
|
||||
const checkPreviousResponses = (user) =>
|
||||
{
|
||||
const xhrPreviousRes = new XMLHttpRequest();
|
||||
xhrPreviousRes.open("GET", apiUrl+questionnaireRoutes+getPreviousAnswers+user.id+"/"+document.getElementById("questionnaireId").value);
|
||||
xhrPreviousRes.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 200)
|
||||
{
|
||||
const nbResponses=response.length;
|
||||
let previousAnswersContent="";
|
||||
addElement(explanationsTitle, "span", previousAnswersTitle.replace("#NOM", user.name));
|
||||
if(nbResponses!==0)
|
||||
{
|
||||
let totNbQuestions=0, totNbCorrectAnswers=0, totDuration=0, mapLineContent;
|
||||
for(let i in response)
|
||||
{
|
||||
totNbQuestions+=response[i].nbQuestions;// ! on ne peut se baser sur la version actuelle du quiz, car le nombre de questions a pu évoluer.
|
||||
totNbCorrectAnswers+=response[i].nbCorrectAnswers;
|
||||
totDuration+=response[i].duration;
|
||||
mapLineContent =
|
||||
{
|
||||
DATEANSWER : dateFormat(response[i].createdAt, lang),
|
||||
NBCORRECTANSWERS : response[i].nbCorrectAnswers,
|
||||
NBQUESTIONS : response[i].nbQuestions,
|
||||
AVGDURATION : response[i].duration
|
||||
};
|
||||
previousAnswersContent+="<li>"+replaceAll(previousAnswersLine, mapLineContent)+"</li>";
|
||||
}
|
||||
mapLineContent =
|
||||
{
|
||||
AVGDURATION : Math.round(totDuration/nbResponses),
|
||||
AVGCORRECTANSWERS : Math.round(totNbCorrectAnswers/totNbQuestions*100)
|
||||
};
|
||||
previousAnswersContent="<h5>"+replaceAll(previousAnswersStats, mapLineContent)+"</h5>"+previousAnswersContent;
|
||||
addElement(explanationsContent, "ul", previousAnswersContent);
|
||||
}
|
||||
else
|
||||
addElement(explanationsContent, "ul", noPreviousAnswer);
|
||||
// dans un cas comme dans l'autre, bouton pour revenir à l'accueil du compte
|
||||
addElement(explanationsContent, "p", "<a href=\"/"+configTemplate.userHomePage+"\" class=\"button cardboard\">"+configTemplate.userHomePageTxt+"</a>", "", ["btn"], "", false);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
xhrPreviousRes.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
xhrPreviousRes.send();
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// -- GESTION DU FORMULAIRE PERMETTANT DE CRÉER SON COMPTE
|
||||
|
||||
/// L'utilisateur peut avoir répondu à un quiz avant d'arriver sur la page d'inscription
|
||||
/// Des ce cas il faut enregistrer son résultat en même temps que les informations de son compte
|
||||
|
||||
// Fichier de configuration tirés du backend :
|
||||
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
|
||||
const lang=availableLangs[0];
|
||||
|
||||
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");// besoin de toutes les déclarations, car appel dynamique : configTemplate[homePage]
|
||||
const configUsers = require("../../config/users");// idem pour configurer formulaire
|
||||
|
||||
// Importation des fonctions utiles au script :
|
||||
import { getLocaly, removeLocaly, saveLocaly } from "./tools/clientstorage.js";
|
||||
import { addElement } from "./tools/dom.js";
|
||||
import { helloDev } from "./tools/everywhere.js";
|
||||
import { getDatasFromInputs, setAttributesToInputs } from "./tools/forms.js";
|
||||
import { loadMatomo } from "./tools/matomo.js";
|
||||
import { checkAnswerDatas, checkSession, getPassword, getTimeDifference } from "./tools/users.js";
|
||||
|
||||
// Dictionnaires :
|
||||
const { notRequired, serverError } = require("../../lang/"+lang+"/general");
|
||||
const { alreadyConnected, godfatherFound, godfatherNotFound, needUniqueEmail, passwordCopied } = require("../../lang/"+lang+"/user");
|
||||
|
||||
// Principaux éléments du DOM manipulés :
|
||||
const myForm=document.getElementById("subscription");
|
||||
const divResponse=document.getElementById("response");
|
||||
const passwordInput=document.getElementById("password");
|
||||
const passwordLink=document.getElementById("getPassword");
|
||||
const passwordHelp=document.getElementById("passwordMessage");
|
||||
const emailInput=document.getElementById("email");
|
||||
const btnSubmit=document.getElementById("submitDatas");
|
||||
const codeGodfatherInput=document.getElementById("codeGodfather");
|
||||
|
||||
helloDev();
|
||||
|
||||
// Test de connexion de l'utilisateur + affichage formulaire d'inscription.
|
||||
const initialise = async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const isConnected=await checkSession();
|
||||
if(isConnected)
|
||||
{
|
||||
//saveLocaly("message", { message: alreadyConnected, color:"info" });// pour l'afficher sur la page suivante
|
||||
//const user=getLocaly("user", true);
|
||||
//const homePage=user.status+"HomePage";
|
||||
//window.location.assign("/"+configTemplate[homePage]);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadMatomo();
|
||||
setAttributesToInputs(configUsers, myForm);
|
||||
myForm.style.display="block";
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
initialise();
|
||||
|
||||
// Générateur de mot de passe "aléatoire"
|
||||
passwordLink.addEventListener("click", function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
passwordInput.type="text";
|
||||
passwordInput.value=getPassword(8, 12);
|
||||
// Copie du mot de passe généré dans le "presse-papier" de l'ordinateur :
|
||||
passwordInput.select();
|
||||
document.execCommand("copy");
|
||||
addElement(passwordHelp, "div", passwordCopied, "", ["success"]);
|
||||
});
|
||||
|
||||
// Test si l'e-mail saisi est déjà utilisé par un autre compte.
|
||||
// Si c'est le cas, la validation du formulaire est bloquée.
|
||||
emailInput.addEventListener("focus", function(e)
|
||||
{
|
||||
document.getElementById("emailMessage").innerHTML="";// pour supprimer l'éventuel message d'erreur déjà affiché
|
||||
});
|
||||
emailInput.addEventListener("blur", function(e)
|
||||
{
|
||||
const emailValue=emailInput.value.trim();
|
||||
if(emailValue!=="")
|
||||
{
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiUrl+configUsers.userRoutes+configUsers.checkIfIsEmailfreeRoute);
|
||||
xhr.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 200 && response.free!==undefined && response.free === false)
|
||||
{
|
||||
addElement(document.getElementById("emailMessage"), "div", needUniqueEmail.replace("#URL", configTemplate.connectionPage), "", ["error"]);
|
||||
btnSubmit.setAttribute("disabled", true);
|
||||
}
|
||||
else
|
||||
btnSubmit.removeAttribute("disabled");
|
||||
}
|
||||
}
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
const datas={ emailTest:emailValue };
|
||||
xhr.send(JSON.stringify(datas));
|
||||
}
|
||||
});
|
||||
|
||||
// Vérification que le code/e-mail de parrainage saisi est valide.
|
||||
codeGodfatherInput.addEventListener("focus", function(e)
|
||||
{ // on efface l'éventuel message d'erreur si on revient sur le champ pour tester un autre code.
|
||||
addElement(document.getElementById("codeGodfatherMessage"), "i", notRequired);
|
||||
});
|
||||
codeGodfatherInput.addEventListener("blur", function(e)
|
||||
{
|
||||
const codeValue=codeGodfatherInput.value.trim();
|
||||
if(codeValue!=="")
|
||||
{
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiUrl+configUsers.userRoutes+configUsers.getGodfatherRoute);
|
||||
xhr.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
if (this.status === 204)
|
||||
addElement(document.getElementById("codeGodfatherMessage"), "div", godfatherNotFound, "", ["error"]);
|
||||
else
|
||||
addElement(document.getElementById("codeGodfatherMessage"), "div", godfatherFound, "", ["success"]);
|
||||
}
|
||||
}
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
const datas={ codeTest:codeValue };
|
||||
xhr.send(JSON.stringify(datas));
|
||||
}
|
||||
});
|
||||
|
||||
// Traitement de l'envoi des données d'inscription :
|
||||
myForm.addEventListener("submit", function(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
e.preventDefault();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiUrl+configUsers.userRoutes+configUsers.subscribeRoute);
|
||||
xhr.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 201)
|
||||
{
|
||||
myForm.style.display="none";
|
||||
addElement(divResponse, "p", response.message, "", ["success"]);
|
||||
removeLocaly("lastAnswer");// ! important pour ne pas enregister plusieurs fois le résultat.
|
||||
}
|
||||
else if (response.errors)
|
||||
{
|
||||
response.errors = response.errors.join("<br>");
|
||||
addElement(divResponse, "p", response.errors, "", ["error"]);
|
||||
}
|
||||
else
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
}
|
||||
}
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
let datas=getDatasFromInputs(myForm);
|
||||
if(datas)
|
||||
{
|
||||
datas.timeDifference=getTimeDifference(configUsers);
|
||||
// si l'utilisateur a précédement répondu à un quiz, j'ajoute les infos de son résultat :
|
||||
datas=checkAnswerDatas(datas);
|
||||
xhr.send(JSON.stringify(datas));
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
});
|
|
@ -6,7 +6,7 @@
|
|||
/// Si pas d'id passé par l'url, on affiche un formulaire vide permettant d'en saisir un nouveau.
|
||||
|
||||
// Fichiers de configuration :
|
||||
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
|
||||
import { apiUrl, availableLangs, siteUrl, theme } from "../../config/instance.js";
|
||||
const lang=availableLangs[0];
|
||||
const configQuestionnaires = require("../../config/questionnaires.js");
|
||||
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");
|
||||
|
@ -27,6 +27,7 @@ const { needBeConnected } = require("../../lang/"+lang+"/user");
|
|||
|
||||
// Principaux éléments du DOM manipulés :
|
||||
const btnNewGroup = document.getElementById("wantNewGroup");
|
||||
const btnPreviewGroup = document.getElementById("previewGroup");
|
||||
const deleteCheckBox = document.getElementById("deleteOkLabel");
|
||||
const divCrash = document.getElementById("crash");
|
||||
const divGroupIntro = document.getElementById("groupIntro");
|
||||
|
@ -41,8 +42,9 @@ const formSearch = document.getElementById("search");
|
|||
const emptyGroupForm = () =>
|
||||
{
|
||||
empyForm(formGroup);
|
||||
// Case de suppression inutile en mode création :
|
||||
// Case de suppression et bouton visualisation inutiles en mode création :
|
||||
deleteCheckBox.style.display="none";
|
||||
btnPreviewGroup.style.display="none";
|
||||
// Intro à vider !
|
||||
divGroupIntro.innerHTML="";
|
||||
}
|
||||
|
@ -66,7 +68,7 @@ const showFormGroupInfos = (id, token) =>
|
|||
GROUP_ID : response.Group.id,
|
||||
DATE_CREA : dateFormat(response.Group.createdAt),
|
||||
DATE_UPDATE : dateFormat(response.Group.updatedAt),
|
||||
NB_ELEMENTS : (response.Group.Questionnaires!==undefined) ? response.Group.Questionnaires.length : 0
|
||||
NB_ELEMENTS : (response.Questionnaires!==undefined) ? response.Questionnaires.length : 0
|
||||
};
|
||||
const groupIntro=replaceAll(infosGroupForAdmin, mapText);
|
||||
addElement(divGroupIntro, "p", groupIntro, "", ["info"]);
|
||||
|
@ -81,6 +83,13 @@ const showFormGroupInfos = (id, token) =>
|
|||
}
|
||||
}
|
||||
deleteCheckBox.style.display="block";
|
||||
btnPreviewGroup.style.display="block";
|
||||
console.log(response.Group);
|
||||
if(response.Group["isPublishable"] === false)
|
||||
btnPreviewGroup.setAttribute("href", apiUrl+configQuestionnaires.groupRoutes+configQuestionnaires.previewGroupRoutes+"/"+id+"/"+token);
|
||||
else
|
||||
btnPreviewGroup.setAttribute("href", siteUrl+"/"+configQuestionnaires.dirWebGroups+"/"+response.Group["slug"]+".html");
|
||||
console.log(btnPreviewGroup);
|
||||
}// ajout gestion erreur 404 ???
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ const { addOkMessage, deleteBtnTxt, serverError, updateBtnTxt } = require("../..
|
|||
const { addIllustrationTxt, defaultAlt, introNoIllustration, introTitleForIllustration } = require("../../lang/"+lang+"/illustration");
|
||||
const { addLinkTxt, defaultValueForLink, introNoLink, introTitleForLink } = require("../../lang/"+lang+"/link");
|
||||
const { addQuestionTxt, introNoQuestion, introTitleForQuestion } = require("../../lang/"+lang+"/question");
|
||||
const { nextDateWithoutQuestionnaire, nextQuestionnairesList, questionnaireNeedBeCompleted, searchQuestionnaireWithNoResult } = require("../../lang/"+lang+"/questionnaire");
|
||||
const { needGroupIfRank, nextDateWithoutQuestionnaire, nextQuestionnairesList, questionnaireNeedBeCompleted, searchQuestionnaireWithNoResult } = require("../../lang/"+lang+"/questionnaire");
|
||||
const { needBeConnected } = require("../../lang/"+lang+"/user");
|
||||
|
||||
// Principaux éléments du DOM manipulés :
|
||||
|
@ -431,6 +431,8 @@ const showFormQuestionnaireInfos = (id, token) =>
|
|||
}
|
||||
formQuestionnaire.elements["classification"].value=classification;
|
||||
}
|
||||
if(!isEmpty(response.Group))
|
||||
formQuestionnaire.elements["group"].value=response.Group.Group.title+" ("+response.Group.Group.id+")";
|
||||
divLinks.style.display="block";
|
||||
divQuestions.style.display="block";
|
||||
divIllustrations.style.display="block";
|
||||
|
@ -650,59 +652,65 @@ const initialise = async () =>
|
|||
e.preventDefault();
|
||||
divResponse.innerHTML="";
|
||||
let datas=getDatasFromInputs(formQuestionnaire);
|
||||
console.log(datas);
|
||||
const xhrQuestionnaireDatas = new XMLHttpRequest();
|
||||
if(!isEmpty(datas.id) && (datas.deleteOk!==undefined))
|
||||
xhrQuestionnaireDatas.open("DELETE", apiUrl+configQuestionnaires.questionnaireRoutes+"/"+datas.id);
|
||||
else if(!isEmpty(datas.id))
|
||||
xhrQuestionnaireDatas.open("PUT", apiUrl+configQuestionnaires.questionnaireRoutes+"/"+datas.id);
|
||||
if(!isEmpty(datas.rankInGroup) && isEmpty(datas.GroupId))
|
||||
addElement(divResponse, "p", needGroupIfRank, "", ["error"]);
|
||||
else
|
||||
xhrQuestionnaireDatas.open("POST", apiUrl+configQuestionnaires.questionnaireRoutes+"/");
|
||||
xhrQuestionnaireDatas.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
if(isEmpty(datas.rankInGroup) && !isEmpty(datas.GroupId))
|
||||
datas.rankInGroup=1;
|
||||
const xhrQuestionnaireDatas = new XMLHttpRequest();
|
||||
if(!isEmpty(datas.id) && (datas.deleteOk!==undefined))
|
||||
xhrQuestionnaireDatas.open("DELETE", apiUrl+configQuestionnaires.questionnaireRoutes+"/"+datas.id);
|
||||
else if(!isEmpty(datas.id))
|
||||
xhrQuestionnaireDatas.open("PUT", apiUrl+configQuestionnaires.questionnaireRoutes+"/"+datas.id);
|
||||
else
|
||||
xhrQuestionnaireDatas.open("POST", apiUrl+configQuestionnaires.questionnaireRoutes+"/");
|
||||
xhrQuestionnaireDatas.onreadystatechange = function()
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 201 && response.id!=undefined)
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
addElement(divResponse, "p", addOkMessage, "", ["success"]);
|
||||
datas.id=response.id;
|
||||
showNextQuestionnaires(user.token);// peut avoir évolué suivant ce qui s'est passé
|
||||
}
|
||||
else if (this.status === 200 && response.message!=undefined)
|
||||
{
|
||||
if(Array.isArray(response.message))
|
||||
response.message = response.message.join("<br>");
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 201 && response.id != undefined)
|
||||
{
|
||||
addElement(divResponse, "p", addOkMessage, "", ["success"]);
|
||||
datas.id=response.id;
|
||||
showNextQuestionnaires(user.token);// peut avoir évolué suivant ce qui s'est passé
|
||||
}
|
||||
else if (this.status === 200 && response.message != undefined)
|
||||
{
|
||||
if(Array.isArray(response.message))
|
||||
response.message = response.message.join("<br>");
|
||||
else
|
||||
response.message = response.message;
|
||||
addElement(divResponse, "p", response.message, "", ["success"]);
|
||||
showNextQuestionnaires(user.token);// peut avoir évolué suivant ce qui s'est passé
|
||||
}
|
||||
else if (response.errors)
|
||||
{
|
||||
if(Array.isArray(response.errors))
|
||||
response.errors = response.errors.join("<br>");
|
||||
else
|
||||
response.errors = serverError;
|
||||
addElement(divResponse, "p", response.errors, "", ["error"]);
|
||||
}
|
||||
else
|
||||
response.message = response.message;
|
||||
addElement(divResponse, "p", response.message, "", ["success"]);
|
||||
showNextQuestionnaires(user.token);// peut avoir évolué suivant ce qui s'est passé
|
||||
}
|
||||
else if (response.errors)
|
||||
{
|
||||
if(Array.isArray(response.errors))
|
||||
response.errors = response.errors.join("<br>");
|
||||
else
|
||||
response.errors = serverError;
|
||||
addElement(divResponse, "p", response.errors, "", ["error"]);
|
||||
}
|
||||
else
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
if(datas.deleteOk===undefined)
|
||||
showFormQuestionnaireInfos(datas.id, user.token);
|
||||
else
|
||||
{
|
||||
formQuestionnaire.reset();
|
||||
divLinks.innerHTML="";
|
||||
divIllustrations.innerHTML="";
|
||||
divQuestions.innerHTML="";
|
||||
addElement(divResponse, "p", serverError, "", ["error"]);
|
||||
if(datas.deleteOk === undefined && response.errors === undefined)
|
||||
showFormQuestionnaireInfos(datas.id, user.token);// on actualise les données
|
||||
else if (response.errors === undefined)
|
||||
{
|
||||
formQuestionnaire.reset();
|
||||
divLinks.innerHTML="";
|
||||
divIllustrations.innerHTML="";
|
||||
divQuestions.innerHTML="";
|
||||
}
|
||||
}
|
||||
}
|
||||
xhrQuestionnaireDatas.setRequestHeader("Content-Type", "application/json");
|
||||
xhrQuestionnaireDatas.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
if(datas)
|
||||
xhrQuestionnaireDatas.send(JSON.stringify(datas));
|
||||
}
|
||||
xhrQuestionnaireDatas.setRequestHeader("Content-Type", "application/json");
|
||||
xhrQuestionnaireDatas.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
if(datas)
|
||||
xhrQuestionnaireDatas.send(JSON.stringify(datas));
|
||||
});
|
||||
formLink.addEventListener("submit", function(e)
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@ module.exports =
|
|||
deconnection: "./src/deconnection.js",
|
||||
deleteValidation: "./src/deleteValidation.js",
|
||||
group: "./src/group.js",
|
||||
groupElement: "./src/groupElement.js",
|
||||
homeManager: "./src/homeManager.js",
|
||||
homeUser: "./src/homeUser.js",
|
||||
index: "./src/index.js",
|
||||
|
|
|
@ -7,8 +7,8 @@ module.exports =
|
|||
groupsName: "Quiz",// nom d'un groupe pour l'affichage dans les vues
|
||||
haveBeenPublished: "#NB nouveaux groupes de quizs ont été publiés.",
|
||||
infosGroupForAdmin: "Ce groupe de quizs a été créé le DATE_CREA, mise à jour la dernière fois le DATE_UPDATE.<br>Son identifiant est <b>GROUP_ID</b>. Il regroupe actuellement les questions de NB_ELEMENTS quizs.",
|
||||
linkFirstElementGroup: "Retour à la première page.",
|
||||
lastUpdated: "Dernière mise à jour, le ",
|
||||
linkFirstElementGroup: "Retour à la première leçon.",
|
||||
lastUpdated: "Dernière mise à jour le ",
|
||||
needCorrectPublishingDate: "La date de publication fournie n'a pas un format valide.",
|
||||
needLanguage: "Vous devez sélectionner la langue de ce groupe de quizs.",
|
||||
needNotTooLongTitle: "Le titre du groupe de quizs ne doit pas compter plus de 255 caractères.",
|
||||
|
@ -16,7 +16,7 @@ module.exports =
|
|||
needUniqueUrl: "L'url du groupe de quizs doit être unique.",
|
||||
needUrl: "Merci de fournir l'url à ce groupe de quizs.",
|
||||
notFound: "Le groupe de quizs (#SEARCH) n'a pas été trouvé.",
|
||||
publishedAt: ", le",
|
||||
publishedAt: " le",
|
||||
publishedBy: "Quiz publié par",
|
||||
searchIsNotLongEnough: "Vous devez saisir au moins #MIN caractères pour votre recherche.",
|
||||
searchWithoutResult: "Aucun groupe n'a été trouvé pour votre recherche.",
|
||||
|
|
|
@ -17,8 +17,11 @@ module.exports =
|
|||
haveBeenPublished : ":NB nouveaux questionnaires ont été publiés.",
|
||||
haveBeenRegenerated : "Les fichiers HTML de #NB1 questionnaires et #NB2 rubriques ont été regénérés.",
|
||||
lastUpdated: "Dernière mise à jour, le ",
|
||||
linkGoToNextElement: "Leçon suivante",
|
||||
linkGoToQuiz: "Accéder au quiz",
|
||||
needCorrectPublishingDate: "La date de publication fournie n'a pas un format valide.",
|
||||
needEstimatedTime: "Merci de sélectionner une estimation de la durée de ce quiz.",
|
||||
needGroupIfRank: "Vous avez saisi un rang de classement, sans sélectionner le groupe du quiz.",
|
||||
needIntroduction: "Merci de fournir un texte d'introduction à votre quiz.",
|
||||
needKnowIfIsPublished: "Il faut savoir si ce quiz est publié.",
|
||||
needLanguage: "Vous devez sélectionner la langue de ce quiz.",
|
||||
|
|
|
@ -120,6 +120,7 @@ module.exports = (sequelize, DataTypes) =>
|
|||
{
|
||||
type: DataTypes.INTEGER(2).UNSIGNED, allowNull: true,
|
||||
comment: "Allows you to classify the questionnaire if it belongs to a group.",
|
||||
set(value) { this.setDataValue("rankInGroup", tool.trimIfNotNull((value))); },
|
||||
validate:
|
||||
{
|
||||
isInt: { msg: txt.needNumberForRank },
|
||||
|
@ -129,6 +130,11 @@ module.exports = (sequelize, DataTypes) =>
|
|||
msg: txt.needNumberForRank
|
||||
}
|
||||
}
|
||||
},
|
||||
GroupId:
|
||||
{
|
||||
type: DataTypes.INTEGER(11).UNSIGNED, allowNull: true,
|
||||
set(value) { this.setDataValue("GroupId", tool.trimIfNotNull((value))); }
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -11,5 +11,6 @@ router.get("/stats", auth, groupCtrl.getStatsGroups);
|
|||
router.put("/:id", auth, groupCtrl.modify);
|
||||
router.delete("/:id", auth, groupCtrl.delete);
|
||||
router.get("/get/:id", auth, groupCtrl.getOneById);
|
||||
router.get("/preview/:id/:token", groupCtrl.showOneGroupById);// prévisualisation HTML, même si groupe "incomplet"
|
||||
|
||||
module.exports = router;
|
|
@ -38,6 +38,7 @@ module.exports =
|
|||
siteSlogan: "Cultivons notre jardin !",
|
||||
noJSNotification: "Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript.",
|
||||
mailRecipientTxt: "Message envoyé à :",
|
||||
licenceTxt: "@copyleft Le contenu de ce site <a href=\"/credits.html\" title=\"En savoir plus ?\">est libre</a> et vous offert sans publicité. Vous pouvez <a href=\"/participer-financement.html\" title=\"Financement participatif\">participer à son financement en cliquant ici</a>.",
|
||||
/* Page d'accueil */
|
||||
homePageTxt: "Page d'accueil",
|
||||
homeTitle1: "De nature curieuse ?",
|
||||
|
@ -55,7 +56,8 @@ module.exports =
|
|||
quizElementSubcriptionFormTitle: "Recevez les prochains WikiLerni",
|
||||
explanationTitle: "Vous découvrez WikiLerni ?",
|
||||
explanationTxt: "<p>Le principe est simple : <b>vous commencez par lire l'article Wikipédia dont le lien vous est proposé</b>.<br>Puis vous <b>afficher le quiz pour vérifier ce que vous avez retenu de votre lecture</b>.</p><p>Suivant les questions, <b>une ou plusieurs réponses peuvent être correctes</b> et doivent donc être cochées.<br>C'est toujours <b>le contenu de l'article Wikipédia qui fait foi</b> concernant les \"bonnes\" réponses.<br>Mais les articles de Wikipédia peuvent évoluer, donc n'hésitez pas <a href='/contact.html'>à me signaler une erreur</a>.</p><h3>Pas le temps de lire l'article Wikipédia ?</h3><p>Il est vrai que certains sont longs ! :-)<br>Dans ce cas, <b>essayez simplement de répondre avec vos connaissances actuelles</b>.<br>Il n'est pas nécessaire de répondre à toutes les questions pour obtenir les réponses.<br>Après validation, vous verrez apparaître les bonnes réponses + un extrait de l'article Wikipédia.<br>Vous pouvez ainsi <b>apprendre de nouvelles choses en quelques minutes</b>.</p><p>Une autre possibilité est d'afficher le quiz avant d'aller chercher les réponses dans l'article Wikipédia... Elles y sont toutes !</p><p><b>Il n'y a pas de bonne façon de faire</b>, et dans tous les cas <b>vous apprenez des choses sur des sujets très variés, ce qui est le but de WikiLerni</b>.</p><p>Quand le sujet s'y prête, ne vous étonnez pas si certaines des réponses proposées peuvent être un peu décalées, absurdes... On peut apprendre avec le sourire, non ? :)</p><p>Une fois votre résultat obtenu, il vous sera proposé de créer un compte pour le sauvegarder. Ce compte vous permettra de <b>tester de nouveau ce quiz</b> pour vérifier ce que vous en avez retenu après plusieurs jours, semaines, mois... Grâce à ce compte, vous pourrez aussi <b>recevoir régulièrement de nouveaux quizs</b> pour continuer à \"cultiver votre jardin\".</p>",
|
||||
questionnaireLicenceTxt: "Ce quiz <a href=\"/credits.html\">est libre</a>, mais il n'est pas gratuit. Vous pouvez <a href=\"/participer-financement.html\">participer à son financement en cliquant ici</a>.",
|
||||
explanationElementTxt: "<p>WikiLerni vous propose de recevoir par e-mail plusieurs cours tirés d'articles Wikipédia. À la fin, un quiz vous permettra de tester ce que vous avez retenu de votre lecture.</p>",
|
||||
explanationGroupTxt: "<p>Ce quiz porte sur plusieurs leçons que vous avez pu lire précédemment. Si ce n'est pas le cas, utilisez le lien pour accéder au premier cours.</p>",
|
||||
/* Autres */
|
||||
illustrationDir : "/img/quizs/",
|
||||
twitterAccount: "WikiLerni",
|
||||
|
|
|
@ -26,7 +26,7 @@ block content
|
|||
const publishedAtTxt=tool.dateFormat(questionnaire.Questionnaire.publishingAt, questionnaire.Questionnaire.language);
|
||||
const updatedAtTxt=tool.dateFormat(questionnaire.Questionnaire.updatedAt, questionnaire.Questionnaire.language);
|
||||
|
||||
if(questionnaire.Illustrations!=undefined && questionnaire.Illustrations.length!==0)
|
||||
if(questionnaire.Illustrations != undefined && questionnaire.Illustrations.length !== 0)
|
||||
div(id="content-picture" class="cardboard")
|
||||
div(style="background-image: url('/img/quizs/"+questionnaire.Illustrations[0].url+"');")
|
||||
img(src="/img/quizs/"+questionnaire.Illustrations[0].url)&attributes(imgAttributes)
|
||||
|
@ -39,41 +39,62 @@ block content
|
|||
h1(class="cardboard")
|
||||
img(id="required-time-icon" src="/themes/wikilerni/img/time-required-"+questionnaire.Questionnaire.estimatedTime+".png" title=txtQuestionnaire.estimatedTime+" "+txtQuestionnaire.estimatedTimeOption[questionnaire.Questionnaire.estimatedTime])
|
||||
span #{questionnaire.Questionnaire.title}
|
||||
if(questionnaire.Illustrations!=undefined && questionnaire.Illustrations.length!==0)
|
||||
if(questionnaire.Illustrations != undefined && questionnaire.Illustrations.length !== 0)
|
||||
a(href="/img/quizs/"+questionnaire.Illustrations[0].url target="_blank" rel="noopener")
|
||||
img(src="/img/quizs/min/"+questionnaire.Illustrations[0].url class="thumb")&attributes(imgAttributes)
|
||||
div(id="content-title-corner")
|
||||
|
||||
div(id="content" class="cardboard")
|
||||
p(id="author-date") #{txtQuestionnaire.publishedBy} #{author}#{txtQuestionnaire.publishedAt} #{publishedAtTxt}. #{txtQuestionnaire.lastUpdated}#{updatedAtTxt}.
|
||||
|
||||
//- Important : ici, on garde volontairement le html !
|
||||
if(questionnaire.Questionnaire.introduction)
|
||||
div#introduction !{questionnaire.Questionnaire.introduction}
|
||||
|
||||
div#links
|
||||
h4 #{configTpl.quizElementLinksIntro}
|
||||
ul#quizElementLinks
|
||||
for link in questionnaire.Links
|
||||
li
|
||||
a(href=link.url target="_blank" rel="noopener" title=link.anchor+" ("+txtGeneral.alertNewWindow+")") #{link.anchor}
|
||||
//- Les sources de l'article
|
||||
if(questionnaire.Links != undefined && questionnaire.Links.length !== 0)
|
||||
div#elementLinks
|
||||
h4 #{configTpl.quizElementLinksIntro}
|
||||
ul#quizElementLinks
|
||||
for link in questionnaire.Links
|
||||
li
|
||||
a(href=link.url target="_blank" rel="noopener" title=link.anchor+" ("+txtGeneral.alertNewWindow+")") #{link.anchor}
|
||||
|
||||
// - lien vers élément suivant ou quiz si dernier ?
|
||||
//div#links
|
||||
// a(href=link.url class="button cardboard" target="_blank" rel="noopener" title=link.anchor+" ("+txtGeneral.alertNewWindow+")") #{link.anchor}
|
||||
// Lien vers l'élément suivant ou le quiz du groupe, si je suis arrivé à la fin :
|
||||
-
|
||||
let nextLink={};
|
||||
if(nextQuestionnaire != null && nextQuestionnaire.Questionnaire.isPublished)
|
||||
{
|
||||
nextLink.href="/"+configQuestionnaires.dirWebQuestionnaires+"/"+nextQuestionnaire.Questionnaire.slug+".html";
|
||||
nextLink.title=nextQuestionnaire.Questionnaire.title;
|
||||
nextLink.anchor=txtQuestionnaire.linkGoToNextElement;
|
||||
}
|
||||
else if(group.Group.isPublishable)
|
||||
{
|
||||
nextLink.href="/"+configQuestionnaires.dirWebGroups+"/"+group.Group.slug+".html";
|
||||
nextLink.title=group.Group.title;
|
||||
nextLink.anchor=txtQuestionnaire.linkGoToQuiz;
|
||||
}
|
||||
if(nextLink.href!=undefined)
|
||||
div#nextLink
|
||||
a(href=nextLink.href class="button cardboard" title=nextLink.title) !{"➔ "+nextLink.anchor}
|
||||
|
||||
div#licence
|
||||
p !{configTpl.questionnaireLicenceTxt}
|
||||
p !{configTpl.licenceTxt}
|
||||
|
||||
noscript
|
||||
div
|
||||
strong #{configTpl.noJSNotification}
|
||||
|
||||
// Formulaire d'inscription :
|
||||
-
|
||||
const cguOkLabel = txtUsers.formsEmailLabel.replace("#link", "/"+configTpl.cguPage);
|
||||
div#signup
|
||||
form(id="subscription" method="POST" class="needJS")
|
||||
const cguOkLabel = txtUser.formsEmailLabel.replace("#link", "/"+configTpl.cguPage); /// remettre class="needJS" au formulaire ci-dessous
|
||||
div#signupForm
|
||||
form(id="subscription" method="POST")
|
||||
h3 #{configTpl.quizElementSubcriptionFormTitle}
|
||||
fieldset
|
||||
label(for="email") #{txtUsers.formsEmailLabel}
|
||||
input(id="email" type="email" name="email" placeholder=txtUsers.formsEmailPlaceholder class="cardboard")
|
||||
label(for="email") #{txtUser.formsEmailLabel}
|
||||
input(id="email" type="email" name="email" placeholder=txtUser.formsEmailPlaceholder class="cardboard")
|
||||
div#emailMessage
|
||||
ul(class="checkbox_li")
|
||||
li(class="checkbox_li")
|
||||
|
@ -82,7 +103,7 @@ block content
|
|||
div(class="checkbox_override")
|
||||
span #{cguOkLabel}
|
||||
div(class="input_wrapper")
|
||||
input(id="submitDatas" type="submit" value=txtUsers.formsSubmitTxt class="cardboard")
|
||||
input(id="submitDatas" type="submit" value=txtUser.formsSubmitTxt class="cardboard")
|
||||
div(id="response")
|
||||
|
||||
div#zerozozio
|
||||
|
@ -96,4 +117,4 @@ block content
|
|||
div#explanations(class="engraved framed")
|
||||
h3#explanationsTitle #{configTpl.explanationTitle}
|
||||
div#explanationsContent
|
||||
p !{configTpl.explanationTxt}
|
||||
p !{configTpl.explanationElementTxt}
|
|
@ -6,29 +6,61 @@ block append scripts
|
|||
|
||||
block content
|
||||
|
||||
// Il n'y a pas d'illustration spécifique au groupe, mais je reprends celle du premier élément du groupe de quizs
|
||||
-
|
||||
const imgAttributes = { alt: txtIllustration.defaultAlt, style: "opacity: 0.0;" };
|
||||
if(group.Questionnaires[0].Illustrations!=undefined && group.Questionnaires[0].Illustrations.length!==0)
|
||||
{
|
||||
if (tool.isEmpty(group.Questionnaires[0].Illustrations[0].alt)===false)
|
||||
imgAttributes.alt=group.Questionnaires[0].Illustrations[0].alt;
|
||||
if(tool.isEmpty(group.Questionnaires[0].Illustrations[0].title)===false)
|
||||
imgAttributes.title=group.Questionnaires[0].Illustrations[0].title;
|
||||
}
|
||||
const publishedAtTxt=tool.dateFormat(group.Group.publishingAt, group.Group.language);
|
||||
const updatedAtTxt=tool.dateFormat(group.Group.updatedAt, group.Group.language);
|
||||
|
||||
if(group.Questionnaires[0].Illustrations!=undefined && group.Questionnaires[0].Illustrations.length!==0)
|
||||
div(id="content-picture" class="cardboard")
|
||||
div(style="background-image: url('/img/quizs/"+group.Questionnaires[0].Illustrations[0].url+"');")
|
||||
img(src="/img/quizs/"+group.Questionnaires[0].Illustrations[0].url)&attributes(imgAttributes)
|
||||
//- Important : ici, on garde volontairement le html saisi car lien possible vers auteur de l'illustration :
|
||||
if(group.Questionnaires[0].Illustrations[0].caption)
|
||||
p !{group.Questionnaires[0].Illustrations[0].caption}
|
||||
|
||||
|
||||
div(id="content-side")
|
||||
div(id="content-title")
|
||||
h1(class="cardboard")
|
||||
span #{group.title}
|
||||
img(id="required-time-icon" src="/themes/wikilerni/img/time-required-medium.png")
|
||||
span #{group.Group.title}
|
||||
if(group.Questionnaires[0].Illustrations!=undefined && group.Questionnaires[0].Illustrations.length!==0)
|
||||
a(href="/img/quizs/"+group.Questionnaires[0].Illustrations[0].url target="_blank" rel="noopener")
|
||||
img(src="/img/quizs/min/"+group.Questionnaires[0].Illustrations[0].url class="thumb")&attributes(imgAttributes)
|
||||
div(id="content-title-corner")
|
||||
|
||||
div(id="content" class="cardboard")
|
||||
p(id="author-date") #{txtGroups.publishedBy} #{author}#{txtGroups.publishedAt} #{publishedAtTxt}. #{txtGroups.lastUpdated}#{updatedAtTxt}.
|
||||
p(id="author-date") #{txtGroups.publishedBy} #{author} #{txtGroups.publishedAt} #{publishedAtTxt}. #{txtGroups.lastUpdated} #{updatedAtTxt}.
|
||||
//- Important : ici, on garde volontairement le html, car cela est accepté pour l'introduction.
|
||||
|
||||
div#introduction
|
||||
if(group.introduction)
|
||||
div !{group.introduction}//- Important : ici, on garde volontairement le html, car cela est accepté pour l'introduction.
|
||||
div txtGroups.commonIntroTxt
|
||||
// - lien vers premier élément du groupe
|
||||
if(group.Group.introduction)
|
||||
div !{group.Group.introduction}
|
||||
div #{txtGroups.commonIntroTxt}
|
||||
|
||||
// - lien vers premier élément du groupe (html autorisé pour permettre les symboles unicodes)
|
||||
div#links
|
||||
a(href=="/"+configQuestionnaires.dirdirWebQuestionnaires+group.Questionnaires[0].slug+".html" class="button cardboard" title=txtGroups.linkFirstElementGroup) #{txtGroups.linkFirstElementGroup}
|
||||
a(href="/"+configQuestionnaires.dirWebQuestionnaires+"/"+group.Questionnaires[0].Questionnaire.slug+".html" class="button cardboard" title=group.Questionnaires[0].Questionnaire.title) !{"← "+txtGroups.linkFirstElementGroup}
|
||||
|
||||
div#licence
|
||||
p !{configTpl.groupLicenceTxt}
|
||||
p !{configTpl.licenceTxt}
|
||||
|
||||
noscript
|
||||
div
|
||||
strong #{configTpl.noJSNotification}
|
||||
|
||||
form(id="group" method="POST" class="needJS")
|
||||
h2 #{group.title}
|
||||
// à cacher si pas de JS !
|
||||
form(id="group" method="POST")
|
||||
h2 #{group.Group.title}
|
||||
div#response
|
||||
div(class="subscribeBtns")
|
||||
p
|
||||
|
@ -39,7 +71,7 @@ block content
|
|||
for question in questionnaire.Questions
|
||||
p(id="question_"+question.Question.id) #{question.Question.text}
|
||||
if(question.Question.explanation)
|
||||
blockquote(class="help" id="help_"+question.Question.id cite=group.Links[0].url) #{txtexplanationBeforeTxt} #{question.Question.explanation}
|
||||
blockquote(class="help" id="help_"+question.Question.id cite=questionnaire.Links[0].url) #{txtexplanationBeforeTxt} #{question.Question.explanation}
|
||||
ul(class="checkbox_li")
|
||||
for response in question.Choices
|
||||
li(class="checkbox_li")
|
||||
|
@ -53,20 +85,20 @@ block content
|
|||
em #{response.text}
|
||||
input(type="hidden" name="isCorrect_response_"+response.id id="isCorrect_response_"+response.id value=""+response.isCorrect)
|
||||
input(type="hidden" name="question_id_response_"+response.id id="question_id_response_"+response.id value=question.Question.id)
|
||||
input(name="groupId" id="groupId" value=group.id type="hidden")
|
||||
input(name="groupId" id="groupId" value=group.Group.id type="hidden")
|
||||
p
|
||||
span(class="input_wrapper")
|
||||
input(id="checkResponses" type="submit" value=txtGroups.btnSendResponse class="cardboard" title=txtGroups.btnSendResponse)
|
||||
|
||||
div#zerozozio
|
||||
a(href="http://sharetodiaspora.github.io/?url="+linkCanonical+"&title="+group.title rel="nofollow noopener" title=txtGroups.btnShareQuizTxt+" diaspora* ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
a(href="http://sharetodiaspora.github.io/?url="+linkCanonical+"&title="+group.Group.title rel="nofollow noopener" title=txtGroups.btnShareQuizTxt+" diaspora* ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
img(src="/themes/wikilerni/img/diaspora.png" alt=txtGroups.btnShareQuizTxt+" diaspora*")
|
||||
a(href="https://www.facebook.com/sharer.php?u="+linkCanonical rel="nofollow noopener" title=txtGroups.btnShareQuizTxt+" facebook ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
img(src="/themes/wikilerni/img/facebook.png" alt=txtGroups.btnShareQuizTxt+" facebook")
|
||||
a(href="https://twitter.com/intent/tweet?url="+linkCanonical+"&text="+group.title+" via @"+configTpl.twitterAccount rel="nofollow noopener" title=txtGroups.btnShareQuizTxt+" twitter ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
a(href="https://twitter.com/intent/tweet?url="+linkCanonical+"&text="+group.Group.title+" via @"+configTpl.twitterAccount rel="nofollow noopener" title=txtGroups.btnShareQuizTxt+" twitter ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
img(src="/themes/wikilerni/img/twitter.png" alt=txtGroups.btnShareQuizTxt+" twitter")
|
||||
|
||||
div#explanations(class="engraved framed")
|
||||
h3#explanationsTitle #{configTpl.explanationTitle}
|
||||
div#explanationsContent
|
||||
p !{configTpl.explanationTxt}
|
||||
p !{configTpl.explanationGroupTxt}
|
|
@ -50,7 +50,7 @@ block content
|
|||
if(questionnaire.Questionnaire.introduction)
|
||||
div#introduction !{questionnaire.Questionnaire.introduction}
|
||||
div#licence
|
||||
p !{configTpl.questionnaireLicenceTxt}
|
||||
p !{configTpl.licenceTxt}
|
||||
|
||||
div#links
|
||||
for link in questionnaire.Links
|
||||
|
|
Loading…
Reference in New Issue