Break out the thread browsing functionality and create a widget for it
authorMagnus Hagander <magnus@hagander.net>
Fri, 19 Jul 2013 14:39:57 +0000 (16:39 +0200)
committerMagnus Hagander <magnus@hagander.net>
Fri, 19 Jul 2013 14:39:57 +0000 (16:39 +0200)
Also, move the javascript to a shared javascript file

pgcommitfest/commitfest/static/commitfest/js/commitfest.js [new file with mode: 0644]
pgcommitfest/commitfest/templates/base.html
pgcommitfest/commitfest/templates/base_form.html
pgcommitfest/commitfest/templates/patch.html
pgcommitfest/commitfest/templates/thread_attach.inc [new file with mode: 0644]
pgcommitfest/commitfest/widgets.py [new file with mode: 0644]

diff --git a/pgcommitfest/commitfest/static/commitfest/js/commitfest.js b/pgcommitfest/commitfest/static/commitfest/js/commitfest.js
new file mode 100644 (file)
index 0000000..8f84d8b
--- /dev/null
@@ -0,0 +1,111 @@
+function verify_reject() {
+   return confirm('Are you sure you want to close this patch as Rejected?\n\nThis should only be done when a patch will never be applied - if more work is needed, it should instead be set to "Returned with Feedback".\n\nSo - are you sure?');
+}
+function verify_returned() {
+   return confirm('Are you sure you want to close this patch as Returned with Feedback?\n\nThis means the patch will be marked as closed in this commitfest, but will automatically be moved to the next one. If no further work is expected on this patch, it should be closed with "Rejected" istead.\n\nSo - are you sure?');
+}
+function verify_committed(is_committer) {
+   if (is_committer) 
+      return confirm('Are you sure you want to close this patch as Committed?');
+   else {
+      alert('Currently, only the committer who actually made the commit can do this. We should make a little prompt for the committer field otherwise..');
+      return false;
+   }
+}
+
+function findLatestThreads() {
+   $('#attachThreadListWrap').addClass('loading');
+   $('#attachThreadSearchButton').addClass('disabled');
+   $.get('/ajax/getThreads/', {
+      's': $('#attachThreadSearchField').val(),
+   }).success(function(data) {
+         sel = $('#attachThreadList');
+         sel.find('option').remove();
+         $.each(data, function(m,i) {
+            sel.append('<option value="' + i.msgid + '">' + i.from + ': ' + i.subj + ' (' + i.date + ')</option>');
+         });
+   }).always(function() {
+      $('#attachThreadListWrap').removeClass('loading');
+      $('#attachThreadSearchButton').removeClass('disabled');
+      attachThreadChanged();
+   });
+   return false;
+}
+
+function browseThreads(attachfunc) {
+   $('#attachThreadList').find('option').remove();
+   $('#attachThreadMessageId').val('');
+   $('#attachModal').modal();
+   findLatestThreads();
+
+   $('#doAttachThreadButton').unbind('click');
+   $('#doAttachThreadButton').click(function() {
+      msgid = $('#attachThreadMessageId').val();
+      if (!msgid || msgid == '') {
+         msgid = $('#attachThreadList').val();
+         if (!msgid) return;
+      }
+
+      $('#attachThreadListWrap').addClass('loading');
+      $('#attachThreadSearchButton').addClass('disabled');
+      $('#attachThreadButton').addClass('disabled');
+      if (attachfunc(msgid)) {
+         $('#attachModal').modal('hide');
+      }
+      $('#attachThreadListWrap').removeClass('loading');
+      $('#attachThreadSearchButton').removeClass('disabled');
+      attachThreadChanged();
+   });
+
+}
+
+function attachThread(cfid, patchid) {
+   browseThreads(function(msgid) {
+      doAttachThread(cfid, patchid, msgid);
+   });
+}
+
+function detachThread(cfid, patchid, msgid) {
+   if (confirm('Are you sure you want to detach the thread with messageid "' + msgid + '" from this patch?')) {
+      $.post('/ajax/detachThread/', {
+         'cf': cfid,
+         'p': patchid,
+         'msg': msgid,
+      }).success(function(data) {
+         location.reload();
+      }).fail(function(data) {
+         alert('Failed to detach thread!');
+      });
+   }
+}
+
+function attachThreadChanged() {
+   if ($('#attachThreadList').val() || $('#attachThreadMessageId').val()) {
+      $('#doAttachThreadButton').removeClass('disabled');
+   }
+   else {
+      $('#doAttachThreadButton').addClass('disabled');
+   }
+}
+
+function doAttachThread(cfid, patchid, msgid) {
+   $.post('/ajax/attachThread/', {
+      'cf': cfid,
+      'p': patchid,
+      'msg': msgid,
+   }).success(function(data) {
+      location.reload();
+      return true;
+   }).fail(function(data) {
+      if (data.status == 404) {
+         alert('Message with messageid ' + msgid + ' not found');
+      }
+      else if (data.status == 503) {
+         alert('Failed to attach thread: ' + data.responseText);
+      }
+      else {
+         alert('Failed to attach thread: ' + data.statusText);
+      }
+      return false;
+   });
+}
index 644c9750d8d62ba85177cc61d98184e336ac71d5..4e94f130f4877985bdf8797c6a4e8e5dc5d0ae3c 100644 (file)
@@ -43,5 +43,6 @@ li.selectable-deck-item .selectable-deck-remove {
 <script src="/static/commitfest/js/bootstrap-collapse.js"></script>
 <script src="/static/commitfest/js/jquery-ui.js"></script>
 <script type="text/javascript" src="/static/selectable/js/jquery.dj.selectable.js"></script>
+<script src="/static/commitfest/js/commitfest.js"></script>
 {%block morescript%}{%endblock%}
 </html>
index e204c347d9bd99bf2a93c380b6d9e6b08835e61e..f51bf760d6a026296e6c65271c3c0590ff2c9533 100644 (file)
@@ -45,4 +45,26 @@ div.control-group div.controls ul li label {
  </div>
 </form>
 
+{%if threadbrowse %}
+{%include "thread_attach.inc" %}
+{%endif%}
+{%endblock%}
+
+{%if threadbrowse %}
+{%block morescript%}
+<script>
+$(document).ready(function() {
+   $('button.attachThreadButton').each(function (i,o) {
+      var b = $(o);
+      b.click(function() {
+         browseThreads(function(msgid) {
+            b.prev().val(msgid);
+            return true;
+         });
+         return false;
+      });
+   });
+});
+</script>
 {%endblock%}
+{%endif%}
index 26071b181ac54f03ba8a6b5297154cb9672a5363..562d7e62529030bd5d96aecb35322f6993c067d5 100644 (file)
@@ -2,16 +2,6 @@
 {%load commitfest%}
 {%block contents%}
 <style>
-#attachThreadListWrap.loading {
-    display: block;
-    background: url('/static/commitfest/spinner.gif') no-repeat center;
-    width: 124px;
-    height: 124px;
-    margin: 0 auto;
-}
-#attachThreadListWrap.loading * {
-    display: none;  
-}
 .close-nofloat {
    float: none !important;
 }
@@ -66,7 +56,7 @@
       <th>Emails</th>
       <td>
 {%if user.is_authenticated%}
-       <div style="float:right"><button class="btn" onclick="attachThread()">Attach thread</button></div>
+       <div style="float:right"><button class="btn" onclick="attachThread({{cf.id}},{{patch.id}})">Attach thread</button></div>
 {%else%}
        <div style="float:right"><button class="btn" onclick="location.href='/login_and_redirect_back'">Attach thread</button></div>
 {%endif%}
 
 {%include "patch_commands.inc"%}
 
-{%comment%}Modal dialog for attach thread{%endcomment%}
-<div class="modal hide fade" id="attachModal" role="dialog" style="width:80%; left: 10%; margin-left:auto; margin-right: auto;">
- <div class="modal-header">
-  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-  <h3>Attach thread</h3>
- </div>
- <div class="modal-body">
-   <form class="form-search" style="margin-bottom: 5px;">
-     <div class="input-append">
-       <input id="attachThreadSearchField" type="text" class="span2 search-query">
-       <button id="attachThreadSearchButton" onclick="return findLatestThreads()" class="btn disabled">Search</button>
-     </div>
-   </form>
-   <div>Pick one of the recent emails from pgsql-hackers, or search above for subject or name:</div>
-   <div id="attachThreadListWrap">
-     <select id="attachThreadList" size="6" style="width:100%;" onchange="attachThreadChanged()">
-     </select>
-   </div>
-   <div>Or enter an <i>exact</i> message id:</div>
-   <input type="text" id="attachThreadMessageId" placeholder="Message id" class="input-block-level" onkeypress="attachThreadChanged()" onchange="attachThreadChanged()">
- </div>
- <div class="modal-footer">
-   <a href="#" class="btn" data-dismiss="modal">Close</a>
-   <a href="#" id="doAttachThreadButton" class="btn btn-primary disabled" onclick="doAttachThread({{cf.id}}, {{patch.id}})">Attach thread</a>
- </div>
-</div>
+{%include "thread_attach.inc"%}
 {%endblock%}
 
 {%block morescript%}
-<script language="javascript">
-/* XXX: Much of this should of course be in a shared .js file instead! */
-function verify_reject() {
-   return confirm('Are you sure you want to close this patch as Rejected?\n\nThis should only be done when a patch will never be applied - if more work is needed, it should instead be set to "Returned with Feedback".\n\nSo - are you sure?');
-}
-function verify_returned() {
-   return confirm('Are you sure you want to close this patch as Returned with Feedback?\n\nThis means the patch will be marked as closed in this commitfest, but will automatically be moved to the next one. If no further work is expected on this patch, it should be closed with "Rejected" istead.\n\nSo - are you sure?');
-}
-function verify_committed() {
-{%if is_committer%}
-   return confirm('Are you sure you want to close this patch as Committed?');
-{%else%}
-   alert('Currently, only the committer who actually made the commit can do this. We should make a little prompt for the committer field otherwise..');
-   return false;
-{%endif%}
-}
-
-function findLatestThreads() {
-   $('#attachThreadListWrap').addClass('loading');
-   $('#attachThreadSearchButton').addClass('disabled');
-   $.get('/ajax/getThreads/', {
-      's': $('#attachThreadSearchField').val(),
-   }).success(function(data) {
-         sel = $('#attachThreadList');
-         sel.find('option').remove();
-         $.each(data, function(m,i) {
-            sel.append('<option value="' + i.msgid + '">' + i.from + ': ' + i.subj + ' (' + i.date + ')</option>');
-         });
-   }).always(function() {
-      $('#attachThreadListWrap').removeClass('loading');
-      $('#attachThreadSearchButton').removeClass('disabled');
-      attachThreadChanged();
-   });
-   return false;
-}
-
-function attachThread() {
-   $('#attachThreadList').find('option').remove();
-   $('#attachThreadMessageId').val('');
-   $('#attachModal').modal();
-   findLatestThreads();
-}
-
-function detachThread(cfid, patchid, msgid) {
-   if (confirm('Are you sure you want to detach the thread with messageid "' + msgid + '" from this patch?')) {
-      $.post('/ajax/detachThread/', {
-         'cf': cfid,
-         'p': patchid,
-         'msg': msgid,
-      }).success(function(data) {
-         location.reload();
-      }).fail(function(data) {
-         alert('Failed to detach thread!');
-      });
-   }
-}
-
-function attachThreadChanged() {
-   if ($('#attachThreadList').val() || $('#attachThreadMessageId').val()) {
-      $('#doAttachThreadButton').removeClass('disabled');
-   }
-   else {
-      $('#doAttachThreadButton').addClass('disabled');
-   }
-}
-
-function doAttachThread(cfid, patchid) {
-   v = $('#attachThreadMessageId').val();
-   if (!v || v == '') {
-      v = $('#attachThreadList').val();
-      if (!v) return;
-   }
-
-   $('#attachThreadListWrap').addClass('loading');
-   $('#attachThreadSearchButton').addClass('disabled');
-   $('#attachThreadButton').addClass('disabled');
-
-   $.post('/ajax/attachThread/', {
-      'cf': cfid,
-      'p': patchid,
-      'msg': $('#attachThreadList').val(),
-   }).success(function(data) {
-      location.reload();
-   }).fail(function(data) {
-      if (data.status == 404) {
-         alert('Message with specified messageid not found');
-      }
-      else if (data.status == 503) {
-         alert('Failed to attach thread: ' + data.responseText);
-      }
-      else {
-         alert('Failed to attach thread: ' + data.statusText);
-      }
-      $('#attachThreadListWrap').removeClass('loading');
-      $('#attachThreadSearchButton').removeClass('disabled');
-      attachThreadChanged();
-   });
-}
-
 /* Run on startup */
 $(document).ready(function() {
    $('button.close-nofloat').each(function(i,o) {
diff --git a/pgcommitfest/commitfest/templates/thread_attach.inc b/pgcommitfest/commitfest/templates/thread_attach.inc
new file mode 100644 (file)
index 0000000..b3eb19a
--- /dev/null
@@ -0,0 +1,39 @@
+{%comment%}Modal dialog for attach thread{%endcomment%}
+<style>
+#attachThreadListWrap.loading {
+    display: block;
+    background: url('/static/commitfest/spinner.gif') no-repeat center;
+    width: 124px;
+    height: 124px;
+    margin: 0 auto;
+}
+#attachThreadListWrap.loading * {
+    display: none;  
+}
+</style>
+
+<div class="modal hide fade" id="attachModal" role="dialog" style="width:80%; left: 10%; margin-left:auto; margin-right: auto;">
+ <div class="modal-header">
+  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+  <h3>Attach thread</h3>
+ </div>
+ <div class="modal-body">
+   <form class="form-search" style="margin-bottom: 5px;">
+     <div class="input-append">
+       <input id="attachThreadSearchField" type="text" class="span2 search-query">
+       <button id="attachThreadSearchButton" onclick="return findLatestThreads()" class="btn disabled">Search</button>
+     </div>
+   </form>
+   <div>Pick one of the recent emails from pgsql-hackers, or search above for subject or name:</div>
+   <div id="attachThreadListWrap">
+     <select id="attachThreadList" size="6" style="width:100%;" onchange="attachThreadChanged()">
+     </select>
+   </div>
+   <div>Or enter an <i>exact</i> message id:</div>
+   <input type="text" id="attachThreadMessageId" placeholder="Message id" class="input-block-level" onkeypress="attachThreadChanged()" onchange="attachThreadChanged()">
+ </div>
+ <div class="modal-footer">
+   <a href="#" class="btn" data-dismiss="modal">Close</a>
+   <a href="#" id="doAttachThreadButton" class="btn btn-primary disabled">Attach thread</a>
+ </div>
+</div>
diff --git a/pgcommitfest/commitfest/widgets.py b/pgcommitfest/commitfest/widgets.py
new file mode 100644 (file)
index 0000000..623643f
--- /dev/null
@@ -0,0 +1,8 @@
+from django.forms import TextInput
+from django.utils.safestring import mark_safe
+
+class ThreadPickWidget(TextInput):
+       def render(self, name, value, attrs=None):
+               html = super(ThreadPickWidget, self).render(name, value, attrs)
+               html = html + '&nbsp;<button class="btn attachThreadButton" id="btn_%s">Find thread</button>' % name
+               return mark_safe(html)