nmbug.js: Add a 'frame' argument to nmbug.show
[nmhive.git] / nmbug.js
1 var nmbug_server = 'http://localhost:5000';
2
3 nmbug = {
4         show: function (message_id, frame) {
5                 var _this = this;
6                 if (frame === undefined) {
7                         frame = window;
8                 }
9                 this._get_available_tags(function (available_tags) {
10                         _this._get_tags(
11                                 message_id,
12                                 _this._edit_tags.bind(_this, frame, available_tags, message_id));
13                 });
14         },
15         _get_available_tags: function (callback) {
16                 var url = [
17                         nmbug_server,
18                         'tags',
19                         ].join('/');
20                 console.log('nmbug: get available tags from ' + url);
21                 var request = new XMLHttpRequest();
22                 request.onload = function () {
23                         if (this.status == 200) {
24                                 var available_tags = JSON.parse(this.response);
25                                 console.log('nmbug: got available tags', available_tags);
26                                 callback(available_tags);
27                         } else {
28                                 throw 'Error fetching ' + url + ' (status ' + this.status + ')';
29                         }
30                 };
31                 request.open('get', url, true);
32                 request.send();
33         },
34         _get_tags: function (message_id, callback) {
35                 var url = [
36                         nmbug_server,
37                         'mid',
38                         encodeURIComponent(message_id),
39                         ].join('/');
40                 console.log('nmbug: get tags from ' + url);
41                 var request = new XMLHttpRequest();
42                 request.onload = function () {
43                         if (this.status == 200) {
44                                 var tags = JSON.parse(this.response);
45                                 console.log('nmbug: got tags', tags);
46                                 callback(tags);
47                         } else {
48                                 throw 'Error fetching ' + url + ' (status ' + this.status + ')';
49                         }
50                 };
51                 request.open('get', url, true);
52                 request.send();
53         },
54         _edit_tags: function (frame, available_tags, message_id, tags) {
55                 if (frame.document.createElement('dialog').show) {
56                         this._x_edit_tags(frame, available_tags, message_id, tags);
57                 } else {
58                         var _this = this;
59                         var needed = [];
60
61                         function basename (element) {
62                                 var url;
63                                 if (!element.tagName) {
64                                         return;
65                                 } else if (element.tagName.toLowerCase() == 'script') {
66                                         url = element.src;
67                                 } else if (element.tagName.toLowerCase() == 'link') {
68                                         url = element.href;
69                                 } else {
70                                         return;
71                                 }
72                                 return url.replace(/.*\//, '');
73                         }
74
75                         function onload () {
76                                 var name = basename(this);
77                                 console.log('nmbug: loaded ' + name, this);
78                                 var index = needed.indexOf(name);
79                                 if (index !== -1) {
80                                         needed.splice(index, 1);
81                                 }
82                                 if (needed.length == 0) {
83                                         _this._x_edit_tags(frame, available_tags, message_id, tags);
84                                 }
85                         }
86
87                         function has_header (name) {
88                                 var nodes = frame.document.head.childNodes;
89                                 for (var i = 0; i < nodes.length; i++) {
90                                         if (basename(nodes[i]) == name) {
91                                                 return true;
92                                         }
93                                 }
94                                 return false;
95                         }
96
97                         if (!has_header('dialog-polyfill.js')) {
98                                 needed.push('dialog-polyfill.js');
99                                 var script = frame.document.createElement('script');
100                                 script.type = 'text/javascript';
101                                 script.src = nmbug_server + '/static/dialog-polyfill/dialog-polyfill.js';
102                                 script.onload = onload;
103                                 console.log('nmbug: loading dialog-polyfill.js');
104                                 frame.document.head.appendChild(script);
105                         }
106
107                         if (!has_header('dialog-polyfill.css')) {
108                                 needed.push('dialog-polyfill.css');
109                                 var link = frame.document.createElement('link');
110                                 link.rel = 'stylesheet';
111                                 link.type = 'text/css';
112                                 link.href = nmbug_server + '/static/dialog-polyfill/dialog-polyfill.css';
113                                 link.onload = onload;
114                                 console.log('nmbug: loading dialog-polyfill.css');
115                                 frame.document.head.appendChild(link);
116                         }
117                 }
118         },
119         _x_edit_tags: function (frame, available_tags, message_id, tags) {
120                 var dialog = frame.document.createElement('dialog');
121                 if (!frame.document.createElement('dialog').show) {
122                         frame.dialogPolyfill.registerDialog(dialog);
123                 }
124
125                 dialog.style.border = '1px solid rgba(0, 0, 0, 0.3)';
126                 dialog.style.borderRadius = '6px';
127                 dialog.style.boxShadow = '0 3px 7px rgba(0, 0, 0, 0.3)';
128
129                 var content = frame.document.createElement('p');
130                 content.innerHTML = 'Edit tags for ' + message_id;
131                 dialog.appendChild(content);
132
133                 var ul = frame.document.createElement('ul');
134                 dialog.appendChild(ul);
135                 for (var i = 0; i < available_tags.length; i++) {
136                         var li = frame.document.createElement('li');
137                         li.innerHTML = available_tags[i];
138                         li.style.cursor = 'pointer';
139                         if (tags.indexOf(available_tags[i]) >= 0) {
140                                 li.style.backgroundColor = 'lime';
141                         }
142                         li.onclick = this._toggle_tag.bind(
143                                 this, message_id, available_tags[i], li);
144                         ul.appendChild(li);
145                 }
146                 var close = frame.document.createElement('button');
147                 close.innerHTML = 'Close';
148                 close.onclick = function () {
149                         dialog.close();
150                 };
151                 dialog.appendChild(close);
152
153                 frame.document.body.appendChild(dialog);
154
155                 dialog.show();
156         },
157         _toggle_tag: function (message_id, tag, li) {
158                 var prefix;
159                 if (li.style.backgroundColor == 'lime') {
160                         prefix = '-';  /* unset */
161                         li.style.backgroundColor = null;
162                 } else {
163                         prefix = '+';  /* set */
164                         li.style.backgroundColor = 'lime';
165                 }
166                 var url = [
167                         nmbug_server,
168                         'mid',
169                         encodeURIComponent(message_id),
170                         ].join('/');
171                 console.log('nmbug: alter tags via ' + url);
172                 var request = new XMLHttpRequest();
173                 request.onload = function () {
174                         if (this.status == 200) {
175                                 var tags = JSON.parse(this.response);
176                                 console.log('nmbug: got tags', tags);
177                         } else {
178                                 throw 'Error posting to ' + url + ' (status ' + this.status + ')';
179                         }
180                 };
181                 request.open('post', url, true);
182                 request.setRequestHeader(
183                         'Content-Type', 'application/json; charset=UTF-8');
184                 request.send(JSON.stringify([prefix + tag]));
185         },
186 };
187
188 var _gmane_handler = {
189         regexp: /gmane[.]org/,
190         handle: function (callback) {
191                 var frame = this._get_frame();
192                 var article = this._article_from_url(frame.document.URL);
193                 this._get_message_id(article, function (message_id) {
194                         callback(message_id, frame);
195                 });
196         },
197         _article_from_url: function (url) {
198                 var regexp = new RegExp('http://article.gmane.org/([^/]+)/([0-9]+)');
199                 var match = regexp.exec(url);
200                 console.log('nmbug: get article from ' + url, match);
201                 if (match) {
202                         return {'group': match[1], 'id': parseInt(match[2])};
203                 }
204         },
205         _get_frame: function () {
206                 var frame = window;
207                 var article = this._article_from_url(frame.document.URL);
208                 var i = 0;
209                 for (var i = 0; !article && i < window.frames.length; i++) {
210                         frame = window.frames[i];
211                         article = this._article_from_url(frame.document.URL);
212                 }
213                 if (!article) {
214                         throw "Cannot extract an article from Gmane's " + document.URL;
215                 }
216                 return frame;
217         },
218         _get_message_id: function (article, callback) {
219                 var url = [
220                         nmbug_server,
221                         'gmane',
222                         article.group,
223                         article.id,
224                 ].join('/');
225                 console.log('nmbug: get Message-ID from ' + url);
226                 var request = new XMLHttpRequest();
227                 request.onload = function () {
228                         var message_id = this.responseText;
229                         callback(message_id);
230                 };
231                 request.open('get', url, true);
232                 request.send();
233         },
234 };
235
236 var handlers = [
237         _gmane_handler,
238 ];
239
240 function _check_handler(handler) {
241         var match = handler.regexp.test(document.URL);
242         console.log('nmbug: testing', handler, match);
243         if (match) {
244                 console.log('nmbug: matched', handler);
245                 handler.handle(nmbug.show.bind(nmbug));
246         }
247         return match;  /* break after the first match */
248 }
249
250 function run() {
251         var matched = handlers.some(_check_handler);
252         if (!matched) {
253                 throw 'No handler for ' + document.URL;
254         }
255 }