|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 var widgets = require('sdk/widget'); |
|
6 var pageMod = require('sdk/page-mod'); |
|
7 var data = require('sdk/self').data; |
|
8 var panels = require('sdk/panel'); |
|
9 var simpleStorage = require('sdk/simple-storage'); |
|
10 var notifications = require("sdk/notifications"); |
|
11 |
|
12 /* |
|
13 Global variables |
|
14 * Boolean to indicate whether the add-on is switched on or not |
|
15 * Array for all workers associated with the 'selector' page mod |
|
16 * Array for all workers associated with the 'matcher' page mod |
|
17 */ |
|
18 var annotatorIsOn = false; |
|
19 var selectors = []; |
|
20 var matchers = []; |
|
21 |
|
22 if (!simpleStorage.storage.annotations) |
|
23 simpleStorage.storage.annotations = []; |
|
24 |
|
25 /* |
|
26 Update the matchers: call this whenever the set of annotations changes |
|
27 */ |
|
28 function updateMatchers() { |
|
29 matchers.forEach(function (matcher) { |
|
30 matcher.postMessage(simpleStorage.storage.annotations); |
|
31 }); |
|
32 } |
|
33 |
|
34 /* |
|
35 Constructor for an Annotation object |
|
36 */ |
|
37 function Annotation(annotationText, anchor) { |
|
38 this.annotationText = annotationText; |
|
39 this.url = anchor[0]; |
|
40 this.ancestorId = anchor[1]; |
|
41 this.anchorText = anchor[2]; |
|
42 } |
|
43 |
|
44 /* |
|
45 Function to deal with a new annotation. |
|
46 Create a new annotation object, store it, and |
|
47 notify all the annotators of the change. |
|
48 */ |
|
49 function handleNewAnnotation(annotationText, anchor) { |
|
50 var newAnnotation = new Annotation(annotationText, anchor); |
|
51 simpleStorage.storage.annotations.push(newAnnotation); |
|
52 updateMatchers(); |
|
53 } |
|
54 |
|
55 /* |
|
56 Function to tell the selector page mod that the add-on has become (in)active |
|
57 */ |
|
58 function activateSelectors() { |
|
59 selectors.forEach( |
|
60 function (selector) { |
|
61 selector.postMessage(annotatorIsOn); |
|
62 }); |
|
63 } |
|
64 |
|
65 /* |
|
66 Toggle activation: update the on/off state and notify the selectors. |
|
67 */ |
|
68 function toggleActivation() { |
|
69 annotatorIsOn = !annotatorIsOn; |
|
70 activateSelectors(); |
|
71 return annotatorIsOn; |
|
72 } |
|
73 |
|
74 function detachWorker(worker, workerArray) { |
|
75 var index = workerArray.indexOf(worker); |
|
76 if(index != -1) { |
|
77 workerArray.splice(index, 1); |
|
78 } |
|
79 } |
|
80 |
|
81 exports.main = function() { |
|
82 |
|
83 /* |
|
84 The widget provides a mechanism to switch the selector on or off, and to |
|
85 view the list of annotations. |
|
86 |
|
87 The selector is switched on/off with a left-click, and the list of annotations |
|
88 is displayed on a right-click. |
|
89 */ |
|
90 var widget = widgets.Widget({ |
|
91 id: 'toggle-switch', |
|
92 label: 'Annotator', |
|
93 contentURL: data.url('widget/pencil-off.png'), |
|
94 contentScriptWhen: 'ready', |
|
95 contentScriptFile: data.url('widget/widget.js') |
|
96 }); |
|
97 |
|
98 widget.port.on('left-click', function() { |
|
99 console.log('activate/deactivate'); |
|
100 widget.contentURL = toggleActivation() ? |
|
101 data.url('widget/pencil-on.png') : |
|
102 data.url('widget/pencil-off.png'); |
|
103 }); |
|
104 |
|
105 widget.port.on('right-click', function() { |
|
106 console.log('show annotation list'); |
|
107 annotationList.show(); |
|
108 }); |
|
109 |
|
110 /* |
|
111 The selector page-mod enables the user to select page elements to annotate. |
|
112 |
|
113 It is attached to all pages but only operates if the add-on is active. |
|
114 |
|
115 The content script highlights any page elements which can be annotated. If the |
|
116 user clicks a highlighted element it sends a message to the add-on containing |
|
117 information about the element clicked, which is called the anchor of the |
|
118 annotation. |
|
119 |
|
120 When we receive this message we assign the anchor to the annotationEditor and |
|
121 display it. |
|
122 */ |
|
123 var selector = pageMod.PageMod({ |
|
124 include: ['*'], |
|
125 contentScriptWhen: 'ready', |
|
126 contentScriptFile: [data.url('jquery-1.4.2.min.js'), |
|
127 data.url('selector.js')], |
|
128 onAttach: function(worker) { |
|
129 worker.postMessage(annotatorIsOn); |
|
130 selectors.push(worker); |
|
131 worker.port.on('show', function(data) { |
|
132 annotationEditor.annotationAnchor = data; |
|
133 annotationEditor.show(); |
|
134 }); |
|
135 worker.on('detach', function () { |
|
136 detachWorker(this, selectors); |
|
137 }); |
|
138 } |
|
139 }); |
|
140 |
|
141 /* |
|
142 The annotationEditor panel is the UI component used for creating |
|
143 new annotations. It contains a text area for the user to |
|
144 enter the annotation. |
|
145 |
|
146 When we are ready to display the editor we assign its 'anchor' property |
|
147 and call its show() method. |
|
148 |
|
149 Its content script sends the content of the text area to the add-on |
|
150 when the user presses the return key. |
|
151 |
|
152 When we receives this message we create a new annotation using the anchor |
|
153 and the text the user entered, store it, and hide the panel. |
|
154 */ |
|
155 var annotationEditor = panels.Panel({ |
|
156 width: 220, |
|
157 height: 220, |
|
158 contentURL: data.url('editor/annotation-editor.html'), |
|
159 contentScriptFile: data.url('editor/annotation-editor.js'), |
|
160 onMessage: function(annotationText) { |
|
161 if (annotationText) |
|
162 handleNewAnnotation(annotationText, this.annotationAnchor); |
|
163 annotationEditor.hide(); |
|
164 }, |
|
165 onShow: function() { |
|
166 this.postMessage('focus'); |
|
167 } |
|
168 }); |
|
169 |
|
170 /* |
|
171 The annotationList panel is the UI component that lists all the annotations |
|
172 the user has entered. |
|
173 |
|
174 On its 'show' event we pass it the array of annotations. |
|
175 |
|
176 The content script creates the HTML elements for the annotations, and |
|
177 intercepts clicks on the links, passing them back to the add-on to open them |
|
178 in the browser. |
|
179 */ |
|
180 var annotationList = panels.Panel({ |
|
181 width: 420, |
|
182 height: 200, |
|
183 contentURL: data.url('list/annotation-list.html'), |
|
184 contentScriptFile: [data.url('jquery-1.4.2.min.js'), |
|
185 data.url('list/annotation-list.js')], |
|
186 contentScriptWhen: 'ready', |
|
187 onShow: function() { |
|
188 this.postMessage(simpleStorage.storage.annotations); |
|
189 }, |
|
190 onMessage: function(message) { |
|
191 require('sdk/tabs').open(message); |
|
192 } |
|
193 }); |
|
194 |
|
195 /* |
|
196 We listen for the OverQuota event from simple-storage. |
|
197 If it fires we just notify the user and delete the most |
|
198 recent annotations until we are back in quota. |
|
199 */ |
|
200 simpleStorage.on("OverQuota", function () { |
|
201 notifications.notify({ |
|
202 title: 'Storage space exceeded', |
|
203 text: 'Removing recent annotations'}); |
|
204 while (simpleStorage.quotaUsage > 1) |
|
205 simpleStorage.storage.annotations.pop(); |
|
206 }); |
|
207 |
|
208 /* |
|
209 The matcher page-mod locates anchors on web pages and prepares for the |
|
210 annotation to be displayed. |
|
211 |
|
212 It is attached to all pages, and when it is attached we pass it the complete |
|
213 list of annotations. It looks for anchors in its page. If it finds one it |
|
214 highlights the anchor and binds mouseenter/mouseout events to 'show' and 'hide' |
|
215 messages to the add-on. |
|
216 |
|
217 When the add-on receives the 'show' message it assigns the annotation text to |
|
218 the annotation panel and shows it. |
|
219 |
|
220 Note that the matcher is active whether or not the add-on is active: |
|
221 'inactive' only means that the user can't create new add-ons, they can still |
|
222 see old ones. |
|
223 */ |
|
224 var matcher = pageMod.PageMod({ |
|
225 include: ['*'], |
|
226 contentScriptWhen: 'ready', |
|
227 contentScriptFile: [data.url('jquery-1.4.2.min.js'), |
|
228 data.url('matcher.js')], |
|
229 onAttach: function(worker) { |
|
230 if(simpleStorage.storage.annotations) { |
|
231 worker.postMessage(simpleStorage.storage.annotations); |
|
232 } |
|
233 worker.port.on('show', function(data) { |
|
234 annotation.content = data; |
|
235 annotation.show(); |
|
236 }); |
|
237 worker.port.on('hide', function() { |
|
238 annotation.content = null; |
|
239 annotation.hide(); |
|
240 }); |
|
241 worker.on('detach', function () { |
|
242 detachWorker(this, matchers); |
|
243 }); |
|
244 matchers.push(worker); |
|
245 } |
|
246 }); |
|
247 |
|
248 /* |
|
249 The annotation panel is the UI component that displays an annotation. |
|
250 |
|
251 When we are ready to show it we assign its 'content' attribute to contain |
|
252 the annotation text, and that gets sent to the content process in onShow(). |
|
253 */ |
|
254 var annotation = panels.Panel({ |
|
255 width: 200, |
|
256 height: 180, |
|
257 contentURL: data.url('annotation/annotation.html'), |
|
258 contentScriptFile: [data.url('jquery-1.4.2.min.js'), |
|
259 data.url('annotation/annotation.js')], |
|
260 contentScriptWhen: 'ready', |
|
261 onShow: function() { |
|
262 this.postMessage(this.content); |
|
263 } |
|
264 }); |
|
265 |
|
266 } |