aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.3/examples/annotator/lib/main.js
blob: 9c9fba344f1ba88f2bc79f310fc00bcb1f640d58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
var widgets = require('widget');
var pageMod = require('page-mod');
var data = require('self').data;
var panels = require('panel');
var simpleStorage = require('simple-storage');
var notifications = require("notifications");
var privateBrowsing = require('private-browsing');

/*
Global variables
* Boolean to indicate whether the add-on is switched on or not
* Array for all workers associated with the 'selector' page mod
* Array for all workers associated with the 'matcher' page mod
*/
var annotatorIsOn = false;
var selectors = [];
var matchers = [];

if (!simpleStorage.storage.annotations)
  simpleStorage.storage.annotations = [];

/*
Update the matchers: call this whenever the set of annotations changes
*/
function updateMatchers() {
  matchers.forEach(function (matcher) {
    matcher.postMessage(simpleStorage.storage.annotations);
  });
}

/*
You can add annotations iff the add-on is on AND private browsing is off
*/
function canEnterAnnotations() {
  return (annotatorIsOn && !privateBrowsing.isActive);
}

/*
Constructor for an Annotation object
*/
function Annotation(annotationText, anchor) {
  this.annotationText = annotationText;
  this.url = anchor[0];
  this.ancestorId = anchor[1];
  this.anchorText = anchor[2];
}

/*
Function to deal with a new annotation.
Create a new annotation object, store it, and
notify all the annotators of the change.
*/
function handleNewAnnotation(annotationText, anchor) {
  var newAnnotation = new Annotation(annotationText, anchor);
  simpleStorage.storage.annotations.push(newAnnotation);
  updateMatchers();
}

/*
Function to tell the selector page mod that the add-on has become (in)active
*/
function activateSelectors() {
  selectors.forEach(
    function (selector) {
      selector.postMessage(canEnterAnnotations());
  });
}

/*
Toggle activation: update the on/off state and notify the selectors.
Toggling activation is disabled when private browsing is on.
*/
function toggleActivation() {
  if (privateBrowsing.isActive) {
    return false;
  }
  annotatorIsOn = !annotatorIsOn;
  activateSelectors();
  return canEnterAnnotations();
}

function detachWorker(worker, workerArray) {
  var index = workerArray.indexOf(worker);
  if(index != -1) {
    workerArray.splice(index, 1);
  }
}

exports.main = function() {

/*
The widget provides a mechanism to switch the selector on or off, and to
view the list of annotations.

The selector is switched on/off with a left-click, and the list of annotations
is displayed on a right-click.
*/
  var widget = widgets.Widget({
    id: 'toggle-switch',
    label: 'Annotator',
    contentURL: data.url('widget/pencil-off.png'),
    contentScriptWhen: 'ready',
    contentScriptFile: data.url('widget/widget.js')
  });

  widget.port.on('left-click', function() {
    console.log('activate/deactivate');
    widget.contentURL = toggleActivation() ?
              data.url('widget/pencil-on.png') :
              data.url('widget/pencil-off.png');
  });

  widget.port.on('right-click', function() {
      console.log('show annotation list');
      annotationList.show();
  });

/*
The selector page-mod enables the user to select page elements to annotate.

It is attached to all pages but only operates if the add-on is active.

The content script highlights any page elements which can be annotated. If the
user clicks a highlighted element it sends a message to the add-on containing
information about the element clicked, which is called the anchor of the
annotation.

When we receive this message we assign the anchor to the annotationEditor and
display it.
*/
  var selector = pageMod.PageMod({
    include: ['*'],
    contentScriptWhen: 'ready',
    contentScriptFile: [data.url('jquery-1.4.2.min.js'),
                        data.url('selector.js')],
    onAttach: function(worker) {
      worker.postMessage(canEnterAnnotations());
      selectors.push(worker);
      worker.port.on('show', function(data) {
        annotationEditor.annotationAnchor = data;
        annotationEditor.show();
      });
      worker.on('detach', function () {
        detachWorker(this, selectors);
      });
    }
  });

/*
The annotationEditor panel is the UI component used for creating
new annotations. It contains a text area for the user to
enter the annotation.

When we are ready to display the editor we assign its 'anchor' property
and call its show() method.

Its content script sends the content of the text area to the add-on
when the user presses the return key.

When we receives this message we create a new annotation using the anchor
and the text the user entered, store it, and hide the panel.
*/
  var annotationEditor = panels.Panel({
    width: 220,
    height: 220,
    contentURL: data.url('editor/annotation-editor.html'),
    contentScriptFile: data.url('editor/annotation-editor.js'),
    onMessage: function(annotationText) {
      if (annotationText)
        handleNewAnnotation(annotationText, this.annotationAnchor);
      annotationEditor.hide();
    },
    onShow: function() {
      this.postMessage('focus');
    }
  });

/*
The annotationList panel is the UI component that lists all the annotations
the user has entered.

On its 'show' event we pass it the array of annotations.

The content script creates the HTML elements for the annotations, and
intercepts clicks on the links, passing them back to the add-on to open them
in the browser.
*/
  var annotationList = panels.Panel({
    width: 420,
    height: 200,
    contentURL: data.url('list/annotation-list.html'),
    contentScriptFile: [data.url('jquery-1.4.2.min.js'),
                        data.url('list/annotation-list.js')],
    contentScriptWhen: 'ready',
    onShow: function() {
      this.postMessage(simpleStorage.storage.annotations);
    },
    onMessage: function(message) {
      require('tabs').open(message);
    }
  });

/*
We listen for the OverQuota event from simple-storage.
If it fires we just notify the user and delete the most
recent annotations until we are back in quota.
*/
  simpleStorage.on("OverQuota", function () {
    notifications.notify({
      title: 'Storage space exceeded',
      text: 'Removing recent annotations'});
    while (simpleStorage.quotaUsage > 1)
      simpleStorage.storage.annotations.pop();
  });

/*
We listen for private browsing start/stop events to change the widget icon
and to notify the selectors of the change in state.
*/
  privateBrowsing.on('start', function() {
    widget.contentURL = data.url('widget/pencil-off.png');
    activateSelectors();
  });

  privateBrowsing.on('stop', function() {
    if (canEnterAnnotations()) {
      widget.contentURL = data.url('widget/pencil-on.png');
      activateSelectors();
    }
  });

/*
The matcher page-mod locates anchors on web pages and prepares for the
annotation to be displayed.

It is attached to all pages, and when it is attached we pass it the complete
list of annotations. It looks for anchors in its page. If it finds one it
highlights the anchor and binds mouseenter/mouseout events to 'show' and 'hide'
messages to the add-on.

When the add-on receives the 'show' message it assigns the annotation text to
the annotation panel and shows it.

Note that the matcher is active whether or not the add-on is active:
'inactive' only means that the user can't create new add-ons, they can still
see old ones.
*/
  var matcher = pageMod.PageMod({
    include: ['*'],
    contentScriptWhen: 'ready',
    contentScriptFile: [data.url('jquery-1.4.2.min.js'),
                        data.url('matcher.js')],
    onAttach: function(worker) {
      if(simpleStorage.storage.annotations) {
        worker.postMessage(simpleStorage.storage.annotations);
      }
      worker.port.on('show', function(data) {
        annotation.content = data;
        annotation.show();
      });
      worker.port.on('hide', function() {
        annotation.content = null;
        annotation.hide();
      });
      worker.on('detach', function () {
        detachWorker(this, matchers);
      });
      matchers.push(worker);
    }
  });

/*
The annotation panel is the UI component that displays an annotation.

When we are ready to show it we assign its 'content' attribute to contain
the annotation text, and that gets sent to the content process in onShow().
*/
  var annotation = panels.Panel({
    width: 200,
    height: 180,
    contentURL: data.url('annotation/annotation.html'),
    contentScriptFile: [data.url('jquery-1.4.2.min.js'),
                        data.url('annotation/annotation.js')],
    contentScriptWhen: 'ready',
    onShow: function() {
      this.postMessage(this.content);
    }
  });

}