Revue de la page d'accueil de l'abonné.

This commit is contained in:
Fabrice PENHOËT 2020-10-28 18:06:20 +01:00
parent fc7949fabe
commit 8ce66d7981
10 changed files with 55 additions and 59 deletions

View File

@ -398,7 +398,7 @@ const creaStatsGroupsJson = async () =>
const stats =
{
nbTot : Groups.length,
nbPublished : GroupsPublished.length, // ! en fait, peuvent être passé de date et non publié : À REVOIR QUAND DES EXEMPLES EN BD!
nbPublished : GroupsPublished.length, // ! en fait, peuvent être passé de date et non publié
nbQuestionnaires : QuestionnairesInGroups.length
}
await toolFile.createJSON(configQuestionnaires.dirCacheGroups, "stats", stats);

View File

@ -195,7 +195,7 @@ exports.showOneQuestionnaireById = async (req, res, next) =>
}
}
// Recherche par mots-clés parmis tous les questionnaires publiés en filtrant parmi ceux auxquels l'abonné a déjà répondu ou pas
// Recherche par mots-clés parmis tous les questionnaires publiés associés à un groupe
// Seul un certain nombre de résultats est renvoyé (pagination)
exports.searchQuestionnaires = async (req, res, next) =>
{
@ -207,11 +207,7 @@ exports.searchQuestionnaires = async (req, res, next) =>
else
{
const db = require("../models/index");
let getQuestionnaires;
if(!tool.isEmpty(req.body.onlyAnswers) && !tool.isEmpty(req.connectedUser.User.id))
getQuestionnaires=await db.sequelize.query("SELECT DISTINCT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `Answers` WHERE `Answers`.`QuestionnaireId`=`Questionnaires`.`id` AND `Answers`.`UserId`=:id AND (`title` LIKE :search OR `keywords` LIKE :search) AND `isPublished` = 1", { replacements: { id:req.connectedUser.User.id, search: "%"+search+"%" }, type: QueryTypes.SELECT });
else
getQuestionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE (`title` LIKE :search OR `keywords` LIKE :search) AND `isPublished` = 1", { replacements: { search: "%"+search+"%" }, type: QueryTypes.SELECT });
const getQuestionnaires = await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE (`title` LIKE :search OR `keywords` LIKE :search) AND `isPublished` = 1 AND GroupId IS NOT NULL", { replacements: { search: "%"+search+"%" }, type: QueryTypes.SELECT });
let begin=0, end, output="";
if(!tool.isEmpty(req.body.begin))
begin=parseInt(req.body.begin, 10);
@ -229,19 +225,14 @@ exports.searchQuestionnaires = async (req, res, next) =>
}
}
// Recherche aléatoire parmi tous les questionnaires publiés
// De nouveau on peut filtrer ou non ceux auxquels l'utilisateur a répondu
// Par contre, ici pas de pagination car on maîtrise le nombre de résultats envoyés
// Recherche aléatoire parmi tous les questionnaires publiés associés à un groupe
// Ici pas de pagination car on maîtrise le nombre de résultats envoyés
exports.getRandomQuestionnaires = async (req, res, next) =>
{
try
{
const db = require("../models/index");
let getQuestionnaires;
if(!tool.isEmpty(req.body.onlyAnswers) && !tool.isEmpty(req.connectedUser.User.id))
getQuestionnaires=await db.sequelize.query("SELECT DISTINCT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `Answers` WHERE `Answers`.`QuestionnaireId`=`Questionnaires`.`id` AND `Answers`.`UserId`=:id AND `isPublished` = 1 ORDER BY RAND() LIMIT "+configQuestionnaires.nbRandomResults, { replacements: { id:req.connectedUser.User.id }, type: QueryTypes.SELECT });
else
getQuestionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE `isPublished` = 1 ORDER BY RAND() LIMIT "+configQuestionnaires.nbRandomResults, { type: QueryTypes.SELECT });
const getQuestionnaires=await db.sequelize.query("SELECT `id` FROM `Questionnaires` WHERE `isPublished` = 1 AND GroupId IS NOT NULL ORDER BY RAND() LIMIT "+configQuestionnaires.nbRandomResults, { type: QueryTypes.SELECT });
let begin=0, end;
end=begin+configTpl.nbQuestionnairesUserHomePage-1;
datas=await getListingsQuestionnairesOuput(getQuestionnaires, begin, end, "html");
@ -732,18 +723,22 @@ const creaNewQuestionnairesJson = async () =>
}
exports.creaNewQuestionnairesJson = creaNewQuestionnairesJson;
// Se limite à compter le nombre total de questionnaires et à le stocker pour éviter de lancer une requête sql à chaque fois
// Se limite à compter le nombre total de questionnaires non groupés et à le stocker pour éviter de lancer une requête sql à chaque fois
const creaStatsQuestionnairesJson = async () =>
{
const db = require("../models/index");
const Questionnaires=await db["Questionnaire"].findAll({ attributes: ["id"] });
const QuestionnairesPublished=await db["Questionnaire"].findAll({ where: { isPublished : true }, attributes: ["id"] });
const Questionnaires=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires`", { type: QueryTypes.SELECT });
const QuestionnairesPublished=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires` WHERE `isPublished`=1", { type: QueryTypes.SELECT });
const QuestionnairesWithoutGroup=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires` WHERE `GroupId` IS NULL", { type: QueryTypes.SELECT });
const QuestionnairesWithoutGroupPublished=await db.sequelize.query("SELECT DISTINCT `id` FROM `Questionnaires` WHERE `GroupId` IS NULL AND `isPublished`=1", { type: QueryTypes.SELECT });
if(Questionnaires && QuestionnairesPublished)
{
const stats =
{
nbTot : Questionnaires.length,
nbPublished : QuestionnairesPublished.length
nbPublished : QuestionnairesPublished.length,
nbTotWithoutGroup : QuestionnairesWithoutGroup.length,
nbWithoutGroupPublished : QuestionnairesWithoutGroupPublished.length
}
await toolFile.createJSON(config.dirCacheQuestionnaires, "stats", stats);
return stats;

View File

@ -51,14 +51,17 @@ exports.getQuestionnairesForUser = async(req, res, next) =>
err.message=txtGeneral.neededParams;
throw err;
}
const questionnaires = await db.sequelize.query("SELECT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `QuestionnaireAccesses` ON `QuestionnaireAccesses`.`QuestionnaireId`=`Questionnaires`.`id` AND `QuestionnaireAccesses`.`UserId`=:id ORDER BY `QuestionnaireAccesses`.`createdAt` DESC LIMIT :begin,:nb", { replacements: { id:req.params.id, begin:req.params.begin, nb:req.params.nb }, type: QueryTypes.SELECT });
const db = require("../models/index");
const questionnaires = await db.sequelize.query("SELECT `Questionnaires`.`id` FROM `Questionnaires` INNER JOIN `QuestionnaireAccesses` ON `QuestionnaireAccesses`.`QuestionnaireId`=`Questionnaires`.`id` AND `QuestionnaireAccesses`.`UserId`=:id ORDER BY `QuestionnaireAccesses`.`createdAt` DESC LIMIT :begin,:nb", { replacements: { id: req.params.id, begin: parseInt(req.params.begin), nb: parseInt(req.params.nb) }, type: QueryTypes.SELECT });
// je vais chercher les infos dans les json car ma vue a besoin des infos présentées de cette manière
const datas=[];
for(let i in questionnaires)
datas.push(await questionnaireCtrl.searchQuestionnaireById(questionnaires[i].id, true));
if(req.params.output !== undefined && req.params.output == "html")
{
if(datas.length !== 0)
const output={};
output.nbTot=datas.length;
if(output.nbTot !== 0)
{
const pug = require("pug");
const striptags = require("striptags");
@ -73,11 +76,9 @@ exports.getQuestionnairesForUser = async(req, res, next) =>
questionnaires: datas,
nbQuestionnairesList:configTpl.nbQuestionnairesUserHomePage
}
datas.html=await compiledFunction(pageDatas);
output.html=await compiledFunction(pageDatas);
}
else
datas.html="";
res.status(200).json(datas);
res.status(200).json(output);
}
else// on retourne seulement les données
res.status(200).json(datas);

View File

@ -42,18 +42,15 @@
<img id="logo" src="/themes/wikilerni/img/wikilerni-purple-2-512.png" alt="Logo WikiLerni" />
<div id="message" class="cardboard"></div>
<h2>Chercher un quiz</h2>
<h2>Votre jardin WikiLerni</h2>
<form id="search" method="POST">
<input id="searchQuestionnaires" type="text" name="searchQuestionnaires" placeholder="Votre recherche" class="cardboard" />
<input id="begin" type="hidden" name="begin" value="0">
<div class="line"><label for="onlyAnswers" class="check"><input type="checkbox" id="onlyAnswers" name="onlyAnswers" /><div class="checkbox_override"></div> Parmi mes réponses.</li></div>
<!--<div class="line"><label for="onlyAnswers" class="check"><input type="checkbox" id="onlyAnswers" name="onlyAnswers" /><div class="checkbox_override"></div> Parmi mes lectures.</li></div>-->
<input type="submit" value="Chercher" class="cardboard" /><br>
<button type="button" id="random" class="button cardboard">Au hasard !</button>
</form>
<h2 id="quizsTitle">Les quizs attendant votre réponse</h2>
<div id="quizsIntro"></div>
<h4 id="quizsIntro"></h4>
</div>
<div id="quizs">

View File

@ -11,7 +11,8 @@
import { apiUrl, availableLangs, theme } from "../../config/instance.js";
const lang=availableLangs[0];
const configTemplate = require("../../views/"+theme+"/config/"+lang+".js");
const { getQuestionnairesWithoutAnswer, getRandomQuestionnairesRoute, getStatsAnswers, questionnaireRoutes, searchQuestionnaires, searchQuestionnairesRoute } = require("../../config/questionnaires");
const { getRandomQuestionnairesRoute, getStatsAnswers, questionnaireRoutes, searchQuestionnaires, searchQuestionnairesRoute } = require("../../config/questionnaires");
const { getUsersQuestionnairesRoute, userRoutes } = require("../../config/users");
// Fonctions utiles au script :
import { getLocaly, removeLocaly, saveLocaly } from "./tools/clientstorage.js";
@ -22,16 +23,16 @@ import { isEmpty, replaceAll } from "../../tools/main";
import { checkSession } from "./tools/users.js";
// Dictionnaires :
const { nbQuestionnaireWithoudAnswer, noQuestionnaireWithoudAnswer, statsUser } = require("../../lang/"+lang+"/answer");
const { statsUser } = require("../../lang/"+lang+"/answer");
const { nextPage, previousPage, serverError } = require("../../lang/"+lang+"/general");
const { searchQuestionnaireResultTitle, searchQuestionnaireWithResult, searchQuestionnaireWithNoResult } = require("../../lang/"+lang+"/questionnaire");
const { searchQuestionnaireWithResult, searchQuestionnaireWithNoResult } = require("../../lang/"+lang+"/questionnaire");
const { lastQuestionnairesForUser, noQuestionnaireAccess } = require("../../lang/"+lang+"/questionnaireaccess");
const { needBeConnected, welcomeMessage } = require("../../lang/"+lang+"/user");
// Principaux éléments du DOM manipulés :
const divMain= document.getElementById("main-content");
const divCrash= document.getElementById("crash");
const divMessage = document.getElementById("message");
const quizTitle = document.getElementById("quizsTitle");
const quizIntro = document.getElementById("quizsIntro");
const quizListing = document.getElementById("quizsList");
const quizPaginationPrevious = document.getElementById("previous");
@ -71,25 +72,30 @@ const initialise = async () =>
let response=JSON.parse(this.responseText);
if (this.status === 200 && !isEmpty(response.nbAnswers) && response.nbAnswers!==0)// pas de stats si aucune réponse !
{
let txtIntro="";
const mapText =
{
NBANSWERS : response.nbAnswers,
NBQUESTIONNAIRES : response.nbQuestionnaires,
NBTOTQUESTIONNAIRES : response.general.nbPublished,
NBTOTQUESTIONNAIRES : response.groups.nbPublished+response.questionnaires.nbWithoutGroupPublished,
AVGDURATION : response.avgDuration,
AVGCORRECTANSWERS : response.avgCorrectAnswers
};
addElement(divMessage, "p", replaceAll(statsUser, mapText), "", "", "", false);
// La situation est plurielle...
txtIntro=replaceAll(statsUser, mapText);
txtIntro=(response.nbAnswers > 1) ? txtIntro.replace("S1", "s") : txtIntro.replace("S1", "");
txtIntro=(response.nbQuestionnaires > 1) ? txtIntro.replace("S2", "s").replace("S3", "s") : txtIntro.replace("S2", "").replace("S3", "s");
txtIntro=(mapText.NBTOTQUESTIONNAIRES > 1) ? txtIntro.replace("S4", "s") : txtIntro.replace("S4", "");
addElement(divMessage, "p", txtIntro, "", "", "", false);
}
}
}
xhrStats.setRequestHeader("Authorization", "Bearer "+user.token);
xhrStats.send();
/*
// Par défaut, on affiche des derniers quizs proposés sans réponse :
// Par défaut, on affiche des derniers éléments supposés lus par l'utilisateur :
const xhrLastQuizs = new XMLHttpRequest();
xhrLastQuizs.open("GET", apiUrl+questionnaireRoutes+getQuestionnairesWithoutAnswer+""+user.id+"/"+0+"/"+configTemplate.nbQuestionnairesUserHomePage+"/html");
xhrLastQuizs.open("GET", apiUrl+userRoutes+getUsersQuestionnairesRoute+""+user.id+"/"+0+"/"+configTemplate.nbQuestionnairesUserHomePage+"/html");
xhrLastQuizs.onreadystatechange = function()
{
if (this.readyState == XMLHttpRequest.DONE)
@ -98,13 +104,11 @@ const initialise = async () =>
if (this.status === 200)
{
if(response.nbTot === 0)
addElement(quizIntro, "p", noQuestionnaireWithoudAnswer, "", ["success"]);
addElement(quizIntro, "p", noQuestionnaireAccess, "", ["info"]);
else if(response.html)
{
addElement(quizIntro, "p", nbQuestionnaireWithoudAnswer.replace("#NB", response.questionnaires.length), "", ["info"]);
addElement(quizIntro, "p", lastQuestionnairesForUser, "", ["info"]);
quizListing.innerHTML=response.html;
window.location.hash="";// sinon les hash s'enchaînent...
window.location.assign("#quizsTitle");
}
else
addElement(quizs, "p", serverError, "", ["error"]);// revoir si intérêt d'afficher quelque chose
@ -114,13 +118,12 @@ const initialise = async () =>
}
}
xhrLastQuizs.setRequestHeader("Authorization", "Bearer "+user.token);
xhrLastQuizs.send();*/
xhrLastQuizs.send();
// Traitement du lancement d'une recherche
// La recherche peut être lancée via la bouton submit ou un lien de pagination
const sendSearch = (type="search") =>
{
quizTitle.innerHTML=searchQuestionnaireResultTitle;
quizListing.innerHTML=""+"";
let datas=getDatasFromInputs(formSearch);
const xhrSearch = new XMLHttpRequest();
@ -139,14 +142,16 @@ const initialise = async () =>
{
addElement(quizIntro, "p", searchQuestionnaireWithNoResult, "", ["info"]);
window.location.hash="";// sinon les hash s'enchaînent...
window.location.assign("#quizsTitle");
window.location.assign("#quizsIntro");
}
else if(response.html)
{
addElement(quizIntro, "p", searchQuestionnaireWithResult.replace("#NB", response.nbTot) , "", ["success"]);
let txtIntro=searchQuestionnaireWithResult.replace("#NB", response.nbTot);
txtIntro=(response.nbTot > 1) ? txtIntro.replace("#S","s") : txtIntro.replace("#S","");
addElement(quizIntro, "p", txtIntro , "", ["success"]);
quizListing.innerHTML=response.html;
window.location.hash="";
window.location.assign("#quizsTitle");
window.location.assign("#quizsIntro");
// Pagination nécessaire ?
// on commence par vider...
quizPaginationPrevious.innerHTML="";
@ -164,7 +169,7 @@ const initialise = async () =>
document.getElementById("begin").value=newBegin;
sendSearch();
window.location.hash="";
window.location.assign("#quizsTitle");// pour remonter
window.location.assign("#quizsIntro");// pour remonter
}
previousPageElt.addEventListener("click", function(e)
{
@ -188,7 +193,7 @@ const initialise = async () =>
document.getElementById("begin").value=response.end+1;
sendSearch();
window.location.hash="";// sinon les hash s'enchaînent...
window.location.assign("#quizsTitle"); // pour remonter
window.location.assign("#quizsIntro"); // pour remonter
});
}
}

View File

@ -3,7 +3,6 @@ module.exports =
checkResponsesOuputFail : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. C'est certain, vous ferez mieux la prochaine fois !",
checkResponsesOuputMedium : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. C'est pas mal du tout !",
checkResponsesOuputSuccess : "Vous avez répondu en DURATION secondes et avez <u><b>NBCORRECTANSWERS bonne(s) réponse(s) sur NBQUESTIONS questions</b></u>. Bravo ! Rien ne vous échappe !",
nbQuestionnaireWithoudAnswer: "Il y a #NB quizs qui vous ont été proposés et auxquels vous n'avez pas répondu. Voici les derniers :!",
needIntegerNumberCorrectResponses : "Le nombre de réponses correctes doit être un nombre entier.",
needIntegerNumberSecondesResponse : "La durée de la réponse doit être un nombre entier de secondes.",
needIntegerNumberUserResponses : "Le nombre de questions auxquelles l'utilisateur a répondu doit être un nombre entier.",
@ -15,12 +14,11 @@ module.exports =
needMinNumberCorrectResponses : "Le nombre de réponses correctes ne peut être négatif.",
needMinNumberSecondesResponse : "La durée de la réponse ne peut être négative.",
noPreviousAnswer: "On dirait que c'est la première fois que vous répondez à ce quiz. Bonne lecture !",
noQuestionnaireWithoudAnswer: "Bravo ! Vous avez répondu à tous les quizs qui vous on été proposés !<br>Il y en a des nouveaux publiés régulièrement et qui vous seront proposés.<br>En attendant vous pouvez peut-être essayer de répondre de nouveau à certain quiz pour voir si vous vous souvenez des bonnes réponses ?",
previousAnswersLine: "Le DATEANSWER, vous avez répondu correctement à NBCORRECTANSWERS questions sur NBQUESTIONS en AVGDURATION secondes.",
previousAnswersStats: "En moyenne, vous avez répondu à ce quiz en AVGDURATION secondes, en ayant <b>AVGCORRECTANSWERS % de bonnes réponses</b>.",
previousAnswersTitle: "Bonjour #NOM, voici vos précédents résultats à ce quiz",
responseSavedError : "Cependant une erreur a été rencontrée durant l'enregistrement de votre résultat. <a href='/#URL'>Accèder à tous vos quizs</a>.",
responseSavedMessage : "Votre résultat a été enregistré. <a href='/#URL'>Accèder à tous vos quizs</a>.",
statsUser: "Vous avez enregistré NBANSWERS réponses à <b>NBQUESTIONNAIRES questionnaires différents</b> sur les NBTOTQUESTIONNAIRES proposés par le site.<br>En moyenne, vous avez mis AVGDURATION secondes à répondre et avez <b>correctement répondu à AVGCORRECTANSWERS % des questions</b>.",
statsUser: "Vous avez enregistré <b>NBANSWERS réponseS1 à NBQUESTIONNAIRES quizS2 différentS3</b> sur les NBTOTQUESTIONNAIRES proposéS4 par le site.<br>En moyenne, vous avez mis AVGDURATION secondes à répondre et avez <b>correctement répondu à AVGCORRECTANSWERS % des questions</b>.",
wantToSaveResponses: "Si vous le souhaitez, vous pouvez <u><b>sauvegarder votre résultat</b></u> en créant votre compte ci-dessous. Cela vous permettra aussi de <u><b>recevoir régulièrement de nouvelles \"graines de culture\"</b></u> directement sur votre e-mail.",
};

View File

@ -4,7 +4,7 @@ module.exports =
addOkMessage : "Les données ont bien été enregistrées.",
alertNewWindow: "nouvelle fenêtre",
badUrl : "Tentative d'accès à une page n'existant pas :",
btnLinkToQuestionnaire : "Aller au quiz !",
btnLinkToQuestionnaire : "Afficher !",
btnProposeConnection: "Je me connecte.",
btnProposeSubscribe: "Je crée mon compte.",
btnShowOnWebSite: "Voir sur #SITE_NAME.",

View File

@ -38,6 +38,6 @@ module.exports =
publishedBy: "Quiz publié par",
searchQuestionnaireResultTitle : "Résultat pour votre recherche",
searchQuestionnaireWithNoResult : "Aucun quiz n'a été trouvé pour votre recherche.",
searchQuestionnaireWithResult : "Voici #NB quizs pour votre recherche :",
searchQuestionnaireWithResult : "Il y a #NB article#S correspondant à votre recherche :",
wrongAnswerTxt: "Mauvaise réponse"
};

View File

@ -1,6 +1,8 @@
module.exports =
{
lastQuestionnairesForUser: "Vos dernières lectures :",
notFound : "Les informations d'un questionnaire attribué à un abonné n'ont pas été trouvées : ",
noQuestionnaireAccess: "Aucun article ne vous a encore été envoyé par e-mail. Sans doute, venez-vous de créer votre compte. En attendant d'en recevoir, vous pouvez mieux découvrir le site WikiLerni <a href=\"/a-propos.html\">en lisant la page à propos</a> ou encore commencer à parcourir librement son contenu à l'aide du moteur de recherche ci-dessus.",
questionnaireRetryInfo : "<b>Vous avez déjà reçu tous les quizs publiés à ce jour ! En attendant la publication de nouveaux quizs, vous pouvez peut-être réessayer le suivant ?</b>",
questionnaireRetryInfoTxt : "Bonjour USER_NAME,\n\nVous avez déjà reçu tous les quizs publiés à ce jour ! En attendant la publication de nouveaux quizs, vous pouvez peut-être réessayer le suivant ?\n\nQUESTIONNAIRE_URL\n\nBonne lecture !\n\nStopper les envois ?\nUNSUBSCRIBE_URL",
searchIsNotLongEnough : "Merci de fournir un mot-clés d'au moins deux caractères pour votre recherche."

View File

@ -7,8 +7,6 @@ const authAdmin = require("../middleware/authAdmin");
const userCtrl = require("../controllers/user");
const subscriptionCtrl = require("../controllers/subscription");
//router.get("/getconfig", userCtrl.getConfig); // ne devrait plus être utile !
router.post("/getgodfatherid", userCtrl.getGodfatherId);
router.post("/isemailfree", userCtrl.checkEmailIsFree);
router.post("/signup", userCtrl.signup);