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

@@ -19,10 +19,7 @@ Traceback (most recent call last): @@ -19,10 +19,7 @@ Traceback (most recent call last):
19 raise nx.NetworkXError("The node %s is not in the graph." % source) 19 raise nx.NetworkXError("The node %s is not in the graph." % source)
20 networkx.exception.NetworkXError: The node programming/languages/pseudo-tcg/functions-produtorio is not in the graph. 20 networkx.exception.NetworkXError: The node programming/languages/pseudo-tcg/functions-produtorio is not in the graph.
21 21
22 -  
23 -  
24 - detectar se em courses.yaml falta declarar ficheiro. Por exemplo se houver goals que não estao em lado nenhum. 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 - registar last_seen e remover os antigos de cada vez que houver um login. 23 - registar last_seen e remover os antigos de cada vez que houver um login.
27 - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) 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 - permite definir goal, mas nao verifica se esta no grafo. rebenta no start_topic. 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,6 +70,7 @@ sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in
73 70
74 # FIXED 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 - caixas com os cursos não se ajustam bem com ecran estreito. 74 - caixas com os cursos não se ajustam bem com ecran estreito.
77 - obter rankings por curso GET course=course_id 75 - obter rankings por curso GET course=course_id
78 - no curso de linear algebra, as perguntas estao shuffled, mas nao deviam estar... nao esta a obedecer a keyword shuffle. 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,7 +82,7 @@ class LearnApp(object):
82 logger.info(f'{len(t):>6} topics in {courses}') 82 logger.info(f'{len(t):>6} topics in {courses}')
83 for f in config.get('topics_from', []): 83 for f in config.get('topics_from', []):
84 c = load_yaml(f) # course configuration 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 self.populate_graph(c) 86 self.populate_graph(c)
87 logger.info(f'Graph has {len(self.deps)} topics') 87 logger.info(f'Graph has {len(self.deps)} topics')
88 88
@@ -217,13 +217,13 @@ class LearnApp(object): @@ -217,13 +217,13 @@ class LearnApp(object):
217 return True 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 student = self.online[uid]['state'] 223 student = self.online[uid]['state']
224 topic = student.get_current_topic() 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 logger.info(f'User "{uid}" got {q["grade"]:.2} in "{q["ref"]}"') 228 logger.info(f'User "{uid}" got {q["grade"]:.2} in "{q["ref"]}"')
229 229
@@ -238,8 +238,8 @@ class LearnApp(object): @@ -238,8 +238,8 @@ class LearnApp(object):
238 topic_id=topic)) 238 topic_id=topic))
239 logger.debug(f'db insert answer of {q["ref"]}') 239 logger.debug(f'db insert answer of {q["ref"]}')
240 240
  241 + # save topic if finished
241 if student.topic_has_finished(): 242 if student.topic_has_finished():
242 - # finished topic, save into database  
243 logger.info(f'User "{uid}" finished "{topic}"') 243 logger.info(f'User "{uid}" finished "{topic}"')
244 level: float = student.get_topic_level(topic) 244 level: float = student.get_topic_level(topic)
245 date: str = str(student.get_topic_date(topic)) 245 date: str = str(student.get_topic_date(topic))
@@ -265,7 +265,13 @@ class LearnApp(object): @@ -265,7 +265,13 @@ class LearnApp(object):
265 265
266 s.add(a) 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 # Start course 277 # Start course
@@ -390,6 +396,7 @@ class LearnApp(object): @@ -390,6 +396,7 @@ class LearnApp(object):
390 topicpath: str = path.join(g.graph['prefix'], tref) 396 topicpath: str = path.join(g.graph['prefix'], tref)
391 fullpath: str = path.join(topicpath, t['file']) 397 fullpath: str = path.join(topicpath, t['file'])
392 398
  399 + logger.debug(f' Loading {fullpath}')
393 questions: List[QDict] = load_yaml(fullpath, default=[]) 400 questions: List[QDict] = load_yaml(fullpath, default=[])
394 401
395 # update refs to include topic as prefix. 402 # update refs to include topic as prefix.
@@ -411,10 +418,11 @@ class LearnApp(object): @@ -411,10 +418,11 @@ class LearnApp(object):
411 for q in questions: 418 for q in questions:
412 if q['ref'] in t['questions']: 419 if q['ref'] in t['questions']:
413 factory[q['ref']] = QFactory(q) 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 return factory 426 return factory
419 427
420 # ------------------------------------------------------------------------ 428 # ------------------------------------------------------------------------
aprendizations/main.py
@@ -182,7 +182,7 @@ def main(): @@ -182,7 +182,7 @@ def main():
182 logging.critical('Failed to start backend.') 182 logging.critical('Failed to start backend.')
183 sys.exit(1) 183 sys.exit(1)
184 else: 184 else:
185 - logging.info('Backend started') 185 + logging.info('LearnApp started')
186 186
187 # --- run webserver forever 187 # --- run webserver forever
188 run_webserver(app=learnapp, ssl=ssl_ctx, port=arg.port, debug=arg.debug) 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,6 +254,7 @@ class TopicHandler(BaseHandler):
254 class FileHandler(BaseHandler): 254 class FileHandler(BaseHandler):
255 @tornado.web.authenticated 255 @tornado.web.authenticated
256 async def get(self, filename): 256 async def get(self, filename):
  257 + # logger.debug(f'[FileHandler] {filename}')
257 uid = self.current_user 258 uid = self.current_user
258 public_dir = self.learn.get_current_public_dir(uid) 259 public_dir = self.learn.get_current_public_dir(uid)
259 filepath = path.expanduser(path.join(public_dir, filename)) 260 filepath = path.expanduser(path.join(public_dir, filename))
@@ -295,10 +296,10 @@ class QuestionHandler(BaseHandler): @@ -295,10 +296,10 @@ class QuestionHandler(BaseHandler):
295 296
296 # --- get question to render 297 # --- get question to render
297 @tornado.web.authenticated 298 @tornado.web.authenticated
298 - def get(self):  
299 - logger.debug('[QuestionHandler.get]') 299 + async def get(self):
  300 + logger.debug('[QuestionHandler]')
300 user = self.current_user 301 user = self.current_user
301 - q = self.learn.get_current_question(user) 302 + q = await self.learn.get_question(user)
302 303
303 if q is not None: 304 if q is not None:
304 qhtml = self.render_string(self.templates[q['type']], 305 qhtml = self.render_string(self.templates[q['type']],
@@ -330,7 +331,7 @@ class QuestionHandler(BaseHandler): @@ -330,7 +331,7 @@ class QuestionHandler(BaseHandler):
330 user = self.current_user 331 user = self.current_user
331 answer = self.get_body_arguments('answer') # list 332 answer = self.get_body_arguments('answer') # list
332 qid = self.get_body_arguments('qid')[0] 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 # --- check if browser opened different questions simultaneously 336 # --- check if browser opened different questions simultaneously
336 if qid != self.learn.get_current_question_id(user): 337 if qid != self.learn.get_current_question_id(user):
@@ -345,7 +346,7 @@ class QuestionHandler(BaseHandler): @@ -345,7 +346,7 @@ class QuestionHandler(BaseHandler):
345 return 346 return
346 347
347 # --- brain hacking ;) 348 # --- brain hacking ;)
348 - await asyncio.sleep(1) 349 + await asyncio.sleep(2)
349 350
350 # --- answers are in a list. fix depending on question type 351 # --- answers are in a list. fix depending on question type
351 qtype = self.learn.get_student_question_type(user) 352 qtype = self.learn.get_student_question_type(user)
@@ -360,11 +361,11 @@ class QuestionHandler(BaseHandler): @@ -360,11 +361,11 @@ class QuestionHandler(BaseHandler):
360 ans = answer 361 ans = answer
361 362
362 # --- check answer (nonblocking) and get corrected question and action 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 # --- built response to return 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 comments_html = self.render_string( 369 comments_html = self.render_string(
369 'comments-right.html', comments=q['comments'], md=md_to_html) 370 'comments-right.html', comments=q['comments'], md=md_to_html)
370 371
@@ -378,7 +379,7 @@ class QuestionHandler(BaseHandler): @@ -378,7 +379,7 @@ class QuestionHandler(BaseHandler):
378 'solution': to_unicode(solution_html), 379 'solution': to_unicode(solution_html),
379 'tries': q['tries'], 380 'tries': q['tries'],
380 } 381 }
381 - elif action == 'try_again': 382 + elif q['status'] == 'try_again':
382 comments_html = self.render_string( 383 comments_html = self.render_string(
383 'comments.html', comments=q['comments'], md=md_to_html) 384 'comments.html', comments=q['comments'], md=md_to_html)
384 385
@@ -388,7 +389,7 @@ class QuestionHandler(BaseHandler): @@ -388,7 +389,7 @@ class QuestionHandler(BaseHandler):
388 'comments': to_unicode(comments_html), 389 'comments': to_unicode(comments_html),
389 'tries': q['tries'], 390 'tries': q['tries'],
390 } 391 }
391 - elif action == 'wrong': # no more tries 392 + elif q['status'] == 'wrong': # no more tries
392 comments_html = self.render_string( 393 comments_html = self.render_string(
393 'comments.html', comments=q['comments'], md=md_to_html) 394 'comments.html', comments=q['comments'], md=md_to_html)
394 395
@@ -403,7 +404,7 @@ class QuestionHandler(BaseHandler): @@ -403,7 +404,7 @@ class QuestionHandler(BaseHandler):
403 'tries': q['tries'], 404 'tries': q['tries'],
404 } 405 }
405 else: 406 else:
406 - logger.error(f'Unknown action: {action}') 407 + logger.error(f'Unknown question status: {q["status"]}')
407 408
408 self.write(response) 409 self.write(response)
409 410
@@ -456,7 +457,7 @@ def run_webserver(app, @@ -456,7 +457,7 @@ def run_webserver(app,
456 457
457 # --- run webserver 458 # --- run webserver
458 signal.signal(signal.SIGINT, signal_handler) 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 try: 462 try:
462 tornado.ioloop.IOLoop.current().start() # running... 463 tornado.ioloop.IOLoop.current().start() # running...
aprendizations/student.py
@@ -74,11 +74,11 @@ class StudentState(object): @@ -74,11 +74,11 @@ class StudentState(object):
74 logger.debug(f'is locked "{topic}"') 74 logger.debug(f'is locked "{topic}"')
75 return 75 return
76 76
77 - # starting new topic 77 + # choose k questions
78 self.current_topic = topic 78 self.current_topic = topic
  79 + # self.current_question = None
79 self.correct_answers = 0 80 self.correct_answers = 0
80 self.wrong_answers = 0 81 self.wrong_answers = 0
81 -  
82 t = self.deps.nodes[topic] 82 t = self.deps.nodes[topic]
83 k = t['choose'] 83 k = t['choose']
84 if t['shuffle_questions']: 84 if t['shuffle_questions']:
@@ -90,8 +90,7 @@ class StudentState(object): @@ -90,8 +90,7 @@ class StudentState(object):
90 self.questions: List[Question] = [await self.factory[ref].gen_async() 90 self.questions: List[Question] = [await self.factory[ref].gen_async()
91 for ref in questions] 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 # get first question 95 # get first question
97 self.next_question() 96 self.next_question()
@@ -110,61 +109,70 @@ class StudentState(object): @@ -110,61 +109,70 @@ class StudentState(object):
110 self.wrong_answers) 109 self.wrong_answers)
111 } 110 }
112 self.current_topic = None 111 self.current_topic = None
  112 + self.current_question = None
113 self.unlock_topics() 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 async def check_answer(self, answer) -> Tuple[Question, str]: 118 async def check_answer(self, answer) -> Tuple[Question, str]:
122 - q: Question = self.current_question 119 + q = self.current_question
123 q['answer'] = answer 120 q['answer'] = answer
124 q['finish_time'] = datetime.now() 121 q['finish_time'] = datetime.now()
125 - logger.debug(f'checking answer of {q["ref"]}...')  
126 await q.correct_async() 122 await q.correct_async()
127 - logger.debug(f'grade = {q["grade"]:.2}')  
128 123
129 if q['grade'] > 0.999: 124 if q['grade'] > 0.999:
130 self.correct_answers += 1 125 self.correct_answers += 1
131 - self.next_question()  
132 - action = 'right' 126 + q['status'] = 'right'
133 127
134 else: 128 else:
135 self.wrong_answers += 1 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 else: 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 try: 165 try:
156 - self.current_question = self.questions.pop(0) 166 + q = self.questions.pop(0)
157 except IndexError: 167 except IndexError:
158 - self.current_question = None  
159 self.finish_topic() 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 # Update proficiency level of the topics using a forgetting factor 178 # Update proficiency level of the topics using a forgetting factor
@@ -177,7 +185,7 @@ class StudentState(object): @@ -177,7 +185,7 @@ class StudentState(object):
177 forgetting_factor = self.deps.nodes[tref]['forgetting_factor'] 185 forgetting_factor = self.deps.nodes[tref]['forgetting_factor']
178 s['level'] *= forgetting_factor ** dt.days # forgetting factor 186 s['level'] *= forgetting_factor ** dt.days # forgetting factor
179 except KeyError: 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 # Unlock topics whose dependencies are satisfied (> min_level) 191 # Unlock topics whose dependencies are satisfied (> min_level)
package-lock.json
@@ -2,244 +2,26 @@ @@ -2,244 +2,26 @@
2 "requires": true, 2 "requires": true,
3 "lockfileVersion": 1, 3 "lockfileVersion": 1,
4 "dependencies": { 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 "@fortawesome/fontawesome-free": { 5 "@fortawesome/fontawesome-free": {
134 "version": "5.11.2", 6 "version": "5.11.2",
135 "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.11.2.tgz", 7 "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.11.2.tgz",
136 "integrity": "sha512-XiUPoS79r1G7PcpnNtq85TJ7inJWe0v+b5oZJZKb0pGHNIV6+UiNeQWiFGmuQ0aj7GEhnD/v9iqxIsjuRKtEnQ==" 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 "codemirror": { 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 "commander": { 15 "commander": {
175 "version": "3.0.1", 16 "version": "3.0.1",
176 "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.1.tgz", 17 "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.1.tgz",
177 "integrity": "sha512-UNgvDd+csKdc9GD4zjtkHKQbT8Aspt2jCBqNSPp53vAS0L1tS9sXB2TCEOPHJ7kt9bN/niWkYj8T3RQSoMXdSQ==" 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 "esm": { 20 "esm": {
201 "version": "3.2.25", 21 "version": "3.2.25",
202 "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", 22 "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
203 "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" 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 "mathjax": { 25 "mathjax": {
244 "version": "3.0.0", 26 "version": "3.0.0",
245 "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.0.0.tgz", 27 "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.0.0.tgz",
@@ -258,56 +40,15 @@ @@ -258,56 +40,15 @@
258 } 40 }
259 }, 41 },
260 "mdbootstrap": { 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 "mj-context-menu": { 47 "mj-context-menu": {
274 "version": "0.2.0", 48 "version": "0.2.0",
275 "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.2.0.tgz", 49 "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.2.0.tgz",
276 "integrity": "sha512-yJxrWBHCjFZEHsZgfs7m5g9OSCNzsVYadW6f6lX3pgZL67vmodtSW/4zhsYmuDKweXfHs0M1kJge1uQIasWA+g==" 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 "speech-rule-engine": { 52 "speech-rule-engine": {
312 "version": "3.0.0-beta.6", 53 "version": "3.0.0-beta.6",
313 "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.0.0-beta.6.tgz", 54 "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.0.0-beta.6.tgz",
@@ -318,24 +59,6 @@ @@ -318,24 +59,6 @@
318 "xmldom-sre": "^0.1.31" 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 "wicked-good-xpath": { 62 "wicked-good-xpath": {
340 "version": "1.3.0", 63 "version": "1.3.0",
341 "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", 64 "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
@@ -3,9 +3,9 @@ @@ -3,9 +3,9 @@
3 "email": "mjsb@uevora.pt", 3 "email": "mjsb@uevora.pt",
4 "dependencies": { 4 "dependencies": {
5 "@fortawesome/fontawesome-free": "^5.11.2", 5 "@fortawesome/fontawesome-free": "^5.11.2",
6 - "codemirror": "^5.49.0", 6 + "codemirror": "^5.49.2",
7 "mathjax": "^3", 7 "mathjax": "^3",
8 - "mdbootstrap": "^4.8.10" 8 + "mdbootstrap": "^4.9.0"
9 }, 9 },
10 "private": true 10 "private": true
11 } 11 }