Commit 939be670c9e323d2967031b2ea4ba56c32c4d729
1 parent
cad28d33
Exists in
dev
update jquery. move links to include-libs.html
Showing
8 changed files
with
188 additions
and
197 deletions
Show diff stats
aprendizations/__init__.py
1 | -# Copyright (C) 2019 Miguel Barão | 1 | +# Copyright © 2022 Miguel Barão |
2 | # | 2 | # |
3 | # THE MIT License | 3 | # THE MIT License |
4 | # | 4 | # |
@@ -30,10 +30,10 @@ are progressively uncovered as the students progress. | @@ -30,10 +30,10 @@ are progressively uncovered as the students progress. | ||
30 | ''' | 30 | ''' |
31 | 31 | ||
32 | APP_NAME = 'aprendizations' | 32 | APP_NAME = 'aprendizations' |
33 | -APP_VERSION = '2022.08.dev1' | 33 | +APP_VERSION = '2022.12.dev1' |
34 | APP_DESCRIPTION = __doc__ | 34 | APP_DESCRIPTION = __doc__ |
35 | 35 | ||
36 | __author__ = 'Miguel Barão' | 36 | __author__ = 'Miguel Barão' |
37 | -__copyright__ = 'Copyright 2021, Miguel Barão' | 37 | +__copyright__ = 'Copyright © 2022, Miguel Barão' |
38 | __license__ = 'MIT license' | 38 | __license__ = 'MIT license' |
39 | __version__ = APP_VERSION | 39 | __version__ = APP_VERSION |
aprendizations/serve.py
@@ -11,6 +11,7 @@ import mimetypes | @@ -11,6 +11,7 @@ import mimetypes | ||
11 | from os.path import join, dirname, expanduser | 11 | from os.path import join, dirname, expanduser |
12 | import signal | 12 | import signal |
13 | import sys | 13 | import sys |
14 | +import re | ||
14 | from typing import List, Optional, Union | 15 | from typing import List, Optional, Union |
15 | import uuid | 16 | import uuid |
16 | 17 | ||
@@ -21,7 +22,7 @@ import tornado.web | @@ -21,7 +22,7 @@ import tornado.web | ||
21 | from tornado.escape import to_unicode | 22 | from tornado.escape import to_unicode |
22 | 23 | ||
23 | # this project | 24 | # this project |
24 | -from aprendizations.tools import md_to_html | 25 | +from aprendizations.renderer_markdown import md_to_html |
25 | from aprendizations.learnapp import LearnException | 26 | from aprendizations.learnapp import LearnException |
26 | from aprendizations import APP_NAME | 27 | from aprendizations import APP_NAME |
27 | 28 | ||
@@ -82,6 +83,7 @@ class BaseHandler(tornado.web.RequestHandler): | @@ -82,6 +83,7 @@ class BaseHandler(tornado.web.RequestHandler): | ||
82 | ''' | 83 | ''' |
83 | Base handler common to all handlers. | 84 | Base handler common to all handlers. |
84 | ''' | 85 | ''' |
86 | + | ||
85 | @property | 87 | @property |
86 | def learn(self): | 88 | def learn(self): |
87 | '''easier access to learnapp''' | 89 | '''easier access to learnapp''' |
@@ -105,6 +107,7 @@ class RankingsHandler(BaseHandler): | @@ -105,6 +107,7 @@ class RankingsHandler(BaseHandler): | ||
105 | ''' | 107 | ''' |
106 | Handles rankings page | 108 | Handles rankings page |
107 | ''' | 109 | ''' |
110 | + | ||
108 | @tornado.web.authenticated | 111 | @tornado.web.authenticated |
109 | def get(self) -> None: | 112 | def get(self) -> None: |
110 | ''' | 113 | ''' |
@@ -130,33 +133,30 @@ class LoginHandler(BaseHandler): | @@ -130,33 +133,30 @@ class LoginHandler(BaseHandler): | ||
130 | ''' | 133 | ''' |
131 | Handles /login | 134 | Handles /login |
132 | ''' | 135 | ''' |
136 | + | ||
133 | def get(self) -> None: | 137 | def get(self) -> None: |
134 | ''' | 138 | ''' |
135 | - Renders login page | 139 | + Render login page |
136 | ''' | 140 | ''' |
137 | - self.render('login.html', | ||
138 | - appname=APP_NAME, | ||
139 | - error='') | 141 | + self.render('login.html', appname=APP_NAME, error='') |
140 | 142 | ||
141 | async def post(self): | 143 | async def post(self): |
142 | ''' | 144 | ''' |
143 | - Perform authentication and redirects to application if successful | 145 | + Authenticate and redirect to application if successful |
144 | ''' | 146 | ''' |
145 | - | ||
146 | - userid = (self.get_body_argument('uid') or '').lstrip('l') | 147 | + userid = self.get_body_argument('uid') or '' |
147 | passwd = self.get_body_argument('pw') | 148 | passwd = self.get_body_argument('pw') |
148 | - | ||
149 | - login_ok = await self.learn.login(userid, passwd) | ||
150 | - | ||
151 | - if login_ok: | ||
152 | - counter = str(self.learn.get_login_counter(userid)) | ||
153 | - self.set_secure_cookie('aprendizations_user', userid) | ||
154 | - self.set_secure_cookie('counter', counter) | ||
155 | - self.redirect('/') | ||
156 | - else: | ||
157 | - self.render('login.html', | ||
158 | - appname=APP_NAME, | ||
159 | - error='Número ou senha incorrectos') | 149 | + match = re.search(r'[0-9]+', userid) # extract number |
150 | + if match is not None: | ||
151 | + userid = match.group(0) # get string with number | ||
152 | + if await self.learn.login(userid, passwd): | ||
153 | + counter = str(self.learn.get_login_counter(userid)) | ||
154 | + self.set_secure_cookie('aprendizations_user', userid) | ||
155 | + self.set_secure_cookie('counter', counter) | ||
156 | + self.redirect('/') | ||
157 | + self.render('login.html', | ||
158 | + appname=APP_NAME, | ||
159 | + error='Número ou senha incorrectos') | ||
160 | 160 | ||
161 | 161 | ||
162 | # ---------------------------------------------------------------------------- | 162 | # ---------------------------------------------------------------------------- |
@@ -167,7 +167,7 @@ class LogoutHandler(BaseHandler): | @@ -167,7 +167,7 @@ class LogoutHandler(BaseHandler): | ||
167 | @tornado.web.authenticated | 167 | @tornado.web.authenticated |
168 | def get(self) -> None: | 168 | def get(self) -> None: |
169 | ''' | 169 | ''' |
170 | - clears cookies and removes user session | 170 | + clear cookies and user session |
171 | ''' | 171 | ''' |
172 | self.clear_cookie('user') | 172 | self.clear_cookie('user') |
173 | self.clear_cookie('counter') | 173 | self.clear_cookie('counter') |
@@ -182,14 +182,14 @@ class ChangePasswordHandler(BaseHandler): | @@ -182,14 +182,14 @@ class ChangePasswordHandler(BaseHandler): | ||
182 | ''' | 182 | ''' |
183 | Handles password change | 183 | Handles password change |
184 | ''' | 184 | ''' |
185 | + | ||
185 | @tornado.web.authenticated | 186 | @tornado.web.authenticated |
186 | async def post(self) -> None: | 187 | async def post(self) -> None: |
187 | ''' | 188 | ''' |
188 | - Tries to perform password change and then replies success/fail status | 189 | + Try to change password and show success/fail status |
189 | ''' | 190 | ''' |
190 | userid = self.current_user | 191 | userid = self.current_user |
191 | passwd = self.get_body_arguments('new_password')[0] | 192 | passwd = self.get_body_arguments('new_password')[0] |
192 | - | ||
193 | changed_ok = await self.learn.change_password(userid, passwd) | 193 | changed_ok = await self.learn.change_password(userid, passwd) |
194 | if changed_ok: | 194 | if changed_ok: |
195 | notification = self.render_string( | 195 | notification = self.render_string( |
@@ -203,7 +203,6 @@ class ChangePasswordHandler(BaseHandler): | @@ -203,7 +203,6 @@ class ChangePasswordHandler(BaseHandler): | ||
203 | type='danger', | 203 | type='danger', |
204 | msg='A password não foi alterada!' | 204 | msg='A password não foi alterada!' |
205 | ) | 205 | ) |
206 | - | ||
207 | self.write({'msg': to_unicode(notification)}) | 206 | self.write({'msg': to_unicode(notification)}) |
208 | 207 | ||
209 | 208 | ||
@@ -212,9 +211,10 @@ class RootHandler(BaseHandler): | @@ -212,9 +211,10 @@ class RootHandler(BaseHandler): | ||
212 | ''' | 211 | ''' |
213 | Handles root / | 212 | Handles root / |
214 | ''' | 213 | ''' |
214 | + | ||
215 | @tornado.web.authenticated | 215 | @tornado.web.authenticated |
216 | def get(self) -> None: | 216 | def get(self) -> None: |
217 | - '''Simply redirects to the main entrypoint''' | 217 | + '''Redirect to main entrypoint''' |
218 | self.redirect('/courses') | 218 | self.redirect('/courses') |
219 | 219 | ||
220 | 220 | ||
@@ -223,12 +223,13 @@ class CoursesHandler(BaseHandler): | @@ -223,12 +223,13 @@ class CoursesHandler(BaseHandler): | ||
223 | ''' | 223 | ''' |
224 | Handles /courses | 224 | Handles /courses |
225 | ''' | 225 | ''' |
226 | + | ||
226 | def set_default_headers(self, *_) -> None: | 227 | def set_default_headers(self, *_) -> None: |
227 | self.set_header('Cache-Control', 'no-cache') | 228 | self.set_header('Cache-Control', 'no-cache') |
228 | 229 | ||
229 | @tornado.web.authenticated | 230 | @tornado.web.authenticated |
230 | def get(self) -> None: | 231 | def get(self) -> None: |
231 | - '''Renders list of available courses''' | 232 | + '''Render available courses''' |
232 | uid = self.current_user | 233 | uid = self.current_user |
233 | self.render('courses.html', | 234 | self.render('courses.html', |
234 | appname=APP_NAME, | 235 | appname=APP_NAME, |
@@ -285,7 +286,7 @@ class TopicHandler(BaseHandler): | @@ -285,7 +286,7 @@ class TopicHandler(BaseHandler): | ||
285 | Starts a given topic | 286 | Starts a given topic |
286 | ''' | 287 | ''' |
287 | uid = self.current_user | 288 | uid = self.current_user |
288 | - | 289 | + logger.debug('[TopicHandler.get] %s', topic) |
289 | try: | 290 | try: |
290 | await self.learn.start_topic(uid, topic) # FIXME GET should not modify state... | 291 | await self.learn.start_topic(uid, topic) # FIXME GET should not modify state... |
291 | except KeyError: | 292 | except KeyError: |
@@ -304,15 +305,18 @@ class FileHandler(BaseHandler): | @@ -304,15 +305,18 @@ class FileHandler(BaseHandler): | ||
304 | ''' | 305 | ''' |
305 | Serves files from the /public subdir of the topics. | 306 | Serves files from the /public subdir of the topics. |
306 | ''' | 307 | ''' |
308 | + | ||
307 | @tornado.web.authenticated | 309 | @tornado.web.authenticated |
308 | async def get(self, filename) -> None: | 310 | async def get(self, filename) -> None: |
309 | ''' | 311 | ''' |
310 | - Serves files from /public subdirectories of a particular topic | 312 | + Serve file from the /public subdirectory of a particular topic |
311 | ''' | 313 | ''' |
312 | uid = self.current_user | 314 | uid = self.current_user |
313 | public_dir = self.learn.get_current_public_dir(uid) | 315 | public_dir = self.learn.get_current_public_dir(uid) |
314 | filepath = expanduser(join(public_dir, filename)) | 316 | filepath = expanduser(join(public_dir, filename)) |
315 | 317 | ||
318 | + logger.debug('uid=%s, public_dir=%s, filepath=%s', uid, public_dir, | ||
319 | + filepath) | ||
316 | try: | 320 | try: |
317 | with open(filepath, 'rb') as file: | 321 | with open(filepath, 'rb') as file: |
318 | data = file.read() | 322 | data = file.read() |
@@ -350,10 +354,9 @@ class QuestionHandler(BaseHandler): | @@ -350,10 +354,9 @@ class QuestionHandler(BaseHandler): | ||
350 | @tornado.web.authenticated | 354 | @tornado.web.authenticated |
351 | async def get(self) -> None: | 355 | async def get(self) -> None: |
352 | ''' | 356 | ''' |
353 | - Gets question to render. | ||
354 | - Shows an animated trophy if there are no more questions in the topic. | 357 | + Get question to render or an animated trophy if no more questions. |
355 | ''' | 358 | ''' |
356 | - logger.debug('[QuestionHandler]') | 359 | + logger.debug('[QuestionHandler.get]') |
357 | user = self.current_user | 360 | user = self.current_user |
358 | question = await self.learn.get_question(user) | 361 | question = await self.learn.get_question(user) |
359 | 362 | ||
@@ -387,7 +390,7 @@ class QuestionHandler(BaseHandler): | @@ -387,7 +390,7 @@ class QuestionHandler(BaseHandler): | ||
387 | @tornado.web.authenticated | 390 | @tornado.web.authenticated |
388 | async def post(self) -> None: | 391 | async def post(self) -> None: |
389 | ''' | 392 | ''' |
390 | - Corrects answer and returns status: right, wrong, try_again | 393 | + Correct answer and return status: right, wrong, try_again |
391 | Does not move to next question. | 394 | Does not move to next question. |
392 | ''' | 395 | ''' |
393 | user = self.current_user | 396 | user = self.current_user |
@@ -422,18 +425,16 @@ class QuestionHandler(BaseHandler): | @@ -422,18 +425,16 @@ class QuestionHandler(BaseHandler): | ||
422 | # --- check answer (nonblocking) and get corrected question and action | 425 | # --- check answer (nonblocking) and get corrected question and action |
423 | question = await self.learn.check_answer(user, ans) | 426 | question = await self.learn.check_answer(user, ans) |
424 | 427 | ||
425 | - # --- built response to return | 428 | + # --- build response |
426 | response = {'method': question['status'], 'params': {}} | 429 | response = {'method': question['status'], 'params': {}} |
427 | 430 | ||
428 | if question['status'] == 'right': # get next question in the topic | 431 | if question['status'] == 'right': # get next question in the topic |
429 | comments = self.render_string('comments-right.html', | 432 | comments = self.render_string('comments-right.html', |
430 | comments=question['comments'], | 433 | comments=question['comments'], |
431 | md=md_to_html) | 434 | md=md_to_html) |
432 | - | ||
433 | solution = self.render_string('solution.html', | 435 | solution = self.render_string('solution.html', |
434 | solution=question['solution'], | 436 | solution=question['solution'], |
435 | md=md_to_html) | 437 | md=md_to_html) |
436 | - | ||
437 | response['params'] = { | 438 | response['params'] = { |
438 | 'type': question['type'], | 439 | 'type': question['type'], |
439 | 'progress': self.learn.get_student_progress(user), | 440 | 'progress': self.learn.get_student_progress(user), |
@@ -445,7 +446,6 @@ class QuestionHandler(BaseHandler): | @@ -445,7 +446,6 @@ class QuestionHandler(BaseHandler): | ||
445 | comments = self.render_string('comments.html', | 446 | comments = self.render_string('comments.html', |
446 | comments=question['comments'], | 447 | comments=question['comments'], |
447 | md=md_to_html) | 448 | md=md_to_html) |
448 | - | ||
449 | response['params'] = { | 449 | response['params'] = { |
450 | 'type': question['type'], | 450 | 'type': question['type'], |
451 | 'progress': self.learn.get_student_progress(user), | 451 | 'progress': self.learn.get_student_progress(user), |
@@ -456,10 +456,8 @@ class QuestionHandler(BaseHandler): | @@ -456,10 +456,8 @@ class QuestionHandler(BaseHandler): | ||
456 | comments = self.render_string('comments.html', | 456 | comments = self.render_string('comments.html', |
457 | comments=question['comments'], | 457 | comments=question['comments'], |
458 | md=md_to_html) | 458 | md=md_to_html) |
459 | - | ||
460 | solution = self.render_string( | 459 | solution = self.render_string( |
461 | 'solution.html', solution=question['solution'], md=md_to_html) | 460 | 'solution.html', solution=question['solution'], md=md_to_html) |
462 | - | ||
463 | response['params'] = { | 461 | response['params'] = { |
464 | 'type': question['type'], | 462 | 'type': question['type'], |
465 | 'progress': self.learn.get_student_progress(user), | 463 | 'progress': self.learn.get_student_progress(user), |
@@ -501,7 +499,7 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None: | @@ -501,7 +499,7 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None: | ||
501 | sys.exit(1) | 499 | sys.exit(1) |
502 | logger.info('Web application started (tornado.web.Application)') | 500 | logger.info('Web application started (tornado.web.Application)') |
503 | 501 | ||
504 | - # --- create tornado webserver | 502 | + # --- create tornado http server |
505 | try: | 503 | try: |
506 | httpserver = tornado.httpserver.HTTPServer(webapp, ssl_options=ssl) | 504 | httpserver = tornado.httpserver.HTTPServer(webapp, ssl_options=ssl) |
507 | except ValueError: | 505 | except ValueError: |
@@ -515,9 +513,11 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None: | @@ -515,9 +513,11 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None: | ||
515 | logger.critical('Cannot bind port %d. Already in use?', port) | 513 | logger.critical('Cannot bind port %d. Already in use?', port) |
516 | sys.exit(1) | 514 | sys.exit(1) |
517 | logger.info('Webserver listening on %d... (Ctrl-C to stop)', port) | 515 | logger.info('Webserver listening on %d... (Ctrl-C to stop)', port) |
516 | + | ||
517 | + # --- set signal handler for Control-C | ||
518 | signal.signal(signal.SIGINT, signal_handler) | 518 | signal.signal(signal.SIGINT, signal_handler) |
519 | 519 | ||
520 | - # --- run webserver | 520 | + # --- run tornado webserver |
521 | try: | 521 | try: |
522 | tornado.ioloop.IOLoop.current().start() # running... | 522 | tornado.ioloop.IOLoop.current().start() # running... |
523 | except Exception: | 523 | except Exception: |
aprendizations/templates/include-libs.html
1 | <!-- jquery --> | 1 | <!-- jquery --> |
2 | -<script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script> | 2 | +<script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script> |
3 | 3 | ||
4 | <!-- bootstrap --> | 4 | <!-- bootstrap --> |
5 | <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> | 5 | <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> |
@@ -7,3 +7,14 @@ | @@ -7,3 +7,14 @@ | ||
7 | 7 | ||
8 | <!-- bootstrap icons --> | 8 | <!-- bootstrap icons --> |
9 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css"> | 9 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css"> |
10 | + | ||
11 | +<!-- MathJax --> | ||
12 | +<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script> | ||
13 | +<script async type="text/javascript" id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> | ||
14 | + | ||
15 | +<!-- codemirror --> | ||
16 | +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.css" /> | ||
17 | +<script defer src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.js"></script> | ||
18 | + | ||
19 | +<!-- animate --> | ||
20 | +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> |
aprendizations/templates/question-checkbox.html
@@ -3,13 +3,14 @@ | @@ -3,13 +3,14 @@ | ||
3 | 3 | ||
4 | {% block answer %} | 4 | {% block answer %} |
5 | <fieldset data-role="controlgroup"> | 5 | <fieldset data-role="controlgroup"> |
6 | - <div class="list-group"> | ||
7 | - {% for n,opt in enumerate(question['options']) %} | ||
8 | - <label class="list-group-item list-group-item-action"> | ||
9 | - <input type="checkbox" class="form-check-input" id="{{ n }}" accesskey="{{ n+1 }}" name="answer" value="{{ n }}"> | ||
10 | - {{ md(opt, strip_p_tag=True) }} | ||
11 | - </label> | ||
12 | - {% end %} | 6 | + <div class="list-group"> |
7 | + {% for n,opt in enumerate(question['options']) %} | ||
8 | + <li class="list-group-item"> | ||
9 | + <input class="form-check-input" type="checkbox" id="{{ n }}" accesskey="{{ n+1 }}" name="answer" value="{{ n }}"> | ||
10 | + <label class="form-check-label" for="{{ n }}"> | ||
11 | + {{ md(opt).removeprefix('<p>').removesuffix('</p>') }} | ||
12 | + </label> | ||
13 | + {% end %} | ||
13 | </div> | 14 | </div> |
14 | </fieldset> | 15 | </fieldset> |
15 | <input type="hidden" name="qid" value="{{ question['qid'] }}"> | 16 | <input type="hidden" name="qid" value="{{ question['qid'] }}"> |
aprendizations/templates/question-radio.html
@@ -5,12 +5,12 @@ | @@ -5,12 +5,12 @@ | ||
5 | <fieldset data-role="controlgroup"> | 5 | <fieldset data-role="controlgroup"> |
6 | <div class="list-group"> | 6 | <div class="list-group"> |
7 | {% for n,opt in enumerate(question['options']) %} | 7 | {% for n,opt in enumerate(question['options']) %} |
8 | - <label class="list-group-item list-group-item-action"> | ||
9 | - <input type="radio" class="form-check-input" id="{{ n }}" accesskey="{{ n+1 }}" name="answer" value="{{ n }}"> | ||
10 | - <label for="{{ n }}" class="custom-control-label"> | ||
11 | - {{ md(opt, strip_p_tag=True) }} | 8 | + <li class="list-group-item"> |
9 | + <input class="form-check-input" type="radio" name="answer" value="{{ n }}" id="{{ n }}"> | ||
10 | + <label class="form-check-label stretched-link" for="{{ n }}"> | ||
11 | + {{ md(opt).removeprefix('<p>').removesuffix('</p>') }} | ||
12 | </label> | 12 | </label> |
13 | - </label> | 13 | + </li> |
14 | {% end %} | 14 | {% end %} |
15 | </div> | 15 | </div> |
16 | </fieldset> | 16 | </fieldset> |
aprendizations/templates/topic.html
@@ -8,27 +8,6 @@ | @@ -8,27 +8,6 @@ | ||
8 | 8 | ||
9 | {% include include-libs.html %} | 9 | {% include include-libs.html %} |
10 | 10 | ||
11 | - <!-- mathjax --> | ||
12 | - <script> | ||
13 | - MathJax = { | ||
14 | - tex: { | ||
15 | - inlineMath: [['$$$', '$$$'], ['\\(', '\\)']] | ||
16 | - }, | ||
17 | - svg: { | ||
18 | - fontCache: 'global' | ||
19 | - } | ||
20 | - }; | ||
21 | - </script> | ||
22 | - <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script> | ||
23 | - <script async type="text/javascript" id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> | ||
24 | - | ||
25 | - <!-- codemirror --> | ||
26 | - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.css" /> | ||
27 | - <script defer src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.js"></script> | ||
28 | - | ||
29 | - <!-- animate --> | ||
30 | - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | ||
31 | - | ||
32 | <!-- local --> | 11 | <!-- local --> |
33 | <link rel="stylesheet" href="{{static_url('css/github.css')}}" /> | 12 | <link rel="stylesheet" href="{{static_url('css/github.css')}}" /> |
34 | <link rel="stylesheet" href="{{static_url('css/topic.css')}}" /> | 13 | <link rel="stylesheet" href="{{static_url('css/topic.css')}}" /> |
@@ -99,7 +78,7 @@ | @@ -99,7 +78,7 @@ | ||
99 | <div id="solution_wrong"></div> | 78 | <div id="solution_wrong"></div> |
100 | </div> | 79 | </div> |
101 | </div> | 80 | </div> |
102 | - <!-- button reponder / continuar --> | 81 | + <!-- button responder / continuar --> |
103 | <div class="d-grid gap-2"> | 82 | <div class="d-grid gap-2"> |
104 | <button type="submit" class="btn btn-primary btn-lg btn-block my-5 shadow bg-gradient" id="submit" data-bs-toggle="button" href="#solution" style="display: none"></button> | 83 | <button type="submit" class="btn btn-primary btn-lg btn-block my-5 shadow bg-gradient" id="submit" data-bs-toggle="button" href="#solution" style="display: none"></button> |
105 | </div> | 84 | </div> |
aprendizations/tools.py
@@ -3,15 +3,15 @@ | @@ -3,15 +3,15 @@ | ||
3 | import asyncio | 3 | import asyncio |
4 | import logging | 4 | import logging |
5 | from os import path | 5 | from os import path |
6 | -import re | 6 | +# import re |
7 | import subprocess | 7 | import subprocess |
8 | from typing import Any, List | 8 | from typing import Any, List |
9 | 9 | ||
10 | # third party libraries | 10 | # third party libraries |
11 | -import mistune | ||
12 | -from pygments import highlight | ||
13 | -from pygments.lexers import get_lexer_by_name | ||
14 | -from pygments.formatters import HtmlFormatter | 11 | +# import mistune |
12 | +# from pygments import highlight | ||
13 | +# from pygments.lexers import get_lexer_by_name | ||
14 | +# from pygments.formatters import HtmlFormatter | ||
15 | import yaml | 15 | import yaml |
16 | 16 | ||
17 | 17 | ||
@@ -28,119 +28,119 @@ logger = logging.getLogger(__name__) | @@ -28,119 +28,119 @@ logger = logging.getLogger(__name__) | ||
28 | # Block math: | 28 | # Block math: |
29 | # $$x$$ or \begin{equation}x\end{equation} | 29 | # $$x$$ or \begin{equation}x\end{equation} |
30 | # ------------------------------------------------------------------------- | 30 | # ------------------------------------------------------------------------- |
31 | -class MathBlockGrammar(mistune.BlockGrammar): | ||
32 | - block_math = re.compile(r'^\$\$(.*?)\$\$', re.DOTALL) | ||
33 | - latex_environment = re.compile(r'^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}', | ||
34 | - re.DOTALL) | ||
35 | - | ||
36 | - | ||
37 | -class MathBlockLexer(mistune.BlockLexer): | ||
38 | - default_rules = ['block_math', 'latex_environment'] \ | ||
39 | - + mistune.BlockLexer.default_rules | ||
40 | - | ||
41 | - def __init__(self, rules=None, **kwargs): | ||
42 | - if rules is None: | ||
43 | - rules = MathBlockGrammar() | ||
44 | - super().__init__(rules, **kwargs) | ||
45 | - | ||
46 | - def parse_block_math(self, m): | ||
47 | - '''Parse a $$math$$ block''' | ||
48 | - self.tokens.append({ | ||
49 | - 'type': 'block_math', | ||
50 | - 'text': m.group(1) | ||
51 | - }) | ||
52 | - | ||
53 | - def parse_latex_environment(self, m): | ||
54 | - r'''Parse an environment \begin{name}text\end{name}''' | ||
55 | - self.tokens.append({ | ||
56 | - 'type': 'latex_environment', | ||
57 | - 'name': m.group(1), | ||
58 | - 'text': m.group(2) | ||
59 | - }) | ||
60 | - | ||
61 | - | ||
62 | -# ------------------------------------------------------------------------- | ||
63 | -# Inline math: $x$ | ||
64 | -# ------------------------------------------------------------------------- | ||
65 | -class MathInlineGrammar(mistune.InlineGrammar): | ||
66 | - math = re.compile(r'^\$(.+?)\$', re.DOTALL) | ||
67 | - block_math = re.compile(r'^\$\$(.+?)\$\$', re.DOTALL) | ||
68 | - text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)') | ||
69 | - | ||
70 | - | ||
71 | -class MathInlineLexer(mistune.InlineLexer): | ||
72 | - default_rules = ['block_math', 'math'] + mistune.InlineLexer.default_rules | ||
73 | - | ||
74 | - def __init__(self, renderer, rules=None, **kwargs): | ||
75 | - if rules is None: | ||
76 | - rules = MathInlineGrammar() | ||
77 | - super().__init__(renderer, rules, **kwargs) | ||
78 | - | ||
79 | - def output_math(self, m): | ||
80 | - return self.renderer.inline_math(m.group(1)) | ||
81 | - | ||
82 | - def output_block_math(self, m): | ||
83 | - return self.renderer.block_math(m.group(1)) | ||
84 | - | ||
85 | - | ||
86 | -class MarkdownWithMath(mistune.Markdown): | ||
87 | - def __init__(self, renderer, **kwargs): | ||
88 | - if 'inline' not in kwargs: | ||
89 | - kwargs['inline'] = MathInlineLexer | ||
90 | - if 'block' not in kwargs: | ||
91 | - kwargs['block'] = MathBlockLexer | ||
92 | - super().__init__(renderer, **kwargs) | ||
93 | - | ||
94 | - def output_block_math(self): | ||
95 | - return self.renderer.block_math(self.token['text']) | ||
96 | - | ||
97 | - def output_latex_environment(self): | ||
98 | - return self.renderer.latex_environment(self.token['name'], | ||
99 | - self.token['text']) | ||
100 | - | ||
101 | - | ||
102 | -class HighlightRenderer(mistune.Renderer): | ||
103 | - def block_code(self, code, lang='text'): | ||
104 | - try: | ||
105 | - lexer = get_lexer_by_name(lang, stripall=False) | ||
106 | - except Exception: | ||
107 | - lexer = get_lexer_by_name('text', stripall=False) | ||
108 | - | ||
109 | - formatter = HtmlFormatter() | ||
110 | - return highlight(code, lexer, formatter) | ||
111 | - | ||
112 | - def table(self, header, body): | ||
113 | - return ('<table class="table table-sm"><thead class="thead-light">' | ||
114 | - f'{header}</thead><tbody>{body}</tbody></table>') | ||
115 | - | ||
116 | - def image(self, src, title, alt): | ||
117 | - alt = mistune.escape(alt, quote=True) | ||
118 | - title = mistune.escape(title or '', quote=True) | ||
119 | - return (f'<img src="/file/{src}" alt="{alt}" title="{title}"' | ||
120 | - f'class="img-fluid">') # class="img-fluid mx-auto d-block" | ||
121 | - | ||
122 | - # Pass math through unaltered - mathjax does the rendering in the browser | ||
123 | - def block_math(self, text): | ||
124 | - return fr'\[ {text} \]' | ||
125 | - | ||
126 | - def latex_environment(self, name, text): | ||
127 | - return fr'\begin{{{name}}} {text} \end{{{name}}}' | ||
128 | - | ||
129 | - def inline_math(self, text): | ||
130 | - return fr'\( {text} \)' | ||
131 | - | ||
132 | - | ||
133 | -# hard_wrap=True to insert <br> on newline | ||
134 | -markdown = MarkdownWithMath(HighlightRenderer(escape=True)) | ||
135 | - | ||
136 | - | ||
137 | -def md_to_html(text: str, strip_p_tag: bool = False) -> str: | ||
138 | - md: str = markdown(text) | ||
139 | - if strip_p_tag and md.startswith('<p>') and md.endswith('</p>\n'): | ||
140 | - return md[3:-5] | ||
141 | - else: | ||
142 | - return md | ||
143 | - | 31 | +# class MathBlockGrammar(mistune.BlockGrammar): |
32 | +# block_math = re.compile(r'^\$\$(.*?)\$\$', re.DOTALL) | ||
33 | +# latex_environment = re.compile(r'^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}', | ||
34 | +# re.DOTALL) | ||
35 | +# | ||
36 | +# | ||
37 | +# class MathBlockLexer(mistune.BlockLexer): | ||
38 | +# default_rules = ['block_math', 'latex_environment'] \ | ||
39 | +# + mistune.BlockLexer.default_rules | ||
40 | +# | ||
41 | +# def __init__(self, rules=None, **kwargs): | ||
42 | +# if rules is None: | ||
43 | +# rules = MathBlockGrammar() | ||
44 | +# super().__init__(rules, **kwargs) | ||
45 | +# | ||
46 | +# def parse_block_math(self, m): | ||
47 | +# '''Parse a $$math$$ block''' | ||
48 | +# self.tokens.append({ | ||
49 | +# 'type': 'block_math', | ||
50 | +# 'text': m.group(1) | ||
51 | +# }) | ||
52 | +# | ||
53 | +# def parse_latex_environment(self, m): | ||
54 | +# r'''Parse an environment \begin{name}text\end{name}''' | ||
55 | +# self.tokens.append({ | ||
56 | +# 'type': 'latex_environment', | ||
57 | +# 'name': m.group(1), | ||
58 | +# 'text': m.group(2) | ||
59 | +# }) | ||
60 | +# | ||
61 | +# | ||
62 | +# # ------------------------------------------------------------------------- | ||
63 | +# # Inline math: $x$ | ||
64 | +# # ------------------------------------------------------------------------- | ||
65 | +# class MathInlineGrammar(mistune.InlineGrammar): | ||
66 | +# math = re.compile(r'^\$(.+?)\$', re.DOTALL) | ||
67 | +# block_math = re.compile(r'^\$\$(.+?)\$\$', re.DOTALL) | ||
68 | +# text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)') | ||
69 | +# | ||
70 | +# | ||
71 | +# class MathInlineLexer(mistune.InlineLexer): | ||
72 | +# default_rules = ['block_math', 'math'] + mistune.InlineLexer.default_rules | ||
73 | +# | ||
74 | +# def __init__(self, renderer, rules=None, **kwargs): | ||
75 | +# if rules is None: | ||
76 | +# rules = MathInlineGrammar() | ||
77 | +# super().__init__(renderer, rules, **kwargs) | ||
78 | +# | ||
79 | +# def output_math(self, m): | ||
80 | +# return self.renderer.inline_math(m.group(1)) | ||
81 | +# | ||
82 | +# def output_block_math(self, m): | ||
83 | +# return self.renderer.block_math(m.group(1)) | ||
84 | +# | ||
85 | +# | ||
86 | +# class MarkdownWithMath(mistune.Markdown): | ||
87 | +# def __init__(self, renderer, **kwargs): | ||
88 | +# if 'inline' not in kwargs: | ||
89 | +# kwargs['inline'] = MathInlineLexer | ||
90 | +# if 'block' not in kwargs: | ||
91 | +# kwargs['block'] = MathBlockLexer | ||
92 | +# super().__init__(renderer, **kwargs) | ||
93 | +# | ||
94 | +# def output_block_math(self): | ||
95 | +# return self.renderer.block_math(self.token['text']) | ||
96 | +# | ||
97 | +# def output_latex_environment(self): | ||
98 | +# return self.renderer.latex_environment(self.token['name'], | ||
99 | +# self.token['text']) | ||
100 | +# | ||
101 | +# | ||
102 | +# class HighlightRenderer(mistune.Renderer): | ||
103 | +# def block_code(self, code, lang='text'): | ||
104 | +# try: | ||
105 | +# lexer = get_lexer_by_name(lang, stripall=False) | ||
106 | +# except Exception: | ||
107 | +# lexer = get_lexer_by_name('text', stripall=False) | ||
108 | +# | ||
109 | +# formatter = HtmlFormatter() | ||
110 | +# return highlight(code, lexer, formatter) | ||
111 | +# | ||
112 | +# def table(self, header, body): | ||
113 | +# return ('<table class="table table-sm"><thead class="thead-light">' | ||
114 | +# f'{header}</thead><tbody>{body}</tbody></table>') | ||
115 | +# | ||
116 | +# def image(self, src, title, alt): | ||
117 | +# alt = mistune.escape(alt, quote=True) | ||
118 | +# title = mistune.escape(title or '', quote=True) | ||
119 | +# return (f'<img src="/file/{src}" alt="{alt}" title="{title}"' | ||
120 | +# f'class="img-fluid">') # class="img-fluid mx-auto d-block" | ||
121 | +# | ||
122 | +# # Pass math through unaltered - mathjax does the rendering in the browser | ||
123 | +# def block_math(self, text): | ||
124 | +# return fr'\[ {text} \]' | ||
125 | +# | ||
126 | +# def latex_environment(self, name, text): | ||
127 | +# return fr'\begin{{{name}}} {text} \end{{{name}}}' | ||
128 | +# | ||
129 | +# def inline_math(self, text): | ||
130 | +# return fr'\( {text} \)' | ||
131 | +# | ||
132 | +# | ||
133 | +# # hard_wrap=True to insert <br> on newline | ||
134 | +# markdown = MarkdownWithMath(HighlightRenderer(escape=True)) | ||
135 | +# | ||
136 | +# | ||
137 | +# def md_to_html(text: str, strip_p_tag: bool = False) -> str: | ||
138 | +# md: str = markdown(text) | ||
139 | +# if strip_p_tag and md.startswith('<p>') and md.endswith('</p>\n'): | ||
140 | +# return md[3:-5] | ||
141 | +# else: | ||
142 | +# return md | ||
143 | +# | ||
144 | 144 | ||
145 | # --------------------------------------------------------------------------- | 145 | # --------------------------------------------------------------------------- |
146 | # load data from yaml file | 146 | # load data from yaml file |
setup.py
@@ -21,7 +21,7 @@ setup( | @@ -21,7 +21,7 @@ setup( | ||
21 | python_requires='>=3.9.*', | 21 | python_requires='>=3.9.*', |
22 | install_requires=[ | 22 | install_requires=[ |
23 | 'tornado>=6.0', | 23 | 'tornado>=6.0', |
24 | - 'mistune<2', | 24 | + 'mistune>=3', |
25 | 'pyyaml>=5.1', | 25 | 'pyyaml>=5.1', |
26 | 'pygments', | 26 | 'pygments', |
27 | 'sqlalchemy>=1.4', | 27 | 'sqlalchemy>=1.4', |