Commit d187aad4125f77a4f052a1a33c81e4f922efc683
1 parent
1a7cc17b
Exists in
master
and in
1 other branch
- adds courses
- updates mathjax to version 3 (not yet working correctly) - updates other javascript libraries
Showing
13 changed files
with
450 additions
and
175 deletions
Show diff stats
BUGS.md
1 | 1 | |
2 | 2 | # BUGS |
3 | 3 | |
4 | +- classificacoes so devia mostrar os que ja fizeram alguma coisa | |
5 | +- menu nao mostra as opcoes correctamente | |
6 | +- mathjax nao esta a correr sobre o titulo e sobre as solucoes. | |
7 | +- QFactory.generate() devia fazer run da gen_async, ou remover. | |
8 | +- finish topic vai para a lista de cursos. devia ficar no mesmo curso. | |
4 | 9 | - marking all options right in a radio question breaks! |
5 | 10 | - impedir que quando students.db não é encontrado, crie um ficheiro vazio. |
6 | 11 | - opcao --prefix devia afectar a base de dados? |
... | ... | @@ -24,6 +29,7 @@ sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in |
24 | 29 | |
25 | 30 | # TODO |
26 | 31 | |
32 | +- use run_script_async to run run_script using asyncio.run? | |
27 | 33 | - ao fim de 3 tentativas de login, envial email para aluno com link para definir nova password (com timeout de 5 minutos). |
28 | 34 | - mostrar capitulos e subtopicos de forma hierarquica. clicar no capitulo expande as dependencias. |
29 | 35 | - mostrar rankings alunos/perguntas respondidas/% correctas/valor esperado topicos. | ... | ... |
README.md
... | ... | @@ -284,11 +284,11 @@ me:\ |
284 | 284 | Common database manipulations: |
285 | 285 | |
286 | 286 | ```sh |
287 | -initdb-aprendizations -u 12345 --pw alibaba # reset student password | |
288 | -initdb-aprendizations -a 12345 --pw alibaba # add new student | |
287 | +initdb-aprendizations -u 12345 --pw alibaba # reset student password | |
288 | +initdb-aprendizations -a 007 "James Bond" --pw mi6 # add new student "007" | |
289 | 289 | ``` |
290 | 290 | |
291 | -Common database queries: | |
291 | +Some common database queries: | |
292 | 292 | |
293 | 293 | ```sh |
294 | 294 | # Which students did at least one topic? | ... | ... |
aprendizations/learnapp.py
1 | 1 | |
2 | 2 | # python standard library |
3 | -from os import path | |
4 | -import logging | |
5 | -from contextlib import contextmanager # `with` statement in db sessions | |
6 | 3 | import asyncio |
4 | +from contextlib import contextmanager # `with` statement in db sessions | |
7 | 5 | from datetime import datetime |
6 | +import logging | |
8 | 7 | from random import random |
8 | +from os import path | |
9 | 9 | from typing import Any, Dict, Iterable, List, Optional, Tuple |
10 | 10 | |
11 | 11 | # third party libraries |
12 | 12 | import bcrypt |
13 | -import sqlalchemy as sa | |
14 | 13 | import networkx as nx |
14 | +import sqlalchemy as sa | |
15 | 15 | |
16 | 16 | # this project |
17 | 17 | from .models import Student, Answer, Topic, StudentTopic |
18 | -from .student import StudentState | |
19 | 18 | from .questions import Question, QFactory, QDict |
19 | +from .student import StudentState | |
20 | 20 | from .tools import load_yaml |
21 | 21 | |
22 | 22 | # setup logger for this module |
... | ... | @@ -57,7 +57,7 @@ class LearnApp(object): |
57 | 57 | # init |
58 | 58 | # ------------------------------------------------------------------------ |
59 | 59 | def __init__(self, |
60 | - config_files: List[str], | |
60 | + courses: str, # filename with course configurations | |
61 | 61 | prefix: str, # path to topics |
62 | 62 | db: str, # database filename |
63 | 63 | check: bool = False) -> None: |
... | ... | @@ -65,9 +65,18 @@ class LearnApp(object): |
65 | 65 | self.db_setup(db) # setup database and check students |
66 | 66 | self.online: Dict[str, Dict] = dict() # online students |
67 | 67 | |
68 | + config: Dict[str, Any] = load_yaml(courses) | |
69 | + | |
70 | + # courses dict | |
71 | + self.courses = config['courses'] | |
72 | + logger.info(f'Courses: {", ".join(self.courses.keys())}') | |
73 | + | |
74 | + # topic dependencies are shared between all courses | |
68 | 75 | self.deps = nx.DiGraph(prefix=prefix) |
69 | - for c in config_files: | |
70 | - self.populate_graph(c) | |
76 | + logger.info('Populating graph:') | |
77 | + for f in config['deps']: | |
78 | + self.populate_graph(f) | |
79 | + logger.info(f'Graph has {len(self.deps)} topics') | |
71 | 80 | |
72 | 81 | # factory is a dict with question generators for all topics |
73 | 82 | self.factory: Dict[str, QFactory] = self.make_factory() |
... | ... | @@ -163,8 +172,9 @@ class LearnApp(object): |
163 | 172 | self.online[uid] = { |
164 | 173 | 'number': uid, |
165 | 174 | 'name': name, |
166 | - 'state': StudentState(deps=self.deps, factory=self.factory, | |
167 | - state=state), | |
175 | + 'state': StudentState(uid=uid, state=state, | |
176 | + courses=self.courses, deps=self.deps, | |
177 | + factory=self.factory), | |
168 | 178 | 'counter': counter + 1, # counts simultaneous logins |
169 | 179 | } |
170 | 180 | |
... | ... | @@ -202,10 +212,10 @@ class LearnApp(object): |
202 | 212 | # checks answer (updating student state) and returns grade. FIXME type of answer |
203 | 213 | # ------------------------------------------------------------------------ |
204 | 214 | async def check_answer(self, uid: str, answer) -> Tuple[Question, str]: |
205 | - knowledge = self.online[uid]['state'] | |
206 | - topic = knowledge.get_current_topic() | |
215 | + student = self.online[uid]['state'] | |
216 | + topic = student.get_current_topic() | |
207 | 217 | |
208 | - q, action = await knowledge.check_answer(answer) # may move questions | |
218 | + q, action = await student.check_answer(answer) # may move questions | |
209 | 219 | |
210 | 220 | logger.info(f'User "{uid}" got {q["grade"]:.2} in "{q["ref"]}"') |
211 | 221 | |
... | ... | @@ -220,11 +230,11 @@ class LearnApp(object): |
220 | 230 | topic_id=topic)) |
221 | 231 | logger.debug(f'db insert answer of {q["ref"]}') |
222 | 232 | |
223 | - if knowledge.topic_has_finished(): | |
233 | + if student.topic_has_finished(): | |
224 | 234 | # finished topic, save into database |
225 | 235 | logger.info(f'User "{uid}" finished "{topic}"') |
226 | - level: float = knowledge.get_topic_level(topic) | |
227 | - date: str = str(knowledge.get_topic_date(topic)) | |
236 | + level: float = student.get_topic_level(topic) | |
237 | + date: str = str(student.get_topic_date(topic)) | |
228 | 238 | |
229 | 239 | with self.db_session() as s: |
230 | 240 | a = s.query(StudentTopic) \ |
... | ... | @@ -250,6 +260,18 @@ class LearnApp(object): |
250 | 260 | return q, action |
251 | 261 | |
252 | 262 | # ------------------------------------------------------------------------ |
263 | + # Start course | |
264 | + # ------------------------------------------------------------------------ | |
265 | + def start_course(self, uid: str, course: str) -> None: | |
266 | + student = self.online[uid]['state'] | |
267 | + try: | |
268 | + student.start_course(course) | |
269 | + except Exception as e: | |
270 | + logger.warning(f'"{uid}" could not start course "{course}": {e}') | |
271 | + else: | |
272 | + logger.info(f'"{uid}" started course "{course}"') | |
273 | + | |
274 | + # ------------------------------------------------------------------------ | |
253 | 275 | # Start new topic |
254 | 276 | # ------------------------------------------------------------------------ |
255 | 277 | async def start_topic(self, uid: str, topic: str) -> None: |
... | ... | @@ -305,8 +327,6 @@ class LearnApp(object): |
305 | 327 | # Edges are obtained from the deps defined in the YAML file for each topic. |
306 | 328 | # ------------------------------------------------------------------------ |
307 | 329 | def populate_graph(self, conffile: str) -> None: |
308 | - | |
309 | - logger.info(f'Populating graph from: {conffile}...') | |
310 | 330 | config: Dict[str, Any] = load_yaml(conffile) # course configuration |
311 | 331 | |
312 | 332 | # default attributes that apply to the topics |
... | ... | @@ -347,7 +367,7 @@ class LearnApp(object): |
347 | 367 | t['append_wrong'] = attr.get('append_wrong', default_append_wrong) |
348 | 368 | t['questions'] = attr.get('questions', []) |
349 | 369 | |
350 | - logger.info(f'Loaded {g.number_of_nodes()} topics') | |
370 | + logger.info(f'{len(topics):>6} topics in {conffile}') | |
351 | 371 | |
352 | 372 | # ======================================================================== |
353 | 373 | # methods that do not change state (pure functions) |
... | ... | @@ -428,8 +448,13 @@ class LearnApp(object): |
428 | 448 | return self.online[uid]['state'].get_current_topic() |
429 | 449 | |
430 | 450 | # ------------------------------------------------------------------------ |
431 | - def get_title(self) -> str: | |
432 | - return self.deps.graph.get('title', '') | |
451 | + def get_student_course_title(self, uid: str) -> str: | |
452 | + return self.online[uid]['state'].get_current_course_title() | |
453 | + | |
454 | + # ------------------------------------------------------------------------ | |
455 | + # def get_title(self) -> str: | |
456 | + # # return self.deps.graph.get('title', '') | |
457 | + # return self. | |
433 | 458 | |
434 | 459 | # ------------------------------------------------------------------------ |
435 | 460 | def get_topic_name(self, ref: str) -> str: |
... | ... | @@ -442,6 +467,10 @@ class LearnApp(object): |
442 | 467 | return path.join(prefix, topic, 'public') |
443 | 468 | |
444 | 469 | # ------------------------------------------------------------------------ |
470 | + def get_courses(self, uid: str) -> Dict: | |
471 | + return self.courses | |
472 | + | |
473 | + # ------------------------------------------------------------------------ | |
445 | 474 | def get_rankings(self, uid: str) -> Iterable[Tuple[str, str, float, float]]: |
446 | 475 | |
447 | 476 | logger.info(f'User "{uid}" get rankings') | ... | ... |
aprendizations/main.py
... | ... | @@ -24,8 +24,8 @@ def parse_cmdline_arguments(): |
24 | 24 | ) |
25 | 25 | |
26 | 26 | argparser.add_argument( |
27 | - 'conffile', type=str, nargs='*', | |
28 | - help='Topics configuration file in YAML format.' | |
27 | + 'courses', type=str, # nargs='*', | |
28 | + help='Courses configuration file in YAML format.' | |
29 | 29 | ) |
30 | 30 | |
31 | 31 | argparser.add_argument( |
... | ... | @@ -161,7 +161,9 @@ def main(): |
161 | 161 | |
162 | 162 | # --- start application |
163 | 163 | try: |
164 | - learnapp = LearnApp(arg.conffile, prefix=arg.prefix, db=arg.db, | |
164 | + learnapp = LearnApp(courses=arg.courses, | |
165 | + prefix=arg.prefix, | |
166 | + db=arg.db, | |
165 | 167 | check=arg.check) |
166 | 168 | except DatabaseUnusableError: |
167 | 169 | logging.critical('Failed to start application.') | ... | ... |
aprendizations/questions.py
... | ... | @@ -461,6 +461,9 @@ class QFactory(object): |
461 | 461 | # i.e. a question object (radio, checkbox, ...). |
462 | 462 | # ----------------------------------------------------------------------- |
463 | 463 | def generate(self) -> Question: |
464 | + # FIXME this is almost the same as the one below | |
465 | + # return asyncio.run(self.gen_async()) | |
466 | + | |
464 | 467 | logger.debug(f'generating {self.question["ref"]}...') |
465 | 468 | # Shallow copy so that script generated questions will not replace |
466 | 469 | # the original generators |
... | ... | @@ -491,7 +494,7 @@ class QFactory(object): |
491 | 494 | return qinstance |
492 | 495 | |
493 | 496 | # ----------------------------------------------------------------------- |
494 | - async def generate_async(self) -> Question: | |
497 | + async def gen_async(self) -> Question: | |
495 | 498 | logger.debug(f'generating {self.question["ref"]}...') |
496 | 499 | # Shallow copy so that script generated questions will not replace |
497 | 500 | # the original generators |
... | ... | @@ -520,5 +523,4 @@ class QFactory(object): |
520 | 523 | logger.error(f'Invalid type "{q["type"]}" in "{q["ref"]}"') |
521 | 524 | raise |
522 | 525 | else: |
523 | - logger.debug('ok') | |
524 | 526 | return qinstance | ... | ... |
aprendizations/serve.py
... | ... | @@ -24,7 +24,7 @@ logger = logging.getLogger(__name__) |
24 | 24 | |
25 | 25 | |
26 | 26 | # ---------------------------------------------------------------------------- |
27 | -# Decorator used to restrict access to the administrator only | |
27 | +# Decorator used to restrict access to the administrator | |
28 | 28 | # ---------------------------------------------------------------------------- |
29 | 29 | def admin_only(func): |
30 | 30 | @functools.wraps(func) |
... | ... | @@ -46,11 +46,14 @@ class WebApplication(tornado.web.Application): |
46 | 46 | (r'/login', LoginHandler), |
47 | 47 | (r'/logout', LogoutHandler), |
48 | 48 | (r'/change_password', ChangePasswordHandler), |
49 | - (r'/question', QuestionHandler), # renders each question | |
50 | - (r'/rankings', RankingsHandler), # student rankings | |
51 | - (r'/topic/(.+)', TopicHandler), # start a topic | |
52 | - (r'/file/(.+)', FileHandler), # serve files | |
53 | - (r'/', RootHandler), # show list of topics | |
49 | + (r'/question', QuestionHandler), # render question | |
50 | + (r'/rankings', RankingsHandler), # rankings table | |
51 | + # (r'/topics', TopicsHandler), # show list of topics | |
52 | + (r'/topic/(.+)', TopicHandler), # start topic | |
53 | + (r'/file/(.+)', FileHandler), # serve file | |
54 | + (r'/courses', CoursesHandler), # show list of courses | |
55 | + (r'/course/(.+)', CourseHandler), # show course topics | |
56 | + (r'/', RootHandler), # redirects | |
54 | 57 | ] |
55 | 58 | settings = { |
56 | 59 | 'template_path': path.join(path.dirname(__file__), 'templates'), |
... | ... | @@ -166,23 +169,73 @@ class ChangePasswordHandler(BaseHandler): |
166 | 169 | |
167 | 170 | |
168 | 171 | # ---------------------------------------------------------------------------- |
169 | -# / (main page) | |
170 | -# Shows a list of topics and proficiency (stars, locked). | |
172 | +# / | |
173 | +# redirects to appropriate place | |
171 | 174 | # ---------------------------------------------------------------------------- |
172 | 175 | class RootHandler(BaseHandler): |
173 | 176 | @tornado.web.authenticated |
174 | 177 | def get(self): |
178 | + self.redirect('/courses') | |
179 | + | |
180 | + | |
181 | +# ---------------------------------------------------------------------------- | |
182 | +# /courses | |
183 | +# Shows a list of available courses | |
184 | +# ---------------------------------------------------------------------------- | |
185 | +class CoursesHandler(BaseHandler): | |
186 | + @tornado.web.authenticated | |
187 | + def get(self): | |
175 | 188 | uid = self.current_user |
189 | + self.render('courses.html', | |
190 | + appname=APP_NAME, | |
191 | + uid=uid, | |
192 | + name=self.learn.get_student_name(uid), | |
193 | + courses=self.learn.get_courses(uid), | |
194 | + ) | |
195 | + | |
196 | + | |
197 | +# ---------------------------------------------------------------------------- | |
198 | +# /course/... | |
199 | +# Start a given course | |
200 | +# ---------------------------------------------------------------------------- | |
201 | +class CourseHandler(BaseHandler): | |
202 | + @tornado.web.authenticated | |
203 | + def get(self, course): | |
204 | + uid = self.current_user | |
205 | + | |
206 | + try: | |
207 | + self.learn.start_course(uid, course) | |
208 | + except KeyError: | |
209 | + self.redirect('/courses') | |
210 | + | |
211 | + # print('TITULO: ---------------->', self.learn.get_title()) | |
176 | 212 | self.render('maintopics-table.html', |
177 | 213 | appname=APP_NAME, |
178 | 214 | uid=uid, |
179 | 215 | name=self.learn.get_student_name(uid), |
180 | 216 | state=self.learn.get_student_state(uid), |
181 | - title=self.learn.get_title(), | |
217 | + title=self.learn.get_student_course_title(uid), | |
182 | 218 | ) |
183 | 219 | |
184 | 220 | |
185 | 221 | # ---------------------------------------------------------------------------- |
222 | +# /topics | |
223 | +# Shows a list of topics and proficiency (stars, locked). | |
224 | +# ---------------------------------------------------------------------------- | |
225 | +# class TopicsHandler(BaseHandler): | |
226 | +# @tornado.web.authenticated | |
227 | +# def get(self): | |
228 | +# uid = self.current_user | |
229 | +# self.render('maintopics-table.html', | |
230 | +# appname=APP_NAME, | |
231 | +# uid=uid, | |
232 | +# name=self.learn.get_student_name(uid), | |
233 | +# state=self.learn.get_student_state(uid), | |
234 | +# title=self.learn.get_title(), | |
235 | +# ) | |
236 | + | |
237 | + | |
238 | +# ---------------------------------------------------------------------------- | |
186 | 239 | # /topic/... |
187 | 240 | # Start a given topic |
188 | 241 | # ---------------------------------------------------------------------------- |
... | ... | @@ -194,7 +247,7 @@ class TopicHandler(BaseHandler): |
194 | 247 | try: |
195 | 248 | await self.learn.start_topic(uid, topic) |
196 | 249 | except KeyError: |
197 | - self.redirect('/') | |
250 | + self.redirect('/topics') | |
198 | 251 | |
199 | 252 | self.render('topic.html', |
200 | 253 | appname=APP_NAME, | ... | ... |
aprendizations/static/js/topic.js
... | ... | @@ -69,7 +69,7 @@ function new_question(type, question, tries, progress) { |
69 | 69 | var btntext = (type == "information") ? "Continuar" : "Responder"; |
70 | 70 | $("#submit").html(btntext).off().click(postAnswer); |
71 | 71 | $('#topic_progress').css('width', (100*progress)+'%').attr('aria-valuenow', 100*progress); |
72 | - MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]); | |
72 | + MathJax.typeset(); | |
73 | 73 | |
74 | 74 | if (type == "radio") { |
75 | 75 | $(".list-group-item").click(function (e) { |
... | ... | @@ -130,12 +130,15 @@ function getFeedback(response) { |
130 | 130 | $('#right').show(); |
131 | 131 | $('#wrong').hide(); |
132 | 132 | $('#comments').html(params['comments']); |
133 | - MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#comments"]); | |
133 | + MathJax.typeset(); | |
134 | + | |
134 | 135 | $("#submit").html("Continuar").off().click(getQuestion); |
135 | 136 | $("#link_solution_on_right").click(function(){ |
136 | 137 | $("#right").hide(); |
137 | 138 | $('#solution').html(params['solution']).animateCSS('flipInX'); |
138 | - MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#solution"]); | |
139 | + // MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#solution"]); | |
140 | + MathJax.typeset(); | |
141 | + | |
139 | 142 | }); |
140 | 143 | break; |
141 | 144 | |
... | ... | @@ -144,7 +147,7 @@ function getFeedback(response) { |
144 | 147 | $('#topic_progress').css('width', (100*params["progress"])+'%').attr('aria-valuenow', 100*params["progress"]); |
145 | 148 | showTriesLeft(params["tries"]); |
146 | 149 | $('#comments').html(params['comments']); |
147 | - MathJax.Hub.Queue(["Typeset",MathJax.Hub,"#comments"]); | |
150 | + MathJax.typeset(); | |
148 | 151 | break; |
149 | 152 | |
150 | 153 | case "wrong": |
... | ... | @@ -153,11 +156,13 @@ function getFeedback(response) { |
153 | 156 | $('#topic_progress').css('width', (100*params["progress"])+'%').attr('aria-valuenow', 100*params["progress"]); |
154 | 157 | showTriesLeft(params["tries"]); |
155 | 158 | $('#comments').html(params['comments']); |
156 | - MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#comments"]); | |
159 | + // MathJax.typeset(); | |
160 | + MathJax.typeset(); | |
157 | 161 | $("#link_solution_on_wrong").click(function () { |
158 | 162 | $("#wrong").hide(); |
159 | 163 | $('#solution').html(params['solution']).animateCSS('flipInX'); |
160 | - MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#solution"]); | |
164 | + // MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#solution"]); | |
165 | + MathJax.typeset(); | |
161 | 166 | }); |
162 | 167 | $("fieldset").attr("disabled", "disabled"); |
163 | 168 | $("#submit").html("Continuar").off().click(getQuestion); | ... | ... |
aprendizations/student.py
1 | 1 | |
2 | 2 | # python standard library |
3 | -import random | |
4 | 3 | from datetime import datetime |
5 | 4 | import logging |
6 | -from typing import List, Optional, Tuple | |
5 | +import random | |
6 | +from typing import Dict, List, Optional, Tuple | |
7 | 7 | |
8 | 8 | # third party libraries |
9 | 9 | import networkx as nx |
... | ... | @@ -28,45 +28,33 @@ class StudentState(object): |
28 | 28 | # ======================================================================= |
29 | 29 | # methods that update state |
30 | 30 | # ======================================================================= |
31 | - def __init__(self, deps, factory, state={}) -> None: | |
32 | - self.deps = deps # shared dependency graph | |
31 | + def __init__(self, uid, state, courses, deps, factory) -> None: | |
32 | + # shared application data between all students | |
33 | + self.deps = deps # dependency graph | |
33 | 34 | self.factory = factory # question factory |
35 | + self.courses = courses # {'course': ['topic_id1', 'topic_id2',...]} | |
36 | + | |
37 | + # data of this student | |
38 | + self.uid = uid # user id '12345' | |
34 | 39 | self.state = state # {'topic': {'level': 0.5, 'date': datetime}, ...} |
35 | 40 | |
41 | + # prepare for running | |
36 | 42 | self.update_topic_levels() # applies forgetting factor |
37 | 43 | self.unlock_topics() # whose dependencies have been completed |
44 | + self.start_course(None) | |
38 | 45 | |
39 | - self.topic_sequence: List[str] = self.recommend_topic_sequence() | |
40 | - self.current_topic: Optional[str] = None | |
41 | - | |
42 | - # ------------------------------------------------------------------------ | |
43 | - # Updates the proficiency levels of the topics, with forgetting factor | |
44 | - # ------------------------------------------------------------------------ | |
45 | - def update_topic_levels(self) -> None: | |
46 | - now = datetime.now() | |
47 | - for tref, s in self.state.items(): | |
48 | - dt = now - s['date'] | |
49 | - forgetting_factor = self.deps.node[tref]['forgetting_factor'] | |
50 | - s['level'] *= forgetting_factor ** dt.days # forgetting factor | |
51 | - | |
52 | - # ------------------------------------------------------------------------ | |
53 | - # Unlock topics whose dependencies are satisfied (> min_level) | |
54 | 46 | # ------------------------------------------------------------------------ |
55 | - def unlock_topics(self) -> None: | |
56 | - for topic in self.deps.nodes(): | |
57 | - if topic not in self.state: # if locked | |
58 | - pred = self.deps.predecessors(topic) | |
59 | - min_level = self.deps.node[topic]['min_level'] | |
60 | - if all(d in self.state and self.state[d]['level'] > min_level | |
61 | - for d in pred): # all deps are greater than min_level | |
62 | - | |
63 | - self.state[topic] = { | |
64 | - 'level': 0.0, # unlocked | |
65 | - 'date': datetime.now() | |
66 | - } | |
67 | - logger.debug(f'unlocked "{topic}"') | |
68 | - # else: # lock this topic if deps do not satisfy min_level | |
69 | - # del self.state[topic] | |
47 | + def start_course(self, course: Optional[str]) -> None: | |
48 | + if course is None: | |
49 | + logger.debug('no active course') | |
50 | + self.current_course: Optional[str] = None | |
51 | + self.topic_sequence: List[str] = [] | |
52 | + self.current_topic: Optional[str] = None | |
53 | + else: | |
54 | + logger.debug(f'starting course {course}') | |
55 | + self.current_course = course | |
56 | + topics = self.courses[course]['topics'] | |
57 | + self.topic_sequence = self.recommend_topic_sequence(topics) | |
70 | 58 | |
71 | 59 | # ------------------------------------------------------------------------ |
72 | 60 | # Start a new topic. |
... | ... | @@ -99,8 +87,8 @@ class StudentState(object): |
99 | 87 | questions = t['questions'][:k] |
100 | 88 | logger.debug(f'selected questions: {", ".join(questions)}') |
101 | 89 | |
102 | - self.questions: List[Question] = [await self.factory[ref].generate_async() | |
103 | - for ref in questions] | |
90 | + self.questions: List[Question] = [await self.factory[ref].gen_async() | |
91 | + for ref in questions] | |
104 | 92 | |
105 | 93 | n = len(self.questions) |
106 | 94 | logger.debug(f'generated {n} questions') |
... | ... | @@ -153,7 +141,9 @@ class StudentState(object): |
153 | 141 | action = 'wrong' |
154 | 142 | if self.current_question['append_wrong']: |
155 | 143 | logger.debug('wrong answer, append new question') |
156 | - self.questions.append(self.factory[q['ref']].generate()) | |
144 | + # self.questions.append(self.factory[q['ref']].generate()) | |
145 | + new_question = await self.factory[q['ref']].gen_async() | |
146 | + self.questions.append(new_question) | |
157 | 147 | self.next_question() |
158 | 148 | |
159 | 149 | # returns corrected question (not new one) which might include comments |
... | ... | @@ -177,6 +167,35 @@ class StudentState(object): |
177 | 167 | |
178 | 168 | return self.current_question # question or None |
179 | 169 | |
170 | + # ------------------------------------------------------------------------ | |
171 | + # Update proficiency level of the topics using a forgetting factor | |
172 | + # ------------------------------------------------------------------------ | |
173 | + def update_topic_levels(self) -> None: | |
174 | + now = datetime.now() | |
175 | + for tref, s in self.state.items(): | |
176 | + dt = now - s['date'] | |
177 | + forgetting_factor = self.deps.node[tref]['forgetting_factor'] | |
178 | + s['level'] *= forgetting_factor ** dt.days # forgetting factor | |
179 | + | |
180 | + # ------------------------------------------------------------------------ | |
181 | + # Unlock topics whose dependencies are satisfied (> min_level) | |
182 | + # ------------------------------------------------------------------------ | |
183 | + def unlock_topics(self) -> None: | |
184 | + for topic in self.deps.nodes(): | |
185 | + if topic not in self.state: # if locked | |
186 | + pred = self.deps.predecessors(topic) | |
187 | + min_level = self.deps.node[topic]['min_level'] | |
188 | + if all(d in self.state and self.state[d]['level'] > min_level | |
189 | + for d in pred): # all deps are greater than min_level | |
190 | + | |
191 | + self.state[topic] = { | |
192 | + 'level': 0.0, # unlock | |
193 | + 'date': datetime.now() | |
194 | + } | |
195 | + logger.debug(f'unlocked "{topic}"') | |
196 | + # else: # lock this topic if deps do not satisfy min_level | |
197 | + # del self.state[topic] | |
198 | + | |
180 | 199 | # ======================================================================== |
181 | 200 | # pure functions of the state (no side effects) |
182 | 201 | # ======================================================================== |
... | ... | @@ -187,17 +206,17 @@ class StudentState(object): |
187 | 206 | # ------------------------------------------------------------------------ |
188 | 207 | # compute recommended sequence of topics ['a', 'b', ...] |
189 | 208 | # ------------------------------------------------------------------------ |
190 | - def recommend_topic_sequence(self, target: str = '') -> List[str]: | |
191 | - tt = list(nx.topological_sort(self.deps)) | |
192 | - try: | |
193 | - idx = tt.index(target) | |
194 | - except ValueError: | |
195 | - pass | |
196 | - else: | |
197 | - del tt[idx:] | |
209 | + def recommend_topic_sequence(self, targets: List[str] = []) -> List[str]: | |
210 | + G = self.deps | |
211 | + ts = set(targets) | |
212 | + for t in targets: | |
213 | + ts.update(nx.ancestors(G, t)) | |
198 | 214 | |
199 | - unlocked = [t for t in tt if t in self.state] | |
200 | - locked = [t for t in tt if t not in unlocked] | |
215 | + tl = list(nx.topological_sort(G.subgraph(ts))) | |
216 | + | |
217 | + # sort with unlocked first | |
218 | + unlocked = [t for t in tl if t in self.state] | |
219 | + locked = [t for t in tl if t not in unlocked] | |
201 | 220 | return unlocked + locked |
202 | 221 | |
203 | 222 | # ------------------------------------------------------------------------ |
... | ... | @@ -209,6 +228,10 @@ class StudentState(object): |
209 | 228 | return self.current_topic |
210 | 229 | |
211 | 230 | # ------------------------------------------------------------------------ |
231 | + def get_current_course_title(self) -> Optional[str]: | |
232 | + return self.courses[self.current_course]['title'] | |
233 | + | |
234 | + # ------------------------------------------------------------------------ | |
212 | 235 | def is_locked(self, topic: str) -> bool: |
213 | 236 | return topic not in self.state |
214 | 237 | ... | ... |
... | ... | @@ -0,0 +1,102 @@ |
1 | +{% autoescape %} | |
2 | + | |
3 | +<!doctype html> | |
4 | +<html lang="pt-PT"> | |
5 | +<head> | |
6 | + <title>{{appname}}</title> | |
7 | + <link rel="icon" href="/static/favicon.ico"> | |
8 | + | |
9 | + <meta charset="utf-8"> | |
10 | + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | |
11 | + <meta name="author" content="Miguel Barão"> | |
12 | + | |
13 | +<!-- Styles --> | |
14 | + <link rel="stylesheet" href="/static/mdbootstrap/css/bootstrap.min.css"> | |
15 | + <link rel="stylesheet" href="/static/mdbootstrap/css/mdb.min.css"> | |
16 | + <link rel="stylesheet" href="/static/css/maintopics.css"> | |
17 | + | |
18 | +<!-- Scripts --> | |
19 | + <script defer src="/static/mdbootstrap/js/jquery-3.4.1.min.js"></script> | |
20 | + <script defer src="/static/mdbootstrap/js/popper.min.js"></script> | |
21 | + <script defer src="/static/mdbootstrap/js/bootstrap.min.js"></script> | |
22 | + <script defer src="/static/mdbootstrap/js/mdb.min.js"></script> | |
23 | + <script defer src="/static/fontawesome-free/js/all.min.js"></script> | |
24 | + <script defer src="/static/js/maintopics.js"></script> | |
25 | + | |
26 | +</head> | |
27 | +<!-- ===================================================================== --> | |
28 | +<body> | |
29 | +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-primary"> | |
30 | + <img src="/static/logo_horizontal.png" height="48" width="120" class="navbar-brand" alt="UEvora"> | |
31 | + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation"> | |
32 | + <span class="navbar-toggler-icon"></span> | |
33 | + </button> | |
34 | + | |
35 | + <div class="collapse navbar-collapse" id="navbarText"> | |
36 | + <ul class="navbar-nav mr-auto"> | |
37 | + <li class="nav-item active"> | |
38 | + <a class="nav-link" href="#">Cursos<span class="sr-only">(actual)</span></a> | |
39 | + </li> | |
40 | + </ul> | |
41 | + | |
42 | + <ul class="navbar-nav"> | |
43 | + <li class="nav-item dropdown"> | |
44 | + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | |
45 | + <i class="fas fa-user" aria-hidden="true"></i> | |
46 | + <span id="name">{{ escape(name) }}</span> | |
47 | + <span class="caret"></span> | |
48 | + </a> | |
49 | + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> | |
50 | + <a class="dropdown-item" data-toggle="modal" data-target="#password_modal">Mudar Password</a> | |
51 | + <div class="dropdown-divider"></div> | |
52 | + <a class="dropdown-item" href="/logout">Sair</a> | |
53 | + </div> | |
54 | + </li> | |
55 | + </ul> | |
56 | + </div> | |
57 | +</nav> | |
58 | +<!-- ===================================================================== --> | |
59 | +<div class="container"> | |
60 | + | |
61 | + <div id="notifications"></div> | |
62 | + | |
63 | + {% for k,v in courses.items() %} | |
64 | + | |
65 | + <a href="/course/{{k}}" class="card text-dark mb-3"> | |
66 | + <div class="card-body"> | |
67 | + <h6 class="card-title">{{ v['title'] }}</h6> | |
68 | + </div> | |
69 | + </a> | |
70 | + | |
71 | + {% end %} <!-- for --> | |
72 | + | |
73 | + | |
74 | +<!-- === Change Password Modal =========================================== --> | |
75 | +<div id="password_modal" class="modal fade" tabindex="-1" role="dialog"> | |
76 | + <div class="modal-dialog" role="document"> | |
77 | + <div class="modal-content"> | |
78 | +<!-- header --> | |
79 | + <div class="modal-header"> | |
80 | + <h5 class="modal-title">Alterar Password</h5> | |
81 | + </div> | |
82 | +<!-- body --> | |
83 | + <div class="modal-body"> | |
84 | + <div class="control-group"> | |
85 | + <label for="new_password" class="control-label">Introduza a nova password:</label> | |
86 | + <div class="controls"> | |
87 | + <input type="password" id="new_password" name="new_password" autocomplete="new-password"> | |
88 | + </div> | |
89 | + </div> | |
90 | + </div> | |
91 | +<!-- footer --> | |
92 | + <div class="modal-footer"> | |
93 | + <button type="button" class="btn btn-default" data-dismiss="modal">Cancelar</button> | |
94 | + <button id="change_password" type="button" class="btn btn-danger" data-dismiss="modal">Alterar</button> | |
95 | + </div> | |
96 | + | |
97 | + </div><!-- /.modal-content --> | |
98 | + </div><!-- /.modal-dialog --> | |
99 | +</div><!-- /.modal --> | |
100 | + | |
101 | +</body> | |
102 | +</html> | ... | ... |
aprendizations/templates/maintopics-table.html
... | ... | @@ -34,8 +34,11 @@ |
34 | 34 | |
35 | 35 | <div class="collapse navbar-collapse" id="navbarText"> |
36 | 36 | <ul class="navbar-nav mr-auto"> |
37 | + <li class="nav-item"> | |
38 | + <a class="nav-link" href="/courses">Cursos</a> | |
39 | + </li> | |
37 | 40 | <li class="nav-item active"> |
38 | - <a class="nav-link" href="#">Tópicos<span class="sr-only">(current)</span></a> | |
41 | + <a class="nav-link" href="#">Tópicos<span class="sr-only">(actual)</span></a> | |
39 | 42 | </li> |
40 | 43 | <li class="nav-item"> |
41 | 44 | <a class="nav-link" href="/rankings">Classificação</a> | ... | ... |
aprendizations/templates/topic.html
... | ... | @@ -8,15 +8,18 @@ |
8 | 8 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
9 | 9 | <meta name="author" content="Miguel Barão"> |
10 | 10 | |
11 | -<!-- MathJax --> | |
12 | - <script type="text/x-mathjax-config"> | |
13 | - MathJax.Hub.Config({ | |
14 | - tex2jax: { | |
15 | - inlineMath: [["$$$","$$$"], ["$","$"], ["\\(","\\)"]] | |
16 | - } | |
17 | - }); | |
18 | - </script> | |
19 | - <script type="text/javascript" src="/static/mathjax/MathJax.js?delayStartupUntil=onload&config=TeX-AMS_CHTML-full"></script> | |
11 | +<!-- MathJax3 --> | |
12 | + <script> | |
13 | + MathJax = { | |
14 | + tex: { | |
15 | + inlineMath: [['$$$', '$$$'], ['\\(', '\\)']] | |
16 | + }, | |
17 | + svg: { | |
18 | + fontCache: 'global' | |
19 | + } | |
20 | + }; | |
21 | + </script> | |
22 | + <script async type="text/javascript" id="MathJax-script" src="/static/mathjax/es5/tex-svg.js"></script> | |
20 | 23 | |
21 | 24 | <!-- Styles --> |
22 | 25 | <link rel="stylesheet" href="/static/mdbootstrap/css/bootstrap.min.css"> | ... | ... |
package-lock.json
... | ... | @@ -3,42 +3,42 @@ |
3 | 3 | "lockfileVersion": 1, |
4 | 4 | "dependencies": { |
5 | 5 | "@babel/code-frame": { |
6 | - "version": "7.0.0", | |
7 | - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", | |
8 | - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", | |
6 | + "version": "7.5.5", | |
7 | + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", | |
8 | + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", | |
9 | 9 | "requires": { |
10 | 10 | "@babel/highlight": "^7.0.0" |
11 | 11 | } |
12 | 12 | }, |
13 | 13 | "@babel/core": { |
14 | - "version": "7.5.4", | |
15 | - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.4.tgz", | |
16 | - "integrity": "sha512-+DaeBEpYq6b2+ZmHx3tHspC+ZRflrvLqwfv8E3hNr5LVQoyBnL8RPKSBCg+rK2W2My9PWlujBiqd0ZPsR9Q6zQ==", | |
14 | + "version": "7.6.0", | |
15 | + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.0.tgz", | |
16 | + "integrity": "sha512-FuRhDRtsd6IptKpHXAa+4WPZYY2ZzgowkbLBecEDDSje1X/apG7jQM33or3NdOmjXBKWGOg4JmSiRfUfuTtHXw==", | |
17 | 17 | "requires": { |
18 | - "@babel/code-frame": "^7.0.0", | |
19 | - "@babel/generator": "^7.5.0", | |
20 | - "@babel/helpers": "^7.5.4", | |
21 | - "@babel/parser": "^7.5.0", | |
22 | - "@babel/template": "^7.4.4", | |
23 | - "@babel/traverse": "^7.5.0", | |
24 | - "@babel/types": "^7.5.0", | |
18 | + "@babel/code-frame": "^7.5.5", | |
19 | + "@babel/generator": "^7.6.0", | |
20 | + "@babel/helpers": "^7.6.0", | |
21 | + "@babel/parser": "^7.6.0", | |
22 | + "@babel/template": "^7.6.0", | |
23 | + "@babel/traverse": "^7.6.0", | |
24 | + "@babel/types": "^7.6.0", | |
25 | 25 | "convert-source-map": "^1.1.0", |
26 | 26 | "debug": "^4.1.0", |
27 | 27 | "json5": "^2.1.0", |
28 | - "lodash": "^4.17.11", | |
28 | + "lodash": "^4.17.13", | |
29 | 29 | "resolve": "^1.3.2", |
30 | 30 | "semver": "^5.4.1", |
31 | 31 | "source-map": "^0.5.0" |
32 | 32 | } |
33 | 33 | }, |
34 | 34 | "@babel/generator": { |
35 | - "version": "7.5.0", | |
36 | - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.0.tgz", | |
37 | - "integrity": "sha512-1TTVrt7J9rcG5PMjvO7VEG3FrEoEJNHxumRq66GemPmzboLWtIjjcJgk8rokuAS7IiRSpgVSu5Vb9lc99iJkOA==", | |
35 | + "version": "7.6.0", | |
36 | + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz", | |
37 | + "integrity": "sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA==", | |
38 | 38 | "requires": { |
39 | - "@babel/types": "^7.5.0", | |
39 | + "@babel/types": "^7.6.0", | |
40 | 40 | "jsesc": "^2.5.1", |
41 | - "lodash": "^4.17.11", | |
41 | + "lodash": "^4.17.13", | |
42 | 42 | "source-map": "^0.5.0", |
43 | 43 | "trim-right": "^1.0.1" |
44 | 44 | } |
... | ... | @@ -70,13 +70,13 @@ |
70 | 70 | } |
71 | 71 | }, |
72 | 72 | "@babel/helpers": { |
73 | - "version": "7.5.4", | |
74 | - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.4.tgz", | |
75 | - "integrity": "sha512-6LJ6xwUEJP51w0sIgKyfvFMJvIb9mWAfohJp0+m6eHJigkFdcH8duZ1sfhn0ltJRzwUIT/yqqhdSfRpCpL7oow==", | |
73 | + "version": "7.6.0", | |
74 | + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.0.tgz", | |
75 | + "integrity": "sha512-W9kao7OBleOjfXtFGgArGRX6eCP0UEcA2ZWEWNkJdRZnHhW4eEbeswbG3EwaRsnQUAEGWYgMq1HsIXuNNNy2eQ==", | |
76 | 76 | "requires": { |
77 | - "@babel/template": "^7.4.4", | |
78 | - "@babel/traverse": "^7.5.0", | |
79 | - "@babel/types": "^7.5.0" | |
77 | + "@babel/template": "^7.6.0", | |
78 | + "@babel/traverse": "^7.6.0", | |
79 | + "@babel/types": "^7.6.0" | |
80 | 80 | } |
81 | 81 | }, |
82 | 82 | "@babel/highlight": { |
... | ... | @@ -90,50 +90,50 @@ |
90 | 90 | } |
91 | 91 | }, |
92 | 92 | "@babel/parser": { |
93 | - "version": "7.5.0", | |
94 | - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.0.tgz", | |
95 | - "integrity": "sha512-I5nW8AhGpOXGCCNYGc+p7ExQIBxRFnS2fd/d862bNOKvmoEPjYPcfIjsfdy0ujagYOIYPczKgD9l3FsgTkAzKA==" | |
93 | + "version": "7.6.0", | |
94 | + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz", | |
95 | + "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ==" | |
96 | 96 | }, |
97 | 97 | "@babel/template": { |
98 | - "version": "7.4.4", | |
99 | - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", | |
100 | - "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", | |
98 | + "version": "7.6.0", | |
99 | + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", | |
100 | + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", | |
101 | 101 | "requires": { |
102 | 102 | "@babel/code-frame": "^7.0.0", |
103 | - "@babel/parser": "^7.4.4", | |
104 | - "@babel/types": "^7.4.4" | |
103 | + "@babel/parser": "^7.6.0", | |
104 | + "@babel/types": "^7.6.0" | |
105 | 105 | } |
106 | 106 | }, |
107 | 107 | "@babel/traverse": { |
108 | - "version": "7.5.0", | |
109 | - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.0.tgz", | |
110 | - "integrity": "sha512-SnA9aLbyOCcnnbQEGwdfBggnc142h/rbqqsXcaATj2hZcegCl903pUD/lfpsNBlBSuWow/YDfRyJuWi2EPR5cg==", | |
108 | + "version": "7.6.0", | |
109 | + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz", | |
110 | + "integrity": "sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ==", | |
111 | 111 | "requires": { |
112 | - "@babel/code-frame": "^7.0.0", | |
113 | - "@babel/generator": "^7.5.0", | |
112 | + "@babel/code-frame": "^7.5.5", | |
113 | + "@babel/generator": "^7.6.0", | |
114 | 114 | "@babel/helper-function-name": "^7.1.0", |
115 | 115 | "@babel/helper-split-export-declaration": "^7.4.4", |
116 | - "@babel/parser": "^7.5.0", | |
117 | - "@babel/types": "^7.5.0", | |
116 | + "@babel/parser": "^7.6.0", | |
117 | + "@babel/types": "^7.6.0", | |
118 | 118 | "debug": "^4.1.0", |
119 | 119 | "globals": "^11.1.0", |
120 | - "lodash": "^4.17.11" | |
120 | + "lodash": "^4.17.13" | |
121 | 121 | } |
122 | 122 | }, |
123 | 123 | "@babel/types": { |
124 | - "version": "7.5.0", | |
125 | - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.0.tgz", | |
126 | - "integrity": "sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==", | |
124 | + "version": "7.6.1", | |
125 | + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", | |
126 | + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", | |
127 | 127 | "requires": { |
128 | 128 | "esutils": "^2.0.2", |
129 | - "lodash": "^4.17.11", | |
129 | + "lodash": "^4.17.13", | |
130 | 130 | "to-fast-properties": "^2.0.0" |
131 | 131 | } |
132 | 132 | }, |
133 | 133 | "@fortawesome/fontawesome-free": { |
134 | - "version": "5.9.0", | |
135 | - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.9.0.tgz", | |
136 | - "integrity": "sha512-g795BBEzM/Hq2SYNPm/NQTIp3IWd4eXSH0ds87Na2jnrAUFX3wkyZAI4Gwj9DOaWMuz2/01i8oWI7P7T/XLkhg==" | |
134 | + "version": "5.10.2", | |
135 | + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.10.2.tgz", | |
136 | + "integrity": "sha512-9pw+Nsnunl9unstGEHQ+u41wBEQue6XPBsILXtJF/4fNN1L3avJcMF/gGF86rIjeTAgfLjTY9ndm68/X4f4idQ==" | |
137 | 137 | }, |
138 | 138 | "ansi-styles": { |
139 | 139 | "version": "3.2.1", |
... | ... | @@ -154,9 +154,9 @@ |
154 | 154 | } |
155 | 155 | }, |
156 | 156 | "codemirror": { |
157 | - "version": "5.48.0", | |
158 | - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.48.0.tgz", | |
159 | - "integrity": "sha512-3Ter+tYtRlTNtxtYdYNPxGxBL/b3cMcvPdPm70gvmcOO2Rauv/fUEewWa0tT596Hosv6ea2mtpx28OXBy1mQCg==" | |
157 | + "version": "5.48.4", | |
158 | + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.48.4.tgz", | |
159 | + "integrity": "sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA==" | |
160 | 160 | }, |
161 | 161 | "color-convert": { |
162 | 162 | "version": "1.9.3", |
... | ... | @@ -171,6 +171,11 @@ |
171 | 171 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", |
172 | 172 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" |
173 | 173 | }, |
174 | + "commander": { | |
175 | + "version": "3.0.1", | |
176 | + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.1.tgz", | |
177 | + "integrity": "sha512-UNgvDd+csKdc9GD4zjtkHKQbT8Aspt2jCBqNSPp53vAS0L1tS9sXB2TCEOPHJ7kt9bN/niWkYj8T3RQSoMXdSQ==" | |
178 | + }, | |
174 | 179 | "convert-source-map": { |
175 | 180 | "version": "1.6.0", |
176 | 181 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", |
... | ... | @@ -192,10 +197,15 @@ |
192 | 197 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", |
193 | 198 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" |
194 | 199 | }, |
200 | + "esm": { | |
201 | + "version": "3.2.25", | |
202 | + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", | |
203 | + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" | |
204 | + }, | |
195 | 205 | "esutils": { |
196 | - "version": "2.0.2", | |
197 | - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", | |
198 | - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" | |
206 | + "version": "2.0.3", | |
207 | + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", | |
208 | + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" | |
199 | 209 | }, |
200 | 210 | "globals": { |
201 | 211 | "version": "11.12.0", |
... | ... | @@ -226,19 +236,31 @@ |
226 | 236 | } |
227 | 237 | }, |
228 | 238 | "lodash": { |
229 | - "version": "4.17.14", | |
230 | - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", | |
231 | - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" | |
239 | + "version": "4.17.15", | |
240 | + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", | |
241 | + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" | |
232 | 242 | }, |
233 | 243 | "mathjax": { |
234 | - "version": "2.7.5", | |
235 | - "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.5.tgz", | |
236 | - "integrity": "sha512-OzsJNitEHAJB3y4IIlPCAvS0yoXwYjlo2Y4kmm9KQzyIBZt2d8yKRalby3uTRNN4fZQiGL2iMXjpdP1u2Rq2DQ==" | |
244 | + "version": "3.0.0", | |
245 | + "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.0.0.tgz", | |
246 | + "integrity": "sha512-z4uLbDHNbs/aRuR6zCcnzwFQuMixkHCcWqgVaommfK/3cA1Ahq7OXemn+m8JwTYcBApSHgcrSbPr9sm3sZFL+A==", | |
247 | + "requires": { | |
248 | + "mathjax-full": "git://github.com/mathjax/MathJax-src.git" | |
249 | + } | |
250 | + }, | |
251 | + "mathjax-full": { | |
252 | + "version": "git://github.com/mathjax/MathJax-src.git#0d74266e1820220d33cb6b29d4ca3575b352ac0d", | |
253 | + "from": "git://github.com/mathjax/MathJax-src.git", | |
254 | + "requires": { | |
255 | + "esm": "^3.2.25", | |
256 | + "mj-context-menu": "^0.2.0", | |
257 | + "speech-rule-engine": "^3.0.0-beta.6" | |
258 | + } | |
237 | 259 | }, |
238 | 260 | "mdbootstrap": { |
239 | - "version": "4.8.5", | |
240 | - "resolved": "https://registry.npmjs.org/mdbootstrap/-/mdbootstrap-4.8.5.tgz", | |
241 | - "integrity": "sha512-e4YGrdyb5dUlkLu0OdH5UKi6ZZEAC1YrfE8T8oWY+GWCbLjr1C0sI5ZEYAZrN8VI6acKN5tboq6lbK/8nWXG6g==", | |
261 | + "version": "4.8.10", | |
262 | + "resolved": "https://registry.npmjs.org/mdbootstrap/-/mdbootstrap-4.8.10.tgz", | |
263 | + "integrity": "sha512-pUjs7Vds4J+MwepOo4obUy7bQ5aMeB8j1c3IxIcEYXOXmn8GOWMSpiRcfSXpH9R4Fgdfie++e0fm5+SebRnTYA==", | |
242 | 264 | "requires": { |
243 | 265 | "@babel/core": "^7.3.3" |
244 | 266 | } |
... | ... | @@ -248,6 +270,11 @@ |
248 | 270 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", |
249 | 271 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" |
250 | 272 | }, |
273 | + "mj-context-menu": { | |
274 | + "version": "0.2.0", | |
275 | + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.2.0.tgz", | |
276 | + "integrity": "sha512-yJxrWBHCjFZEHsZgfs7m5g9OSCNzsVYadW6f6lX3pgZL67vmodtSW/4zhsYmuDKweXfHs0M1kJge1uQIasWA+g==" | |
277 | + }, | |
251 | 278 | "ms": { |
252 | 279 | "version": "2.1.2", |
253 | 280 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |
... | ... | @@ -259,9 +286,9 @@ |
259 | 286 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" |
260 | 287 | }, |
261 | 288 | "resolve": { |
262 | - "version": "1.11.1", | |
263 | - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", | |
264 | - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", | |
289 | + "version": "1.12.0", | |
290 | + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", | |
291 | + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", | |
265 | 292 | "requires": { |
266 | 293 | "path-parse": "^1.0.6" |
267 | 294 | } |
... | ... | @@ -272,15 +299,25 @@ |
272 | 299 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" |
273 | 300 | }, |
274 | 301 | "semver": { |
275 | - "version": "5.7.0", | |
276 | - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", | |
277 | - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" | |
302 | + "version": "5.7.1", | |
303 | + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", | |
304 | + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" | |
278 | 305 | }, |
279 | 306 | "source-map": { |
280 | 307 | "version": "0.5.7", |
281 | 308 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", |
282 | 309 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" |
283 | 310 | }, |
311 | + "speech-rule-engine": { | |
312 | + "version": "3.0.0-beta.6", | |
313 | + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.0.0-beta.6.tgz", | |
314 | + "integrity": "sha512-B7gcT53jAsKpx7WvFYQcyUlFmgS3Wa9KlDy0FY8SOTa+Wz5EqmI0MpCD5/fYm8/2qiCPp8HwZg+H3cBgM+sNVw==", | |
315 | + "requires": { | |
316 | + "commander": "*", | |
317 | + "wicked-good-xpath": "*", | |
318 | + "xmldom-sre": "^0.1.31" | |
319 | + } | |
320 | + }, | |
284 | 321 | "supports-color": { |
285 | 322 | "version": "5.5.0", |
286 | 323 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", |
... | ... | @@ -298,6 +335,16 @@ |
298 | 335 | "version": "1.0.1", |
299 | 336 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", |
300 | 337 | "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" |
338 | + }, | |
339 | + "wicked-good-xpath": { | |
340 | + "version": "1.3.0", | |
341 | + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", | |
342 | + "integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w=" | |
343 | + }, | |
344 | + "xmldom-sre": { | |
345 | + "version": "0.1.31", | |
346 | + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", | |
347 | + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==" | |
301 | 348 | } |
302 | 349 | } |
303 | 350 | } | ... | ... |
package.json
... | ... | @@ -2,10 +2,10 @@ |
2 | 2 | "description": "Javascript libraries required to run the server", |
3 | 3 | "email": "mjsb@uevora.pt", |
4 | 4 | "dependencies": { |
5 | - "@fortawesome/fontawesome-free": "^5.9.0", | |
6 | - "codemirror": "^5.48.0", | |
7 | - "mathjax": "^2.7.5", | |
8 | - "mdbootstrap": "^4.8.5" | |
5 | + "@fortawesome/fontawesome-free": "^5", | |
6 | + "codemirror": "^5.48", | |
7 | + "mathjax": "^3", | |
8 | + "mdbootstrap": "^4.8.10" | |
9 | 9 | }, |
10 | 10 | "private": true |
11 | 11 | } | ... | ... |