main.js 7.9 KB

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