1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/examples/annotator/lib/main.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,266 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +var widgets = require('sdk/widget'); 1.9 +var pageMod = require('sdk/page-mod'); 1.10 +var data = require('sdk/self').data; 1.11 +var panels = require('sdk/panel'); 1.12 +var simpleStorage = require('sdk/simple-storage'); 1.13 +var notifications = require("sdk/notifications"); 1.14 + 1.15 +/* 1.16 +Global variables 1.17 +* Boolean to indicate whether the add-on is switched on or not 1.18 +* Array for all workers associated with the 'selector' page mod 1.19 +* Array for all workers associated with the 'matcher' page mod 1.20 +*/ 1.21 +var annotatorIsOn = false; 1.22 +var selectors = []; 1.23 +var matchers = []; 1.24 + 1.25 +if (!simpleStorage.storage.annotations) 1.26 + simpleStorage.storage.annotations = []; 1.27 + 1.28 +/* 1.29 +Update the matchers: call this whenever the set of annotations changes 1.30 +*/ 1.31 +function updateMatchers() { 1.32 + matchers.forEach(function (matcher) { 1.33 + matcher.postMessage(simpleStorage.storage.annotations); 1.34 + }); 1.35 +} 1.36 + 1.37 +/* 1.38 +Constructor for an Annotation object 1.39 +*/ 1.40 +function Annotation(annotationText, anchor) { 1.41 + this.annotationText = annotationText; 1.42 + this.url = anchor[0]; 1.43 + this.ancestorId = anchor[1]; 1.44 + this.anchorText = anchor[2]; 1.45 +} 1.46 + 1.47 +/* 1.48 +Function to deal with a new annotation. 1.49 +Create a new annotation object, store it, and 1.50 +notify all the annotators of the change. 1.51 +*/ 1.52 +function handleNewAnnotation(annotationText, anchor) { 1.53 + var newAnnotation = new Annotation(annotationText, anchor); 1.54 + simpleStorage.storage.annotations.push(newAnnotation); 1.55 + updateMatchers(); 1.56 +} 1.57 + 1.58 +/* 1.59 +Function to tell the selector page mod that the add-on has become (in)active 1.60 +*/ 1.61 +function activateSelectors() { 1.62 + selectors.forEach( 1.63 + function (selector) { 1.64 + selector.postMessage(annotatorIsOn); 1.65 + }); 1.66 +} 1.67 + 1.68 +/* 1.69 +Toggle activation: update the on/off state and notify the selectors. 1.70 +*/ 1.71 +function toggleActivation() { 1.72 + annotatorIsOn = !annotatorIsOn; 1.73 + activateSelectors(); 1.74 + return annotatorIsOn; 1.75 +} 1.76 + 1.77 +function detachWorker(worker, workerArray) { 1.78 + var index = workerArray.indexOf(worker); 1.79 + if(index != -1) { 1.80 + workerArray.splice(index, 1); 1.81 + } 1.82 +} 1.83 + 1.84 +exports.main = function() { 1.85 + 1.86 +/* 1.87 +The widget provides a mechanism to switch the selector on or off, and to 1.88 +view the list of annotations. 1.89 + 1.90 +The selector is switched on/off with a left-click, and the list of annotations 1.91 +is displayed on a right-click. 1.92 +*/ 1.93 + var widget = widgets.Widget({ 1.94 + id: 'toggle-switch', 1.95 + label: 'Annotator', 1.96 + contentURL: data.url('widget/pencil-off.png'), 1.97 + contentScriptWhen: 'ready', 1.98 + contentScriptFile: data.url('widget/widget.js') 1.99 + }); 1.100 + 1.101 + widget.port.on('left-click', function() { 1.102 + console.log('activate/deactivate'); 1.103 + widget.contentURL = toggleActivation() ? 1.104 + data.url('widget/pencil-on.png') : 1.105 + data.url('widget/pencil-off.png'); 1.106 + }); 1.107 + 1.108 + widget.port.on('right-click', function() { 1.109 + console.log('show annotation list'); 1.110 + annotationList.show(); 1.111 + }); 1.112 + 1.113 +/* 1.114 +The selector page-mod enables the user to select page elements to annotate. 1.115 + 1.116 +It is attached to all pages but only operates if the add-on is active. 1.117 + 1.118 +The content script highlights any page elements which can be annotated. If the 1.119 +user clicks a highlighted element it sends a message to the add-on containing 1.120 +information about the element clicked, which is called the anchor of the 1.121 +annotation. 1.122 + 1.123 +When we receive this message we assign the anchor to the annotationEditor and 1.124 +display it. 1.125 +*/ 1.126 + var selector = pageMod.PageMod({ 1.127 + include: ['*'], 1.128 + contentScriptWhen: 'ready', 1.129 + contentScriptFile: [data.url('jquery-1.4.2.min.js'), 1.130 + data.url('selector.js')], 1.131 + onAttach: function(worker) { 1.132 + worker.postMessage(annotatorIsOn); 1.133 + selectors.push(worker); 1.134 + worker.port.on('show', function(data) { 1.135 + annotationEditor.annotationAnchor = data; 1.136 + annotationEditor.show(); 1.137 + }); 1.138 + worker.on('detach', function () { 1.139 + detachWorker(this, selectors); 1.140 + }); 1.141 + } 1.142 + }); 1.143 + 1.144 +/* 1.145 +The annotationEditor panel is the UI component used for creating 1.146 +new annotations. It contains a text area for the user to 1.147 +enter the annotation. 1.148 + 1.149 +When we are ready to display the editor we assign its 'anchor' property 1.150 +and call its show() method. 1.151 + 1.152 +Its content script sends the content of the text area to the add-on 1.153 +when the user presses the return key. 1.154 + 1.155 +When we receives this message we create a new annotation using the anchor 1.156 +and the text the user entered, store it, and hide the panel. 1.157 +*/ 1.158 + var annotationEditor = panels.Panel({ 1.159 + width: 220, 1.160 + height: 220, 1.161 + contentURL: data.url('editor/annotation-editor.html'), 1.162 + contentScriptFile: data.url('editor/annotation-editor.js'), 1.163 + onMessage: function(annotationText) { 1.164 + if (annotationText) 1.165 + handleNewAnnotation(annotationText, this.annotationAnchor); 1.166 + annotationEditor.hide(); 1.167 + }, 1.168 + onShow: function() { 1.169 + this.postMessage('focus'); 1.170 + } 1.171 + }); 1.172 + 1.173 +/* 1.174 +The annotationList panel is the UI component that lists all the annotations 1.175 +the user has entered. 1.176 + 1.177 +On its 'show' event we pass it the array of annotations. 1.178 + 1.179 +The content script creates the HTML elements for the annotations, and 1.180 +intercepts clicks on the links, passing them back to the add-on to open them 1.181 +in the browser. 1.182 +*/ 1.183 + var annotationList = panels.Panel({ 1.184 + width: 420, 1.185 + height: 200, 1.186 + contentURL: data.url('list/annotation-list.html'), 1.187 + contentScriptFile: [data.url('jquery-1.4.2.min.js'), 1.188 + data.url('list/annotation-list.js')], 1.189 + contentScriptWhen: 'ready', 1.190 + onShow: function() { 1.191 + this.postMessage(simpleStorage.storage.annotations); 1.192 + }, 1.193 + onMessage: function(message) { 1.194 + require('sdk/tabs').open(message); 1.195 + } 1.196 + }); 1.197 + 1.198 +/* 1.199 +We listen for the OverQuota event from simple-storage. 1.200 +If it fires we just notify the user and delete the most 1.201 +recent annotations until we are back in quota. 1.202 +*/ 1.203 + simpleStorage.on("OverQuota", function () { 1.204 + notifications.notify({ 1.205 + title: 'Storage space exceeded', 1.206 + text: 'Removing recent annotations'}); 1.207 + while (simpleStorage.quotaUsage > 1) 1.208 + simpleStorage.storage.annotations.pop(); 1.209 + }); 1.210 + 1.211 +/* 1.212 +The matcher page-mod locates anchors on web pages and prepares for the 1.213 +annotation to be displayed. 1.214 + 1.215 +It is attached to all pages, and when it is attached we pass it the complete 1.216 +list of annotations. It looks for anchors in its page. If it finds one it 1.217 +highlights the anchor and binds mouseenter/mouseout events to 'show' and 'hide' 1.218 +messages to the add-on. 1.219 + 1.220 +When the add-on receives the 'show' message it assigns the annotation text to 1.221 +the annotation panel and shows it. 1.222 + 1.223 +Note that the matcher is active whether or not the add-on is active: 1.224 +'inactive' only means that the user can't create new add-ons, they can still 1.225 +see old ones. 1.226 +*/ 1.227 + var matcher = pageMod.PageMod({ 1.228 + include: ['*'], 1.229 + contentScriptWhen: 'ready', 1.230 + contentScriptFile: [data.url('jquery-1.4.2.min.js'), 1.231 + data.url('matcher.js')], 1.232 + onAttach: function(worker) { 1.233 + if(simpleStorage.storage.annotations) { 1.234 + worker.postMessage(simpleStorage.storage.annotations); 1.235 + } 1.236 + worker.port.on('show', function(data) { 1.237 + annotation.content = data; 1.238 + annotation.show(); 1.239 + }); 1.240 + worker.port.on('hide', function() { 1.241 + annotation.content = null; 1.242 + annotation.hide(); 1.243 + }); 1.244 + worker.on('detach', function () { 1.245 + detachWorker(this, matchers); 1.246 + }); 1.247 + matchers.push(worker); 1.248 + } 1.249 + }); 1.250 + 1.251 +/* 1.252 +The annotation panel is the UI component that displays an annotation. 1.253 + 1.254 +When we are ready to show it we assign its 'content' attribute to contain 1.255 +the annotation text, and that gets sent to the content process in onShow(). 1.256 +*/ 1.257 + var annotation = panels.Panel({ 1.258 + width: 200, 1.259 + height: 180, 1.260 + contentURL: data.url('annotation/annotation.html'), 1.261 + contentScriptFile: [data.url('jquery-1.4.2.min.js'), 1.262 + data.url('annotation/annotation.js')], 1.263 + contentScriptWhen: 'ready', 1.264 + onShow: function() { 1.265 + this.postMessage(this.content); 1.266 + } 1.267 + }); 1.268 + 1.269 +}