README.rst: Unwind 'If ..., returns a 404' -> 'Returns a 404 if ...'
[nmhive.git] / nmbug.js
1 /*
2  Copyright (C) 2014 W. Trevor King <wking@tremily.us>
3
4  Redistribution and use in source and binary forms, with or without
5  modification, are permitted provided that the following conditions are met:
6
7  * Redistributions of source code must retain the above copyright notice, this
8  list of conditions and the following disclaimer.
9
10  * Redistributions in binary form must reproduce the above copyright notice,
11  this list of conditions and the following disclaimer in the documentation
12  and/or other materials provided with the distribution.
13
14  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 var nmbug_server = 'http://localhost:5000';
28
29 nmbug = {
30         show: function (message_id, frame) {
31                 var _this = this;
32                 if (frame === undefined) {
33                         frame = window;
34                 }
35                 this._get_available_tags(function (available_tags) {
36                         _this._get_tags(
37                                 message_id,
38                                 _this._edit_tags.bind(_this, frame, available_tags, message_id));
39                 });
40         },
41         _get_available_tags: function (callback) {
42                 var url = [
43                         nmbug_server,
44                         'tags',
45                         ].join('/');
46                 console.log('nmbug: get available tags from ' + url);
47                 var request = new XMLHttpRequest();
48                 request.onload = function () {
49                         if (this.status == 200) {
50                                 var available_tags = JSON.parse(this.response);
51                                 console.log('nmbug: got available tags', available_tags);
52                                 callback(available_tags);
53                         } else {
54                                 throw 'Error fetching ' + url + ' (status ' + this.status + ')';
55                         }
56                 };
57                 request.open('get', url, true);
58                 request.send();
59         },
60         _get_tags: function (message_id, callback) {
61                 var url = [
62                         nmbug_server,
63                         'mid',
64                         encodeURIComponent(message_id),
65                         ].join('/');
66                 console.log('nmbug: get tags from ' + url);
67                 var request = new XMLHttpRequest();
68                 request.onload = function () {
69                         if (this.status == 200) {
70                                 var tags = JSON.parse(this.response);
71                                 console.log('nmbug: got tags', tags);
72                                 callback(tags);
73                         } else {
74                                 throw 'Error fetching ' + url + ' (status ' + this.status + ')';
75                         }
76                 };
77                 request.open('get', url, true);
78                 request.send();
79         },
80         _edit_tags: function (frame, available_tags, message_id, tags) {
81                 var have_dialog_polyfill;
82                 try {
83                         have_dialog_polyfill = frame.dialogPolyfill !== undefined;
84                 } catch (error) {
85                         if (error.name == 'ReferenceError') {
86                                 have_dialog_polyfill = false;
87                         }
88                 }
89                 if (frame.document.createElement('dialog').show || have_dialog_polyfill) {
90                         this._x_edit_tags(frame, available_tags, message_id, tags);
91                 } else {
92                         var script = frame.document.createElement('script');
93                         script.type = 'text/javascript';
94                         script.src = nmbug_server + '/static/dialog-polyfill/dialog-polyfill.js';
95                         script.async = false;
96                         console.log('nmbug: loading dialog-polyfill.js');
97                         frame.document.head.appendChild(script);
98
99                         var link = frame.document.createElement('link');
100                         link.rel = 'stylesheet';
101                         link.type = 'text/css';
102                         link.href = nmbug_server + '/static/dialog-polyfill/dialog-polyfill.css';
103                         link.async = false;
104                         console.log('nmbug: loading dialog-polyfill.css');
105                         frame.document.head.appendChild(link);
106
107                         var _this = this;
108                         function edit_tags_after_dialog_polyfill () {
109                                 try {
110                                         have_dialog_polyfill = frame.dialogPolyfill !== undefined;
111                                         console.log('have dialogPolyfill');
112                                         window.setTimeout(
113                                                         _this._x_edit_tags.bind(_this), 200,
114                                                         frame, available_tags, message_id, tags);
115                                 } catch (error) {
116                                         if (error.name == 'ReferenceError') {
117                                                 console.log('waiting for dialogPolyfill');
118                                                 window.setTimeout(edit_tags_after_dialog_polyfill, 200);
119                                         }
120                                 }
121                         }
122                         edit_tags_after_dialog_polyfill();
123                 }
124         },
125         _x_edit_tags: function (frame, available_tags, message_id, tags) {
126                 var dialog = frame.document.createElement('dialog');
127                 if (!frame.document.createElement('dialog').show) {
128                         frame.dialogPolyfill.registerDialog(dialog);
129                 }
130
131                 dialog.style.border = '1px solid rgba(0, 0, 0, 0.3)';
132                 dialog.style.borderRadius = '6px';
133                 dialog.style.boxShadow = '0 3px 7px rgba(0, 0, 0, 0.3)';
134                 dialog.style.marginLeft = '10em';
135                 dialog.style.marginRight = '10em';
136
137                 var content = frame.document.createElement('p');
138                 content.innerHTML = 'Edit tags for ' + message_id;
139                 dialog.appendChild(content);
140
141                 var tag_list = frame.document.createElement('p');
142                 dialog.appendChild(tag_list);
143                 for (var i = 0; i < available_tags.length; i++) {
144                         var tag = frame.document.createElement('a');
145                         tag.innerHTML = available_tags[i];
146                         tag.style.cursor = 'pointer';
147                         if (tags.indexOf(available_tags[i]) >= 0) {
148                                 tag.style.backgroundColor = 'lime';
149                         }
150                         tag.onclick = this._toggle_tag.bind(
151                                 this, message_id, available_tags[i], tag);
152                         tag_list.appendChild(tag);
153                         tag_list.appendChild(frame.document.createTextNode(' '));
154                 }
155                 var close = frame.document.createElement('button');
156                 close.innerHTML = 'Close';
157                 close.onclick = function () {
158                         dialog.close();
159                 };
160                 dialog.appendChild(close);
161
162                 frame.document.body.insertBefore(dialog, frame.document.body.firstChild);
163
164                 dialog.show();
165         },
166         _toggle_tag: function (message_id, tag, element) {
167                 var prefix;
168                 if (element.style.backgroundColor == 'lime') {
169                         prefix = '-';  /* unset */
170                         element.style.backgroundColor = null;
171                 } else {
172                         prefix = '+';  /* set */
173                         element.style.backgroundColor = 'lime';
174                 }
175                 var url = [
176                         nmbug_server,
177                         'mid',
178                         encodeURIComponent(message_id),
179                         ].join('/');
180                 console.log('nmbug: alter tags via ' + url);
181                 var request = new XMLHttpRequest();
182                 request.onload = function () {
183                         if (this.status == 200) {
184                                 var tags = JSON.parse(this.response);
185                                 console.log('nmbug: got tags', tags);
186                         } else {
187                                 throw 'Error posting to ' + url + ' (status ' + this.status + ')';
188                         }
189                 };
190                 request.open('post', url, true);
191                 request.setRequestHeader(
192                         'Content-Type', 'application/json; charset=UTF-8');
193                 request.send(JSON.stringify([prefix + tag]));
194         },
195 };
196
197 var _gmane_handler = {
198         regexp: /gmane[.]org/,
199         handle: function (callback) {
200                 var frame = this._get_frame();
201                 var article = this._article_from_url(frame.document.URL);
202                 this._get_message_id(article, function (message_id) {
203                         callback(message_id, frame);
204                 });
205         },
206         _article_from_url: function (url) {
207                 var regexp = new RegExp('http://article.gmane.org/([^/]+)/([0-9]+)');
208                 var match = regexp.exec(url);
209                 console.log('nmbug: get article from ' + url, match);
210                 if (match) {
211                         return {'group': match[1], 'id': parseInt(match[2])};
212                 }
213         },
214         _get_frame: function () {
215                 var frame = window;
216                 var article = this._article_from_url(frame.document.URL);
217                 var i = 0;
218                 for (var i = 0; !article && i < window.frames.length; i++) {
219                         frame = window.frames[i];
220                         article = this._article_from_url(frame.document.URL);
221                 }
222                 if (!article) {
223                         throw "Cannot extract an article from Gmane's " + document.URL;
224                 }
225                 return frame;
226         },
227         _get_message_id: function (article, callback) {
228                 var url = [
229                         nmbug_server,
230                         'gmane',
231                         article.group,
232                         article.id,
233                 ].join('/');
234                 console.log('nmbug: get Message-ID from ' + url);
235                 var request = new XMLHttpRequest();
236                 request.onload = function () {
237                         var message_id = this.responseText;
238                         callback(message_id);
239                 };
240                 request.open('get', url, true);
241                 request.send();
242         },
243 };
244
245 var handlers = [
246         _gmane_handler,
247 ];
248
249 function _check_handler(handler) {
250         var match = handler.regexp.test(document.URL);
251         console.log('nmbug: testing', handler, match);
252         if (match) {
253                 console.log('nmbug: matched', handler);
254                 handler.handle(nmbug.show.bind(nmbug));
255         }
256         return match;  /* break after the first match */
257 }
258
259 function run() {
260         var matched = handlers.some(_check_handler);
261         if (!matched) {
262                 throw 'No handler for ' + document.URL;
263         }
264 }