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 | # BUGS | 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 | - marking all options right in a radio question breaks! | 9 | - marking all options right in a radio question breaks! |
5 | - impedir que quando students.db não é encontrado, crie um ficheiro vazio. | 10 | - impedir que quando students.db não é encontrado, crie um ficheiro vazio. |
6 | - opcao --prefix devia afectar a base de dados? | 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,6 +29,7 @@ sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in | ||
24 | 29 | ||
25 | # TODO | 30 | # TODO |
26 | 31 | ||
32 | +- use run_script_async to run run_script using asyncio.run? | ||
27 | - ao fim de 3 tentativas de login, envial email para aluno com link para definir nova password (com timeout de 5 minutos). | 33 | - ao fim de 3 tentativas de login, envial email para aluno com link para definir nova password (com timeout de 5 minutos). |
28 | - mostrar capitulos e subtopicos de forma hierarquica. clicar no capitulo expande as dependencias. | 34 | - mostrar capitulos e subtopicos de forma hierarquica. clicar no capitulo expande as dependencias. |
29 | - mostrar rankings alunos/perguntas respondidas/% correctas/valor esperado topicos. | 35 | - mostrar rankings alunos/perguntas respondidas/% correctas/valor esperado topicos. |
README.md
@@ -284,11 +284,11 @@ me:\ | @@ -284,11 +284,11 @@ me:\ | ||
284 | Common database manipulations: | 284 | Common database manipulations: |
285 | 285 | ||
286 | ```sh | 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 | ```sh | 293 | ```sh |
294 | # Which students did at least one topic? | 294 | # Which students did at least one topic? |
aprendizations/learnapp.py
1 | 1 | ||
2 | # python standard library | 2 | # python standard library |
3 | -from os import path | ||
4 | -import logging | ||
5 | -from contextlib import contextmanager # `with` statement in db sessions | ||
6 | import asyncio | 3 | import asyncio |
4 | +from contextlib import contextmanager # `with` statement in db sessions | ||
7 | from datetime import datetime | 5 | from datetime import datetime |
6 | +import logging | ||
8 | from random import random | 7 | from random import random |
8 | +from os import path | ||
9 | from typing import Any, Dict, Iterable, List, Optional, Tuple | 9 | from typing import Any, Dict, Iterable, List, Optional, Tuple |
10 | 10 | ||
11 | # third party libraries | 11 | # third party libraries |
12 | import bcrypt | 12 | import bcrypt |
13 | -import sqlalchemy as sa | ||
14 | import networkx as nx | 13 | import networkx as nx |
14 | +import sqlalchemy as sa | ||
15 | 15 | ||
16 | # this project | 16 | # this project |
17 | from .models import Student, Answer, Topic, StudentTopic | 17 | from .models import Student, Answer, Topic, StudentTopic |
18 | -from .student import StudentState | ||
19 | from .questions import Question, QFactory, QDict | 18 | from .questions import Question, QFactory, QDict |
19 | +from .student import StudentState | ||
20 | from .tools import load_yaml | 20 | from .tools import load_yaml |
21 | 21 | ||
22 | # setup logger for this module | 22 | # setup logger for this module |
@@ -57,7 +57,7 @@ class LearnApp(object): | @@ -57,7 +57,7 @@ class LearnApp(object): | ||
57 | # init | 57 | # init |
58 | # ------------------------------------------------------------------------ | 58 | # ------------------------------------------------------------------------ |
59 | def __init__(self, | 59 | def __init__(self, |
60 | - config_files: List[str], | 60 | + courses: str, # filename with course configurations |
61 | prefix: str, # path to topics | 61 | prefix: str, # path to topics |
62 | db: str, # database filename | 62 | db: str, # database filename |
63 | check: bool = False) -> None: | 63 | check: bool = False) -> None: |
@@ -65,9 +65,18 @@ class LearnApp(object): | @@ -65,9 +65,18 @@ class LearnApp(object): | ||
65 | self.db_setup(db) # setup database and check students | 65 | self.db_setup(db) # setup database and check students |
66 | self.online: Dict[str, Dict] = dict() # online students | 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 | self.deps = nx.DiGraph(prefix=prefix) | 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 | # factory is a dict with question generators for all topics | 81 | # factory is a dict with question generators for all topics |
73 | self.factory: Dict[str, QFactory] = self.make_factory() | 82 | self.factory: Dict[str, QFactory] = self.make_factory() |
@@ -163,8 +172,9 @@ class LearnApp(object): | @@ -163,8 +172,9 @@ class LearnApp(object): | ||
163 | self.online[uid] = { | 172 | self.online[uid] = { |
164 | 'number': uid, | 173 | 'number': uid, |
165 | 'name': name, | 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 | 'counter': counter + 1, # counts simultaneous logins | 178 | 'counter': counter + 1, # counts simultaneous logins |
169 | } | 179 | } |
170 | 180 | ||
@@ -202,10 +212,10 @@ class LearnApp(object): | @@ -202,10 +212,10 @@ class LearnApp(object): | ||
202 | # checks answer (updating student state) and returns grade. FIXME type of answer | 212 | # checks answer (updating student state) and returns grade. FIXME type of answer |
203 | # ------------------------------------------------------------------------ | 213 | # ------------------------------------------------------------------------ |
204 | async def check_answer(self, uid: str, answer) -> Tuple[Question, str]: | 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 | logger.info(f'User "{uid}" got {q["grade"]:.2} in "{q["ref"]}"') | 220 | logger.info(f'User "{uid}" got {q["grade"]:.2} in "{q["ref"]}"') |
211 | 221 | ||
@@ -220,11 +230,11 @@ class LearnApp(object): | @@ -220,11 +230,11 @@ class LearnApp(object): | ||
220 | topic_id=topic)) | 230 | topic_id=topic)) |
221 | logger.debug(f'db insert answer of {q["ref"]}') | 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 | # finished topic, save into database | 234 | # finished topic, save into database |
225 | logger.info(f'User "{uid}" finished "{topic}"') | 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 | with self.db_session() as s: | 239 | with self.db_session() as s: |
230 | a = s.query(StudentTopic) \ | 240 | a = s.query(StudentTopic) \ |
@@ -250,6 +260,18 @@ class LearnApp(object): | @@ -250,6 +260,18 @@ class LearnApp(object): | ||
250 | return q, action | 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 | # Start new topic | 275 | # Start new topic |
254 | # ------------------------------------------------------------------------ | 276 | # ------------------------------------------------------------------------ |
255 | async def start_topic(self, uid: str, topic: str) -> None: | 277 | async def start_topic(self, uid: str, topic: str) -> None: |
@@ -305,8 +327,6 @@ class LearnApp(object): | @@ -305,8 +327,6 @@ class LearnApp(object): | ||
305 | # Edges are obtained from the deps defined in the YAML file for each topic. | 327 | # Edges are obtained from the deps defined in the YAML file for each topic. |
306 | # ------------------------------------------------------------------------ | 328 | # ------------------------------------------------------------------------ |
307 | def populate_graph(self, conffile: str) -> None: | 329 | def populate_graph(self, conffile: str) -> None: |
308 | - | ||
309 | - logger.info(f'Populating graph from: {conffile}...') | ||
310 | config: Dict[str, Any] = load_yaml(conffile) # course configuration | 330 | config: Dict[str, Any] = load_yaml(conffile) # course configuration |
311 | 331 | ||
312 | # default attributes that apply to the topics | 332 | # default attributes that apply to the topics |
@@ -347,7 +367,7 @@ class LearnApp(object): | @@ -347,7 +367,7 @@ class LearnApp(object): | ||
347 | t['append_wrong'] = attr.get('append_wrong', default_append_wrong) | 367 | t['append_wrong'] = attr.get('append_wrong', default_append_wrong) |
348 | t['questions'] = attr.get('questions', []) | 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 | # methods that do not change state (pure functions) | 373 | # methods that do not change state (pure functions) |
@@ -428,8 +448,13 @@ class LearnApp(object): | @@ -428,8 +448,13 @@ class LearnApp(object): | ||
428 | return self.online[uid]['state'].get_current_topic() | 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 | def get_topic_name(self, ref: str) -> str: | 460 | def get_topic_name(self, ref: str) -> str: |
@@ -442,6 +467,10 @@ class LearnApp(object): | @@ -442,6 +467,10 @@ class LearnApp(object): | ||
442 | return path.join(prefix, topic, 'public') | 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 | def get_rankings(self, uid: str) -> Iterable[Tuple[str, str, float, float]]: | 474 | def get_rankings(self, uid: str) -> Iterable[Tuple[str, str, float, float]]: |
446 | 475 | ||
447 | logger.info(f'User "{uid}" get rankings') | 476 | logger.info(f'User "{uid}" get rankings') |
aprendizations/main.py
@@ -24,8 +24,8 @@ def parse_cmdline_arguments(): | @@ -24,8 +24,8 @@ def parse_cmdline_arguments(): | ||
24 | ) | 24 | ) |
25 | 25 | ||
26 | argparser.add_argument( | 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 | argparser.add_argument( | 31 | argparser.add_argument( |
@@ -161,7 +161,9 @@ def main(): | @@ -161,7 +161,9 @@ def main(): | ||
161 | 161 | ||
162 | # --- start application | 162 | # --- start application |
163 | try: | 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 | check=arg.check) | 167 | check=arg.check) |
166 | except DatabaseUnusableError: | 168 | except DatabaseUnusableError: |
167 | logging.critical('Failed to start application.') | 169 | logging.critical('Failed to start application.') |
aprendizations/questions.py
@@ -461,6 +461,9 @@ class QFactory(object): | @@ -461,6 +461,9 @@ class QFactory(object): | ||
461 | # i.e. a question object (radio, checkbox, ...). | 461 | # i.e. a question object (radio, checkbox, ...). |
462 | # ----------------------------------------------------------------------- | 462 | # ----------------------------------------------------------------------- |
463 | def generate(self) -> Question: | 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 | logger.debug(f'generating {self.question["ref"]}...') | 467 | logger.debug(f'generating {self.question["ref"]}...') |
465 | # Shallow copy so that script generated questions will not replace | 468 | # Shallow copy so that script generated questions will not replace |
466 | # the original generators | 469 | # the original generators |
@@ -491,7 +494,7 @@ class QFactory(object): | @@ -491,7 +494,7 @@ class QFactory(object): | ||
491 | return qinstance | 494 | return qinstance |
492 | 495 | ||
493 | # ----------------------------------------------------------------------- | 496 | # ----------------------------------------------------------------------- |
494 | - async def generate_async(self) -> Question: | 497 | + async def gen_async(self) -> Question: |
495 | logger.debug(f'generating {self.question["ref"]}...') | 498 | logger.debug(f'generating {self.question["ref"]}...') |
496 | # Shallow copy so that script generated questions will not replace | 499 | # Shallow copy so that script generated questions will not replace |
497 | # the original generators | 500 | # the original generators |
@@ -520,5 +523,4 @@ class QFactory(object): | @@ -520,5 +523,4 @@ class QFactory(object): | ||
520 | logger.error(f'Invalid type "{q["type"]}" in "{q["ref"]}"') | 523 | logger.error(f'Invalid type "{q["type"]}" in "{q["ref"]}"') |
521 | raise | 524 | raise |
522 | else: | 525 | else: |
523 | - logger.debug('ok') | ||
524 | return qinstance | 526 | return qinstance |
aprendizations/serve.py
@@ -24,7 +24,7 @@ logger = logging.getLogger(__name__) | @@ -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 | def admin_only(func): | 29 | def admin_only(func): |
30 | @functools.wraps(func) | 30 | @functools.wraps(func) |
@@ -46,11 +46,14 @@ class WebApplication(tornado.web.Application): | @@ -46,11 +46,14 @@ class WebApplication(tornado.web.Application): | ||
46 | (r'/login', LoginHandler), | 46 | (r'/login', LoginHandler), |
47 | (r'/logout', LogoutHandler), | 47 | (r'/logout', LogoutHandler), |
48 | (r'/change_password', ChangePasswordHandler), | 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 | settings = { | 58 | settings = { |
56 | 'template_path': path.join(path.dirname(__file__), 'templates'), | 59 | 'template_path': path.join(path.dirname(__file__), 'templates'), |
@@ -166,23 +169,73 @@ class ChangePasswordHandler(BaseHandler): | @@ -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 | class RootHandler(BaseHandler): | 175 | class RootHandler(BaseHandler): |
173 | @tornado.web.authenticated | 176 | @tornado.web.authenticated |
174 | def get(self): | 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 | uid = self.current_user | 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 | self.render('maintopics-table.html', | 212 | self.render('maintopics-table.html', |
177 | appname=APP_NAME, | 213 | appname=APP_NAME, |
178 | uid=uid, | 214 | uid=uid, |
179 | name=self.learn.get_student_name(uid), | 215 | name=self.learn.get_student_name(uid), |
180 | state=self.learn.get_student_state(uid), | 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 | # /topic/... | 239 | # /topic/... |
187 | # Start a given topic | 240 | # Start a given topic |
188 | # ---------------------------------------------------------------------------- | 241 | # ---------------------------------------------------------------------------- |
@@ -194,7 +247,7 @@ class TopicHandler(BaseHandler): | @@ -194,7 +247,7 @@ class TopicHandler(BaseHandler): | ||
194 | try: | 247 | try: |
195 | await self.learn.start_topic(uid, topic) | 248 | await self.learn.start_topic(uid, topic) |
196 | except KeyError: | 249 | except KeyError: |
197 | - self.redirect('/') | 250 | + self.redirect('/topics') |
198 | 251 | ||
199 | self.render('topic.html', | 252 | self.render('topic.html', |
200 | appname=APP_NAME, | 253 | appname=APP_NAME, |
aprendizations/static/js/topic.js
@@ -69,7 +69,7 @@ function new_question(type, question, tries, progress) { | @@ -69,7 +69,7 @@ function new_question(type, question, tries, progress) { | ||
69 | var btntext = (type == "information") ? "Continuar" : "Responder"; | 69 | var btntext = (type == "information") ? "Continuar" : "Responder"; |
70 | $("#submit").html(btntext).off().click(postAnswer); | 70 | $("#submit").html(btntext).off().click(postAnswer); |
71 | $('#topic_progress').css('width', (100*progress)+'%').attr('aria-valuenow', 100*progress); | 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 | if (type == "radio") { | 74 | if (type == "radio") { |
75 | $(".list-group-item").click(function (e) { | 75 | $(".list-group-item").click(function (e) { |
@@ -130,12 +130,15 @@ function getFeedback(response) { | @@ -130,12 +130,15 @@ function getFeedback(response) { | ||
130 | $('#right').show(); | 130 | $('#right').show(); |
131 | $('#wrong').hide(); | 131 | $('#wrong').hide(); |
132 | $('#comments').html(params['comments']); | 132 | $('#comments').html(params['comments']); |
133 | - MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#comments"]); | 133 | + MathJax.typeset(); |
134 | + | ||
134 | $("#submit").html("Continuar").off().click(getQuestion); | 135 | $("#submit").html("Continuar").off().click(getQuestion); |
135 | $("#link_solution_on_right").click(function(){ | 136 | $("#link_solution_on_right").click(function(){ |
136 | $("#right").hide(); | 137 | $("#right").hide(); |
137 | $('#solution').html(params['solution']).animateCSS('flipInX'); | 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 | break; | 143 | break; |
141 | 144 | ||
@@ -144,7 +147,7 @@ function getFeedback(response) { | @@ -144,7 +147,7 @@ function getFeedback(response) { | ||
144 | $('#topic_progress').css('width', (100*params["progress"])+'%').attr('aria-valuenow', 100*params["progress"]); | 147 | $('#topic_progress').css('width', (100*params["progress"])+'%').attr('aria-valuenow', 100*params["progress"]); |
145 | showTriesLeft(params["tries"]); | 148 | showTriesLeft(params["tries"]); |
146 | $('#comments').html(params['comments']); | 149 | $('#comments').html(params['comments']); |
147 | - MathJax.Hub.Queue(["Typeset",MathJax.Hub,"#comments"]); | 150 | + MathJax.typeset(); |
148 | break; | 151 | break; |
149 | 152 | ||
150 | case "wrong": | 153 | case "wrong": |
@@ -153,11 +156,13 @@ function getFeedback(response) { | @@ -153,11 +156,13 @@ function getFeedback(response) { | ||
153 | $('#topic_progress').css('width', (100*params["progress"])+'%').attr('aria-valuenow', 100*params["progress"]); | 156 | $('#topic_progress').css('width', (100*params["progress"])+'%').attr('aria-valuenow', 100*params["progress"]); |
154 | showTriesLeft(params["tries"]); | 157 | showTriesLeft(params["tries"]); |
155 | $('#comments').html(params['comments']); | 158 | $('#comments').html(params['comments']); |
156 | - MathJax.Hub.Queue(["Typeset", MathJax.Hub, "#comments"]); | 159 | + // MathJax.typeset(); |
160 | + MathJax.typeset(); | ||
157 | $("#link_solution_on_wrong").click(function () { | 161 | $("#link_solution_on_wrong").click(function () { |
158 | $("#wrong").hide(); | 162 | $("#wrong").hide(); |
159 | $('#solution').html(params['solution']).animateCSS('flipInX'); | 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 | $("fieldset").attr("disabled", "disabled"); | 167 | $("fieldset").attr("disabled", "disabled"); |
163 | $("#submit").html("Continuar").off().click(getQuestion); | 168 | $("#submit").html("Continuar").off().click(getQuestion); |
aprendizations/student.py
1 | 1 | ||
2 | # python standard library | 2 | # python standard library |
3 | -import random | ||
4 | from datetime import datetime | 3 | from datetime import datetime |
5 | import logging | 4 | import logging |
6 | -from typing import List, Optional, Tuple | 5 | +import random |
6 | +from typing import Dict, List, Optional, Tuple | ||
7 | 7 | ||
8 | # third party libraries | 8 | # third party libraries |
9 | import networkx as nx | 9 | import networkx as nx |
@@ -28,45 +28,33 @@ class StudentState(object): | @@ -28,45 +28,33 @@ class StudentState(object): | ||
28 | # ======================================================================= | 28 | # ======================================================================= |
29 | # methods that update state | 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 | self.factory = factory # question factory | 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 | self.state = state # {'topic': {'level': 0.5, 'date': datetime}, ...} | 39 | self.state = state # {'topic': {'level': 0.5, 'date': datetime}, ...} |
35 | 40 | ||
41 | + # prepare for running | ||
36 | self.update_topic_levels() # applies forgetting factor | 42 | self.update_topic_levels() # applies forgetting factor |
37 | self.unlock_topics() # whose dependencies have been completed | 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 | # Start a new topic. | 60 | # Start a new topic. |
@@ -99,8 +87,8 @@ class StudentState(object): | @@ -99,8 +87,8 @@ class StudentState(object): | ||
99 | questions = t['questions'][:k] | 87 | questions = t['questions'][:k] |
100 | logger.debug(f'selected questions: {", ".join(questions)}') | 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 | n = len(self.questions) | 93 | n = len(self.questions) |
106 | logger.debug(f'generated {n} questions') | 94 | logger.debug(f'generated {n} questions') |
@@ -153,7 +141,9 @@ class StudentState(object): | @@ -153,7 +141,9 @@ class StudentState(object): | ||
153 | action = 'wrong' | 141 | action = 'wrong' |
154 | if self.current_question['append_wrong']: | 142 | if self.current_question['append_wrong']: |
155 | logger.debug('wrong answer, append new question') | 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 | self.next_question() | 147 | self.next_question() |
158 | 148 | ||
159 | # returns corrected question (not new one) which might include comments | 149 | # returns corrected question (not new one) which might include comments |
@@ -177,6 +167,35 @@ class StudentState(object): | @@ -177,6 +167,35 @@ class StudentState(object): | ||
177 | 167 | ||
178 | return self.current_question # question or None | 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 | # pure functions of the state (no side effects) | 200 | # pure functions of the state (no side effects) |
182 | # ======================================================================== | 201 | # ======================================================================== |
@@ -187,17 +206,17 @@ class StudentState(object): | @@ -187,17 +206,17 @@ class StudentState(object): | ||
187 | # ------------------------------------------------------------------------ | 206 | # ------------------------------------------------------------------------ |
188 | # compute recommended sequence of topics ['a', 'b', ...] | 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 | return unlocked + locked | 220 | return unlocked + locked |
202 | 221 | ||
203 | # ------------------------------------------------------------------------ | 222 | # ------------------------------------------------------------------------ |
@@ -209,6 +228,10 @@ class StudentState(object): | @@ -209,6 +228,10 @@ class StudentState(object): | ||
209 | return self.current_topic | 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 | def is_locked(self, topic: str) -> bool: | 235 | def is_locked(self, topic: str) -> bool: |
213 | return topic not in self.state | 236 | return topic not in self.state |
214 | 237 |
@@ -0,0 +1,102 @@ | @@ -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,8 +34,11 @@ | ||
34 | 34 | ||
35 | <div class="collapse navbar-collapse" id="navbarText"> | 35 | <div class="collapse navbar-collapse" id="navbarText"> |
36 | <ul class="navbar-nav mr-auto"> | 36 | <ul class="navbar-nav mr-auto"> |
37 | + <li class="nav-item"> | ||
38 | + <a class="nav-link" href="/courses">Cursos</a> | ||
39 | + </li> | ||
37 | <li class="nav-item active"> | 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 | </li> | 42 | </li> |
40 | <li class="nav-item"> | 43 | <li class="nav-item"> |
41 | <a class="nav-link" href="/rankings">Classificação</a> | 44 | <a class="nav-link" href="/rankings">Classificação</a> |
aprendizations/templates/topic.html
@@ -8,15 +8,18 @@ | @@ -8,15 +8,18 @@ | ||
8 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | 8 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
9 | <meta name="author" content="Miguel Barão"> | 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 | <!-- Styles --> | 24 | <!-- Styles --> |
22 | <link rel="stylesheet" href="/static/mdbootstrap/css/bootstrap.min.css"> | 25 | <link rel="stylesheet" href="/static/mdbootstrap/css/bootstrap.min.css"> |
package-lock.json
@@ -3,42 +3,42 @@ | @@ -3,42 +3,42 @@ | ||
3 | "lockfileVersion": 1, | 3 | "lockfileVersion": 1, |
4 | "dependencies": { | 4 | "dependencies": { |
5 | "@babel/code-frame": { | 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 | "requires": { | 9 | "requires": { |
10 | "@babel/highlight": "^7.0.0" | 10 | "@babel/highlight": "^7.0.0" |
11 | } | 11 | } |
12 | }, | 12 | }, |
13 | "@babel/core": { | 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 | "requires": { | 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 | "convert-source-map": "^1.1.0", | 25 | "convert-source-map": "^1.1.0", |
26 | "debug": "^4.1.0", | 26 | "debug": "^4.1.0", |
27 | "json5": "^2.1.0", | 27 | "json5": "^2.1.0", |
28 | - "lodash": "^4.17.11", | 28 | + "lodash": "^4.17.13", |
29 | "resolve": "^1.3.2", | 29 | "resolve": "^1.3.2", |
30 | "semver": "^5.4.1", | 30 | "semver": "^5.4.1", |
31 | "source-map": "^0.5.0" | 31 | "source-map": "^0.5.0" |
32 | } | 32 | } |
33 | }, | 33 | }, |
34 | "@babel/generator": { | 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 | "requires": { | 38 | "requires": { |
39 | - "@babel/types": "^7.5.0", | 39 | + "@babel/types": "^7.6.0", |
40 | "jsesc": "^2.5.1", | 40 | "jsesc": "^2.5.1", |
41 | - "lodash": "^4.17.11", | 41 | + "lodash": "^4.17.13", |
42 | "source-map": "^0.5.0", | 42 | "source-map": "^0.5.0", |
43 | "trim-right": "^1.0.1" | 43 | "trim-right": "^1.0.1" |
44 | } | 44 | } |
@@ -70,13 +70,13 @@ | @@ -70,13 +70,13 @@ | ||
70 | } | 70 | } |
71 | }, | 71 | }, |
72 | "@babel/helpers": { | 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 | "requires": { | 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 | "@babel/highlight": { | 82 | "@babel/highlight": { |
@@ -90,50 +90,50 @@ | @@ -90,50 +90,50 @@ | ||
90 | } | 90 | } |
91 | }, | 91 | }, |
92 | "@babel/parser": { | 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 | "@babel/template": { | 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 | "requires": { | 101 | "requires": { |
102 | "@babel/code-frame": "^7.0.0", | 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 | "@babel/traverse": { | 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 | "requires": { | 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 | "@babel/helper-function-name": "^7.1.0", | 114 | "@babel/helper-function-name": "^7.1.0", |
115 | "@babel/helper-split-export-declaration": "^7.4.4", | 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 | "debug": "^4.1.0", | 118 | "debug": "^4.1.0", |
119 | "globals": "^11.1.0", | 119 | "globals": "^11.1.0", |
120 | - "lodash": "^4.17.11" | 120 | + "lodash": "^4.17.13" |
121 | } | 121 | } |
122 | }, | 122 | }, |
123 | "@babel/types": { | 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 | "requires": { | 127 | "requires": { |
128 | "esutils": "^2.0.2", | 128 | "esutils": "^2.0.2", |
129 | - "lodash": "^4.17.11", | 129 | + "lodash": "^4.17.13", |
130 | "to-fast-properties": "^2.0.0" | 130 | "to-fast-properties": "^2.0.0" |
131 | } | 131 | } |
132 | }, | 132 | }, |
133 | "@fortawesome/fontawesome-free": { | 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 | "ansi-styles": { | 138 | "ansi-styles": { |
139 | "version": "3.2.1", | 139 | "version": "3.2.1", |
@@ -154,9 +154,9 @@ | @@ -154,9 +154,9 @@ | ||
154 | } | 154 | } |
155 | }, | 155 | }, |
156 | "codemirror": { | 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 | "color-convert": { | 161 | "color-convert": { |
162 | "version": "1.9.3", | 162 | "version": "1.9.3", |
@@ -171,6 +171,11 @@ | @@ -171,6 +171,11 @@ | ||
171 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", | 171 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", |
172 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" | 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 | "convert-source-map": { | 179 | "convert-source-map": { |
175 | "version": "1.6.0", | 180 | "version": "1.6.0", |
176 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", | 181 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", |
@@ -192,10 +197,15 @@ | @@ -192,10 +197,15 @@ | ||
192 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", | 197 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", |
193 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" | 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 | "esutils": { | 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 | "globals": { | 210 | "globals": { |
201 | "version": "11.12.0", | 211 | "version": "11.12.0", |
@@ -226,19 +236,31 @@ | @@ -226,19 +236,31 @@ | ||
226 | } | 236 | } |
227 | }, | 237 | }, |
228 | "lodash": { | 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 | "mathjax": { | 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 | "mdbootstrap": { | 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 | "requires": { | 264 | "requires": { |
243 | "@babel/core": "^7.3.3" | 265 | "@babel/core": "^7.3.3" |
244 | } | 266 | } |
@@ -248,6 +270,11 @@ | @@ -248,6 +270,11 @@ | ||
248 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", | 270 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", |
249 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" | 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 | "ms": { | 278 | "ms": { |
252 | "version": "2.1.2", | 279 | "version": "2.1.2", |
253 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | 280 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |
@@ -259,9 +286,9 @@ | @@ -259,9 +286,9 @@ | ||
259 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" | 286 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" |
260 | }, | 287 | }, |
261 | "resolve": { | 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 | "requires": { | 292 | "requires": { |
266 | "path-parse": "^1.0.6" | 293 | "path-parse": "^1.0.6" |
267 | } | 294 | } |
@@ -272,15 +299,25 @@ | @@ -272,15 +299,25 @@ | ||
272 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" | 299 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" |
273 | }, | 300 | }, |
274 | "semver": { | 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 | "source-map": { | 306 | "source-map": { |
280 | "version": "0.5.7", | 307 | "version": "0.5.7", |
281 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", | 308 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", |
282 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" | 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 | "supports-color": { | 321 | "supports-color": { |
285 | "version": "5.5.0", | 322 | "version": "5.5.0", |
286 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", | 323 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", |
@@ -298,6 +335,16 @@ | @@ -298,6 +335,16 @@ | ||
298 | "version": "1.0.1", | 335 | "version": "1.0.1", |
299 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", | 336 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", |
300 | "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" | 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,10 +2,10 @@ | ||
2 | "description": "Javascript libraries required to run the server", | 2 | "description": "Javascript libraries required to run the server", |
3 | "email": "mjsb@uevora.pt", | 3 | "email": "mjsb@uevora.pt", |
4 | "dependencies": { | 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 | "private": true | 10 | "private": true |
11 | } | 11 | } |