In this small tutorial, I will describe how to write a simple extension for QuickFox Notes(QFN) version >= 2.4C.

[Q] How to install an extension in QFN?

[A] To install an extension you need to have QFN version equal or greater than 2.4C. From Tools menu open Add-ons, then find Open add-ons folder link and click on it. Now copy the following piece of JS code into a text file and save it as extTemp-v0.1.js into this folder (Valid coding are UTF-8 without BOM or ANSI).

var EXPORTED_SYMBOLS = ["extLoader"];   //Do not change this line

var extLoader = { //Do not change this line
  pointers: null, //Do not change this line; QFN will load this for you.
  info: {
    GUID: "cfb1902c-98f1-4e39-a5bf-9544599beb91", //This one MUST be unique; Get one from: www.guidgenerator.com
    name: "Test add-on",                          //Name of add-on
    version: "0.1",                               //Add-on version
    developer: "Developer's name here",           //Developer's name
    description: "Extension's description here",  //Description here
    qfnVersion: ["2.4C", "*"],                    //Min and Max version of compatible QFN
    ffVersion: ["3.*", "*"]                       //Min and Max version of compatible Firefox
  },
  //CSS is Optional
  css: "menuitem#mymenuitem{color: red;}",
  //XUL is Optional
  //Use "me" for pointing to functions in this scope, QFN will fix this pointer for you
  xul: "<menupopup id='editor-tabbox-toolbar-tools-menu'><menuitem id='mymenuitem' label='Test' insertbefore='editor-toolsmenu-extensions' oncommand='me.myCommand()'/></menupopup>",

  initialize: function () { //Do not change this line
    var window = this.pointers.window;        //Pointer to main window
    var document = this.pointers.document;      //Pointer to main document

    //initialize code here! This function is equal to window.onload() function.
  },
  /*Extension's functions (Optional)*/
  myCommand: function () {
    var window = this.pointers.window;
    var document = this.pointers.document;

    window.alert('Hi from myCommand');
  }
}

Now close your QFN and re-open it. If everything goes fine, new item appears in the Tools menu!

Now, simply modify something. For the changes to take effect there are two options:

  1. Rename the JS file and only restart QFN, without restarting Firefox
  2. Restart Firefox

The reason of such a behaiviour is that the script is cached by Firefox when first time it was loaded and subsequent imports do not reload a new version of the script.

To disable this extension, use extension manager from Tools>Add-ons

Here is another useful example without any interface. It just adds a shortkey (Ctrl + H) to your editor which inserts a long text at cursor’s position.

var EXPORTED_SYMBOLS = ["extLoader"];   //Do not change this line

var extLoader = { //Do not change this line
  pointers: null,   //Do not change this line
  info: {
    GUID: "dtb1902c-98e1-4e29-a5bf-9444599beb31", //Note that this is a new GUID
    name: "Long text inserter",
    version: "0.1",
    developer: "Developer's name",
    description: "Insert a long text by Ctrl + H",
    qfnVersion: ["2.4C", "*"],
    ffVersion: ["3.*", "*"]
  },

  initialize: function () { //Do not change this line
    var window = this.pointers.window;  //Pointer to main window

    window.addEventListener("keypress", function(e) {
      if ((e.which ==  "h".charCodeAt(0) || e.which ==  "H".charCodeAt(0)) && e.ctrlKey)  //Ctrl+H
        extLoader.myInsert();
    }, true); //To stop propagation of the event use true
  },
  /*Extension's functions (Optional)*/
  myInsert: function () {
    this.pointers.api.insertTextAtCursorPoint("My looooong text!");
  }
}

Let’s take a more precise look at the above example.

QFN resorts to Components.utils.import function to add your JS snippet into its body. This function was introduced in Firefox 3 and is used for easy sharing code snippet between different scopes. Note that QFN defines a new scope for each extension to prevent potential conflicts. Consequently, there is no direct method to access the main Window’s object form your snippet, however, QFN offers you a set of pointers (extLoader.pointers) to a few useful objects:

var window = extLoader.pointers.window;   //Main QFN's window object
var document = extLoader.pointers.document; //Main QFN's document object
var QFN = extLoader.pointers.QFN;     //Main QFN's object
var api = extLoader.pointers.api;     //Pointer to a set of functions (will be introduced later)

So to have access to any XUL element, simply follow:

var document = extLoader.pointers.document;
document.getElementById('element_id');

As it is mentioned, the snippet is run inside an isolated scope, so you cannot even use simple functions such as alert() directly. One remedy to this issue is to switch into the Window’s scope like this:

var window = extLoader.pointers.window;
window.alert('...');  //Note that this method to show an alert box is not recommended. An alternative method will be introduced latter.

How to add XUL elements to QFN’s window:

It is pretty easy. Just write a xul code and put it into the extLoader.xul section like this:

xul: "<menupopup id='editor-tabbox-toolbar-tools-menu'><menuitem label='Test' insertbefore='editor-toolsmenu-extensions' oncommand='me.myCommand()'/></menupopup>",

Here are some samples of important locations:

(Recommended area)

<menupopup id='editor-tabbox-toolbar-tools-menu'>
  <menuitem label='Test' insertbefore='editor-toolsmenu-extensions'/>
</menupopup>

(Recommended area)

<hbox id='top-toolbar'>
  <toolbarbutton label='toolbarbutton' insertbefore='editor-tabbox-toolbar-toolbarbutton6'/>
</hbox>

(Not recommended area)

<hbox id='editor-tabbox-tabpanelshbox'>
  <vbox insertbefore='editor-tabbox-tabpanels' style='background: black; width: 100px;'/>
</hbox>

(Not recommended area)

<hbox id='editor-tabbox-tabpanelshbox'>
  <vbox insertafter='editor-tabbox-tabpanels' style='background: black; width: 100px;'/>
</hbox>

(Not recommended area)

<toolbar id='editor-tabbox-toolbar'>
  <label value='Here' insertbefore='editor-tabbox-toolbar-spacer-extensions'/>
</toolbar>

Another important issue is how to access your JS class from xul section.

Note that QFN uses document.loadOverlay() function to load and merge the extLoader.xul string into the main Window (proper elements such as <overlay></overlay> will be added before calling the document.loadOverlay()). Consequently, the scope of xul elements are same as main Window’s scope (objects like document, and window and function such as alert() work properly without any pre-definition). However, functions in your script are not directly visible to XUL elements; the remedy to this issue is to use me pointer. QFN will replace it with proper operator.

<menupopup id="editor-tabbox-toolbar-tools-menu">
  <menuitem label="Test" insertbefore="editor-toolsmenu-extensions" oncommand="me.myCommand()"/>
</menupopup>

The last note to consider is that each JS file needs to have a unique GUID. In case where there are two files with same GUID, QFN only loads the first one (Files will be sorted in Z>A order, so in case that there are two files with names extTest-v0.1.js and extTest-v0.2.js, extTest-v0.2.js is the one that will be loaded, thus always higher version has higher priority).

Another issue that you might need to know is how to add an image to buttons, labels, etc. The easiest way to deal with this issue is to embed your image via data:url. There are many online tools that can convert your image to data:url, here is an example:

<hbox align='center'>
  <image src='data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw=='/>
</hbox>

In this section I will review a few important objects and pointers:

Notepad’s element. This object can be accessed by:

var editor1 = extLoader.pointers.QFN.getEditor(); //Textbox of current notepad
var text1 = editor1.value;

var editor2 = extLoader.pointers.QFN.getEditor(1); //Textbox of first tab
var text2 = editor2.value;

Notepad’s title can be obtained by:

var title = extLoader.pointers.api.get.noteTitle();
var content = extLoader.pointers.api.get.noteContent();

To inset a string at cursor’s position:

extLoader.pointers.api.insertTextAtCursorPoint("String here");

Loop over all open tabs:

var tab = extLoader.pointers.api.list.tab();

for (var i = 0; i < tab.length; i++) {
  var noteTitle = extLoader.pointers.api.get.noteTitle(tab[i]);
  var noteBody = extLoader.pointers.api.get.noteContent(i);
}

Loop over all Archive note:

var archive = extLoader.pointers.api.list.archive();  //Return an array of menuitems

for (var i = 0; i < archive.length; i++) {
  var noteTitle = extLoader.pointers.api.get.archiveTitle(archive[i]);
  var noteBody =  extLoader.pointers.api.get.archiveContent(archive[i]);
}

Add a new tab, modify the title and content.

var obj = extLoader.pointers.QFN.addSingleTab();
var textbox = obj._textbox;
var tab = obj._tab;

textbox.value = "new content";
tab.label = "new title";

An overview of extLoader.pointers.api object:

list.tab: function() {...}    //Return an array of <tab> elements
list.note: function() {...}   //Return an array of <textbox> elements
list.archive: function() {...}  //Return an array of <menuitem> elements

statusNotification.show: function(msg) {...}  //Show a notification in Statusbar
titleNotification.show: function(msg) {...}   //Show a notification in Titlebar
alert: function(from, msg) {...}        //Show a notification in alert box
  //from: name of file that execute alert (e.g: extTest)

get.noteTitle: function (tab) {...}     //Return title of a tab element
  //if argument is null, current tab will be considered
get.noteContent: function(index) {...}    //Return content of a tab
  //Note that argument is index of note, not <textbox> element
  //if argument is null, current tab will be considered
get.archiveTitle: function(menuitem) {...}  //Return title of a menuitem element in archive menu
get.archiveContent: function(menuitem) {...}//Return content of a menuitem element in archive menu

getClipboard: function () {}                //Return clipboard text
insertTextAtCursorPoint: function (txt) {...}       //Insert a string at cursor position
sendKey: function (type, keycode, ctrl, alt, shift) {...} //Send a key combination to editor
  //type can be "keypress", "keyup", and "keydown"

saveToFile: function(path, data) {...}  //Return true if operation is successful
path: function (location) {...} //Return OS based folder path
  //location can be
    //"Desk" for Desktop folder
    //"Progs" for program files
    //... check rest here: https://developer.mozilla.org/en/Code_snippets/File_I%2F%2FO

isWindowMode: function (){...}  //Check to find out if QFN is opened in "Window" mode

References:

  1. http://firefox.add0n.com/quickfox.html
  2. http://add0n.com/quickfox/forum