Premières évolutions liées à la nouvelle version, dont création du formulaire d'édition des groupes de quizs.
This commit is contained in:
parent
7a23a76f8e
commit
9a6bb5d882
2
app.js
2
app.js
@ -11,6 +11,7 @@ const userRoutes = require("./routes/user");
|
||||
const userPausesRoutes = require("./routes/pause");
|
||||
const userPaymentsRoutes = require("./routes/payment");
|
||||
const questionnairesRoutes = require("./routes/questionnaire");
|
||||
const groupsRoutes = require("./routes/group");
|
||||
const questionsRoutes = require("./routes/question");
|
||||
const choicesRoutes = require("./routes/choice");
|
||||
const illustrationRoutes = require("./routes/illustration");
|
||||
@ -39,6 +40,7 @@ app.use("/api/user", userRoutes);
|
||||
app.use("/api/pause", userPausesRoutes);
|
||||
app.use("/api/payment", userPaymentsRoutes);
|
||||
app.use("/api/questionnaire", questionnairesRoutes);
|
||||
app.use("/api/group", groupsRoutes);
|
||||
app.use("/api/questionnaire", tagRoutes);
|
||||
app.use("/api/question", questionsRoutes);
|
||||
app.use("/api/question", choicesRoutes);
|
||||
|
@ -41,6 +41,9 @@ module.exports =
|
||||
numberNewQuestionnaireAtSameTime: 50, // for mass mailing sending new quiz
|
||||
minSearchQuestionnaires: 3,
|
||||
fieldNewQuestionnaires : "publishingAt", // field to be used to create the list of the last questionnaires, can be "createdAt", "updatedAt" or "publishingAt"
|
||||
// Groups :
|
||||
nbQuestionnairesByGroupMin: 2,
|
||||
nbQuestionnairesByGroupMax: 0,
|
||||
// Illustrations:
|
||||
nbIllustrationsMin: 0,
|
||||
nbIllustrationsMax: 1,
|
||||
@ -63,6 +66,6 @@ module.exports =
|
||||
dirCacheQuestionnaires: questionnaires.dirCacheQuestionnaires,
|
||||
dirCacheQuestions: questionnaires.dirCacheQuestions,
|
||||
dirCacheUsersQuestionnaires: questionnaires.dirCacheUsersQuestionnaires,
|
||||
dirHTMLQuestionnaire: questionnaires.dirHTMLQuestionnaire,
|
||||
dirWebQuestionnaire: questionnaires.dirWebQuestionnaire
|
||||
dirHTMLQuestionnaires: questionnaires.dirHTMLQuestionnaires,
|
||||
dirWebQuestionnaires: questionnaires.dirWebQuestionnaires
|
||||
};
|
@ -1,34 +1,43 @@
|
||||
module.exports =
|
||||
{
|
||||
// API'routes (after "apiUrl" defined in instance.js)
|
||||
questionnaireRoutes: "/questionnaire",
|
||||
questionnaireRoutes: "/questionnaire",// la base à laquelle s'ajoute les routes suivantes
|
||||
getListNextQuestionnaires: "/getlistnextquestionnaires/",
|
||||
getQuestionnaireRoutes: "/get",
|
||||
getRandomQuestionnairesRoute : "/getrandom",
|
||||
getStatsQuestionnaires : "/stats/",
|
||||
previewQuestionnaireRoutes: "/preview",
|
||||
publishedQuestionnaireRoutes: "/quiz/",
|
||||
saveAnswersRoute: "/answer/",
|
||||
getStatsQuestionnaires : "/stats/",
|
||||
searchQuestionnairesRoute : "/search",
|
||||
getRandomQuestionnairesRoute : "/getrandom",
|
||||
searchAdminQuestionnairesRoute : "/searchadmin",
|
||||
getListNextQuestionnaires: "/getlistnextquestionnaires/",
|
||||
regenerateHTML: "/htmlregenerated",
|
||||
saveAnswersRoute: "/answer/",
|
||||
searchAdminQuestionnairesRoute : "/searchadmin",
|
||||
searchQuestionnairesRoute : "/search",
|
||||
// -- groupes :
|
||||
groupRoutes: "/group",
|
||||
getGroupRoute: "/get/",
|
||||
searchGroupsRoute : "/search",
|
||||
// -- questions & choices :
|
||||
questionsRoute: "/question/",
|
||||
// -- tags :
|
||||
tagsSearchRoute: "/tags/search/",
|
||||
// -- answers :
|
||||
getQuestionnairesWithoutAnswer: "/withoutanswer/user/",
|
||||
getPreviousAnswers: "/user/answers/",
|
||||
getStatsAnswers : "/user/anwswers/stats/",
|
||||
getAdminStats: "/getadminstats/",
|
||||
getPreviousAnswers: "/user/answers/",
|
||||
getQuestionnairesWithoutAnswer: "/withoutanswer/user/",
|
||||
getStatsAnswers : "/user/anwswers/stats/",
|
||||
// forms : à compléter avec valeurs par défaut, etc. cf modèle
|
||||
Questionnaire :
|
||||
{
|
||||
title: { maxlength: 255, required: true },
|
||||
slug: { maxlength: 150 }, // champ requis mais calculé à partir du titre qd vide
|
||||
slug: { maxlength: 150 }, // champ requis mais calculé à partir du titre qd laissé vide dans le formulaire
|
||||
introduction: { required: true }
|
||||
},
|
||||
searchQuestionnaires : { minlength: 3, required: true },
|
||||
Group :
|
||||
{
|
||||
title: { maxlength: 255, required: true },
|
||||
slug: { maxlength: 150 }, // champ requis mais calculé à partir du titre qd laissé vide dans le formulaire
|
||||
},
|
||||
Question :
|
||||
{
|
||||
text: { maxlength: 255, required: true },
|
||||
@ -39,20 +48,27 @@ module.exports =
|
||||
text: { maxlength: 255, required: true }
|
||||
},
|
||||
search: { minlength: 3, required: true },
|
||||
/* valeurs en fait définies dans instance.js donc à supprimer quand plus utilisées ailleurs */
|
||||
searchGroups: { minlength: 3, required: true },
|
||||
// Emplacement des fichiers JSON générés :
|
||||
dirCacheGroups : "datas/questionnaires/groups",
|
||||
dirCacheQuestionnaires : "datas/questionnaires",
|
||||
dirCacheQuestions : "datas/questionnaires/questions",
|
||||
dirCacheTags : "datas/questionnaires/tags",
|
||||
dirCacheUsersQuestionnaires : "datas/users/questionnaires",
|
||||
// Emplacement des fichiers HTML générés :
|
||||
dirHTMLGroups : "front/public/quizs/gp",
|
||||
dirHTMLQuestionnaires : "front/public/quiz",
|
||||
dirHTMLTags : "front/public/quizs",
|
||||
// Idem mais pour urls :
|
||||
dirWebGroups : "quizs/gp",
|
||||
dirWebQuestionnaires : "quiz",
|
||||
dirWebTags : "quizs/",
|
||||
// limite des résultat du moteur de recherche, quand demande de résultats au hasard :
|
||||
nbRandomResults : 3,
|
||||
/* Valeurs en fait définies dans instance.js donc à supprimer quand plus utilisées ailleurs : */
|
||||
nbQuestionsMin: 1,
|
||||
nbQuestionsMax: 0,
|
||||
nbChoicesMax: 10,
|
||||
nbTagsMin: 0,
|
||||
nbTagsMax: 0, // 0 = not max
|
||||
// JSON and HTML dir
|
||||
dirCacheQuestionnaires : "datas/questionnaires",
|
||||
dirCacheQuestions : "datas/questionnaires/questions",
|
||||
dirCacheUsersQuestionnaires : "datas/users/questionnaires",
|
||||
dirCacheTags : "datas/questionnaires/tags",
|
||||
dirHTMLQuestionnaire : "front/public/quiz",
|
||||
dirHTMLTags : "front/public/quizs",
|
||||
dirWebQuestionnaire : "quiz",//pour url page
|
||||
dirWebTags : "quizs/",// idem
|
||||
nbRandomResults : 3// limite les résultat du moteur de recherche quand demande de résultats au hasard
|
||||
};
|
385
controllers/group.js
Normal file
385
controllers/group.js
Normal file
@ -0,0 +1,385 @@
|
||||
const { Op, QueryTypes } = require("sequelize");
|
||||
const pug = require("pug");
|
||||
const striptags = require("striptags");
|
||||
|
||||
const config = require("../config/main.js");
|
||||
const configQuestionnaires = require("../config/questionnaires.js");
|
||||
|
||||
const tool = require("../tools/main");
|
||||
const toolError = require("../tools/error");
|
||||
const toolFile = require("../tools/file");
|
||||
|
||||
const questionnaireCtrl = require("./questionnaire");
|
||||
const userCtrl = require("./user");
|
||||
|
||||
const txtGeneral = require("../lang/"+config.adminLang+"/general");
|
||||
const txtGroups = require("../lang/"+config.adminLang+"/group");
|
||||
|
||||
exports.create = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const db = require("../models/index");
|
||||
req.body.CreatorId=req.connectedUser.User.id;
|
||||
const group=await db["Group"].create({ ...req.body }, { fields: ["title", "slug", "introduction", "publishingAt", "language", "CreatorId"] });
|
||||
creaGroupJson(group.id);
|
||||
res.status(201).json({ message: txtGeneral.addedOkMessage, id: group.id });
|
||||
next();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
const returnAPI=toolError.returnSequelize(e);
|
||||
if(returnAPI.messages)
|
||||
{
|
||||
res.status(returnAPI.status).json({ errors : returnAPI.messages });
|
||||
next();
|
||||
}
|
||||
else
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
exports.modify = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const group=await searchGroupById(req.params.id);
|
||||
if(!group)
|
||||
{
|
||||
const Err=new Error;
|
||||
error.status=404;
|
||||
error.message=txtGroups.notFound.replace("#SEARCH", req.params.id);
|
||||
throw Err;
|
||||
}
|
||||
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==group.CreatorId)
|
||||
res.status(401).json({ errors: txtGeneral.notAllowed });
|
||||
else
|
||||
await db["Group"].update({ ...req.body }, { where: { id : req.params.id } , fields: ["title", "slug", "introduction", "publishingAt", "language"], limit:1 });
|
||||
await creaGroupJson(req.params.id);
|
||||
res.status(200).json({ message: txtGeneral.updateOkMessage });
|
||||
next();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
const returnAPI=toolError.returnSequelize(e);
|
||||
if(returnAPI.messages)
|
||||
{
|
||||
res.status(returnAPI.status).json({ errors : returnAPI.messages });
|
||||
next();
|
||||
}
|
||||
else
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
exports.delete = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const group=await searchGroupById(req.params.id);
|
||||
if(!group)
|
||||
{
|
||||
const Err=new Error;
|
||||
error.status=404;
|
||||
error.message=txtGroups.notFound.replace("#SEARCH", req.params.id);
|
||||
throw Err;
|
||||
}
|
||||
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==group.CreatorId)
|
||||
res.status(401).json({ errors: txtGeneral.notAllowed });
|
||||
else
|
||||
{
|
||||
// La suppression sera bloquée par SQL si des quizs dépendent de ce groupe
|
||||
// Donc il faut d'abord supprimer tous les quizs du groupe :
|
||||
for(let i in group.Questionnaires)
|
||||
await questionnaireCtrl.deleteQuestionnaireById(group.Questionnaires[i].id);
|
||||
const nb=await db["Group"].destroy( { where: { id : req.params.id }, limit:1 });
|
||||
if(nb===1)
|
||||
{
|
||||
toolFile.deleteJSON(configQuestionnaires.dirCacheGroups, req.params.id);
|
||||
toolFile.deleteFile(configQuestionnaires.dirHTMLGroups, group.Group.slug+".html");
|
||||
creaStatsGroupsJson();
|
||||
res.status(200).json({ message: txtGeneral.deleteOkMessage });
|
||||
}
|
||||
else
|
||||
{
|
||||
const Err=new Error;
|
||||
error.status=404;
|
||||
error.message=txtGroups.deleteFailMessage.replace("#ID", req.params.id);
|
||||
throw Err;
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Recherche par mots-clés parmis tous les groupes (y compris ceux non publiés).
|
||||
exports.searchGroups = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
let search=tool.trimIfNotNull(req.body.searchGroups);
|
||||
if(search === null || search === "" || search.length < configQuestionnaires.searchGroups.minlength)
|
||||
res.status(400).json(txtGroups.searchIsNotLongEnough.replace("#MIN", configQuestionnaires.searchGroups.minlength));
|
||||
else
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const getGroups=await db.sequelize.query("SELECT `id`,`title` FROM `Groups` WHERE (`title` LIKE :search OR `introduction` LIKE :search) ORDER BY `title` ASC", { replacements: { search: "%"+search+"%" }, type: QueryTypes.SELECT });
|
||||
res.status(200).json(getGroups);
|
||||
}
|
||||
next();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
exports.getOneById = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const datas=await searchGroupById(req.params.id);
|
||||
if(datas)
|
||||
res.status(200).json(datas);
|
||||
else
|
||||
res.status(404).json({ message:txtGroups.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) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const stats=await getStatsGroups();
|
||||
res.status(200).json(stats);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CRONS
|
||||
|
||||
// Supprime fichiers json de groupes n'existant plus.
|
||||
exports.deleteJsonFiles = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const groups=await db["Group"].findAll({ attributes: ["id"] });
|
||||
for(let i in groups)
|
||||
saveFiles.push(groups[i].id+".json");
|
||||
const deleteFiles = await toolFile.deleteFilesInDirectory(configQuestionnaires.dirCacheGroups, saveFiles);
|
||||
res.status(200).json(deleteFiles);
|
||||
next();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Teste si des groupes doivent être publiés
|
||||
exports.checkGroupsNeedToBePublished = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const nb=await checkGroupsNeedToBePublished();
|
||||
res.status(200).json(txtGroups.haveBeenPublished.replace("#NB", nb));
|
||||
next();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
next(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// FONCTIONS PARTAGÉES
|
||||
|
||||
const creaGroupJson = async (id) =>
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const Group=await db["Group"].findByPk(id);
|
||||
if(Group)
|
||||
{
|
||||
let datas={ Group };
|
||||
const Questionnaires=await db["Questionnaire"].findAll({ where: { GroupId: Group.id }, 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 :
|
||||
if(checkGroupIsPublishable(datas))
|
||||
creaGroupHTML(id);
|
||||
else // dans le cas contraire, on supprime l'éventuel fichier préexistant
|
||||
toolFile.deleteFile(config.dirHTMLGroups, Group.slug+".html");
|
||||
// + mise à jour des statistiques :
|
||||
creaStatsGroupsJson();
|
||||
return datas;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
exports.creaGroupJson = creaGroupJson;
|
||||
|
||||
const checkGroupIsPublishable = (datas, checkDate=true) =>
|
||||
{
|
||||
if(checkDate)
|
||||
{
|
||||
if(datas.Group.publishingAt === null)
|
||||
return false;
|
||||
else
|
||||
{
|
||||
const today=new Date();
|
||||
today.setHours(0,0,0,0);// !! attention au décalage horaire du fait de l'enregistrement en UTC dans mysql
|
||||
const publishingAt=new Date(datas.Group.publishingAt);
|
||||
if (publishingAt.getTime() > today.getTime())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(datas.Questionnaires === undefined || datas.Questionnaires.length < config.nbQuestionnairesByGroupMin)
|
||||
return false;
|
||||
return 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)
|
||||
return false;
|
||||
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
|
||||
{
|
||||
config: config,
|
||||
configQuestionnaires: configQuestionnaires,
|
||||
configTpl: configTpl,
|
||||
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+")",
|
||||
group: group,
|
||||
linkCanonical: config.siteUrl+"/"+config.dirWebGroups+"/"+group.slug+".html"
|
||||
}
|
||||
const html=await compiledFunction(pageDatas);
|
||||
if(preview === false)
|
||||
{
|
||||
await toolFile.createHTML(configQuestionnaires.dirHTMLGroups, group.slug, html);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return html;
|
||||
}
|
||||
|
||||
// Remonte toutes les données du groupe + les données des questionnaires y étant classés si reassemble=true
|
||||
const searchGroupById = async (id, reassemble=false) =>
|
||||
{
|
||||
let group=await toolFile.readJSON(configQuestionnaires.dirCacheGroups, id);
|
||||
if(!group)
|
||||
group=await creaGroupJson(id);
|
||||
if(!group)
|
||||
return false;
|
||||
if(reassemble)
|
||||
{
|
||||
let questionnaire; Questionnaires=[];
|
||||
const author=await userCtrl.searchUserById(group.Group.CreatorId);
|
||||
if(author)
|
||||
group.Group.CreatorName=author.User.name;
|
||||
for(let i in group.Questionnaires)
|
||||
{
|
||||
questionnaire=await questionnaireCtrl.searchQuestionnaireById(questionnaire.Questions[i].id, true);
|
||||
if(questionnaire)
|
||||
Questionnaires.push(questionnaire);
|
||||
}
|
||||
group.Questionnaires=Questionnaires;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
exports.searchGroupById = searchGroupById;
|
||||
|
||||
// Cherche si il y a des groupes de questionnaires dont la date de publication est passée mais qui ne sont pas publiés
|
||||
// Vérifie si ils sont publiables et si oui génère le HTML
|
||||
// Si regenerate=true, tous les fichiers sont (ré)générés, même s'ils existent déjà (évolution template...)
|
||||
// Retourne le nombre de fichiers ayant été (ré)générés
|
||||
const checkGroupsNeedToBePublished = async (regenerate=false) =>
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const groups = await db.sequelize.query("SELECT `id`,`slug` FROM `groups` WHERE `publishingAt` < NOW()", { type: QueryTypes.SELECT });
|
||||
let publishedOk=false, nb=0;
|
||||
for(let i in groups)
|
||||
{
|
||||
if(regenerate===false)
|
||||
{
|
||||
if(await toolFile.checkIfFileExist(configQuestionnaires.dirHTMLGroups, group.Group.slug+".html")===false)
|
||||
{
|
||||
publishedOk=await creaGroupHTML(groups[i].id);// creaGroupHTLM contrôle que le groupe est publiable
|
||||
if(publishedOk)
|
||||
nb++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
publishedOk=await creaGroupHTML(groups[i].id);
|
||||
if(publishedOk)
|
||||
nb++;
|
||||
}
|
||||
}
|
||||
return nb;
|
||||
}
|
||||
|
||||
// Compte le nombre total de groupes et le stocke
|
||||
const creaStatsGroupsJson = async () =>
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const Groups=await db["Group"].findAll({ attributes: ["id"] });
|
||||
const GroupsPublished=await db.sequelize.query("SELECT `id` FROM `Groups` WHERE `publishingAt` < NOW()", { type: QueryTypes.SELECT });
|
||||
const QuestionnairesInGroups=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires` WHERE `GroupId` IS NOT NULL", { type: QueryTypes.SELECT });
|
||||
if(Groups && GroupsPublished && QuestionnairesInGroups)
|
||||
{
|
||||
const stats =
|
||||
{
|
||||
nbTot : Groups.length,
|
||||
nbPublished : GroupsPublished.length, // ! en fait, peuvent être passé de date et non publié : À REVOIR QUAND DES EXEMPLES EN BD!
|
||||
nbQuestionnaires : QuestionnairesInGroups.length
|
||||
}
|
||||
await toolFile.createJSON(configQuestionnaires.dirCacheGroups, "stats", stats);
|
||||
return stats;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
exports.creaStatsGroupsJson = creaStatsGroupsJson;
|
||||
|
||||
// Retourne les données créées par la fonction précédente
|
||||
const getStatsGroups = async () =>
|
||||
{
|
||||
let stats=await toolFile.readJSON(configQuestionnaires.dirCacheGroups, "stats");
|
||||
if(!stats)
|
||||
stats=await creaStatsGroupsJson();
|
||||
if(!stats)
|
||||
return false;
|
||||
else
|
||||
return stats;
|
||||
}
|
||||
exports.getStatsGroups = getStatsGroups;
|
@ -91,50 +91,11 @@ exports.delete = async (req, res, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const questionnaire=await searchQuestionnaireById(req.params.id);
|
||||
if(!questionnaire)
|
||||
{
|
||||
const Err=new Error;
|
||||
error.status=404;
|
||||
error.message=txtQuestionnaire.notFound+" ("+req.params.id+")";
|
||||
const del=deleteQuestionnaire(req.params.id, req);
|
||||
if(typeof del===Error)
|
||||
throw Err;
|
||||
}
|
||||
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId)
|
||||
res.status(401).json({ errors: txtGeneral.notAllowed });
|
||||
else
|
||||
{
|
||||
// Permet de supprimer les fichiers associés en plus du sql. Inutile pour link qui n'a pas de fichier.
|
||||
// À faire avant la suppression SQL du questionnaire entraînant la suppression en cascade du reste.
|
||||
for(i in questionnaire.Questions)
|
||||
await questionCtrl.deleteQuestionById(questionnaire.Questions[i].id);
|
||||
|
||||
for(i in questionnaire.Illustrations)
|
||||
await illustrationCtrl.deleteIllustrationById(questionnaire.Illustrations[i].id);
|
||||
|
||||
const nb=await db["Questionnaire"].destroy( { where: { id : req.params.id }, limit:1 });
|
||||
if(nb===1)
|
||||
{
|
||||
await toolFile.deleteJSON(config.dirCacheQuestionnaires, req.params.id);
|
||||
res.status(200).json({ message: txtGeneral.deleteOkMessage });
|
||||
// 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();
|
||||
// Éventuellement regénérer les caches listant les réponses/quizs des users ayant accès à ce questionnaire ?
|
||||
// ++ HTML
|
||||
}
|
||||
else
|
||||
{
|
||||
const Err=new Error;
|
||||
error.status=404;
|
||||
error.message=txtQuestionnaire.notFound+" ("+req.params.id+")";
|
||||
throw Err;
|
||||
}
|
||||
}
|
||||
res.status(200).json({ message: txtGeneral.deleteOkMessage });
|
||||
next();
|
||||
}
|
||||
catch(e)
|
||||
@ -403,7 +364,7 @@ exports.checkQuestionnairesNeedToBePublished= async (req, res, next) =>
|
||||
}
|
||||
|
||||
|
||||
// FONCTIONS UTILITAIRES
|
||||
// FONCTIONS PARTAGÉES
|
||||
|
||||
const creaQuestionnaireJson = async (id) =>
|
||||
{
|
||||
@ -435,7 +396,7 @@ const creaQuestionnaireJson = async (id) =>
|
||||
tagCtrl.creaUsedTagsJson();
|
||||
// si le quiz était publié jusqu'ici, il me faut supprimer son fichier HTML (revenir pour réactiver)
|
||||
if(wasPublished)
|
||||
toolFile.deleteFile(config.dirHTMLQuestionnaire, Questionnaire.slug+".html");
|
||||
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é
|
||||
@ -451,6 +412,52 @@ const creaQuestionnaireJson = async (id) =>
|
||||
}
|
||||
exports.creaQuestionnaireJson = creaQuestionnaireJson;
|
||||
|
||||
// Supprime un questionnaire et toutes ses dépendances
|
||||
const deleteQuestionnaire = async (id, req) =>
|
||||
{
|
||||
const db = require("../models/index");
|
||||
const questionnaire=await searchQuestionnaireById(id);
|
||||
if(!questionnaire)
|
||||
{
|
||||
const Err=new Error;
|
||||
error.status=404;
|
||||
error.message=txtQuestionnaire.notFound+" ("+req.params.id+")";
|
||||
return Err;
|
||||
}
|
||||
else if(req.connectedUser.User.status==="creator" && req.connectedUser.User.id!==questionnaire.CreatorId)
|
||||
{
|
||||
const Err=new Error;
|
||||
error.status=401;
|
||||
error.message=txtGeneral.notAllowed;
|
||||
return Err;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Suppression des fichiers associés en plus du sql. Inutile pour link qui n'a pas de fichier.
|
||||
// À faire avant la suppression SQL du questionnaire qui entraîne des suppressions en cascade dans la base de données.
|
||||
for(let i in questionnaire.Questions)
|
||||
await questionCtrl.deleteQuestionById(questionnaire.Questions[i].id);
|
||||
for(let i in questionnaire.Illustrations)
|
||||
await illustrationCtrl.deleteIllustrationById(questionnaire.Illustrations[i].id);
|
||||
const nb=await db["Questionnaire"].destroy( { where: { id : req.params.id }, limit:1 });
|
||||
if(nb===1)// = json existant, bien que sql déjà supprimé
|
||||
{
|
||||
toolFile.deleteJSON(configQuestionnaires.dirCacheQuestionnaires, req.params.id);
|
||||
// + HTML :
|
||||
toolFile.deleteFile(configQuestionnaires.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug+".html");
|
||||
// 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();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.deleteQuestionnaire = deleteQuestionnaire;
|
||||
|
||||
const checkQuestionnaireIsPublishable = (datas, checkDate=true) =>
|
||||
{
|
||||
if(checkDate)
|
||||
@ -502,12 +509,12 @@ const creaQuestionnaireHTML = async (id, preview=false) =>
|
||||
pageTitle: questionnaire.Questionnaire.title+" ("+txtQuestionnaire.questionnairesName+")",
|
||||
contentTitle: questionnaire.Questionnaire.title,
|
||||
questionnaire: questionnaire,
|
||||
linkCanonical: config.siteUrl+"/"+config.dirWebQuestionnaire+"/"+questionnaire.Questionnaire.slug+".html"
|
||||
linkCanonical: config.siteUrl+"/"+config.dirWebQuestionnaires+"/"+questionnaire.Questionnaire.slug+".html"
|
||||
}
|
||||
const html=await compiledFunction(pageDatas);
|
||||
if(preview===false)
|
||||
{
|
||||
await toolFile.createHTML(config.dirHTMLQuestionnaire, questionnaire.Questionnaire.slug, html);
|
||||
await toolFile.createHTML(config.dirHTMLQuestionnaires, questionnaire.Questionnaire.slug, html);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -575,7 +582,7 @@ const checkQuestionnairesPublishedHaveHTML = async (regenerate=false) =>
|
||||
await creaQuestionnaireHTML(questionnaires[i].id);
|
||||
nb++;
|
||||
}
|
||||
else if(await toolFile.checkIfFileExist(config.dirHTMLQuestionnaire, questionnaires[i].slug+".html")===false)
|
||||
else if(await toolFile.checkIfFileExist(config.dirHTMLQuestionnaires, questionnaires[i].slug+".html")===false)
|
||||
{
|
||||
await creaQuestionnaireHTML(questionnaires[i].id);
|
||||
nb++;
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1555
front/public/JS/group.app.js
Normal file
1555
front/public/JS/group.app.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
80
front/public/gestion-groups.html
Normal file
80
front/public/gestion-groups.html
Normal file
@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex">
|
||||
<title>Gestion des groupes de quizs</title>
|
||||
<!-- Version lisible des scripts : https://gitlab.com/lefablab/wikilerni/-/tree/master/front/src -->
|
||||
<script src="/JS/polyfill.app.js" defer></script>
|
||||
<script src="/JS/manageGroups.app.js" defer></script>
|
||||
<link rel="shortcut icon" href="/img/favicon.ico">
|
||||
<link rel="stylesheet" href="/themes/wikilerni/css/style.css">
|
||||
</head>
|
||||
|
||||
<body class="cardboard">
|
||||
<!-- En tête -->
|
||||
<header class="cardboard">
|
||||
<a href="/" title="Page d'accueil WikLerni"><img src="/themes/wikilerni/img/wikilerni-purple-2-128.png" alt="WikiLerni (logo)" title="Accéder à la page d'accueil de WikiLerni" /></a>
|
||||
<ul id="headLinks">
|
||||
<li><a href="/contact.html" rel="nofollow">Contact</a></li>
|
||||
<li><a href="/quizs/" id="indexHeadLink" title="Les derniers quizs">Parcourir</a></li>
|
||||
<li><a href="/connexion.html" id="accountHeadLink">Mon compte</a></li>
|
||||
<li><a href="/a-propos.html">À propos</a></li>
|
||||
<li><a href="/" title="Page d'accueil de WikiLerni">Accueil</a></li>
|
||||
</ul>
|
||||
</header>
|
||||
|
||||
<div id="crash"></div>
|
||||
|
||||
<section id="main-content" class="needJS">
|
||||
|
||||
<ul id="menu" class="cardboard">
|
||||
<li><a href="/gestion.html">Gestion WikiLerni</a></li>
|
||||
<li><a href="/gestion-quizs.html" title="Publication des quizs">Les quizs</a></li>
|
||||
<li><a href="/gestion-utilisateurs.html" title="Les comptes utilisateurs">Les abonné(e)s</a></li>
|
||||
<li><a href="/sortie.html">Me déconnecter</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="account" class="cardboard">
|
||||
|
||||
<h1 class="cardboard" id="infos">Les groupes de quizs</h1>
|
||||
<h2>Chercher un groupe</h2>
|
||||
<form id="search" method="POST">
|
||||
<input id="searchGroups" type="text" name="searchGroups" placeholder="Votre recherche" class="cardboard" />
|
||||
<div class="input_wrapper"><input type="submit" value="Chercher" class="cardboard" /></div>
|
||||
<div id="searchResult"></div>
|
||||
</form>
|
||||
|
||||
<h2 id="infos">Informations du groupe</h2>
|
||||
<div id="groupIntro"></div>
|
||||
<div id="message"></div>
|
||||
<form id="groups" method="POST">
|
||||
<fieldset><label for="title">Titre</label><input id="title" type="text" name="title" class="cardboard"></fieldset>
|
||||
<fieldset><label for="slug">Slug</label><input id="slug" type="text" name="slug" class="cardboard"></fieldset>
|
||||
<fieldset><label for="introduction">Introduction</label><textarea id="introduction" name="introduction" rows="10" class="cardboard"></textarea></fieldset>
|
||||
<fieldset><label for="publishingAt">Date de publication</label><input id="publishingAt" type="date" name="publishingAt" class="cardboard"></fieldset>
|
||||
<ul class="checkbox_li">
|
||||
<li class="checkbox_li">
|
||||
<label for="deleteOk" class="check" id="deleteOkLabel"><input type="checkbox" id="deleteOk" name="deleteOk" value="true" /><div class="checkbox_override"></div> <span class="error">Je souhaite supprimer ce groupe.</span></label>
|
||||
</li>
|
||||
</ul>
|
||||
<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 id="response"></div>
|
||||
</form>
|
||||
<div id="response"></div>
|
||||
</section>
|
||||
|
||||
<footer class="cardboard">
|
||||
<ul id="footLinks">
|
||||
<li><a href="https://framasphere.org/people/7e54b7a0b53201389eef2a0000053625" title="Blog WikiLerni sur diaspora*">Blog</a></li>
|
||||
<li><a href="/credits.html">Crédits</a></li>
|
||||
<li><a href="/mentions-legales.html" rel="nofollow">Mentions légales</a></li>
|
||||
<li><a href="/donnees.html">Données personnelles</a></li>
|
||||
<li><a href="/CGV-CGU.html" rel="nofollow">CGV & CGU</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
@ -45,11 +45,11 @@
|
||||
<div class="input_wrapper"><input type="submit" value="Chercher" class="cardboard" /></div>
|
||||
<div id="searchResult"></div>
|
||||
</form>
|
||||
|
||||
<div id="message"></div>
|
||||
|
||||
<form id="questionnaires" method="POST">
|
||||
<h2>Informations du quiz</h2>
|
||||
<div class="input_wrapper"><a class="button cardboard" href="/gestion-groups.html">Gérer les groupes de quizs.</a></div>
|
||||
<fieldset><label for="title">Titre</label><input id="title" type="text" name="title" class="cardboard"></fieldset>
|
||||
<fieldset><label for="slug">Slug</label><input id="slug" type="text" name="slug" class="cardboard"></fieldset>
|
||||
<fieldset><label for="introduction">Introduction</label><textarea id="introduction" name="introduction" rows="10" class="cardboard"></textarea></fieldset>
|
||||
|
@ -46,19 +46,16 @@
|
||||
<div id="emailMessage"></div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<label for="password">Mot de passe : </label><input id="password" type="password" name="password" placeholder="Mot de passe de votre choix" class="cardboard">
|
||||
<div id="passwordMessage"><span class="info">Au moins 8 caractères. <a href="#password" id="getPassword">Générer un mot de passe</a>.</span></div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<label for="codeGodfather">Code ou e-mail parrain : </label><input id="codeGodfather" type="text" name="codeGodfather" placeholder="Code ou email de votre parrain." class="cardboard">
|
||||
<div id="codeGodfatherMessage"><span class="info">Facultatif.</span></div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<ul class="checkbox_li">
|
||||
<li class="checkbox_li">
|
||||
<label for="cguOk" class="check"><input type="checkbox" id="cguOk" name="cguOk" value="true" /><div class="checkbox_override"></div> J'accepte <a href="/CGV-CGU.html" target="_blank" rel="noopener" title="À lire :)">les Conditions Générale d'Utilisation</a> du site (requis).</label>
|
||||
|
@ -417,7 +417,7 @@ margin:3em 0;
|
||||
}
|
||||
#questionsList p a
|
||||
{
|
||||
font-size:1.5em;
|
||||
font-size:1.5em;
|
||||
}
|
||||
|
||||
|
||||
@ -597,9 +597,10 @@ font-size:1em;
|
||||
{
|
||||
padding:0.2em 1em;
|
||||
}
|
||||
#searchQuestionnaires
|
||||
#searchQuestionnaires, #searchUsers, #searchGroups
|
||||
{
|
||||
width:65%;
|
||||
margin:auto;
|
||||
}
|
||||
#manageQuestionnaires #questionnairesList li
|
||||
{
|
||||
@ -1399,7 +1400,7 @@ font-size:0.9em;
|
||||
}
|
||||
#signup h2, #login h2, #account h2, #manageQuestionnaires h2
|
||||
{
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
#signup p, #login p, #account p, #manageQuestionnaires p
|
||||
{
|
||||
|
@ -119,7 +119,10 @@ myForm.addEventListener("submit", function(e)
|
||||
}
|
||||
else if (response.errors)
|
||||
{
|
||||
response.errors = response.errors.join("<br>");
|
||||
if(Array.isArray(response.errors))
|
||||
response.errors = response.errors.join("<br>");
|
||||
else
|
||||
response.errors = serverError;
|
||||
addElement(divResponse, "p", response.errors, "", ["error"]);
|
||||
}
|
||||
else
|
||||
|
181
front/src/group.js
Normal file
181
front/src/group.js
Normal file
@ -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);
|
||||
}
|
||||
});
|
235
front/src/manageGroups.js
Normal file
235
front/src/manageGroups.js
Normal file
@ -0,0 +1,235 @@
|
||||
// -- GESTION DU FORMULAIRE PERMETTANT DE SAISIR / ÉDITER LES INFOS DES GROUPES DE QUIZS
|
||||
|
||||
/// Vérifie que l'utilisateur est bien connecté, a le bon statut et le redirige vers le formulaire d'inscription si ce n'est pas le cas.
|
||||
/// Si c'est ok, propose un moteur de recherche permettant de chercher un groupe.
|
||||
/// Si un id est passé par l'url on affiche les informations du groupe dans un formulaire permettant de l'éditer/supprimer.
|
||||
/// Si pas d'id passé par l'url, on affiche un formulaire vide permettant d'en saisir un nouveau.
|
||||
|
||||
// Fichier de configuration côté client :
|
||||
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
|
||||
const lang=availableLangs[0];
|
||||
const configQuestionnaires = require("../../config/questionnaires.js");
|
||||
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");
|
||||
|
||||
// Fonctions utiles au script :
|
||||
import { getLocaly, removeLocaly } from "./tools/clientstorage.js";
|
||||
import { addElement } from "./tools/dom.js";
|
||||
import { helloDev, updateAccountLink } from "./tools/everywhere.js";
|
||||
import { empyForm, getDatasFromInputs, setAttributesToInputs } from "./tools/forms.js";
|
||||
import { dateFormat, isEmpty, replaceAll } from "../../tools/main";
|
||||
import { getUrlParams } from "./tools/url.js";
|
||||
import { checkSession } from "./tools/users.js";
|
||||
|
||||
// Dictionnaires :
|
||||
const { addOkMessage, serverError } = require("../../lang/"+lang+"/general");
|
||||
const { infosGroupForAdmin, searchWithoutResult } = require("../../lang/"+lang+"/group");
|
||||
const { needBeConnected } = require("../../lang/"+lang+"/user");
|
||||
|
||||
// Principaux éléments du DOM manipulés :
|
||||
const divMain = document.getElementById("main-content");
|
||||
const divMessage = document.getElementById("message");
|
||||
const divResponse = document.getElementById("response");
|
||||
const divCrash = document.getElementById("crash");
|
||||
const divGroupIntro = document.getElementById("groupIntro");
|
||||
const formGroup = document.getElementById("groups");
|
||||
const deleteCheckBox = document.getElementById("deleteOkLabel");
|
||||
const btnNewGroup = document.getElementById("wantNewGroup");
|
||||
const formSearch = document.getElementById("search");
|
||||
const divSearchResult = document.getElementById("searchResult");
|
||||
|
||||
helloDev();
|
||||
|
||||
const initialise = async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const isConnected=await checkSession(["manager", "admin"], "/"+configTemplate.connectionPage, { message: needBeConnected, color:"error" }, window.location);
|
||||
if(isConnected)
|
||||
{
|
||||
const user=getLocaly("user", true);
|
||||
updateAccountLink(user.status, configTemplate);
|
||||
divMain.style.display="block";
|
||||
if(!isEmpty(getLocaly("message")))
|
||||
{
|
||||
addElement(divMessage, "p", getLocaly("message", true).message, "", [getLocaly("message", true).color], "", false);
|
||||
removeLocaly("message");
|
||||
}
|
||||
// Initialisation du formulaire de recherche :
|
||||
setAttributesToInputs(configQuestionnaires, formSearch);
|
||||
|
||||
// Fonction utile pour vider le formulaire, y compris les champs hidden, etc.
|
||||
// Cache aussi certains champs en mode création
|
||||
const emptyGroupForm = () =>
|
||||
{
|
||||
empyForm(formGroup);
|
||||
// Case de suppression cachée par défaut, car inutile pour formulaire de création
|
||||
deleteCheckBox.style.display="none";
|
||||
}
|
||||
emptyGroupForm();
|
||||
// Initialise les contraintes du formulaire :
|
||||
setAttributesToInputs(configQuestionnaires.Group, formGroup);
|
||||
|
||||
// Fonction affichant les infos connues concernant un utilisateur et son abonnement
|
||||
const showFormGroupInfos = (id) =>
|
||||
{
|
||||
// on commence par tout vider, des fois que... :
|
||||
emptyGroupForm();
|
||||
const xhrGetInfos = new XMLHttpRequest();
|
||||
xhrGetInfos.open("GET", apiUrl+configQuestionnaires.groupRoutes+configQuestionnaires.getGroupRoute+id);
|
||||
xhrGetInfos.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 200 && response.Group != undefined)
|
||||
{
|
||||
const mapText =
|
||||
{
|
||||
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
|
||||
};
|
||||
const groupIntro=replaceAll(infosGroupForAdmin, mapText);
|
||||
addElement(divGroupIntro, "p", groupIntro, "", ["info"]);
|
||||
for(let data in response.Group)
|
||||
{
|
||||
if(formGroup.elements[data]!==undefined)
|
||||
{
|
||||
if(data==="publishingAt" && response.Group[data]!==null)
|
||||
formGroup.elements[data].value=dateFormat(response.Group[data], "form");// !! revoir car format pouvant poser soucis si navigateur ne gère pas les champs de type "date"
|
||||
else
|
||||
formGroup.elements[data].value=response.Group[data];
|
||||
}
|
||||
}
|
||||
deleteCheckBox.style.display="block";
|
||||
}
|
||||
}
|
||||
}
|
||||
xhrGetInfos.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
xhrGetInfos.send();
|
||||
}
|
||||
|
||||
// Si un id est passé par l'url, on essaye d'afficher les infos :
|
||||
let urlDatas=getUrlParams();
|
||||
if(urlDatas && urlDatas.id!==undefined)
|
||||
showFormGroupInfos(urlDatas.id);
|
||||
|
||||
// Besoin d'un coup de Kärcher ?
|
||||
btnNewGroup.addEventListener("click", function(e)
|
||||
{
|
||||
emptyGroupForm();
|
||||
});
|
||||
|
||||
// Envoi du formulaire des infos du groupe
|
||||
formGroup.addEventListener("submit", function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
divResponse.innerHTML="";
|
||||
let datas=getDatasFromInputs(formGroup);
|
||||
const xhrGroupDatas = new XMLHttpRequest();
|
||||
if(!isEmpty(datas.id) && (datas.deleteOk!==undefined))
|
||||
xhrGroupDatas.open("DELETE", apiUrl+configQuestionnaires.groupRoutes+"/"+datas.id);
|
||||
else if(!isEmpty(datas.id))
|
||||
xhrGroupDatas.open("PUT", apiUrl+configQuestionnaires.groupRoutes+"/"+datas.id);
|
||||
else
|
||||
xhrGroupDatas.open("POST", apiUrl+configQuestionnaires.groupRoutes);
|
||||
xhrGroupDatas.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 201 && response.id!=undefined)
|
||||
{
|
||||
addElement(divResponse, "p", addOkMessage, "", ["success"]);
|
||||
datas.id=response.id;
|
||||
}
|
||||
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"]);
|
||||
}
|
||||
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(isEmpty(response.errors))
|
||||
{
|
||||
if(datas.deleteOk===undefined)
|
||||
showFormGroupInfos(datas.id);
|
||||
else
|
||||
emptyGroupForm();
|
||||
}
|
||||
}
|
||||
}
|
||||
xhrGroupDatas.setRequestHeader("Content-Type", "application/json");
|
||||
xhrGroupDatas.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
if(datas)
|
||||
xhrGroupDatas.send(JSON.stringify(datas));
|
||||
});
|
||||
|
||||
// Traitement du lancement d'une recherche
|
||||
formSearch.addEventListener("submit", function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
let datas=getDatasFromInputs(formSearch);
|
||||
const xhrSearch = new XMLHttpRequest();
|
||||
xhrSearch.open("POST", apiUrl+configQuestionnaires.groupRoutes+configQuestionnaires.searchGroupsRoute);
|
||||
xhrSearch.onreadystatechange = function()
|
||||
{
|
||||
if (this.readyState == XMLHttpRequest.DONE)
|
||||
{
|
||||
let response=JSON.parse(this.responseText);
|
||||
if (this.status === 200 && Array.isArray(response))
|
||||
{
|
||||
if(response.length===0)
|
||||
addElement(divSearchResult, "p", searchWithoutResult, "", ["info"]);// vérifier et importer texte
|
||||
else
|
||||
{
|
||||
let selectHTML="<option value=''></option>";
|
||||
for(let i in response)
|
||||
selectHTML+="<option value='"+response[i].id+"'>"+response[i].title+"</option>";
|
||||
addElement(divSearchResult, "select", selectHTML, "selectSearch");
|
||||
const searchSelect=document.getElementById("selectSearch");
|
||||
searchSelect.addEventListener("change", function()
|
||||
{
|
||||
if(searchSelect.value!=="")
|
||||
showFormGroupInfos(searchSelect.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (response.errors)
|
||||
{
|
||||
if(Array.isArray(response.errors))
|
||||
response.errors = response.errors.join("<br>");
|
||||
else
|
||||
response.errors = serverError;
|
||||
addElement(divSearchResult, "p", response.errors, "", ["error"]);
|
||||
}
|
||||
else
|
||||
addElement(divSearchResult, "p", serverError, "", ["error"]);
|
||||
}
|
||||
}
|
||||
xhrSearch.setRequestHeader("Content-Type", "application/json");
|
||||
xhrSearch.setRequestHeader("Authorization", "Bearer "+user.token);
|
||||
if(datas)
|
||||
xhrSearch.send(JSON.stringify(datas));
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
addElement(divCrash, "p", serverError, "", ["error"]);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
initialise();
|
@ -10,10 +10,12 @@ module.exports =
|
||||
connection: "./src/connection.js",
|
||||
deconnection: "./src/deconnection.js",
|
||||
deleteValidation: "./src/deleteValidation.js",
|
||||
group: "./src/group.js",
|
||||
homeManager: "./src/homeManager.js",
|
||||
homeUser: "./src/homeUser.js",
|
||||
index: "./src/index.js",
|
||||
loginLink: "./src/loginLink.js",
|
||||
manageGroups: "./src/manageGroups.js",
|
||||
manageQuestionnaires: "./src/manageQuestionnaires.js",
|
||||
manageUsers: "./src/manageUsers.js",
|
||||
newLoginValidation: "./src/newLoginValidation.js",
|
||||
|
@ -5,7 +5,10 @@ module.exports =
|
||||
alertNewWindow: "nouvelle fenêtre",
|
||||
badUrl : "Tentative d'accès à une page n'existant pas :",
|
||||
btnLinkToQuestionnaire : "Aller au quiz !",
|
||||
btnProposeConnection: "Je me connecte.",
|
||||
btnProposeSubscribe: "Je crée mon compte.",
|
||||
deleteBtnTxt: "Supprimer",
|
||||
deleteFailMessage : "La suppression de l'enregistrement #ID a échoué.",
|
||||
deleteOkMessage : "La suppression a bien été enregistrée.",
|
||||
failAuth : "Erreur d'authentification.",
|
||||
failAuthCron : "Tentative de lancement d'un cron sans le bon token.",
|
||||
@ -25,6 +28,7 @@ module.exports =
|
||||
scriptTimingAlert : "*** Script lent : SCRIPT_TIMING millisecondes, route : SCRIPT_URL",
|
||||
scriptTimingInfo : "Durée de la réponse : SCRIPT_TIMING millisecondes, route : SCRIPT_URL",
|
||||
statsAdmin : "Durant les dernières 24h : NB_USERS_24H comptes ont été créés, NB_SUBSCRIPTIONS_24H validés et NB_USERS_DELETED_24H supprimés. NB_ANSWERS_24H réponses aux quizs ont été enregistrées.<br>En tout, il y a : NB_USERS_TOT comptes, dont NB_SUBSCRIPTIONS_TOT validés et NB_SUBSCRIPTIONS_PREMIUM comptes prémium. NB_ANSWERS_TOT réponses aux quizs ont été enregistrées.<br>Parmi les NB_USERS_DELETED_TOT comptes supprimés, NB_USERS_DELETED_VALIDED avaient validé leur compte et NB_USERS_DELETED_PREMIUM avaient souscrit un compte prémium.",
|
||||
subscriptionCall: "Inscrivez-vous !",
|
||||
updateBtnTxt: "Modifier",
|
||||
updateOkMessage : "La mise à jour à jour a bien été enregistrée."
|
||||
};
|
@ -1,14 +1,24 @@
|
||||
module.exports =
|
||||
{
|
||||
btnSendResponse: "Testez vos réponses.",
|
||||
btnShareQuizTxt: "Partager ce quiz sur ",
|
||||
commonIntroTxt: "Ce quiz vous permet de tester ce que vous avez retenu des textes qui vous ont été proposés à la lecture. Au besoin, cliquez sur le bouton précédent pour les relire.",
|
||||
correctAnswerTxt: "Bonne réponse",
|
||||
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 ",
|
||||
needCorrectPublishingDate: "La date de publication fournie n'a pas un format valide.",
|
||||
needLanguage: "Vous devez sélectionner la langue de ce quiz.",
|
||||
needNotTooLongTitle: "Le titre du quiz ne doit pas compter plus de 255 caractères.",
|
||||
needTitle: "Merci de fournir un titre à votre quiz.",
|
||||
needUniqueUrl: "L'url du quiz doit être unique.",
|
||||
needUrl: "Merci de fournir l'url à votre quiz.",
|
||||
/*
|
||||
questionnairesName: "quiz",
|
||||
questionnaireNeedBeCompleted: "Quiz incomplet",
|
||||
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.",
|
||||
needTitle: "Merci de fournir un titre à ce groupe de quizs.",
|
||||
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",
|
||||
publishedBy: "Quiz publié par"*/
|
||||
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.",
|
||||
wrongAnswerTxt: "Mauvaise réponse"
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
module.exports =
|
||||
{
|
||||
btnProposeConnection: "Je me connecte.",
|
||||
btnProposeSubscribe: "Je crée mon compte.",
|
||||
btnProposeConnection: "Je me connecte.",// déplacé dans general.js
|
||||
btnProposeSubscribe: "Je crée mon compte.",//idem
|
||||
btnSendResponse: "Testez vos réponses.",
|
||||
btnShareQuizTxt: "Partager ce quiz sur ",
|
||||
btnShowQuestionnaire: "Afficher le quiz !",
|
||||
|
@ -11,6 +11,10 @@ module.exports =
|
||||
deleteInactiveUsersMessage: " comptes utilisateurs inactifs ont été supprimés.",
|
||||
deleteOkMessage: "L'utilisateur a bien été supprimé.",
|
||||
emailNotFound: "Aucun compte utilisateur n'a été trouvé pour cette adresse e-mail.",
|
||||
formsEmailLabel: "E-mail :",
|
||||
formsEmailPlaceholder: "Votre adresse e-mail",
|
||||
formsCGUOkLabel: "J'accepte <a href=#link target=\"_blank\" rel=\"noopener\" title=\"À lire :)\">les Conditions Générale d'Utilisation</a> du site (requis).",
|
||||
formsSubmitTxt: "Je m'abonne !",
|
||||
godfatherFound: "Votre \"parrain\" a bien été trouvé :)",
|
||||
godfatherNotFound: "Désolé mais aucun utilisateur n'a été trouvé pour ce code/e-mail parrain :(",
|
||||
infosAdminGodfather: "Cet utilisateur a été parrainé par ",
|
||||
|
@ -12,7 +12,7 @@ const txtGeneral = require("../lang/"+config.adminLang+"/general");
|
||||
|
||||
module.exports = (sequelize, DataTypes) =>
|
||||
{
|
||||
const Group = sequelize.define("Groupe",
|
||||
const Group = sequelize.define("Group",
|
||||
{
|
||||
title:
|
||||
{
|
||||
@ -85,10 +85,10 @@ module.exports = (sequelize, DataTypes) =>
|
||||
collate: "utf8mb4_unicode_ci"
|
||||
}
|
||||
);
|
||||
Questionnaire.associate = function(models)
|
||||
Group.associate = function(models)
|
||||
{
|
||||
Questionnaire.hasMany(models.Questionnaire);
|
||||
Questionnaire.belongsTo(models.User, { as: "Creator", foreignKey: { name: "CreatorId", allowNull: false } });
|
||||
Group.hasMany(models.Questionnaire);
|
||||
Group.belongsTo(models.User, { as: "Creator", foreignKey: { name: "CreatorId", allowNull: false } });
|
||||
};
|
||||
return Questionnaire;
|
||||
return Group;
|
||||
};
|
@ -6,6 +6,7 @@ const cronAuth = require("../middleware/cronAuth");
|
||||
const ctrlUser = require("../controllers/user");
|
||||
const ctrlPause = require("../controllers/pause");
|
||||
const ctrlQuestionnaire = require("../controllers/questionnaire");
|
||||
const ctrlGroup = require("../controllers/group");
|
||||
const ctrlQuestion = require("../controllers/question");
|
||||
const ctrlIllustration = require("../controllers/illustration");
|
||||
const ctrlSubscription = require("../controllers/subscription");
|
||||
@ -28,6 +29,9 @@ router.get("/addquestionnairetouser/:token", cronAuth, ctrlSubscription.addNewQu
|
||||
router.get("/deletequestionnairesfiles/:token", cronAuth, ctrlQuestionnaire.deleteJsonFiles);
|
||||
router.get("/deletequestionsfiles/:token", cronAuth, ctrlQuestion.deleteJsonFiles);
|
||||
router.get("/publishquestionnaires/:token", cronAuth, ctrlQuestionnaire.checkQuestionnairesNeedToBePublished);
|
||||
// + Groupes
|
||||
router.get("/deletegroupsfiles/:token", cronAuth, ctrlGroup.deleteJsonFiles);
|
||||
router.get("/publishgroups/:token", cronAuth, ctrlGroup.checkGroupsNeedToBePublished);
|
||||
|
||||
// Illustrations des questionnaires
|
||||
router.get("/deleteoldillustrations/:token", cronAuth, ctrlIllustration.deleteOldFiles);
|
||||
|
15
routes/group.js
Normal file
15
routes/group.js
Normal file
@ -0,0 +1,15 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
const auth = require("../middleware/authAdmin");
|
||||
|
||||
const groupCtrl = require("../controllers/group");
|
||||
|
||||
router.post("/search", auth, groupCtrl.searchGroups);
|
||||
router.post("/", auth, groupCtrl.create);
|
||||
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);
|
||||
|
||||
module.exports = router;
|
@ -1,5 +1,6 @@
|
||||
module.exports =
|
||||
{
|
||||
// liens de l'interface
|
||||
headLinks:
|
||||
[
|
||||
{ anchor: "Contact", attributes: { href:"/contact.html", rel: "nofollow" } },
|
||||
@ -16,27 +17,28 @@ module.exports =
|
||||
{ anchor: "Données personnelles", attributes: { href:"/donnees.html", title:"Vos données personnelles sur WikiLerni" } },
|
||||
{ anchor: "CGV & CGU", attributes: { href:"/CGV-CGU.html", rel: "nofollow" } }
|
||||
],
|
||||
maxQuestionnairesSiteHomePage: 3,
|
||||
maxQuestionnairesByPage: 12,
|
||||
nbQuestionnairesUserHomePage : 3,
|
||||
accountPage: "compte.html",
|
||||
aboutPage: "a-propos.html",
|
||||
adminHomePage: "admin.html",
|
||||
cguPage: "CGV-CGU.html",
|
||||
connectionPage : "connexion.html",
|
||||
deleteLinkPage : "aurevoir.html?t=",
|
||||
loginLinkPage : "login.html?t=",
|
||||
managerHomePage : "gestion.html",
|
||||
newLoginLinkPage : "newlogin.html?t=",
|
||||
questionnairesManagementPage: "gestion-quizs.html",
|
||||
stopMailPage : "stop-mail.html?t=",
|
||||
subscribePage : "inscription.html",
|
||||
updateAccountPage: "compte.html",
|
||||
userHomePage : "accueil.html",
|
||||
userHomePageTxt : "Ma page d'accueil.",
|
||||
adminHomePage : "admin.html",
|
||||
managerHomePage : "gestion.html",
|
||||
subscribePage : "inscription.html",
|
||||
connectionPage : "connexion.html",
|
||||
validationLinkPage : "validation.html?t=",
|
||||
loginLinkPage : "login.html?t=",
|
||||
newLoginLinkPage : "newlogin.html?t=",
|
||||
deleteLinkPage : "aurevoir.html?t=",
|
||||
stopMailPage : "stop-mail.html?t=",
|
||||
accountPage: "compte.html",
|
||||
updateAccountPage: "compte.html",
|
||||
questionnairesManagementPage: "gestion-quizs.html",
|
||||
usersManagementPage: "gestion-utilisateurs.html",
|
||||
aboutPage: "a-propos.html",
|
||||
illustrationDir : "/img/quizs/",
|
||||
validationLinkPage : "validation.html?t=",
|
||||
/* Textes (général) */
|
||||
siteSlogan: "Cultivons notre jardin !",
|
||||
noJSNotification: "Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript.",
|
||||
mailRecipientTxt: "Message envoyé à :",
|
||||
/* Page d'accueil */
|
||||
homePageTxt: "Page d'accueil",
|
||||
homeTitle1: "De nature curieuse ?",
|
||||
homeP1: "Avec WikiLerni, vous apprenez chaque jour de nouvelles choses.<br>Des articles de Wikipédia sont sélectionnés pour vous et sont suivis d'un quiz vous permettant de tester ce que vous en avez retenu.<br>De jour en jour de nouvelles graines de savoir sont ainsi semées dans votre \"jardin\".",
|
||||
@ -44,13 +46,20 @@ module.exports =
|
||||
homeP2: "Tout comme sur Wikipédia <span class='postscriptum'>(*)</span>, le logiciel et le contenu partagé sur WikiLerni <a href=\"/credits.html\" title=\"En savoir plus\">sont libres</a>.<br>Sur WikiLerni, pas de publicité, ni de commercialisation de vos données.<br>Vous pouvez venir y \"cultiver votre jardin\" en toute tranquillité.",
|
||||
homeBtnAboutTxt: "En savoir plus sur WikiLerni ?",
|
||||
homeBtnSubscribeTxt: "Tester WikiLerni gratuitement.",
|
||||
/* Page dernières publications... */
|
||||
newQuestionnairesTitle: "Culture générale - apprenez de nouvelles choses avec WikiLerni",
|
||||
newQuestionnairesIntro: "WikiLerni : testez vos connaissances et apprenez de nouvelles choses avec les quizs WikiLerni.",
|
||||
newsListTitle: "<h3>1 article Wikipédia + 1 quiz = 1 WikiLerni</h3><p>WikiLerni, ce sont plusieurs quizs publiés chaque semaine, chacun associé à un article Wikipédia.<br>Sans publicité, ni commerce de vos données, <b>vous apprenez de nouvelles choses en toute liberté</b>.</p><blockquote>Aristote : « L’homme a naturellement la passion de connaître… »</blockquote>",
|
||||
/* Page quizs */
|
||||
quizElementLinksIntro: "Source(s)",
|
||||
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>.",
|
||||
noJSNotification: "Désolé, mais pour l'instant, l'utilisation de WikiLerni nécessite l'activation du JavaScript.",
|
||||
newsListTitle: "<h3>1 article Wikipédia + 1 quiz = 1 WikiLerni</h3><p>WikiLerni, ce sont plusieurs quizs publiés chaque semaine, chacun associé à un article Wikipédia.<br>Sans publicité, ni commerce de vos données, <b>vous apprenez de nouvelles choses en toute liberté</b>.</p><blockquote>Aristote : « L’homme a naturellement la passion de connaître… »</blockquote>",
|
||||
mailRecipientTxt: "Message envoyé à :",
|
||||
twitterAccount: "@WikiLerni",
|
||||
/* Autres */
|
||||
illustrationDir : "/img/quizs/",
|
||||
twitterAccount: "WikiLerni",
|
||||
maxQuestionnairesByPage: 12,
|
||||
maxQuestionnairesSiteHomePage: 3,
|
||||
nbQuestionnairesUserHomePage : 3,
|
||||
};
|
99
views/wikilerni/quiz-element.pug
Normal file
99
views/wikilerni/quiz-element.pug
Normal file
@ -0,0 +1,99 @@
|
||||
extends layout.pug
|
||||
|
||||
block append scripts
|
||||
script(src="/JS/polyfill.app.js" defer)
|
||||
script(src="/JS/groupElement.app.js" defer)
|
||||
|
||||
block content
|
||||
|
||||
div(id="tags" class="cardboard")
|
||||
ul
|
||||
li
|
||||
a(href="/") #{config.siteName}
|
||||
for tag in questionnaire.Tags
|
||||
li
|
||||
a(href="/quizs/"+tag.slug+".html") #{tag.name}
|
||||
|
||||
-
|
||||
const imgAttributes = { alt: txtIllustration.defaultAlt, style: "opacity: 0.0;" };
|
||||
if(questionnaire.Illustrations!=undefined && questionnaire.Illustrations.length!==0)
|
||||
{
|
||||
if (tool.isEmpty(questionnaire.Illustrations[0].alt)===false)
|
||||
imgAttributes.alt=questionnaire.Illustrations[0].alt;
|
||||
if(tool.isEmpty(questionnaire.Illustrations[0].title)===false)
|
||||
imgAttributes.title=questionnaire.Illustrations[0].title;
|
||||
}
|
||||
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)
|
||||
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)
|
||||
//- Important : ici, on garde volontairement le html saisi car lien possible vers auteur de l'illustration :
|
||||
if(questionnaire.Illustrations[0].caption)
|
||||
p !{questionnaire.Illustrations[0].caption}
|
||||
|
||||
div(id="content-side")
|
||||
div(id="content-title")
|
||||
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)
|
||||
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}
|
||||
|
||||
// - 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}
|
||||
|
||||
div#licence
|
||||
p !{configTpl.questionnaireLicenceTxt}
|
||||
|
||||
noscript
|
||||
div
|
||||
strong #{configTpl.noJSNotification}
|
||||
-
|
||||
const cguOkLabel = txtUsers.formsEmailLabel.replace("#link", "/"+configTpl.cguPage);
|
||||
div#signup
|
||||
form(id="subscription" method="POST" class="needJS")
|
||||
h3 #{configTpl.quizElementSubcriptionFormTitle}
|
||||
fieldset
|
||||
label(for="email") #{txtUsers.formsEmailLabel}
|
||||
input(id="email" type="email" name="email" placeholder=txtUsers.formsEmailPlaceholder class="cardboard")
|
||||
div#emailMessage
|
||||
ul(class="checkbox_li")
|
||||
li(class="checkbox_li")
|
||||
label(for="cguOk" class="check")
|
||||
input(type="checkbox" id="cguOk" name="cguOk" value="true")
|
||||
div(class="checkbox_override")
|
||||
span #{cguOkLabel}
|
||||
div(class="input_wrapper")
|
||||
input(id="submitDatas" type="submit" value=txtUsers.formsSubmitTxt class="cardboard")
|
||||
div(id="response")
|
||||
|
||||
div#zerozozio
|
||||
a(href="http://sharetodiaspora.github.io/?url="+linkCanonical+"&title="+questionnaire.Questionnaire.title rel="nofollow noopener" title=txtQuestionnaire.btnShareQuizTxt+" diaspora* ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
img(src="/themes/wikilerni/img/diaspora.png" alt=txtQuestionnaire.btnShareQuizTxt+" diaspora*")
|
||||
a(href="https://www.facebook.com/sharer.php?u="+linkCanonical rel="nofollow noopener" title=txtQuestionnaire.btnShareQuizTxt+" facebook ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
img(src="/themes/wikilerni/img/facebook.png" alt=txtQuestionnaire.btnShareQuizTxt+" facebook")
|
||||
a(href="https://twitter.com/intent/tweet?url="+linkCanonical+"&text="+questionnaire.Questionnaire.title+" via @"+configTpl.twitterAccount rel="nofollow noopener" title=txtQuestionnaire.btnShareQuizTxt+" twitter ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
img(src="/themes/wikilerni/img/twitter.png" alt=txtQuestionnaire.btnShareQuizTxt+" twitter")
|
||||
|
||||
div#explanations(class="engraved framed")
|
||||
h3#explanationsTitle #{configTpl.explanationTitle}
|
||||
div#explanationsContent
|
||||
p !{configTpl.explanationTxt}
|
72
views/wikilerni/quiz-group.pug
Normal file
72
views/wikilerni/quiz-group.pug
Normal file
@ -0,0 +1,72 @@
|
||||
extends layout.pug
|
||||
|
||||
block append scripts
|
||||
script(src="/JS/polyfill.app.js" defer)
|
||||
script(src="/JS/group.app.js" defer)
|
||||
|
||||
block content
|
||||
|
||||
div(id="content-side")
|
||||
div(id="content-title")
|
||||
h1(class="cardboard")
|
||||
span #{group.title}
|
||||
div(id="content-title-corner")
|
||||
div(id="content" class="cardboard")
|
||||
p(id="author-date") #{txtGroups.publishedBy} #{author}#{txtGroups.publishedAt} #{publishedAtTxt}. #{txtGroups.lastUpdated}#{updatedAtTxt}.
|
||||
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
|
||||
div#links
|
||||
a(href=="/"+configQuestionnaires.dirdirWebQuestionnaires+group.Questionnaires[0].slug+".html" class="button cardboard" title=txtGroups.linkFirstElementGroup) #{txtGroups.linkFirstElementGroup}
|
||||
div#licence
|
||||
p !{configTpl.groupLicenceTxt}
|
||||
|
||||
noscript
|
||||
div
|
||||
strong #{configTpl.noJSNotification}
|
||||
|
||||
form(id="group" method="POST" class="needJS")
|
||||
h2 #{group.title}
|
||||
div#response
|
||||
div(class="subscribeBtns")
|
||||
p
|
||||
a(class="button cardboard" href=configTpl.subscribePage) #{txtGeneral.btnProposeSubscribe}
|
||||
p
|
||||
a(class="button cardboard" href=configTpl.connectionPage) #{txtGeneral.btnProposeConnection}
|
||||
for questionnaire in group.Questionnaires
|
||||
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}
|
||||
ul(class="checkbox_li")
|
||||
for response in question.Choices
|
||||
li(class="checkbox_li")
|
||||
label(class="check" for="response_"+response.id)
|
||||
input(type="checkbox" name="response_"+response.id id="response_"+response.id)
|
||||
div(class="checkbox_override")
|
||||
span(class="wrongResponse")
|
||||
img(src="/themes/wikilerni/img/wrong-min.png" title=txtGroups.wrongAnswerTxt)
|
||||
span(class="rightResponse")
|
||||
img(src="/themes/wikilerni/img/correct-min.png" title=txtGroups.correctAnswerTxt)
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
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}
|
@ -99,7 +99,7 @@ block content
|
||||
img(src="/themes/wikilerni/img/diaspora.png" alt=txtQuestionnaire.btnShareQuizTxt+" diaspora*")
|
||||
a(href="https://www.facebook.com/sharer.php?u="+linkCanonical rel="nofollow noopener" title=txtQuestionnaire.btnShareQuizTxt+" facebook ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
img(src="/themes/wikilerni/img/facebook.png" alt=txtQuestionnaire.btnShareQuizTxt+" facebook")
|
||||
a(href="https://twitter.com/intent/tweet?url="+linkCanonical+"&text="+questionnaire.Questionnaire.title+" via "+configTpl.twitterAccount rel="nofollow noopener" title=txtQuestionnaire.btnShareQuizTxt+" twitter ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
a(href="https://twitter.com/intent/tweet?url="+linkCanonical+"&text="+questionnaire.Questionnaire.title+" via @"+configTpl.twitterAccount rel="nofollow noopener" title=txtQuestionnaire.btnShareQuizTxt+" twitter ("+txtGeneral.alertNewWindow+")" target="_blank")
|
||||
img(src="/themes/wikilerni/img/twitter.png" alt=txtQuestionnaire.btnShareQuizTxt+" twitter")
|
||||
|
||||
div#explanations(class="engraved framed")
|
||||
|
Loading…
x
Reference in New Issue
Block a user