Commit 027c621a49ebc6f4c060f208386190e29533b50d

Authored by Miguel Barão
1 parent ba69141e
Exists in master and in 1 other branch dev

- fix bug where last question would not show images in the solution

BUGS.md
... ... @@ -19,10 +19,7 @@ Traceback (most recent call last):
19 19 raise nx.NetworkXError("The node %s is not in the graph." % source)
20 20 networkx.exception.NetworkXError: The node programming/languages/pseudo-tcg/functions-produtorio is not in the graph.
21 21  
22   -
23   -
24 22 - detectar se em courses.yaml falta declarar ficheiro. Por exemplo se houver goals que não estao em lado nenhum.
25   -- se num topico, a ultima pergunta tem imagens, o servidor nao fornece as imagengs porque o current_topic passa a None antes de carregar no botao continuar. O caminho é prefix+None e dá erro.
26 23 - registar last_seen e remover os antigos de cada vez que houver um login.
27 24 - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a)
28 25 - permite definir goal, mas nao verifica se esta no grafo. rebenta no start_topic.
... ... @@ -73,6 +70,7 @@ sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in
73 70  
74 71 # FIXED
75 72  
  73 +- se num topico, a ultima pergunta tem imagens, o servidor nao fornece as imagengs porque o current_topic passa a None antes de carregar no botao continuar. O caminho é prefix+None e dá erro.
76 74 - caixas com os cursos não se ajustam bem com ecran estreito.
77 75 - obter rankings por curso GET course=course_id
78 76 - no curso de linear algebra, as perguntas estao shuffled, mas nao deviam estar... nao esta a obedecer a keyword shuffle.
... ...
aprendizations/learnapp.py
... ... @@ -82,7 +82,7 @@ class LearnApp(object):
82 82 logger.info(f'{len(t):>6} topics in {courses}')
83 83 for f in config.get('topics_from', []):
84 84 c = load_yaml(f) # course configuration
85   - logger.info(f'{len(c["topics"]):>6} topics from {f}')
  85 + logger.info(f'{len(c["topics"]):>6} topics imported from {f}')
86 86 self.populate_graph(c)
87 87 logger.info(f'Graph has {len(self.deps)} topics')
88 88  
... ... @@ -217,13 +217,13 @@ class LearnApp(object):
217 217 return True
218 218  
219 219 # ------------------------------------------------------------------------
220   - # checks answer (updating student state) and returns grade. FIXME type of answer
  220 + # Checks answer and update database. Returns corrected question.
221 221 # ------------------------------------------------------------------------
222   - async def check_answer(self, uid: str, answer) -> Tuple[Question, str]:
  222 + async def check_answer(self, uid: str, answer) -> Question:
223 223 student = self.online[uid]['state']
224 224 topic = student.get_current_topic()
225 225  
226   - q, action = await student.check_answer(answer) # may move questions
  226 + q = await student.check_answer(answer)
227 227  
228 228 logger.info(f'User "{uid}" got {q["grade"]:.2} in "{q["ref"]}"')
229 229  
... ... @@ -238,8 +238,8 @@ class LearnApp(object):
238 238 topic_id=topic))
239 239 logger.debug(f'db insert answer of {q["ref"]}')
240 240  
  241 + # save topic if finished
241 242 if student.topic_has_finished():
242   - # finished topic, save into database
243 243 logger.info(f'User "{uid}" finished "{topic}"')
244 244 level: float = student.get_topic_level(topic)
245 245 date: str = str(student.get_topic_date(topic))
... ... @@ -265,7 +265,13 @@ class LearnApp(object):
265 265  
266 266 s.add(a)
267 267  
268   - return q, action
  268 + return q
  269 +
  270 + # ------------------------------------------------------------------------
  271 + # get the question to show (current or new one)
  272 + # ------------------------------------------------------------------------
  273 + async def get_question(self, uid: str) -> Optional[Question]:
  274 + return await self.online[uid]['state'].get_question()
269 275  
270 276 # ------------------------------------------------------------------------
271 277 # Start course
... ... @@ -390,6 +396,7 @@ class LearnApp(object):
390 396 topicpath: str = path.join(g.graph['prefix'], tref)
391 397 fullpath: str = path.join(topicpath, t['file'])
392 398  
  399 + logger.debug(f' Loading {fullpath}')
393 400 questions: List[QDict] = load_yaml(fullpath, default=[])
394 401  
395 402 # update refs to include topic as prefix.
... ... @@ -411,10 +418,11 @@ class LearnApp(object):
411 418 for q in questions:
412 419 if q['ref'] in t['questions']:
413 420 factory[q['ref']] = QFactory(q)
  421 + logger.debug(f' + {q["ref"]}')
414 422  
415   - logger.info(f'{len(t["questions"]):6} {tref}')
  423 + logger.info(f'{len(t["questions"]):6} questions in {tref}')
416 424  
417   - logger.info(f'Factory contains {len(factory)} questions')
  425 + logger.info(f'Factory has {len(factory)} questions')
418 426 return factory
419 427  
420 428 # ------------------------------------------------------------------------
... ...
aprendizations/main.py
... ... @@ -182,7 +182,7 @@ def main():
182 182 logging.critical('Failed to start backend.')
183 183 sys.exit(1)
184 184 else:
185   - logging.info('Backend started')
  185 + logging.info('LearnApp started')
186 186  
187 187 # --- run webserver forever
188 188 run_webserver(app=learnapp, ssl=ssl_ctx, port=arg.port, debug=arg.debug)
... ...
aprendizations/serve.py
... ... @@ -254,6 +254,7 @@ class TopicHandler(BaseHandler):
254 254 class FileHandler(BaseHandler):
255 255 @tornado.web.authenticated
256 256 async def get(self, filename):
  257 + # logger.debug(f'[FileHandler] {filename}')
257 258 uid = self.current_user
258 259 public_dir = self.learn.get_current_public_dir(uid)
259 260 filepath = path.expanduser(path.join(public_dir, filename))
... ... @@ -295,10 +296,10 @@ class QuestionHandler(BaseHandler):
295 296  
296 297 # --- get question to render
297 298 @tornado.web.authenticated
298   - def get(self):
299   - logger.debug('[QuestionHandler.get]')
  299 + async def get(self):
  300 + logger.debug('[QuestionHandler]')
300 301 user = self.current_user
301   - q = self.learn.get_current_question(user)
  302 + q = await self.learn.get_question(user)
302 303  
303 304 if q is not None:
304 305 qhtml = self.render_string(self.templates[q['type']],
... ... @@ -330,7 +331,7 @@ class QuestionHandler(BaseHandler):
330 331 user = self.current_user
331 332 answer = self.get_body_arguments('answer') # list
332 333 qid = self.get_body_arguments('qid')[0]
333   - logger.debug(f'[QuestionHandler.post] {user}, {qid}, answer={answer}')
  334 + logger.debug(f'[QuestionHandler] user={user}, answer={answer}')
334 335  
335 336 # --- check if browser opened different questions simultaneously
336 337 if qid != self.learn.get_current_question_id(user):
... ... @@ -345,7 +346,7 @@ class QuestionHandler(BaseHandler):
345 346 return
346 347  
347 348 # --- brain hacking ;)
348   - await asyncio.sleep(1)
  349 + await asyncio.sleep(2)
349 350  
350 351 # --- answers are in a list. fix depending on question type
351 352 qtype = self.learn.get_student_question_type(user)
... ... @@ -360,11 +361,11 @@ class QuestionHandler(BaseHandler):
360 361 ans = answer
361 362  
362 363 # --- check answer (nonblocking) and get corrected question and action
363   - q, action = await self.learn.check_answer(user, ans)
  364 + q = await self.learn.check_answer(user, ans)
364 365  
365 366 # --- built response to return
366   - response = {'method': action, 'params': {}}
367   - if action == 'right': # get next question in the topic
  367 + response = {'method': q['status'], 'params': {}}
  368 + if q['status'] == 'right': # get next question in the topic
368 369 comments_html = self.render_string(
369 370 'comments-right.html', comments=q['comments'], md=md_to_html)
370 371  
... ... @@ -378,7 +379,7 @@ class QuestionHandler(BaseHandler):
378 379 'solution': to_unicode(solution_html),
379 380 'tries': q['tries'],
380 381 }
381   - elif action == 'try_again':
  382 + elif q['status'] == 'try_again':
382 383 comments_html = self.render_string(
383 384 'comments.html', comments=q['comments'], md=md_to_html)
384 385  
... ... @@ -388,7 +389,7 @@ class QuestionHandler(BaseHandler):
388 389 'comments': to_unicode(comments_html),
389 390 'tries': q['tries'],
390 391 }
391   - elif action == 'wrong': # no more tries
  392 + elif q['status'] == 'wrong': # no more tries
392 393 comments_html = self.render_string(
393 394 'comments.html', comments=q['comments'], md=md_to_html)
394 395  
... ... @@ -403,7 +404,7 @@ class QuestionHandler(BaseHandler):
403 404 'tries': q['tries'],
404 405 }
405 406 else:
406   - logger.error(f'Unknown action: {action}')
  407 + logger.error(f'Unknown question status: {q["status"]}')
407 408  
408 409 self.write(response)
409 410  
... ... @@ -456,7 +457,7 @@ def run_webserver(app,
456 457  
457 458 # --- run webserver
458 459 signal.signal(signal.SIGINT, signal_handler)
459   - logger.info('Webserver running. (Ctrl-C to stop)')
  460 + logger.info('Webserver running... (Ctrl-C to stop)')
460 461  
461 462 try:
462 463 tornado.ioloop.IOLoop.current().start() # running...
... ...
aprendizations/student.py
... ... @@ -74,11 +74,11 @@ class StudentState(object):
74 74 logger.debug(f'is locked "{topic}"')
75 75 return
76 76  
77   - # starting new topic
  77 + # choose k questions
78 78 self.current_topic = topic
  79 + # self.current_question = None
79 80 self.correct_answers = 0
80 81 self.wrong_answers = 0
81   -
82 82 t = self.deps.nodes[topic]
83 83 k = t['choose']
84 84 if t['shuffle_questions']:
... ... @@ -90,8 +90,7 @@ class StudentState(object):
90 90 self.questions: List[Question] = [await self.factory[ref].gen_async()
91 91 for ref in questions]
92 92  
93   - n = len(self.questions)
94   - logger.debug(f'generated {n} questions')
  93 + logger.debug(f'generated {len(self.questions)} questions')
95 94  
96 95 # get first question
97 96 self.next_question()
... ... @@ -110,61 +109,70 @@ class StudentState(object):
110 109 self.wrong_answers)
111 110 }
112 111 self.current_topic = None
  112 + self.current_question = None
113 113 self.unlock_topics()
114 114  
115 115 # ------------------------------------------------------------------------
116   - # corrects current question with provided answer.
117   - # implements the logic:
118   - # - if answer ok, goes to next question
119   - # - if wrong, counts number of tries. If exceeded, moves on.
  116 + # corrects current question
120 117 # ------------------------------------------------------------------------
121 118 async def check_answer(self, answer) -> Tuple[Question, str]:
122   - q: Question = self.current_question
  119 + q = self.current_question
123 120 q['answer'] = answer
124 121 q['finish_time'] = datetime.now()
125   - logger.debug(f'checking answer of {q["ref"]}...')
126 122 await q.correct_async()
127   - logger.debug(f'grade = {q["grade"]:.2}')
128 123  
129 124 if q['grade'] > 0.999:
130 125 self.correct_answers += 1
131   - self.next_question()
132   - action = 'right'
  126 + q['status'] = 'right'
133 127  
134 128 else:
135 129 self.wrong_answers += 1
136   - self.current_question['tries'] -= 1
137   -
138   - if self.current_question['tries'] > 0:
139   - action = 'try_again'
  130 + q['tries'] -= 1
  131 + if q['tries'] > 0:
  132 + q['status'] = 'try_again'
140 133 else:
141   - action = 'wrong'
142   - if self.current_question['append_wrong']:
143   - logger.debug('wrong answer, append new question')
144   - new_question = await self.factory[q['ref']].gen_async()
145   - self.questions.append(new_question)
146   - self.next_question()
  134 + q['status'] = 'wrong'
147 135  
148   - # returns corrected question (not new one) which might include comments
149   - return q, action
  136 + logger.debug(f'ref = {q["ref"]}, status = {q["status"]}')
  137 + return q
150 138  
151 139 # ------------------------------------------------------------------------
152   - # Move to next question, or None
  140 + # get question to show, current or next
153 141 # ------------------------------------------------------------------------
154   - def next_question(self) -> Optional[Question]:
  142 + async def get_question(self) -> Optional[Question]:
  143 + q = self.current_question
  144 + logger.debug(f'{q["ref"]} status = {q["status"]}')
  145 +
  146 + if q['status'] == 'right':
  147 + self.next_question()
  148 + elif q['status'] == 'wrong':
  149 + if q['append_wrong']:
  150 + logger.debug(' wrong answer => append new question')
  151 + new_question = await self.factory[q['ref']].gen_async()
  152 + self.questions.append(new_question)
  153 + self.next_question()
  154 + # elif q['status'] == 'new':
  155 + # pass
  156 + # elif q['status'] == 'try_again':
  157 + # pass
  158 +
  159 + return self.current_question
  160 +
  161 + # ------------------------------------------------------------------------
  162 + # moves to next question
  163 + # ------------------------------------------------------------------------
  164 + def next_question(self) -> None:
155 165 try:
156   - self.current_question = self.questions.pop(0)
  166 + q = self.questions.pop(0)
157 167 except IndexError:
158   - self.current_question = None
159 168 self.finish_topic()
160   - else:
161   - self.current_question['start_time'] = datetime.now()
162   - default_maxtries = self.deps.nodes[self.current_topic]['max_tries']
163   - maxtries = self.current_question.get('max_tries', default_maxtries)
164   - self.current_question['tries'] = maxtries
165   - logger.debug(f'current_question = {self.current_question["ref"]}')
  169 + return
166 170  
167   - return self.current_question # question or None
  171 + t = self.deps.nodes[self.current_topic]
  172 + q['start_time'] = datetime.now()
  173 + q['tries'] = q.get('max_tries', t['max_tries'])
  174 + q['status'] = 'new'
  175 + self.current_question = q
168 176  
169 177 # ------------------------------------------------------------------------
170 178 # Update proficiency level of the topics using a forgetting factor
... ... @@ -177,7 +185,7 @@ class StudentState(object):
177 185 forgetting_factor = self.deps.nodes[tref]['forgetting_factor']
178 186 s['level'] *= forgetting_factor ** dt.days # forgetting factor
179 187 except KeyError:
180   - logger.warning(f'Topic {tref} is not on the graph!')
  188 + logger.warning(f'Update topic levels: {tref} not in the graph')
181 189  
182 190 # ------------------------------------------------------------------------
183 191 # Unlock topics whose dependencies are satisfied (> min_level)
... ...
package-lock.json
... ... @@ -2,244 +2,26 @@
2 2 "requires": true,
3 3 "lockfileVersion": 1,
4 4 "dependencies": {
5   - "@babel/code-frame": {
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": {
10   - "@babel/highlight": "^7.0.0"
11   - }
12   - },
13   - "@babel/core": {
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": {
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",
26   - "debug": "^4.1.0",
27   - "json5": "^2.1.0",
28   - "lodash": "^4.17.13",
29   - "resolve": "^1.3.2",
30   - "semver": "^5.4.1",
31   - "source-map": "^0.5.0"
32   - }
33   - },
34   - "@babel/generator": {
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": {
39   - "@babel/types": "^7.6.0",
40   - "jsesc": "^2.5.1",
41   - "lodash": "^4.17.13",
42   - "source-map": "^0.5.0",
43   - "trim-right": "^1.0.1"
44   - }
45   - },
46   - "@babel/helper-function-name": {
47   - "version": "7.1.0",
48   - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
49   - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
50   - "requires": {
51   - "@babel/helper-get-function-arity": "^7.0.0",
52   - "@babel/template": "^7.1.0",
53   - "@babel/types": "^7.0.0"
54   - }
55   - },
56   - "@babel/helper-get-function-arity": {
57   - "version": "7.0.0",
58   - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
59   - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
60   - "requires": {
61   - "@babel/types": "^7.0.0"
62   - }
63   - },
64   - "@babel/helper-split-export-declaration": {
65   - "version": "7.4.4",
66   - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
67   - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
68   - "requires": {
69   - "@babel/types": "^7.4.4"
70   - }
71   - },
72   - "@babel/helpers": {
73   - "version": "7.6.0",
74   - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.0.tgz",
75   - "integrity": "sha512-W9kao7OBleOjfXtFGgArGRX6eCP0UEcA2ZWEWNkJdRZnHhW4eEbeswbG3EwaRsnQUAEGWYgMq1HsIXuNNNy2eQ==",
76   - "requires": {
77   - "@babel/template": "^7.6.0",
78   - "@babel/traverse": "^7.6.0",
79   - "@babel/types": "^7.6.0"
80   - }
81   - },
82   - "@babel/highlight": {
83   - "version": "7.5.0",
84   - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
85   - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
86   - "requires": {
87   - "chalk": "^2.0.0",
88   - "esutils": "^2.0.2",
89   - "js-tokens": "^4.0.0"
90   - }
91   - },
92   - "@babel/parser": {
93   - "version": "7.6.0",
94   - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz",
95   - "integrity": "sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ=="
96   - },
97   - "@babel/template": {
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": {
102   - "@babel/code-frame": "^7.0.0",
103   - "@babel/parser": "^7.6.0",
104   - "@babel/types": "^7.6.0"
105   - }
106   - },
107   - "@babel/traverse": {
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": {
112   - "@babel/code-frame": "^7.5.5",
113   - "@babel/generator": "^7.6.0",
114   - "@babel/helper-function-name": "^7.1.0",
115   - "@babel/helper-split-export-declaration": "^7.4.4",
116   - "@babel/parser": "^7.6.0",
117   - "@babel/types": "^7.6.0",
118   - "debug": "^4.1.0",
119   - "globals": "^11.1.0",
120   - "lodash": "^4.17.13"
121   - }
122   - },
123   - "@babel/types": {
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": {
128   - "esutils": "^2.0.2",
129   - "lodash": "^4.17.13",
130   - "to-fast-properties": "^2.0.0"
131   - }
132   - },
133 5 "@fortawesome/fontawesome-free": {
134 6 "version": "5.11.2",
135 7 "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.11.2.tgz",
136 8 "integrity": "sha512-XiUPoS79r1G7PcpnNtq85TJ7inJWe0v+b5oZJZKb0pGHNIV6+UiNeQWiFGmuQ0aj7GEhnD/v9iqxIsjuRKtEnQ=="
137 9 },
138   - "ansi-styles": {
139   - "version": "3.2.1",
140   - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
141   - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
142   - "requires": {
143   - "color-convert": "^1.9.0"
144   - }
145   - },
146   - "chalk": {
147   - "version": "2.4.2",
148   - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
149   - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
150   - "requires": {
151   - "ansi-styles": "^3.2.1",
152   - "escape-string-regexp": "^1.0.5",
153   - "supports-color": "^5.3.0"
154   - }
155   - },
156 10 "codemirror": {
157   - "version": "5.49.0",
158   - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.49.0.tgz",
159   - "integrity": "sha512-Hyzr0HToBdZpLBN9dYFO/KlJAsKH37/cXVHPAqa+imml0R92tb9AkmsvjnXL+SluEvjjdfkDgRjc65NG5jnMYA=="
160   - },
161   - "color-convert": {
162   - "version": "1.9.3",
163   - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
164   - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
165   - "requires": {
166   - "color-name": "1.1.3"
167   - }
168   - },
169   - "color-name": {
170   - "version": "1.1.3",
171   - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
172   - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
  11 + "version": "5.49.2",
  12 + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.49.2.tgz",
  13 + "integrity": "sha512-dwJ2HRPHm8w51WB5YTF9J7m6Z5dtkqbU9ntMZ1dqXyFB9IpjoUFDj80ahRVEoVanfIp6pfASJbOlbWdEf8FOzQ=="
173 14 },
174 15 "commander": {
175 16 "version": "3.0.1",
176 17 "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.1.tgz",
177 18 "integrity": "sha512-UNgvDd+csKdc9GD4zjtkHKQbT8Aspt2jCBqNSPp53vAS0L1tS9sXB2TCEOPHJ7kt9bN/niWkYj8T3RQSoMXdSQ=="
178 19 },
179   - "convert-source-map": {
180   - "version": "1.6.0",
181   - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
182   - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
183   - "requires": {
184   - "safe-buffer": "~5.1.1"
185   - }
186   - },
187   - "debug": {
188   - "version": "4.1.1",
189   - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
190   - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
191   - "requires": {
192   - "ms": "^2.1.1"
193   - }
194   - },
195   - "escape-string-regexp": {
196   - "version": "1.0.5",
197   - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
198   - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
199   - },
200 20 "esm": {
201 21 "version": "3.2.25",
202 22 "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
203 23 "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
204 24 },
205   - "esutils": {
206   - "version": "2.0.3",
207   - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
208   - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
209   - },
210   - "globals": {
211   - "version": "11.12.0",
212   - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
213   - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
214   - },
215   - "has-flag": {
216   - "version": "3.0.0",
217   - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
218   - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
219   - },
220   - "js-tokens": {
221   - "version": "4.0.0",
222   - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
223   - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
224   - },
225   - "jsesc": {
226   - "version": "2.5.2",
227   - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
228   - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
229   - },
230   - "json5": {
231   - "version": "2.1.0",
232   - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz",
233   - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==",
234   - "requires": {
235   - "minimist": "^1.2.0"
236   - }
237   - },
238   - "lodash": {
239   - "version": "4.17.15",
240   - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
241   - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
242   - },
243 25 "mathjax": {
244 26 "version": "3.0.0",
245 27 "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.0.0.tgz",
... ... @@ -258,56 +40,15 @@
258 40 }
259 41 },
260 42 "mdbootstrap": {
261   - "version": "4.8.10",
262   - "resolved": "https://registry.npmjs.org/mdbootstrap/-/mdbootstrap-4.8.10.tgz",
263   - "integrity": "sha512-pUjs7Vds4J+MwepOo4obUy7bQ5aMeB8j1c3IxIcEYXOXmn8GOWMSpiRcfSXpH9R4Fgdfie++e0fm5+SebRnTYA==",
264   - "requires": {
265   - "@babel/core": "^7.3.3"
266   - }
267   - },
268   - "minimist": {
269   - "version": "1.2.0",
270   - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
271   - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
  43 + "version": "4.9.0",
  44 + "resolved": "https://registry.npmjs.org/mdbootstrap/-/mdbootstrap-4.9.0.tgz",
  45 + "integrity": "sha512-6R3j5D9Qmp+Aa90FblOVAwVDSqpAICYW2dpNxh6uaVB9E9MCaBLdaTKLrXCB7xznReHEaA57pNABXgFoi2z7Rg=="
272 46 },
273 47 "mj-context-menu": {
274 48 "version": "0.2.0",
275 49 "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.2.0.tgz",
276 50 "integrity": "sha512-yJxrWBHCjFZEHsZgfs7m5g9OSCNzsVYadW6f6lX3pgZL67vmodtSW/4zhsYmuDKweXfHs0M1kJge1uQIasWA+g=="
277 51 },
278   - "ms": {
279   - "version": "2.1.2",
280   - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
281   - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
282   - },
283   - "path-parse": {
284   - "version": "1.0.6",
285   - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
286   - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
287   - },
288   - "resolve": {
289   - "version": "1.12.0",
290   - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
291   - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
292   - "requires": {
293   - "path-parse": "^1.0.6"
294   - }
295   - },
296   - "safe-buffer": {
297   - "version": "5.1.2",
298   - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
299   - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
300   - },
301   - "semver": {
302   - "version": "5.7.1",
303   - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
304   - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
305   - },
306   - "source-map": {
307   - "version": "0.5.7",
308   - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
309   - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
310   - },
311 52 "speech-rule-engine": {
312 53 "version": "3.0.0-beta.6",
313 54 "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.0.0-beta.6.tgz",
... ... @@ -318,24 +59,6 @@
318 59 "xmldom-sre": "^0.1.31"
319 60 }
320 61 },
321   - "supports-color": {
322   - "version": "5.5.0",
323   - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
324   - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
325   - "requires": {
326   - "has-flag": "^3.0.0"
327   - }
328   - },
329   - "to-fast-properties": {
330   - "version": "2.0.0",
331   - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
332   - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
333   - },
334   - "trim-right": {
335   - "version": "1.0.1",
336   - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
337   - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM="
338   - },
339 62 "wicked-good-xpath": {
340 63 "version": "1.3.0",
341 64 "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
... ...
package.json
... ... @@ -3,9 +3,9 @@
3 3 "email": "mjsb@uevora.pt",
4 4 "dependencies": {
5 5 "@fortawesome/fontawesome-free": "^5.11.2",
6   - "codemirror": "^5.49.0",
  6 + "codemirror": "^5.49.2",
7 7 "mathjax": "^3",
8   - "mdbootstrap": "^4.8.10"
  8 + "mdbootstrap": "^4.9.0"
9 9 },
10 10 "private": true
11 11 }
... ...