Commit dc4532f0d6c0ae1ef5350af7759b056f7b6bfe83

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

- code simplification in knowledge.py

config/logger-debug.yaml
... ... @@ -5,7 +5,7 @@ formatters:
5 5 void:
6 6 format: ''
7 7 standard:
8   - format: '%(asctime)s | %(levelname)-8s | %(name)-8s | %(message)s'
  8 + format: '%(asctime)s | %(levelname)-9s | %(name)-10s | %(message)s'
9 9  
10 10 handlers:
11 11 default:
... ...
knowledge.py
... ... @@ -35,7 +35,6 @@ class StudentKnowledge(object):
35 35 dt = now - s['date']
36 36 s['level'] *= 0.975 ** dt.days
37 37  
38   -
39 38 # compute recommended sequence of topics ['a', 'b', ...]
40 39 self.topic_sequence = list(nx.topological_sort(self.deps))
41 40  
... ... @@ -60,10 +59,27 @@ class StudentKnowledge(object):
60 59  
61 60  
62 61 # ------------------------------------------------------------------------
63   - # Recommends a topic to practice/learn from the state
  62 + # Recommends a topic to practice/learn from the state.
64 63 # ------------------------------------------------------------------------
65   - def recommend_topic(self):
66   - pass # FIXME
  64 + def recommended_topic(self):
  65 + return min(self.state.items(), key=lambda x: x[1])[0]
  66 +
  67 +
  68 + # if not topic:
  69 + # for topic in self.topic_sequence:
  70 + # unlocked = topic in self.state
  71 + # needs_work = unlocked and self.state[topic]['level'] < 0.8
  72 + # factory = self.deps.node[topic]['factory']
  73 + # if factory and (not unlocked or needs_work):
  74 + # break
  75 +
  76 + # use given topic if possible
  77 + # else:
  78 + # unlocked = topic in self.state
  79 + # factory = self.deps.node[topic]['factory']
  80 + # if not factory or not unlocked:
  81 + # logger.debug(f'Can\'t start topic "{topic}".')
  82 + # return
67 83  
68 84 # ------------------------------------------------------------------------
69 85 # Start a new topic. If not provided, selects the first with level < 0.8
... ... @@ -71,34 +87,21 @@ class StudentKnowledge(object):
71 87 # ------------------------------------------------------------------------
72 88 def start_topic(self, topic=''):
73 89 # unlock topics whose dependencies are done
74   - logger.debug('start_topic ' + topic)
75 90  
76 91 self.unlock_topics()
77 92  
78   - # choose topic
79 93 if not topic:
80   - for topic in self.topic_sequence:
81   - unlocked = topic in self.state
82   - needs_work = unlocked and self.state[topic]['level'] < 0.8
83   - factory = self.deps.node[topic]['factory']
84   - if factory and (not unlocked or needs_work):
85   - break
86   -
87   - # use given topic if possible
88   - else:
89   - unlocked = topic in self.state
90   - factory = self.deps.node[topic]['factory']
91   - if not factory or not unlocked:
92   - logger.debug(f'Can\'t start topic "{topic}".')
93   - return
94   -
  94 + topic = self.recommended_topic()
95 95  
96 96  
97 97 self.current_topic = topic
98 98 logger.info(f'Topic set to "{topic}"')
99 99  
  100 +
100 101 # generate question instances for current topic
  102 + factory = self.deps.node[topic]['factory']
101 103 questionlist = self.deps.node[topic]['questions']
  104 +
102 105 self.questions = [factory[qref].generate() for qref in questionlist]
103 106 self.current_question = self.questions.pop(0) # FIXME crashes if questions==[]
104 107 self.current_question['start_time'] = datetime.now()
... ...
serve.py
... ... @@ -32,7 +32,7 @@ class WebApplication(tornado.web.Application):
32 32 (r'/logout', LogoutHandler),
33 33 (r'/change_password', ChangePasswordHandler),
34 34 (r'/question', QuestionHandler),
35   - (r'/', LearnHandler),
  35 + (r'/', RootHandler),
36 36 (r'/topic/(.+)', TopicHandler),
37 37 (r'/(.+)', FileHandler),
38 38 ]
... ... @@ -112,7 +112,7 @@ class ChangePasswordHandler(BaseHandler):
112 112 # Main page: /
113 113 # Shows a list of topics and proficiency (stars, locked).
114 114 # ----------------------------------------------------------------------------
115   -class LearnHandler(BaseHandler):
  115 +class RootHandler(BaseHandler):
116 116 @tornado.web.authenticated
117 117 def get(self):
118 118 uid = self.current_user
... ... @@ -130,11 +130,12 @@ class TopicHandler(BaseHandler):
130 130 def get(self, topic):
131 131 uid = self.current_user
132 132 # topic = self.get_query_argument('topic', default='')
  133 +
133 134 self.learn.start_topic(uid, topic)
134 135 self.render('topic.html',
135 136 uid=uid,
136 137 name=self.learn.get_student_name(uid),
137   - title=self.learn.get_title(),
  138 + title=self.learn.get_title()
138 139 )
139 140  
140 141  
... ...
static/css/learn.css
1 1 body {
2 2 margin: 0;
3   - padding-top: 100px;
  3 + padding-top: 0px;
4 4 }
5 5  
6 6 img {
... ...
templates/learn.html
... ... @@ -1,256 +0,0 @@
1   -<!DOCTYPE html>
2   -<html>
3   -<head>
4   - <meta charset="utf-8">
5   - <meta http-equiv="X-UA-Compatible" content="IE=edge">
6   - <meta name="viewport" content="width=device-width, initial-scale=1">
7   - <meta name="author" content="Miguel Barão">
8   -
9   - <title>iLearn</title>
10   - <link rel="icon" href="/static/favicon.ico">
11   -
12   -<!-- MathJax -->
13   - <script type="text/x-mathjax-config">
14   - MathJax.Hub.Config({
15   - tex2jax: {
16   - inlineMath: [["$$$","$$$"], ["$","$"], ["\\(","\\)"]]
17   - }
18   - });
19   - </script>
20   - <script type="text/javascript" src="/static/mathjax/MathJax.js?delayStartupUntil=onload&config=TeX-AMS_CHTML-full"></script>
21   -
22   -<!-- Bootstrap -->
23   - <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
24   - <link rel="stylesheet" href="/static/css/bootstrap-theme.min.css"> <!-- optional -->
25   -
26   -<!-- other -->
27   - <link rel="stylesheet" href="/static/css/animate.min.css">
28   - <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css">
29   - <link rel="stylesheet" href="/static/css/github.css">
30   - <link rel="stylesheet" href="/static/css/learn.css">
31   -
32   - <script src="/static/js/jquery.min.js"></script>
33   - <script src="/static/bootstrap/js/bootstrap.min.js"></script>
34   -</head>
35   -<!-- ===================================================================== -->
36   -<body>
37   -
38   -<!-- Navbar -->
39   -<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
40   - <div class="container-fluid">
41   -<!-- Brand and toggle get grouped for better mobile display -->
42   - <div class="navbar-header">
43   - <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#myNavbar" aria-expanded="false">
44   - <span class="sr-only">Toggle navigation</span>
45   - <span class="icon-bar"></span>
46   - <span class="icon-bar"></span>
47   - <span class="icon-bar"></span>
48   - </button>
49   - <a class="navbar-brand" href="#">{{ title }}</a>
50   - </div>
51   -<!-- nav links and other content for toggling -->
52   - <div class="collapse navbar-collapse" id="myNavbar">
53   - <ul class="nav navbar-nav navbar-right">
54   - <li class="dropdown">
55   - <a class="dropdown-toggle" data-toggle="dropdown" href="#">
56   - <i class="fa fa-graduation-cap" aria-hidden="true"></i>
57   - <span id="name"> {{ name }}</span>
58   - <span class="caret"></span>
59   - </a>
60   - <ul class="dropdown-menu">
61   - <li><a data-toggle="modal" href="#password_modal"><i class="fa fa-key" aria-hidden="true"></i> Alterar password</a></li>
62   - <li><a href="/logout"><i class="fa fa-sign-out" aria-hidden="true"></i> Sair</a></li>
63   - </ul>
64   - </li>
65   - </ul>
66   - </div>
67   - </div>
68   -</nav>
69   -<!-- ===================================================================== -->
70   -<!-- main panel with questions -->
71   -<div id="main">
72   - <div id="body">
73   - <div class="progress">
74   - <div class="progress-bar progress-bar-success" id="topic_progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width: 0em;width: 0%"></div>
75   - </div>
76   -
77   - <div class="col-md-12">
78   -
79   - <p class="visible-xs">
80   - <button type="button" class="btn btn-primary btn-xs" data-toggle="offcanvas"><i class="glyphicon glyphicon-chevron-left"></i></button>
81   - </p>
82   -
83   - <div id="notifications">
84   - <!-- notifications go here -->
85   - </div>
86   -
87   - <!-- Question body -->
88   - <form action="/question" method="post" id="question_form" autocomplete="off">
89   - {% module xsrf_form_html() %}
90   -
91   - <div id="question_div">
92   - <!-- questions go here -->
93   - </div>
94   -
95   - </form>
96   - <button class="btn btn-primary" id="submit" data-toggle="tooltip" data-placement="right" title="Shift-Enter">Continuar</button>
97   - </div>
98   - </div> <!-- body -->
99   -</div> <!-- main -->
100   -
101   -<!-- === Change Password Modal =========================================== -->
102   -<div id="password_modal" class="modal fade" tabindex="-1" role="dialog">
103   - <div class="modal-dialog modal-sm" role="document">
104   - <div class="modal-content">
105   -<!-- header -->
106   - <div class="modal-header">
107   - <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
108   - <h4 class="modal-title">Alterar Password</h4>
109   - </div>
110   -<!-- body -->
111   - <div class="modal-body">
112   - <div class="control-group">
113   - <label for="new_password" class="control-label">Nova Password</label>
114   - <div class="controls">
115   - <input type="password" id="new_password" name="new_password" autocomplete="new-password">
116   - </div>
117   - </div>
118   - </div>
119   -<!-- footer -->
120   - <div class="modal-footer">
121   - <button type="button" class="btn btn-default" data-dismiss="modal">Cancelar</button>
122   - <button id="change_password" type="button" class="btn btn-danger" data-dismiss="modal">Alterar</button>
123   - </div>
124   -
125   - </div><!-- /.modal-content -->
126   - </div><!-- /.modal-dialog -->
127   -</div><!-- /.modal -->
128   -
129   -
130   -<!-- ===================================================================== -->
131   -<!-- JAVASCRIP -->
132   -<!-- ===================================================================== -->
133   -<script>
134   -$.fn.extend({
135   - animateCSS: function (animation) {
136   - var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
137   - this.addClass('animated ' + animation).one(animationEnd, function() {
138   - $(this).removeClass('animated ' + animation);
139   - });
140   - }
141   -});
142   -
143   -// Process the response given by the server
144   -function updateQuestion(response){
145   - switch (response["method"]) {
146   - case "new_question":
147   - $("#question_div").html(response["params"]["question"]);
148   - $("#topics").html(response["params"]["state"]);
149   - $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]);
150   -
151   - MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]);
152   -
153   - // enable shift+enter to submit and tab to spaces conversion
154   - $("input:text, input:radio, input:checkbox").keydown(function (e) {
155   - if (e.keyCode == 13) {
156   - e.preventDefault();
157   - if (e.shiftKey) postQuestion();
158   - return false;
159   - }
160   - });
161   - $("textarea").keydown(function (e) {
162   - if (e.keyCode == 13 && e.shiftKey) { // shift enter
163   - e.preventDefault();
164   - postQuestion();
165   - }
166   - else if (e.keyCode === 9) { // tab
167   - e.preventDefault(); // prevent loosing focus
168   - // get caret position/selection
169   - var start = this.selectionStart;
170   - var end = this.selectionEnd;
171   - var value = $(this).val();
172   -
173   - // set textarea value to: text before caret + tab + text after caret
174   - $(this).val(value.substring(0, start) + " " + value.substring(end));
175   -
176   - // put caret at right position again (add one for the tab)
177   - this.selectionStart = this.selectionEnd = start + 4;
178   - }
179   - });
180   -
181   - // var audio = new Audio('/static/sounds/correct.mp3');
182   - // audio.play();
183   - $('#question_div').animateCSS('zoomIn');
184   - break;
185   -
186   - case "shake":
187   - // var audio = new Audio('/static/sounds/wrong.mp3');
188   - // audio.play();
189   - $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]);
190   - $('#question_div').animateCSS('shake');
191   - break;
192   -
193   - case "finished_topic":
194   - $('#topic_progress').css('width', '100%').attr('aria-valuenow', 100);
195   - $("#topics").html(response["params"]["state"]);
196   - $("#question_div").html('<img src="/static/trophy.png" alt="trophy" class="img-rounded img-responsive center-block">'); // FIXME size
197   - break;
198   - }
199   -}
200   -
201   -// Send answer and receive a response.
202   -// The response can be a new_question or a shake if the answer is wrong.
203   -function postQuestion() {
204   - $.ajax({
205   - type: "POST",
206   - url: "/question",
207   - // headers: {"X-XSRFToken": token},
208   - data: $("#question_form").serialize(), // {'a':10,'b':20},
209   - dataType: "json", // expected from server
210   - success: updateQuestion,
211   - error: function() {alert("O servidor não responde.");}
212   - });
213   -}
214   -
215   -// Get current question
216   -function getQuestion() {
217   - $.ajax({
218   - url: "/question",
219   - // headers: {"X-XSRFToken": token},
220   - dataType: "json", // expected from server
221   - success: updateQuestion,
222   - error: function() {alert("O servidor não responde.");}
223   - });
224   -}
225   -
226   -function change_password() {
227   - $.ajax({
228   - type: "POST",
229   - url: "/change_password",
230   - data: {
231   - "new_password": $("#new_password").val(),
232   - },
233   - dataType: "json",
234   - success: function(r) {
235   - $("#notifications").html(r["msg"]);
236   - $("#notification").fadeIn(250).delay(5000).fadeOut(500);
237   - },
238   - error: function(r) {
239   - $("#notifications").html(r["msg"]);
240   - $("#notification").fadeIn(250).delay(5000).fadeOut(500);
241   - },
242   - });
243   -}
244   -
245   -$(document).ready(function() {
246   - getQuestion();
247   - $("#submit").click(postQuestion);
248   - $('[data-toggle=offcanvas]').click(function() {
249   - $('.row-offcanvas').toggleClass('active');
250   - });
251   - $("#change_password").click(change_password);
252   -});
253   -</script>
254   -
255   -</body>
256   -</html>
templates/maintopics.html
... ... @@ -3,7 +3,7 @@
3 3 <!DOCTYPE html>
4 4 <html lang="pt-PT">
5 5 <head>
6   - <title>iLearn</title>
  6 + <title>GotIT</title>
7 7 <link rel="icon" href="/static/favicon.ico">
8 8  
9 9 <meta charset="utf-8">
... ... @@ -40,6 +40,7 @@
40 40 </span>
41 41 </div>
42 42 </nav>
  43 +<!-- ===================================================================== -->
43 44 <div class="container">
44 45  
45 46 <h3>Tópicos</h3>
... ...
templates/topic.html 0 → 100644
... ... @@ -0,0 +1,242 @@
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <title>GotIT</title>
  5 + <link rel="icon" href="/static/favicon.ico">
  6 +
  7 + <meta charset="utf-8">
  8 + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  9 + <meta name="author" content="Miguel Barão">
  10 +
  11 +<!-- MathJax -->
  12 + <script type="text/x-mathjax-config">
  13 + MathJax.Hub.Config({
  14 + tex2jax: {
  15 + inlineMath: [["$$$","$$$"], ["$","$"], ["\\(","\\)"]]
  16 + }
  17 + });
  18 + </script>
  19 + <script type="text/javascript" src="/static/mathjax/MathJax.js?delayStartupUntil=onload&config=TeX-AMS_CHTML-full"></script>
  20 +
  21 +<!-- Bootstrap -->
  22 + <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
  23 + <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css">
  24 + <link rel="stylesheet" href="/static/css/animate.min.css">
  25 + <link rel="stylesheet" href="/static/css/github.css">
  26 +
  27 +<!-- Other -->
  28 + <link rel="stylesheet" href="/static/css/learn.css">
  29 +
  30 +</head>
  31 +<!-- ===================================================================== -->
  32 +<body>
  33 +
  34 +<!-- Navbar -->
  35 +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-dark">
  36 + <a class="navbar-brand" href="#">
  37 + <i class="fa fa-clock-o" aria-hidden="true"></i>
  38 + </a>
  39 + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
  40 + <span class="navbar-toggler-icon"></span>
  41 + </button>
  42 +
  43 + <div class="collapse navbar-collapse" id="navbarText">
  44 + <ul class="navbar-nav mr-auto">
  45 + </ul>
  46 +
  47 + <span class="navbar-text">
  48 + <i class="fa fa-user" aria-hidden="true"></i>
  49 + <span id="name">{{ escape(name) }}</span>
  50 + (<span id="number">{{ escape(uid) }}</span>)
  51 + <span class="caret"></span>
  52 + </span>
  53 + </div>
  54 +</nav>
  55 +
  56 +<!-- ===================================================================== -->
  57 +<!-- main panel with questions -->
  58 +<div id="main">
  59 + <div id="body">
  60 + <div class="progress">
  61 + <div class="progress-bar progress-bar-success" id="topic_progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width: 0em;width: 0%"></div>
  62 + </div>
  63 +
  64 + <!-- <div class="col-md-12"> -->
  65 +
  66 + <div id="notifications">
  67 + <!-- notifications go here -->
  68 + </div>
  69 +
  70 + <!-- Question body -->
  71 + <form action="/question" method="post" id="question_form" autocomplete="off">
  72 + {% module xsrf_form_html() %}
  73 +
  74 + <div id="question_div">
  75 + <!-- questions go here -->
  76 + </div>
  77 +
  78 + </form>
  79 + <button class="btn btn-primary" id="submit" data-toggle="tooltip" data-placement="right" title="Shift-Enter">Continuar</button>
  80 + <!-- </div> -->
  81 + </div> <!-- body -->
  82 +</div> <!-- main -->
  83 +
  84 +<!-- === Change Password Modal =========================================== -->
  85 +<div id="password_modal" class="modal fade" tabindex="-1" role="dialog">
  86 + <div class="modal-dialog modal-sm" role="document">
  87 + <div class="modal-content">
  88 +<!-- header -->
  89 + <div class="modal-header">
  90 + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  91 + <h4 class="modal-title">Alterar Password</h4>
  92 + </div>
  93 +<!-- body -->
  94 + <div class="modal-body">
  95 + <div class="control-group">
  96 + <label for="new_password" class="control-label">Nova Password</label>
  97 + <div class="controls">
  98 + <input type="password" id="new_password" name="new_password" autocomplete="new-password">
  99 + </div>
  100 + </div>
  101 + </div>
  102 +<!-- footer -->
  103 + <div class="modal-footer">
  104 + <button type="button" class="btn btn-default" data-dismiss="modal">Cancelar</button>
  105 + <button id="change_password" type="button" class="btn btn-danger" data-dismiss="modal">Alterar</button>
  106 + </div>
  107 +
  108 + </div><!-- /.modal-content -->
  109 + </div><!-- /.modal-dialog -->
  110 +</div><!-- /.modal -->
  111 +
  112 +
  113 +<!-- ===================================================================== -->
  114 +<!-- JAVASCRIPT -->
  115 +<!-- ===================================================================== -->
  116 +<script>
  117 +$.fn.extend({
  118 + animateCSS: function (animation) {
  119 + var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
  120 + this.addClass('animated ' + animation).one(animationEnd, function() {
  121 + $(this).removeClass('animated ' + animation);
  122 + });
  123 + }
  124 +});
  125 +
  126 +// Process the response given by the server
  127 +function updateQuestion(response){
  128 + switch (response["method"]) {
  129 + case "new_question":
  130 + $("#question_div").html(response["params"]["question"]);
  131 + $("#topics").html(response["params"]["state"]);
  132 + $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]);
  133 +
  134 + MathJax.Hub.Queue(["Typeset",MathJax.Hub,"question_div"]);
  135 +
  136 + // enable shift+enter to submit and tab to spaces conversion
  137 + $("input:text, input:radio, input:checkbox").keydown(function (e) {
  138 + if (e.keyCode == 13) {
  139 + e.preventDefault();
  140 + if (e.shiftKey) postQuestion();
  141 + return false;
  142 + }
  143 + });
  144 + $("textarea").keydown(function (e) {
  145 + if (e.keyCode == 13 && e.shiftKey) { // shift enter
  146 + e.preventDefault();
  147 + postQuestion();
  148 + }
  149 + else if (e.keyCode === 9) { // tab
  150 + e.preventDefault(); // prevent loosing focus
  151 + // get caret position/selection
  152 + var start = this.selectionStart;
  153 + var end = this.selectionEnd;
  154 + var value = $(this).val();
  155 +
  156 + // set textarea value to: text before caret + tab + text after caret
  157 + $(this).val(value.substring(0, start) + " " + value.substring(end));
  158 +
  159 + // put caret at right position again (add one for the tab)
  160 + this.selectionStart = this.selectionEnd = start + 4;
  161 + }
  162 + });
  163 +
  164 + // var audio = new Audio('/static/sounds/correct.mp3');
  165 + // audio.play();
  166 + $('#question_div').animateCSS('zoomIn');
  167 + break;
  168 +
  169 + case "shake":
  170 + // var audio = new Audio('/static/sounds/wrong.mp3');
  171 + // audio.play();
  172 + $('#topic_progress').css('width', (100*response["params"]["progress"])+'%').attr('aria-valuenow', 100*response["params"]["progress"]);
  173 + $('#question_div').animateCSS('shake');
  174 + break;
  175 +
  176 + case "finished_topic":
  177 + $('#topic_progress').css('width', '100%').attr('aria-valuenow', 100);
  178 + $("#topics").html(response["params"]["state"]);
  179 + $("#question_div").html('<img src="/static/trophy.png" alt="trophy" class="img-rounded img-responsive center-block">'); // FIXME size
  180 + break;
  181 + }
  182 +}
  183 +
  184 +// Send answer and receive a response.
  185 +// The response can be a new_question or a shake if the answer is wrong.
  186 +function postQuestion() {
  187 + $.ajax({
  188 + type: "POST",
  189 + url: "/question",
  190 + // headers: {"X-XSRFToken": token},
  191 + data: $("#question_form").serialize(), // {'a':10,'b':20},
  192 + dataType: "json", // expected from server
  193 + success: updateQuestion,
  194 + error: function() {alert("O servidor não responde.");}
  195 + });
  196 +}
  197 +
  198 +// Get current question
  199 +function getQuestion() {
  200 + $.ajax({
  201 + url: "/question",
  202 + // headers: {"X-XSRFToken": token},
  203 + dataType: "json", // expected from server
  204 + success: updateQuestion,
  205 + error: function() {alert("O servidor não responde.");}
  206 + });
  207 +}
  208 +
  209 +function change_password() {
  210 + $.ajax({
  211 + type: "POST",
  212 + url: "/change_password",
  213 + data: {
  214 + "new_password": $("#new_password").val(),
  215 + },
  216 + dataType: "json",
  217 + success: function(r) {
  218 + $("#notifications").html(r["msg"]);
  219 + $("#notification").fadeIn(250).delay(5000).fadeOut(500);
  220 + },
  221 + error: function(r) {
  222 + $("#notifications").html(r["msg"]);
  223 + $("#notification").fadeIn(250).delay(5000).fadeOut(500);
  224 + },
  225 + });
  226 +}
  227 +
  228 +$(document).ready(function() {
  229 + getQuestion();
  230 + $("#submit").click(postQuestion);
  231 + $('[data-toggle=offcanvas]').click(function() {
  232 + $('.row-offcanvas').toggleClass('active');
  233 + });
  234 + $("#change_password").click(change_password);
  235 +});
  236 +</script>
  237 +
  238 + <script src="/static/js/jquery.min.js"></script>
  239 + <script src="/static/bootstrap/js/bootstrap.min.js"></script>
  240 +
  241 +</body>
  242 +</html>
... ...