--- /dev/null
+content itsalltext chrome/content/
+locale itsalltext en-US chrome/locale/en-US/
+
+overlay chrome://browser/content/browser.xul chrome://itsalltext/content/itsalltext.xul
--- /dev/null
+/*
+ This file is used to allow external editors to work inside your chrome XUL.
+
+ ***** How To Use This API *****
+ Add this script line to your .xul file:
+ <script type="application/javascript" src="chrome://itsalltext/content/API.js"/>
+
+ If "It's All Text!" isn't installed in the browser, it will fail safely.
+ It only generates an info message in the error console.
+
+ You then have two choices. You can call ItsAllTextopenEditor() directly
+ via JavaScript or you can add one or two attributes to a XUL element and
+ it'll automatically be set up right.
+
+ The suggested method is to add the correct attributes to your XUL button
+ or menuitem and let "It's All Text!" do it for you.
+
+ Attributes:
+ 'itsalltext-control' -- This should be set to the id of the textbox
+ that you want to edit when command is executed
+ on this XUL element. This is required.
+ 'itsalltext-extension' -- This is the file extension. Include the
+ leading dot character. Example: '.css'
+ It defaults to '.txt' and is optional.
+
+ If you don't want this XUL element to be visible unless "It's All Text!"
+ is installed, then you should set it's CSS style display to 'none'.
+
+ Example using attributes (recommended method):
+ <hbox>
+ <spacer flex="1"/>
+ <button label="It's All Text!"
+ itsalltext-control="code"
+ itsalltext-extension=".css"
+ style="display: none;"
+ />
+ </hbox>
+
+ Example calling openEditor() directly:
+ if(some_condition && ItsAllText) {
+ ItsAllText.openEditor('id-of-textarea', '.extension');
+ }
+
+ */
+
+(function () {
+ /* Load up the main It's All Text! file */
+ var objScriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
+ objScriptLoader.loadSubScript('chrome://itsalltext/content/itsalltext.js');
+
+ var onload = function (event) {
+ /* Start watching the document, but force it. */
+ ItsAllText.monitor.watch(document, true);
+
+ /* Turn on all the hidden CSS */
+ var nodes = [];
+ var nodesIter = document.evaluate("//node()[@itsalltext-control]",
+ document, null,
+ XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
+
+ var node = nodesIter.iterateNext();
+ while (node) {
+ nodes.push(node);
+ node = nodesIter.iterateNext();
+ }
+ var command = function(event) {
+ ItsAllText.openEditor( this.getAttribute("itsalltext-control"),
+ this.getAttribute("itsalltext-extension") );
+ return false;
+ };
+ for(i in nodes) {
+ node = nodes[i];
+ node.addEventListener('command', command, true);
+ node.style.display = '-moz-box';
+ }
+
+ };
+ window.addEventListener("load", onload, true);
+})();
+
+/**
+ * This is part of the public XUL API.
+ * Use this to open an editor for a specific textarea or textbox with
+ * the id 'id'. The file will have the extension 'extension'. Include
+ * the leading dot in the extension.
+ * @param {String} id The id of textarea or textbody that should be opened in the editor.
+ * @param {String} extension The extension of the file used as a temporary file. Example: '.css' (optional)
+ */
+ItsAllText.openEditor = function(id, extension) {
+ var node = document.getElementById(id);
+ /* The only way I can adjust the background of the textbox is
+ * to turn off the -moz-appearance attribute.
+ */
+ node.style.MozAppearance = 'none';
+ var cache_object = node && ItsAllText.getCacheObj(node);
+ if(!cache_object) { return; }
+ cache_object.edit(extension);
+};
+
--- /dev/null
+/**
+ * Author: Lachlan Hunt
+ * Date: 2005-11-24
+ * Version: 1.0-cgh1
+ * Contributor: Christian G. Höltje
+ *
+ * Licence: Public Domain
+ * Attribution is considered ethical, but not required.
+ *
+ * Usage:
+ * Color(255, 255, 255);
+ * Color(255, 255, 255, 1.0);
+ * Color("#FFF");
+ * Color("#FFFFFF");
+ * Color("rgb(255, 255, 255)");
+ * Color("rgba(255, 255, 255, 1.0)");
+ * Color("white"); - CSS 2.1 Color keywords only
+ */
+var Color = function() {
+
+ // CSS 2.1 Colour Keywords
+ var keyword = {
+ maroon : "#800000",
+ red : "#ff0000",
+ orange : "#ffA500",
+ yellow : "#ffff00",
+ olive : "#808000",
+ purple : "#800080",
+ fuchsia : "#ff00ff",
+ white : "#ffffff",
+ lime : "#00ff00",
+ green : "#008000",
+ navy : "#000080",
+ blue : "#0000ff",
+ aqua : "#00ffff",
+ teal : "#008080",
+ black : "#000000",
+ silver : "#c0c0c0",
+ gray : "#808080"
+ };
+
+ // CSS Functional Notations and Hex Patterns
+ var func = {
+ rgb : /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\);?$/,
+ "rgb%" : /^rgb\(\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\);?$/,
+ rgba : /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*((?:\d+(?:\.\d+)?)|(?:\.\d+))\s*\);?$/,
+ "rgba%" : /^rgba\(\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*((?:\d+(?:\.\d+)?)|(?:\.\d+))\s*\);?$/,
+ hex3 : /^#([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f]);?$/,
+ hex6 : /^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2});?$/
+ };
+
+ /**
+ * Clamp the value between the low value and the high value
+ * @private
+ */
+ var clamp = function(value, low, high) {
+ if (value < low) {
+ value = low;
+ }
+ else if (value > high) {
+ value = high;
+ }
+ return value;
+ };
+
+ /**
+ * @private
+ */
+ var alphaBlend = function(forground, background, alpha) {
+ return Math.round(background * (1.0 - alpha) + forground * (alpha));
+ };
+
+ /*
+ * Return the colour in hexadecimal notation: #RRGGBB. e.g. #FF9933
+ * @param bg - Optional parameter used for calculating the colour if an alpha value less than 1.0 has been specified.
+ * If not specified, the alpha value will be ignored.
+ */
+ this.hex = function(bg) {
+ var r, g, b;
+ if (bg) {
+ r = alphaBlend(this.red, bg.red, this.alpha);
+ g = alphaBlend(this.green, bg.green, this.alpha);
+ b = alphaBlend(this.blue, bg.blue, this.alpha);
+ } else {
+ r = this.red;
+ g = this.green;
+ b = this.blue;
+ }
+
+ var strHexR = r.toString(16).toUpperCase();
+ var strHexG = g.toString(16).toUpperCase();
+ var strHexB = b.toString(16).toUpperCase();
+
+ if (strHexR.length < 2) { strHexR = "0" + strHexR; }
+ if (strHexG.length < 2) { strHexG = "0" + strHexG; }
+ if (strHexB.length < 2) { strHexB = "0" + strHexB; }
+
+ return "#" + strHexR + strHexG + strHexB;
+ };
+
+ /**
+ * Return the colour in CSS rgb() functional notation, using integers 0-255: rgb(255, 255 255);
+ * @param bg - Optional parameter used for calculating the colour if an alpha value less than 1.0 has been specified.
+ * If not specified, the alpha value will be ignored.
+ */
+ this.rgb = function(bg) {
+ var r, g, b;
+ if (bg) {
+ r = alphaBlend(this.red, bg.red, this.alpha);
+ g = alphaBlend(this.green, bg.green, this.alpha);
+ b = alphaBlend(this.blue, bg.blue, this.alpha);
+ } else {
+ r = this.red;
+ g = this.green;
+ b = this.blue;
+ }
+
+ return "rgb(" + r + ", " + g + ", " + b + ")";
+ };
+
+ /**
+ * Return the colour in CSS rgba() functional notation, using integers 0-255 for color components: rgb(255, 255 255, 1.0);
+ * @param bg - Optional parameter used for calculating the colour if an alpha value less than 1.0 has been specified.
+ * If not specified, and there is an alpha value, black will be used as the background colour.
+ */
+ this.rgba = function() {
+ return "rgba(" + this.red + ", " + this.green + ", " + this.blue + ", " + this.alpha + ")";
+ };
+
+ /**
+ * Returns a Color object with the values inverted. Ignores alpha.
+ */
+ this.invert = function() {
+ return new Color("rgb(" +
+ (255 - this.red) + ", " +
+ (255 - this.green) + ", " +
+ (255 - this.blue) + ")");
+ };
+
+ /**
+ * Blend this colour with the colour specified and return a pallet with all the steps in between.
+ * @param color - The colour to blend with
+ * @param steps - The number of steps to take to reach the color.
+ */
+ this.blend = function(color, steps) {
+ var pallet = [];
+ var r, g, b, i;
+
+ var step = {
+ red : (alphaBlend(color.red, this.red, color.alpha) - this.red) / steps,
+ green : (alphaBlend(color.green, this.green, color.alpha) - this.green) / steps,
+ blue : (alphaBlend(color.blue, this.blue, color.alpha) - this.blue) / steps
+ };
+ for (i = 0; i < steps + 1; i++) {
+ r = Math.round(this.red + (step.red * i));
+ g = Math.round(this.green + (step.green * i));
+ b = Math.round(this.blue + (step.blue * i));
+ pallet.push(new Color(r, g, b));
+ }
+ return pallet;
+ };
+
+ /**
+ * Constructor function
+ */
+ this.toString = this.hex;
+
+ var value;
+ var components, pattern;
+ var key, base, m;
+ var r, g, b, a;
+ if (arguments.length >= 3) {
+ /* r, g, b or r, g, b, a */
+ r = arguments[0];
+ g = arguments[1];
+ b = arguments[2];
+ a = arguments[3];
+
+ this.red = (!isNaN(r)) ? clamp(r, 0, 255) : 0;
+ this.green = (!isNaN(g)) ? clamp(g, 0, 255) : 0;
+ this.blue = (!isNaN(b)) ? clamp(b, 0, 255) : 0;
+ this.alpha = (!isNaN(a)) ? clamp(a, 0.0, 1.0) : 1.0;
+ } else if (arguments.length == 1) {
+ /* CSS Colour keyword or value */
+ value = keyword[arguments[0]] ? keyword[arguments[0]] : arguments[0];
+
+ for (key in func) {
+ if (func[key].test(value)) {
+ pattern = key;
+ }
+ }
+
+ components = value.match(func[pattern]);
+ base = 10;
+ m = 1; // Multiplier for percentage values
+
+ switch (pattern) {
+ case "rgb%":
+ case "rgba%":
+ m = 2.55;
+ base = 10;
+ break;
+ case "rgb":
+ case "rgba":
+ base = 10;
+ break;
+ case "hex3":
+ components[1] = components[1] + "" + components[1];
+ components[2] = components[2] + "" + components[2];
+ components[3] = components[3] + "" + components[3];
+ base = 16;
+ break;
+ case "hex6":
+ base = 16;
+ break;
+ default:
+ components = [0, "255", "255", "255", "1.0"];
+ }
+
+ this.red = clamp(Math.round(parseInt(components[1],base) * m), 0, 255);
+ this.green = clamp(Math.round(parseInt(components[2],base) * m), 0, 255);
+ this.blue = clamp(Math.round(parseInt(components[3],base) * m), 0, 255);
+
+ if (isNaN(components[4])) {
+ this.alpha = 1;
+ } else {
+ this.alpha = clamp(parseFloat("0" + components[4]), 0.0, 1.0);
+ }
+ }
+};
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://itsalltext/locale/about.dtd" >
+<window id="itsalltext_about"
+ title="&title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox style="max-width: 40em">
+ <description style="white-space: pre; margin: 1em;">&description;</description>
+ <hbox align="right">
+ <button oncommand="window.close();" tabindex="1">
+ <description>&close;</description>
+ </button>
+ </hbox>
+ </vbox>
+</window>
--- /dev/null
+/*jslint nomen: true, evil: false, browser: true */
+/**
+ * Pass back the values that that the user selected.
+ */
+function onOK() {
+ window['arguments'][0].out = {
+ do_preferences: true
+ };
+ return true;
+}
+function doOnload() {
+ var locale = document.getElementById("strings");
+ var params = window['arguments'][0];
+ var reason = document.getElementById('reason');
+ var textnode = '**error**';
+ /* Errors are from
+ * http://lxr.mozilla.org/seamonkey/source/xpcom/base/nsError.h#262 */
+ if(params.exception == 'NS_ERROR_FILE_INVALID_PATH' ||
+ params.exception == 'NS_ERROR_FILE_UNRECOGNIZED_PATH' ||
+ params.exception == 'NS_ERROR_FILE_TARGET_DOES_NOT_EXIST' ||
+ params.exception == 'NS_ERROR_FILE_INVALID_PATH' ||
+ params.exception == 'NS_ERROR_FILE_NOT_FOUND' ||
+ params.exception == 'NS_ERROR_FILE_NAME_TOO_LONG' ) {
+ textnode = locale.getFormattedString('bad.noent', [params.path]);
+ } else if(params.exception == 'NS_ERROR_FILE_ACCESS_DENIED' ||
+ params.exception == 'NS_ERROR_FILE_IS_DIRECTORY' ||
+ params.exception == 'NS_ERROR_FILE_IS_LOCKED' ) {
+ textnode = locale.getFormattedString('bad.noexec', []);
+
+ /* At this point, we don't know exactly why it failed...
+ * Try some heuristics. */
+ } else if(!params.path) {
+ textnode = locale.getFormattedString('bad.noset',[]);
+ } else if(params.exists) {
+ textnode = locale.getFormattedString('bad.noexec', []);
+ } else {
+ textnode = locale.getFormattedString('bad.noent', [params.path]);
+ }
+ reason.appendChild(document.createTextNode(textnode));
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE badeditorwindow SYSTEM "chrome://itsalltext/locale/badeditor.dtd" >
+
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="ItsAllTextBadEditor"
+ title="&title;"
+ buttonlabelaccept="&pref.label;"
+ ondialogaccept="return onOK();"
+ onload="doOnload();"
+ >
+
+ <script type="application/x-javascript" src="chrome://itsalltext/content/badeditor.js"/>
+ <stringbundleset id="strbundles">
+ <stringbundle id="strings" src="chrome://itsalltext/locale/badeditor.properties"/>
+ </stringbundleset>
+
+ <dialogheader title="&header;"/>
+ <vbox>
+ <description>&helptext;</description>
+ <spacer flex="1"/>
+ <groupbox flex="1">
+ <caption label="&reason;"/>
+ <description id="reason" style="max-width: 30em;">
+ </description>
+ </groupbox>
+ </vbox>
+
+</dialog>
+<!-- Local Variables: -->
+<!-- mode: xml -->
+<!-- End: -->
--- /dev/null
+/**
+ * A Cache object is used to manage the node and the file behind it.
+ * @constructor
+ * @param {Object} node A DOM Node to watch.
+ */
+function CacheObj(node) {
+ var that = this;
+
+ /* Gumdrop Image URL */
+ that.gumdrop_url = 'chrome://itsalltext/locale/gumdrop.png';
+ /* Gumdrop Image Width */
+ that.gumdrop_width = ItsAllText.localeString('gumdrop.width');
+ /* Gumdrop Image Height */
+ that.gumdrop_height = ItsAllText.localeString('gumdrop.height');
+
+ that.timestamp = 0;
+ that.size = 0;
+ that.node = node;
+ that.button = null;
+ that.initial_background = '';
+ that._is_watching = false;
+
+ that.node_id = that.getNodeIdentifier(node);
+ var doc = node.ownerDocument;
+
+ /* This is a unique identifier for use on the web page to prevent the
+ * web page from knowing what it's connected to.
+ * @type String
+ */
+ that.uid = that.hashString([ doc.location.toString(),
+ Math.random(),
+ that.node_id ].join(':'));
+ // @todo [security] Add a serial to the uid hash.
+
+ node.setAttribute(ItsAllText.MYSTRING+'_UID', that.uid);
+ ItsAllText.tracker[that.uid] = that;
+
+ /* Figure out where we will store the file. While the filename can
+ * change, the directory that the file is stored in should not!
+ */
+ var host = window.escape(doc.location.hostname);
+ var hash = that.hashString([ doc.location.protocol,
+ doc.location.port,
+ doc.location.search,
+ doc.location.pathname,
+ that.node_id ].join(':'));
+ that.base_filename = [host, hash.slice(0,10)].join('.');
+ /* The current extension.
+ * @type String
+ */
+ that.extension = null;
+
+ /* Stores an nsILocalFile pointing to the current filename.
+ * @type nsILocalFile
+ */
+ that.file = null;
+
+ /* Set the default extension and create the nsIFile object. */
+ var extension = node.getAttribute('itsalltext-extension');
+ if (typeof(extension) != 'string' || !extension.match(/^[.a-z0-9]+$/i)) {
+ extension = ItsAllText.getExtensions()[0];
+ }
+ that.setExtension(extension);
+
+ that.initFromExistingFile();
+
+ /**
+ * A callback for when the textarea/textbox or button has
+ * the mouse waved over it.
+ * @param {Event} event The event object.
+ */
+ that.mouseover = function(event) {
+ var style = that.button?that.button.style:null;
+ if (style) {
+ style.setProperty('opacity', '0.7', 'important');
+ ItsAllText.refreshTextarea(that.node);
+ }
+ };
+
+ /**
+ * A callback for when the textarea/textbox or button has
+ * the mouse waved over it and the moved off.
+ * @param {Event} event The event object.
+ */
+ that.mouseout = function(event) {
+ var style = that.button?that.button.style:null;
+ if (style) {
+ style.setProperty('opacity', '0.1', 'important');
+ }
+ };
+}
+
+/**
+ * Set the extension for the file to ext.
+ * @param {String} ext The extension. Must include the dot. Example: .txt
+ */
+CacheObj.prototype.setExtension = function(ext) {
+ if (ext == this.extension) {
+ return; /* It's already set. No problem. */
+ }
+
+ /* Create the nsIFile object */
+ var file = ItsAllText.factoryFile();
+ file.initWithFile(ItsAllText.getEditDir());
+ file.append([this.base_filename,ext].join(''));
+
+ this.extension = ext;
+ this.file = file;
+};
+
+/**
+ * This function looks for an existing file and starts to monitor
+ * if the file exists already. It also deletes all existing files for
+ * this cache object.
+ */
+CacheObj.prototype.initFromExistingFile = function() {
+ var base = this.base_filename;
+ var fobj = ItsAllText.getEditDir();
+ var entries = fobj.directoryEntries;
+ var ext = null;
+ var tmpfiles = /(\.bak|.tmp|~)$/;
+ var entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.getNext();
+ entry.QueryInterface(Components.interfaces.nsIFile);
+ if (entry.leafName.indexOf(base) === 0) {
+ // startswith
+ if (ext === null && !entry.leafName.match(tmpfiles)) {
+ ext = entry.leafName.slice(base.length);
+ }
+ try{
+ entry.remove(false);
+ } catch(e) {
+ that.debug('unable to remove',entry,'because:',e);
+ }
+ }
+ }
+ if (ext !== null) {
+ this.setExtension(ext);
+ this._is_watching = true;
+ }
+};
+
+/**
+ * Returns a unique identifier for the node, within the document.
+ * @returns {String} the unique identifier.
+ */
+CacheObj.prototype.getNodeIdentifier = function(node) {
+ var id = node.getAttribute('id');
+ var name, doc, attr, serial;
+ if (!id) {
+ name = node.getAttribute('name');
+ doc = node.ownerDocument.getElementsByTagName('html')[0];
+ attr = ItsAllText.MYSTRING+'_id_serial';
+
+ /* Get a serial that's unique to this document */
+ serial = doc.getAttribute(attr);
+ if (serial) { serial = parseInt(serial, 10)+1;
+ } else { serial = 1; }
+ id = [ItsAllText.MYSTRING,'generated_id',name,serial].join('_');
+ doc.setAttribute(attr,serial);
+ node.setAttribute('id',id);
+ }
+ return id;
+};
+
+/**
+ * Convert to this object to a useful string.
+ * @returns {String} A string representation of this object.
+ */
+CacheObj.prototype.toString = function() {
+ return [ "CacheObj",
+ " uid=",this.uid,
+ " timestamp=",this.timestamp,
+ " size=",this.size
+ ].join('');
+};
+
+/**
+ * Write out the contents of the node.
+ */
+CacheObj.prototype.write = function() {
+ var foStream = Components.
+ classes["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Components.interfaces.nsIFileOutputStream);
+
+ /* write, create, truncate */
+ foStream.init(this.file, 0x02 | 0x08 | 0x20,
+ parseInt('0600',8), 0);
+
+ /* We convert to charset */
+ var conv = Components.
+ classes["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ conv.charset = ItsAllText.getCharset();
+
+ var text = conv.ConvertFromUnicode(this.node.value);
+ foStream.write(text, text.length);
+ foStream.close();
+
+ /* Reset Timestamp and filesize, to prevent a spurious refresh */
+ this.timestamp = this.file.lastModifiedTime;
+ this.size = this.file.fileSize;
+
+ /* Register the file to be deleted on app exit. */
+ Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"].
+ getService(Components.interfaces.nsPIExternalAppLauncher).
+ deleteTemporaryFileOnExit(this.file);
+
+ return this.file.path;
+};
+
+/**
+ * Fetches the computed CSS attribute for a specific node
+ * @param {DOM} node The DOM node to get the information for.
+ * @param {String} attr The CSS-style attribute to fetch (not DOM name).
+ * @returns attribute
+ */
+CacheObj.prototype.getStyle = function(node, attr) {
+ var view = node ? node.ownerDocument.defaultView : null;
+ var style = view.getComputedStyle(node, '');
+ return style.getPropertyCSSValue(attr).cssText;
+};
+
+// @todo [9] IDEA: Pass in the line number to the editor, arbitrary command?
+// @todo [9] IDEA: Allow the user to pick an alternative editor?
+// @todo [9] IDEA: A different editor per extension?
+/**
+ * Edit a textarea as a file.
+ * @param {String} extension The extension of the file to edit.
+ */
+CacheObj.prototype.edit = function(extension) {
+ if (typeof(extension) == 'string') {
+ this.setExtension(extension);
+ }
+ var filename = this.write();
+ this.initial_background = this.node.style.backgroundColor;
+ this.initial_color = this.node.style.color;
+ var program = null;
+ var process;
+ var args, result, ec, e, params;
+
+ try {
+ program = ItsAllText.getEditor();
+ // checks
+ if (program === null) { throw {name:"Editor is not set."}; }
+ if (!program.exists()) { throw {name:"NS_ERROR_FILE_NOT_FOUND"}; }
+ /* Mac check because of
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=322865 */
+ if (!(ItsAllText.isDarwin() || program.isExecutable())) {
+ throw {name:"NS_ERROR_FILE_ACCESS_DENIED"}; }
+
+ // create an nsIProcess
+ process = Components.
+ classes["@mozilla.org/process/util;1"].
+ createInstance(Components.interfaces.nsIProcess);
+ process.init(program);
+
+ // Run the process.
+ // If first param is true, calling thread will be blocked until
+ // called process terminates.
+ // Second and third params are used to pass command-line arguments
+ // to the process.
+ args = [filename];
+ result = {};
+ ec = process.run(false, args, args.length, result);
+ this._is_watching = true;
+ } catch(e) {
+ params = {out:null,
+ exists: program ? program.exists() : false,
+ path: ItsAllText.preferences.editor,
+ exception: e.name };
+ window.openDialog('chrome://itsalltext/chrome/badeditor.xul',
+ null,
+ "chrome,titlebar,toolbar,centerscreen,modal",
+ params);
+ if(params.out !== null && params.out.do_preferences) {
+ ItsAllText.openPreferences(true);
+ this.edit(extension);
+ }
+ }
+};
+
+/**
+ * Delete the file from disk.
+ */
+CacheObj.prototype.remove = function() {
+ if(this.file.exists()) {
+ try {
+ this.file.remove();
+ } catch(e) {
+ that.debug('remove(',this.file.path,'): ',e);
+ return false;
+ }
+ }
+ return true;
+};
+
+/**
+ * Read the file from disk.
+ */
+CacheObj.prototype.read = function() {
+ /* read file, reset ts & size */
+ var DEFAULT_REPLACEMENT_CHARACTER = 65533;
+ var buffer = [];
+ var fis, istream, str, e;
+
+ try {
+ fis = Components.
+ classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ fis.init(this.file, 0x01, parseInt('00400',8), 0);
+ // MODE_RDONLY | PERM_IRUSR
+
+ istream = Components.
+ classes["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Components.interfaces.nsIConverterInputStream);
+ istream.init(fis, ItsAllText.getCharset(), 4096, DEFAULT_REPLACEMENT_CHARACTER);
+
+ str = {};
+ while (istream.readString(4096, str) !== 0) {
+ buffer.push(str.value);
+ }
+
+ istream.close();
+ fis.close();
+
+ this.timestamp = this.file.lastModifiedTime;
+ this.size = this.file.fileSize;
+
+ return buffer.join('');
+ } catch(e) {
+ return null;
+ }
+};
+
+/**
+ * Has the file object changed?
+ * @returns {boolean} returns true if the file has changed on disk.
+ */
+ CacheObj.prototype.hasChanged = function() {
+ /* Check exists. Check ts and size. */
+ if(!this._is_watching ||
+ !this.file.exists() ||
+ !this.file.isReadable() ||
+ (this.file.lastModifiedTime == this.timestamp &&
+ this.file.fileSize == this.size)) {
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+/**
+ * Part of the fading technique.
+ * @param {Object} pallet A Color blend pallet object.
+ * @param {int} step Size of a step.
+ * @param {delay} delay Delay in microseconds.
+ */
+CacheObj.prototype.fadeStep = function(background_pallet, color_pallet, step, delay) {
+ var that = this;
+ return function() {
+ if (step < background_pallet.length) {
+ that.node.style.backgroundColor = background_pallet[step].hex();
+ that.node.style.color = color_pallet[step].hex();
+ step++;
+ setTimeout(that.fadeStep(background_pallet, color_pallet, step, delay),delay);
+ } else {
+ that.node.style.backgroundColor = that.initial_background;
+ that.node.style.color = that.initial_color;
+ }
+ };
+};
+
+/**
+ * Node fade technique.
+ * @param {int} steps Number of steps in the transition.
+ * @param {int} delay How long to wait between delay (microseconds).
+ */
+CacheObj.prototype.fade = function(steps, delay) {
+ var color = this.getStyle(this.node, 'color');
+ var color_stop = new ItsAllText.Color(color);
+ var color_start = new ItsAllText.Color('black');
+ var color_pallet = color_start.blend(color_stop, steps);
+
+ var background = this.getStyle(this.node, 'background-color');
+ var background_stop = new ItsAllText.Color(background);
+ var background_start = new ItsAllText.Color('yellow');
+ var background_pallet = background_start.blend(background_stop, steps);
+ setTimeout(this.fadeStep(background_pallet, color_pallet, 0, delay), delay);
+};
+
+/**
+ * Update the node from the file.
+ * @returns {boolean} Returns true ifthe file changed.
+ */
+CacheObj.prototype.update = function() {
+ var value;
+ if (this.hasChanged()) {
+ value = this.read();
+ if (value !== null) {
+ this.fade(20, 100);
+ this.node.value = value;
+ return true;
+ }
+ }
+ return false; // If we fall through, we
+};
+
+/**
+ * Add the gumdrop to a textarea.
+ * @param {Object} cache_object The Cache Object that contains the node.
+ */
+CacheObj.prototype.addGumDrop = function() {
+ var cache_object = this;
+ if (cache_object.button !== null) {
+ cache_object.adjust();
+ return; /*already done*/
+ }
+ if (ItsAllText.getDisableGumdrops()) {
+ return;
+ }
+ ItsAllText.debug('addGumDrop()',cache_object.node_id,cache_object.uid);
+
+ var node = cache_object.node;
+ var doc = node.ownerDocument;
+ var offsetNode = node;
+ if (!node.parentNode) { return; }
+
+ var gumdrop = doc.createElementNS(ItsAllText.XHTMLNS, "img");
+ gumdrop.setAttribute('src', this.gumdrop_url);
+ var gid = cache_object.getNodeIdentifier(gumdrop);
+
+ if (ItsAllText.getDebug()) {
+ gumdrop.setAttribute('title', cache_object.node_id);
+ } else {
+ gumdrop.setAttribute('title', ItsAllText.localeString('program_name'));
+ }
+ cache_object.button = gumdrop; // Store it for easy finding in the future.
+
+ // Image Attributes
+ gumdrop.style.setProperty('cursor', 'pointer', 'important');
+ gumdrop.style.setProperty('display', 'block', 'important');
+ gumdrop.style.setProperty('position', 'absolute', 'important');
+ gumdrop.style.setProperty('padding', '0', 'important');
+ gumdrop.style.setProperty('margin', '0', 'important');
+ gumdrop.style.setProperty('border', 'none', 'important');
+ gumdrop.style.setProperty('zIndex', '1', 'important'); // we want it just above normal items.
+
+ gumdrop.style.setProperty('width', this.gumdrop_width+'px', 'important');
+ gumdrop.style.setProperty('height', this.gumdrop_height+'px', 'important');
+
+ gumdrop.setAttribute(ItsAllText.MYSTRING+'_UID', cache_object.uid);
+
+ var clickfun = function(event) {
+ cache_object.edit();
+ event.stopPropagation();
+ return false;
+ };
+ var contextfun = function(event) {
+ /* This took forever to fix; roughly 80+ man hours were spent
+ * over 5 months trying to make this stupid thing work.
+ * The documentation is completely wrong and useless.
+ *
+ * Excuse me while I scream.
+ *
+ * See Mozilla bugs: 287357, 362403, 279703
+ */
+ var popup = ItsAllText.rebuildMenu(cache_object.uid);
+ document.popupNode = popup;
+ popup.showPopup(popup,
+ event.screenX, event.screenY,
+ 'context', null, null);
+ event.stopPropagation();
+ return false;
+ };
+
+ // Click event handler
+ gumdrop.addEventListener("click", clickfun, false);
+ gumdrop.addEventListener("contextmenu", contextfun, false);
+
+ // Insert it into the document
+ var parent = node.parentNode;
+ var nextSibling = node.nextSibling;
+
+ if (nextSibling) {
+ parent.insertBefore(gumdrop, nextSibling);
+ } else {
+ parent.appendChild(gumdrop);
+ }
+
+ // Add mouseovers/outs
+ node.addEventListener("mouseover", cache_object.mouseover, false);
+ node.addEventListener("mouseout", cache_object.mouseout, false);
+ gumdrop.addEventListener("mouseover", cache_object.mouseover, false);
+ gumdrop.addEventListener("mouseout", cache_object.mouseout, false);
+
+ cache_object.mouseout(null);
+ cache_object.adjust();
+};
+
+/**
+ * Updates the position of the gumdrop, incase the textarea shifts around.
+ */
+CacheObj.prototype.adjust = function() {
+ var gumdrop = this.button;
+ var el = this.node;
+ var doc = el.ownerDocument;
+
+ if (ItsAllText.getDisableGumdrops()) {
+ if(gumdrop && gumdrop.style.display != 'none') {
+ gumdrop.style.setProperty('display', 'none', 'important');
+ }
+ return;
+ }
+
+ var style = gumdrop.style;
+ if (!gumdrop || !el) { return; }
+ var display = '';
+ var cstyle = doc.defaultView.getComputedStyle(el, '');
+ if (cstyle.display == 'none' ||
+ cstyle.visibility == 'hidden' ||
+ el.getAttribute('readonly') ||
+ el.getAttribute('disabled')
+ ) {
+ display = 'none';
+ }
+ if (style.display != display) {
+ style.setProperty('display', display, 'important');
+ }
+
+ /* Reposition the gumdrops incase the dom changed. */
+ var left = Math.max(1, el.offsetWidth-this.gumdrop_width);
+ var top = el.offsetHeight;
+ var coord;
+ if (el.offsetParent === gumdrop.offsetParent) {
+ left += el.offsetLeft;
+ top += el.offsetTop;
+ } else {
+ coord = ItsAllText.getContainingBlockOffset(el, gumdrop.offsetParent);
+ left += coord[0];
+ top += coord[1];
+ }
+ if(left && top) {
+ left = [left,'px'].join('');
+ top = [top,'px'].join('');
+ if(style.left != left) { style.setProperty('left', left, 'important');}
+ if(style.top != top) { style.setProperty('top', top, 'important');}
+ }
+};
+
+/**
+ * Creates a mostly unique hash of a string
+ * Most of this code is from:
+ * http://developer.mozilla.org/en/docs/nsICryptoHash
+ * @param {String} some_string The string to hash.
+ * @returns {String} a hashed string.
+ */
+CacheObj.prototype.hashString = function(some_string) {
+ var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+
+ /* result is the result of the hashing. It's not yet a string,
+ * that'll be in retval.
+ * result.value will contain the array length
+ */
+ var result = {};
+
+ /* data is an array of bytes */
+ var data = converter.convertToByteArray(some_string, result);
+ var ch = Components.classes["@mozilla.org/security/hash;1"].createInstance(Components.interfaces.nsICryptoHash);
+
+ ch.init(ch.MD5);
+ ch.update(data, data.length);
+ var hash = ch.finish(true);
+
+ // return the two-digit hexadecimal code for a byte
+ var toHexString = function(charCode) {
+ return ("0" + charCode.toString(36)).slice(-2);
+ };
+
+ // convert the binary hash data to a hex string.
+ var retval = [];
+ for(i in hash) {
+ retval[i] = toHexString(hash.charCodeAt(i));
+ }
+
+ return(retval.join(""));
+};
--- /dev/null
+/*
+ * It's All Text - Easy external editing of web forms.
+ * Copyright 2006 Christian Höltje
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+// @todo [9] IDEA: dropdown list for charsets (utf-8, western-iso, default)?
+// @todo [3] Have a menu/context menu item for turning on monitoring/watch.
+// @todo [9] Menu item to pick the file to load into a textarea.
+// @todo [9] Hot-keys for editing or opening the context menu.
+
+var ItsAllText = function() {
+ /**
+ * This data is all private, which prevents security problems and it
+ * prevents clutter and collection.
+ * @type Object
+ */
+ var that = this;
+
+ /**
+ * Used for tracking all the all the textareas that we are watching.
+ * @type Hash
+ */
+ that.tracker = {};
+
+ /**
+ * Keeps track of all the refreshes we are running.
+ * @type Array
+ */
+ var cron = [null]; // Eat the 0th position
+
+ /**
+ * A constant, a string used for things like the preferences.
+ * @type String
+ */
+ that.MYSTRING = 'itsalltext';
+
+ /**
+ * A constant, the version number. Set by the Makefile.
+ * @type String
+ */
+ that.VERSION = '0.7.3';
+
+ /**
+ * A constant, the url to the readme.
+ * @type String
+ */
+ that.README = 'chrome://itsalltext/locale/readme.xhtml';
+
+ /* The XHTML Namespace */
+ that.XHTMLNS = "http://www.w3.org/1999/xhtml";
+
+ /* The XUL Namespace */
+ that.XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+
+ var string_bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].
+ getService(Components.interfaces.nsIStringBundleService);
+ /**
+ * A localization bundle. Use it like so:
+ * ItsAllText.locale.getStringFromName('blah');
+ */
+ that.locale = string_bundle.createBundle("chrome://itsalltext/locale/itsalltext.properties");
+ /**
+ * Formats a locale string, replacing $N with the arguments in arr.
+ * @param {String} name Locale property name
+ * @param {Array} arr Array of strings to replace in the string.
+ * @returns String
+ */
+ that.localeFormat = function(name, arr) {
+ return this.locale.formatStringFromName(name, arr, arr.length);
+ };
+ /**
+ * Returns the locale string matching name.
+ * @param {String} name Locale property name
+ * @returns String
+ */
+ that.localeString = function(name) {
+ return this.locale.GetStringFromName(name);
+ };
+
+ /**
+ * Create an error message from given arguments.
+ * @param {Object} message One or more objects to be made into strings...
+ */
+ that.logString = function() {
+ var args = Array.prototype.slice.apply(arguments,[0]);
+ for (var i=0; i<args.length; i++) {
+ try {
+ args[i] = args[i].toString();
+ } catch(e) {
+ Components.utils.reportError(e);
+ args[i] = 'toStringFailed';
+ }
+ }
+ args.unshift(that.MYSTRING+':');
+ return args.join(' ');
+ };
+
+ /**
+ * This is a handy debug message. I'll remove it or disable it when
+ * I release this.
+ * @param {Object} message One or more objects can be passed in to display.
+ */
+ that.log = function() {
+ var message = that.logString.apply(that, arguments);
+ var consoleService, e;
+ try {
+ // idiom: Convert arguments to an array for easy handling.
+ consoleService = Components.
+ classes["@mozilla.org/consoleservice;1"].
+ getService(Components.interfaces.nsIConsoleService);
+ consoleService.logStringMessage(message);
+ } catch(e) {
+ Components.utils.reportError(message);
+ }
+ };
+
+ /**
+ * Uses log iff debugging is turned on. Used for messages that need to
+ * globally logged (firebug only logs locally).
+ * @param {Object} message One or more objects can be passed in to display.
+ */
+ that.debuglog = function() {
+ if (that.preferences.debug) {
+ that.log.apply(that,arguments);
+ }
+ };
+
+ /**
+ * Displays debug information, if debugging is turned on.
+ * Requires Firebug.
+ * @param {Object} message One or more objects can be passed in to display.
+ */
+ that.debug = function() {
+ if (that.preferences.debug) {
+ try { Firebug.Console.logFormatted(arguments); }
+ catch(e) {
+ that.log.apply(that,arguments);
+ }
+ }
+ };
+
+ /**
+ * A factory method to make an nsILocalFile object.
+ * @param {String} path A path to initialize the object with (optional).
+ * @returns {nsILocalFile}
+ */
+ that.factoryFile = function(path) {
+ var file = Components.
+ classes["@mozilla.org/file/local;1"].
+ createInstance(Components.interfaces.nsILocalFile);
+ if (typeof(path) == 'string' && path !== '') {
+ file.initWithPath(path);
+ }
+ return file;
+ };
+
+ /**
+ * Returns the directory where we put files to edit.
+ * @returns nsILocalFile The location where we should write editable files.
+ */
+ that.getEditDir = function() {
+ /* Where is the directory that we use. */
+ var fobj = Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("ProfD", Components.interfaces.nsIFile);
+ fobj.append(that.MYSTRING);
+ if (!fobj.exists()) {
+ fobj.create(Components.interfaces.nsIFile.DIRECTORY_TYPE,
+ parseInt('0700',8));
+ }
+ if (!fobj.isDirectory()) {
+ that.error(that.localeFormat('problem_making_directory', [fobj.path]));
+ }
+ return fobj;
+ };
+
+ /**
+ * Cleans out the edit directory, deleting all old files.
+ */
+ that.cleanEditDir = function(force) {
+ force = (force && typeof(force) != 'undefined');
+ var last_week = Date.now() - (1000*60*60*24*7);
+ var fobj = that.getEditDir();
+ var entries = fobj.directoryEntries;
+ var entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.getNext();
+ entry.QueryInterface(Components.interfaces.nsIFile);
+ if(force || !entry.exists() || entry.lastModifiedTime < last_week){
+ try{
+ entry.remove(false);
+ } catch(e) {
+ that.debug('unable to remove',entry,'because:',e);
+ }
+ }
+ }
+ };
+
+ /* Clean the edit directory whenever we create a new window. */
+ that.cleanEditDir();
+
+ /* Load the various bits needed to make this work. */
+ var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
+ loader.loadSubScript('chrome://itsalltext/content/Color.js', that);
+ loader.loadSubScript('chrome://itsalltext/content/cacheobj.js', that);
+
+ /**
+ * Dictionary for storing the preferences in.
+ * @type Hash
+ */
+ that.preferences = {
+ debug: true,
+
+ /**
+ * Fetches the current value of the preference.
+ * @private
+ * @param {String} aData The name of the pref to fetch.
+ * @returns {Object} The value of the preference.
+ */
+ _get: function(aData) {
+ var po = that.preference_observer;
+ return po._branch['get'+(po.types[aData])+'Pref'](aData);
+ },
+
+ /**
+ * Sets the current preference.
+ * @param {String} aData The name of the pref to change.
+ * @param {Object} value The value to set.
+ */
+ _set: function(aData, value) {
+ var po = that.preference_observer;
+ return po._branch['set'+(po.types[aData])+'Pref'](aData, value);
+ }
+ };
+
+ /**
+ * A Preference Observer.
+ */
+ that.preference_observer = {
+ /**
+ * Dictionary of types (well, really the method needed to get/set the
+ * type.
+ * @type Hash
+ */
+ types: {
+ charset: 'Char',
+ editor: 'Char',
+ refresh: 'Int',
+ debug: 'Bool',
+ disable_gumdrops: 'Bool',
+ extensions: 'Char'
+ },
+
+ /**
+ * Register the observer.
+ */
+ register: function() {
+ var prefService = Components.
+ classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefService);
+ this._branch = prefService.getBranch("extensions."+that.MYSTRING+".");
+ this._branch.QueryInterface(Components.interfaces.nsIPrefBranch2);
+ this._branch.addObserver("", this, false);
+ /* setup the preferences */
+ for(var type in this.types) {
+ that.preferences[type] = that.preferences._get(type);
+ }
+ },
+
+ /**
+ * Unregister the observer. Not currently used, but may be
+ * useful in the future.
+ */
+ unregister: function() {
+ if (!this._branch) {return;}
+ this._branch.removeObserver("", this);
+ },
+
+ /**
+ * Observation callback.
+ * @param {String} aSubject The nsIPrefBranch we're observing (after appropriate QI)e
+ * @param {String} aData The name of the pref that's been changed (relative to the aSubject).
+ * @param {String} aTopic The string defined by NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
+ */
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "nsPref:changed") {return;}
+ if (that.preferences) {
+ that.preferences[aData] = that.preferences._get(aData);
+ if (aData == 'refresh') {
+ that.monitor.restart();
+ }
+ }
+ }
+ };
+
+ /**
+ * A Preference Option: What character set should the file use?
+ * @returns {String} the charset to be used.
+ */
+ that.getCharset = function() {
+ return that.preferences.charset;
+ };
+
+ /**
+ * A Preference Option: How often should we search for new content?
+ * @returns {int} The number of seconds between checking for new content.
+ */
+ that.getRefresh = function() {
+ var refresh = that.preferences.refresh;
+ if (!refresh || refresh < 1) {
+ that.debug('Invalid refresh gotten:',refresh);
+ refresh = 1;
+ }
+ var retval = 1000*refresh;
+ return retval;
+
+ };
+
+ /**
+ * Returns true if the system is running Mac OS X.
+ * @returns {boolean} Is this a Mac OS X system?
+ */
+ that.isDarwin = function() {
+ /* more help:
+ http://developer.mozilla.org/en/docs/Code_snippets:Miscellaneous#Operating_system_detection
+ */
+
+ var is_darwin = that._is_darwin;
+ if (typeof(is_darwin) == 'undefined') {
+ is_darwin = /^Darwin/i.test(Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULRuntime).OS);
+ that._is_darwin = is_darwin;
+ }
+ return is_darwin;
+ };
+
+ /**
+ * A Preference Option: What editor should we use?
+ *
+ * Note: On some platforms, this can return an
+ * NS_ERROR_FILE_INVALID_PATH exception and possibly others.
+ *
+ * For a complete list of exceptions, see:
+ * http://lxr.mozilla.org/seamonkey/source/xpcom/base/nsError.h#262
+ * @returns {nsILocalFile} A file object of the editor.
+ */
+ that.getEditor = function() {
+ var editor = that.preferences.editor;
+ var retval = null;
+
+ if (editor === '' && that.isDarwin()) {
+ editor = '/usr/bin/open';
+ that.preferences._set('editor', editor);
+ }
+
+ if (editor !== '') {
+ retval = that.factoryFile(editor);
+ }
+ return retval;
+ };
+
+ /**
+ * A Preference Option: should we display debugging info?
+ * @returns {bool}
+ */
+ that.getDebug = function() {
+ return that.preferences.debug;
+ };
+
+ /**
+ * A Preference Option: Are the edit gumdrops disabled?
+ * @returns {bool}
+ */
+ that.getDisableGumdrops = function() {
+ return that.preferences.disable_gumdrops;
+ };
+
+ /**
+ * A Preference Option: The list of extensions
+ * @returns Array
+ */
+ that.getExtensions = function() {
+ var string = that.preferences.extensions.replace(/[\n\t ]+/g,'');
+ var extensions = string.split(',');
+ if (extensions.length === 0) {
+ return ['.txt'];
+ } else {
+ return extensions;
+ }
+ };
+
+ /**
+ * Open the preferences dialog box.
+ * @param{boolean} wait The function won't return until the preference is set.
+ * @private
+ * Borrowed from http://wiki.mozilla.org/XUL:Windows
+ * and utilityOverlay.js's openPreferences()
+ */
+ that.openPreferences = function (wait) {
+ wait = typeof(wait)=='boolean'?wait:false;
+ var paneID = that.MYSTRING + '_preferences';
+ var instantApply = getBoolPref("browser.preferences.instantApply", false) && !wait;
+ var features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal");
+
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("Browser:Preferences");
+ var pane;
+ if (win) {
+ win.focus();
+ if (paneID) {
+ pane = win.document.getElementById(paneID);
+ win.document.documentElement.showPane(pane);
+ }
+ } else {
+ openDialog('chrome://itsalltext/chrome/preferences.xul',
+ "", features, paneID);
+ }
+ };
+
+ /**
+ * A Preference Option: Append an extension
+ * @returns Array
+ */
+ that.appendExtensions = function(ext) {
+ ext = ext.replace(/[\n\t ]+/g,'');
+ var current = that.getExtensions();
+ for(var i=0; i<current.length; i++) {
+ if(ext == current[i]) {
+ return; // Don't add a duplicate.
+ }
+ }
+
+ var value = that.preferences.extensions;
+ if(value.replace(/[\t\n ]+/g) === '') {
+ value = ext;
+ } else {
+ value = [value,',',ext].join('');
+ }
+ that.preferences._set('extensions', value);
+ };
+
+ // @todo [3] Profiling and optimization.
+
+ /**
+ * Returns a cache object
+ * Note: These UIDs are only unique for Its All Text.
+ * @param {Object} node A dom object node or ID to one.
+ * @returns {String} the UID or null.
+ */
+ that.getCacheObj = function(node) {
+ var cobj = null;
+ var str = that.MYSTRING+"_UID";
+ if (typeof(node) == 'string') {
+ cobj = that.tracker[node];
+ } else {
+ if (node && node.hasAttribute(str)) {
+ cobj = that.tracker[node.getAttribute(str)];
+ }
+ if (!cobj) {
+ cobj = new ItsAllText.CacheObj(node);
+ }
+ }
+ return cobj;
+ };
+
+ /**
+ * Cleans out all old cache objects.
+ */
+ that.cleanCacheObjs = function() {
+ var count = 0;
+ var cobj, id;
+ for(id in that.tracker) {
+ cobj = that.tracker[id];
+ if (cobj.node.ownerDocument.location === null) {
+ that.debug('cleaning %s', id);
+ delete cobj.node;
+ delete cobj.button;
+ delete that.tracker[id];
+ } else {
+ count += 1;
+ }
+ }
+ that.debuglog('tracker count:', count);
+ };
+
+ /**
+ * Refresh Textarea.
+ * @param {Object} node A specific textarea dom object to update.
+ */
+ that.refreshTextarea = function(node, is_chrome) {
+ var cobj = ItsAllText.getCacheObj(node);
+ if(!cobj) { return; }
+
+ cobj.update();
+ if (!is_chrome) { cobj.addGumDrop(); }
+ };
+
+ // @todo [5] Refresh textarea on editor quit.
+ // @todo [9] IDEA: support for input elements as well?
+
+ /**
+ * Refresh Document.
+ * @param {Object} doc The document to refresh.
+ */
+ that.refreshDocument = function(doc) {
+ if(!doc.location) { return; } // it's being cached, but not shown.
+ var is_chrome = (doc.location.protocol == 'chrome:' &&
+ doc.location.href != that.README);
+ var nodes = doc.getElementsByTagName('textarea');
+ var i;
+ for(i=0; i < nodes.length; i++) {
+ that.refreshTextarea(nodes[i], is_chrome);
+ }
+ nodes = doc.getElementsByTagName('textbox');
+ for(i=0; i < nodes.length; i++) {
+ that.refreshTextarea(nodes[i], is_chrome);
+ }
+ };
+
+ /**
+ * Returns the offset from the containing block.
+ * @param {Object} node A DOM element.
+ * @param {Object} container If unset, then this will use the offsetParent of node. Pass in null to go all the way to the root.
+ * @return {Array} The X & Y page offsets
+ */
+ that.getContainingBlockOffset = function(node, container) {
+ if(typeof(container) == 'undefined') {
+ container = node.offsetParent;
+ }
+ var pos = [node.offsetLeft, node.offsetTop];
+ var pnode = node.offsetParent;
+ while(pnode && (container === null || pnode != container)) {
+ pos[0] += pnode.offsetLeft || 0;
+ pos[1] += pnode.offsetTop || 0;
+ pos[0] -= pnode.scrollLeft || 0;
+ pos[1] -= pnode.scrollTop || 0;
+ pnode = pnode.offsetParent;
+ }
+ return pos;
+ };
+
+
+ /**
+ * This function is called regularly to watch changes to web documents.
+ */
+ that.monitor = {
+ id: null,
+ last_now:0,
+ documents: [],
+ /**
+ * Starts or restarts the document monitor.
+ */
+ restart: function() {
+ var rate = that.getRefresh();
+ var id = that.monitor.id;
+ if (id) {
+ clearInterval(id);
+ }
+ that.monitor.id = setInterval(that.monitor.watcher, rate);
+ },
+ /**
+ * watches the document 'doc'.
+ * @param {Object} doc The document to watch.
+ */
+ watch: function(doc, force) {
+ var contentType, location, is_html, is_usable, is_my_readme;
+ if (!force) {
+ /* Check that this is a document we want to play with. */
+ contentType = doc.contentType;
+ location = doc.location;
+ is_html = (contentType=='text/html' ||
+ contentType=='text/xhtml' ||
+ contentType=='application/xhtml+xml');
+ //var is_xul=(contentType=='application/vnd.mozilla.xul+xml');
+ is_usable = (is_html) &&
+ location.protocol != 'about:' &&
+ location.protocol != 'chrome:';
+ is_my_readme = location.href == that.README;
+ if (!(is_usable || is_my_readme)) {
+ that.debuglog('watch(): ignoring -- ',
+ location, contentType);
+ return;
+ }
+ }
+
+ that.refreshDocument(doc);
+ that.monitor.documents.push(doc);
+ },
+ /**
+ * Callback to be used by restart()
+ * @private
+ */
+ watcher: function(offset) {
+ var monitor = that.monitor;
+ var rate = that.getRefresh();
+
+ var now = Date.now();
+ if (now - monitor.last_now < Math.round(rate * 0.9)) {
+ that.debuglog('monitor.watcher(',offset,') -- skipping catchup refresh');
+ return;
+ }
+ monitor.last_now = now;
+
+ /* Walk the documents looking for changes */
+ var documents = monitor.documents;
+ that.debuglog('monitor.watcher(',offset,'): ', documents.length);
+ var i, doc;
+ var did_delete = false;
+ for(i in documents) {
+ doc = documents[i];
+ if (doc.location) {
+ that.debuglog('refreshing', doc.location);
+ that.refreshDocument(doc);
+ }
+ }
+ },
+ /**
+ * Stops watching doc.
+ * @param {Object} doc The document to watch.
+ */
+ unwatch: function(doc) {
+ var documents = that.monitor.documents;
+ var i;
+ for(i in documents) {
+ if (documents[i] === doc) {
+ that.debug('unwatching', doc);
+ delete documents[i];
+ }
+ }
+ that.cleanCacheObjs();
+ for(i=documents.length - 1; i >= 0; i--) {
+ if(typeof(documents[i]) == 'undefined') {
+ documents.splice(i,1);
+ }
+ }
+ }
+ };
+
+ /**
+ * Callback whenever the DOM content in a window or tab is loaded.
+ * @param {Object} event An event passed in.
+ */
+ that.onDOMContentLoad = function(event) {
+ if (event.originalTarget.nodeName != "#document") { return; }
+ var doc = event.originalTarget || document;
+ that.monitor.watch(doc);
+ return;
+ };
+
+ /**
+ * Open the editor for a selected node.
+ * @param {Object} node The textarea to get.
+ */
+ that.onEditNode = function(node) {
+ var cobj = that.getCacheObj(node);
+ if(cobj) {
+ cobj.edit();
+ }
+ return;
+ };
+
+ /**
+ * Triggered when the context menu is shown.
+ * @param {Object} event The event passed in by the event handler.
+ */
+ that.onContextMenu = function(event) {
+ var tid, node, tag, is_disabled, cobj, menu;
+ if(event.target) {
+ tid = event.target.id;
+ if (tid == "itsalltext-context-popup" ||
+ tid == "contentAreaContextMenu") {
+ node = document.popupNode;
+ tag = node.nodeName.toLowerCase();
+ is_disabled = (!(tag == 'textarea' ||
+ tag == 'textbox') ||
+ node.style.display == 'none' ||
+ node.getAttribute('readonly') ||
+ node.getAttribute('disabled')
+ );
+ if (tid == "itsalltext-context-popup") {
+ cobj = that.getCacheObj(node);
+ that.rebuildMenu(cobj.uid,
+ 'itsalltext-context-popup',
+ is_disabled);
+ } else {
+ // tid == "contentAreaContextMenu"
+ menu = document.getElementById("itsalltext-contextmenu");
+ menu.setAttribute('hidden', is_disabled);
+ }
+
+ }
+ }
+ return true;
+ };
+
+ that.openReadme = function() {
+ browser = getBrowser();
+ browser.selectedTab = browser.addTab(that.README, null);
+ };
+
+ /**
+ * Initialize the module. Should be called once, when a window is loaded.
+ * @private
+ */
+ var windowload = function(event) {
+ that.debug("startup(): It's All Text! is watching this window...");
+
+ // Start watching the preferences.
+ that.preference_observer.register();
+
+ // Start the monitor
+ that.monitor.restart();
+
+ var appcontent = document.getElementById("appcontent"); // The Browser
+ if (appcontent) {
+ // Normal web-page.
+ appcontent.addEventListener("DOMContentLoaded", that.onDOMContentLoad,
+ true);
+ } else {
+ that.onDOMContentLoad(event);
+ }
+ // Attach the context menu, if we can.
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ if (contentAreaContextMenu) {
+ contentAreaContextMenu.addEventListener("popupshowing",
+ that.onContextMenu, false);
+ }
+ };
+
+ // Do the startup when things are loaded.
+ window.addEventListener("load", windowload, true);
+ // Do the startup when things are unloaded.
+ window.addEventListener("unload", function(event){that.monitor.unwatch(event.originalTarget||document); that.preference_observer.unregister();}, true);
+
+};
+
+/**
+ * The command that is called when picking a new extension.
+ * @param {Event} event
+ */
+ItsAllText.prototype.menuNewExtEdit = function(event) {
+ var that = this;
+ var uid = this._current_uid;
+ var cobj = that.getCacheObj(uid);
+
+ var params = {out:null};
+ window.openDialog("chrome://itsalltext/chrome/newextension.xul", "",
+ "chrome, dialog, modal, resizable=yes", params).focus();
+ var ext;
+ if (params.out) {
+ ext = params.out.extension.replace(/[\n\t ]+/g,'');
+ if(params.out.do_save) {
+ that.appendExtensions(ext);
+ }
+ cobj.edit(ext);
+ }
+};
+
+/**
+ * The command that is called when selecting an existing extension.
+ * @param {Event} event
+ */
+ItsAllText.prototype.menuExtEdit = function(event) {
+ var that = this;
+ var uid = that._current_uid;
+ var ext = event.target.getAttribute('label');
+ var cobj = that.getCacheObj(uid);
+ cobj.edit(ext);
+};
+
+/**
+ * Rebuilds the option menu, to reflect the current list of extensions.
+ * @private
+ * @param {String} uid The UID to show in the option menu.
+ */
+ItsAllText.prototype.rebuildMenu = function(uid, menu_id, is_disabled) {
+ menu_id = typeof(menu_id) == 'string'?menu_id:'itsalltext-optionmenu';
+ is_disabled = (typeof(is_disabled) == 'undefined'||!is_disabled)?false:(is_disabled&&true);
+ var i;
+ var that = this;
+ var exts = that.getExtensions();
+ var menu = document.getElementById(menu_id);
+ var items = menu.childNodes;
+ var items_length = items.length - 1; /* We ignore the preferences item */
+ var node;
+ that._current_uid = uid;
+ var magic_stop_node = null;
+ var magic_start = null;
+ var magic_stop = null;
+
+ // Find the beginning and end of the magic replacement parts.
+ for(i=0; i<items_length; i++) {
+ node = items[i];
+ if (node.nodeName.toLowerCase() == 'menuseparator') {
+ if(magic_start === null) {
+ magic_start = i;
+ } else if (magic_stop === null) {
+ magic_stop = i;
+ magic_stop_node = node;
+ }
+ } else if (node.nodeName.toLowerCase() == 'menuitem') {
+ node.setAttribute('disabled', is_disabled?'true':'false');
+ }
+ }
+
+ // Remove old magic bits
+ for(i = magic_stop - 1; i > magic_start; i--) {
+ menu.removeChild(items[i]);
+ }
+
+ // Insert the new magic bits
+ for(i=0; i<exts.length; i++) {
+ node = document.createElementNS(that.XULNS, 'menuitem');
+ node.setAttribute('label', exts[i]);
+ node.addEventListener('command', function(event){return that.menuExtEdit(event);}, false);
+ node.setAttribute('disabled', is_disabled?'true':'false');
+ menu.insertBefore(node, magic_stop_node);
+
+ }
+ return menu;
+};
+
+ItsAllText = new ItsAllText();
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE overlay SYSTEM "chrome://itsalltext/locale/itsalltext.dtd" >
+
+<overlay id="ItsAllTextOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/x-javascript" src="chrome://itsalltext/chrome/itsalltext.js" />
+
+ <!-- The merge point is contentAreaContextMenu -->
+ <popup id="contentAreaContextMenu">
+ <menu id="itsalltext-contextmenu" label="&top.label;" accesskey="&top.key;">
+ <menupopup id="itsalltext-context-popup">
+ <menuitem label="&edit.label;"
+ oncommand="ItsAllText.onEditNode(document.popupNode)"
+ accesskey="&edit.key;" />
+ <menuitem label="&newext.label;"
+ accesskey="&newext.key;"
+ oncommand="ItsAllText.menuNewExtEdit(event);" />
+ <menuseparator/>
+ <menuitem label=".txt" oncommand="ItsAllText.menuExtEdit(event);"/>
+ <menuseparator/>
+ <menuitem label="&readme.label;"
+ oncommand="ItsAllText.openReadme();"/>
+ <menuitem label="&pref.label;"
+ accesskey="&pref.key;"
+ oncommand="ItsAllText.openPreferences();"/>
+ </menupopup>
+ </menu>
+ </popup>
+
+ <!-- The merge point is main-window -->
+ <window id="main-window">
+ <popupset id="itsalltext-optionmenu-set">
+ <popup id="itsalltext-optionmenu">
+ <menuitem label="&newext.label;"
+ accesskey="&newext.key;"
+ oncommand="ItsAllText.menuNewExtEdit(event);" />
+ <menuseparator/>
+ <menuitem label=".txt" oncommand="ItsAllText.menuExtEdit(event);"/>
+ <menuseparator/>
+ <menuitem label="&readme.label;"
+ oncommand="ItsAllText.openReadme();"/>
+ <menuitem label="&pref.label;"
+ accesskey="&pref.key;"
+ oncommand="ItsAllText.openPreferences();"/>
+ </popup>
+ </popupset>
+ </window>
+
+ <!-- The merge point is the Tools menu -->
+ <menupopup id="menu_ToolsPopup">
+ <menu id="menu_itsalltext" class="menuitem-iconic"
+ image="chrome://itsalltext/chrome/icon16.png"
+ label="&top.label;"
+ insertbefore="sanitizeSeparator">
+ <menupopup>
+ <menuitem label="&readme.label;"
+ oncommand="ItsAllText.openReadme();"/>
+ <menuitem label="&pref.label;"
+ accesskey="&pref.key;"
+ oncommand="ItsAllText.openPreferences();"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+</overlay>
+<!-- Local Variables: -->
+<!-- mode: xml -->
+<!-- End: -->
--- /dev/null
+/**
+ * Pass back the values that that the user selected.
+ */
+function onOK() {
+ window['arguments'][0].out = {
+ extension: document.getElementById('new_ext').value,
+ do_save: document.getElementById('do_save').checked
+ };
+ return true;
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE newextwindow SYSTEM "chrome://itsalltext/locale/newextension.dtd" >
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="ItsAllTextNewExtension"
+ title="&title;"
+ ondialogaccept="return onOK();"
+ persist="screenX screenY width height"
+ windowtype="ItsAllTextWindowType">
+
+ <script type="application/x-javascript" src="chrome://itsalltext/content/newextension.js"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&extension.label;"/>
+ <textbox id="new_ext" value=".txt"/>
+ </row>
+ <row align="center">
+ <spacer/>
+ <checkbox id="do_save" label="&save.label;"/>
+ </row>
+ </rows>
+ </grid>
+</dialog>
+<!-- Local Variables: -->
+<!-- mode: xml -->
+<!-- End: -->
--- /dev/null
+// @todo [6] [pref] Better strategy for getting the default editor: EDITOR env variable or view_source.editor.path
+// @todo [8] [pref] Option to make the textarea uneditable when using editor.
+
+/**
+ * Open a filepicker to select the value of the editor.
+ */
+function pref_editor_select() {
+ var locale = document.getElementById("strings");
+
+ var pref_editor = document.getElementById('pref_editor');
+ var nsIFilePicker = Components.interfaces.nsIFilePicker;
+
+ var fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ fp.init(window,
+ locale.getString('picker.window.title'),
+ nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterApps);
+
+ var initdir = Components.classes["@mozilla.org/file/local;1"].
+ createInstance(Components.interfaces.nsILocalFile);
+ try {
+ initdir.initWithPath(pref_editor.value);
+ initdir = initdir.parent;
+ if (initdir.exists() && initdir.isDirectory()) {
+ fp.displayDirectory = initdir;
+ }
+ } catch(e) {
+ // Ignore error, the pref may not have been set or who knows.
+ }
+
+ var rv = fp.show();
+ var file;
+ var editor;
+ if (rv == nsIFilePicker.returnOK) {
+ file = fp.file;
+ pref_editor.value = file.path;
+ editor = document.getElementById('editor');
+ editor.style.color = 'inherit';
+ editor.style.backgroundColor = 'inherit';
+ }
+}
+
+function setHelp(text) {
+ var help = document.getElementById('help');
+ while (help.firstChild) {
+ help.removeChild(help.firstChild);
+ }
+ var textnode = document.createTextNode(text);
+ help.appendChild(textnode);
+}
+
+function pref_onload() {
+ var locale = document.getElementById("strings");
+ document.getElementById('browse').focus();
+ var editor;
+ var box;
+ var desc;
+ var textnode;
+ if (window['arguments'] && window['arguments'][0] && window['arguments'][0] == 'badeditor') {
+ editor = document.getElementById('editor');
+ editor.style.color = 'black';
+ editor.style.backgroundColor = '#fb4';
+ box = document.getElementById('help');
+ // Clean it out
+ while (box.firstChild) {
+ box.removeChild(box.firstChild);
+ }
+ desc = document.createElement('description');
+ textnode = document.createTextNode(locale.getFormattedString('problem.editor', [editor.value]));
+ desc.appendChild(textnode);
+ desc.style.maxWidth = '18em';
+ box.appendChild(desc);
+
+ desc = document.createElement('description');
+ textnode = document.createTextNode(locale.getString('mac.hint'));
+ desc.appendChild(textnode);
+ desc.style.maxWidth = '18em';
+ box.appendChild(desc);
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE prefwindow SYSTEM "chrome://itsalltext/locale/preferences.dtd" >
+<prefwindow id="itsalltext-prefs"
+ title="&title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="accept,cancel"
+ onload="pref_onload();">
+
+<script type="application/x-javascript" src="preferences.js"/>
+
+<stringbundleset id="strbundles">
+<stringbundle id="strings" src="chrome://itsalltext/locale/preferences.properties"/>
+</stringbundleset>
+
+<prefpane
+ id="itsalltext-pane"
+ label="&title;"
+ flex="1">
+ <preferences>
+ <preference id="pref_charset"
+ name="extensions.itsalltext.charset" type="string"/>
+ <preference id="pref_editor"
+ name="extensions.itsalltext.editor" type="string"/>
+ <preference id="pref_seconds"
+ name="extensions.itsalltext.refresh" type="int"/>
+ <preference id="pref_extensions"
+ name="extensions.itsalltext.extensions" type="string"/>
+ <preference id="pref_disable_gumdrops"
+ name="extensions.itsalltext.disable_gumdrops" type="bool"/>
+ <preference id="pref_debug"
+ name="extensions.itsalltext.debug" type="bool"/>
+ </preferences>
+
+ <hbox>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label control="editor" value="&editor.label;"/>
+ <vbox>
+ <textbox preference="pref_editor" id="editor" size="20"
+ style="-moz-appearance: none !important; background:
+ inherit;" />
+ <hbox align="right">
+ <spacer/>
+ <button oncommand="pref_editor_select();" label="&picker.label;"
+ id="browse" accesskey="b" tabindex="1"/>
+ </hbox>
+ </vbox>
+ </row>
+ <row align="center">
+ <label control="seconds" value="&seconds.label;"/>
+ <hbox>
+ <textbox preference="pref_seconds" id="seconds" size="2"
+ maxlength="2" tabindex="2"/>
+ <spacer/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="charset"
+ value="&charset.label;"/>
+ <hbox>
+ <textbox preference="pref_charset" id="charset" size="8" tabindex="3"/>
+ <spacer/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="extensions"
+ value="&extensions.label;"/>
+ <textbox preference="pref_extensions" id="extensions" size="30" tabindex="4"/>
+ </row>
+ <row align="center">
+ <label control="disable_gumdrops"
+ value="&disable_gumdrops.label;"/>
+ <checkbox preference="pref_disable_gumdrops" id="disable_gumdrops" tabindex="5"
+ label="&disable_gumdrops.checkbox;"/>
+ </row>
+ <row align="center">
+ <label control="debug"
+ value="&debug.label;"/>
+ <checkbox preference="pref_debug" id="debug" tabindex="5"
+ label="&debug.humor;"/>
+ </row>
+ </rows>
+ </grid>
+ <vbox id="help" flex="1">
+ </vbox>
+ </hbox>
+</prefpane>
+
+</prefwindow>
--- /dev/null
+<!ENTITY title "About It's All Text!">
+<!ENTITY description "It's All Text! - Easy external editing of web forms.
+
+Copyright (C) 2006-2007 Christian Höltje
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License or
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.">
+<!ENTITY close "Close">
+
--- /dev/null
+<!ENTITY title "It's All Text! was unable to open your editor">
+<!ENTITY header "Unable to open your editor">
+<!ENTITY pref.label "Preferences">
+<!ENTITY helptext "It's All Text! was unable to run your editor.
+
+You can now either cancel your edit or you can use the preferences to choose a new editor. Please make sure that you use the full path to the editor and that the editor is marked executable.
+
+Thank you.
+">
+<!ENTITY reason "Reason:">
--- /dev/null
+bad.noent=The path '%1$S' does not exist.
+bad.noexec=Unable to execute your editor.
+bad.noset=Your editor has not been set.
+
--- /dev/null
+<!ENTITY top.label "It's All Text!">
+<!ENTITY top.key "i">
+
+<!ENTITY edit.label "Edit with default extension">
+<!ENTITY edit.key "e">
+
+<!ENTITY newext.label "Edit with new extension...">
+<!ENTITY newext.key "n">
+
+<!ENTITY pref.label "Preferences...">
+<!ENTITY pref.key "p">
+
+<!ENTITY readme.label "View the README...">
+
--- /dev/null
+extensions.itsalltext@docwhat.gerf.org.description=Edit text using your favorite editor!
+program_name=It's All Text!
+no_editor_pref=Preferences: Please pick an editor.
+problem_making_directory=I'm having a problem finding or creating the directory: %1$S
+gumdrop.width=28
+gumdrop.height=14
--- /dev/null
+<!ENTITY title "It's All Text! New Extension">
+<!ENTITY extension.label "Extension (with leading dot):">
+<!ENTITY save.label "Save for future use">
--- /dev/null
+<!ENTITY title "It's All Text! Preferences">
+<!ENTITY editor.label "Editor:">
+<!ENTITY picker.label "Browse">
+<!ENTITY seconds.label "Seconds between refreshing:">
+<!ENTITY charset.label "Character Set (default: UTF-8):">
+<!ENTITY extensions.label "File Extensions:">
+<!ENTITY debug.label "Debugging:">
+<!ENTITY debug.humor "Remove all bugs">
+<!ENTITY disable_gumdrops.label "In-Page Edit Buttons:">
+<!ENTITY disable_gumdrops.checkbox "Disable">
--- /dev/null
+picker.window.title=Choose your editor
+problem.editor=I was unable to run your editor, '%1$S'. Use the browse button to choose another editor and try again.
+mac.hint=If you use Mac OS X then you probably want to use '/usr/bin/open' -- it will open the file using the default application for that file.
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" >
+ <head>
+ <title>Welcome to It's All Text! version 0.7.3</title>
+ <style type="text/css">
+ html { background: #abd; color: #000; padding: 0; margin: 0; }
+ body { background: #fff; color: #000; margin: 1ex auto; padding: 1em;
+ -moz-border-radius: 1em; width: 30em;
+ font-family: sans-serif;}
+ h1, h2, h3, h4, h5, h6 { background: #ffa; color: #06d; -moz-border-radius: 1ex; padding: 0 0.8em 0 2em; margin-left: -2em;}
+ h1 { padding: 0.5ex 1em 0.5ex 2em; margin: 0 -2em;}
+ h2, h3, h4, h5, h6 { display: block; width: 20em; clear: both; }
+
+ tt, code { font-size: 1.1em; font-weight: bold; color: #048; }
+ dt { font-weight: bold; color: #048; }
+
+ #ver { margin: 0; text-align: right; }
+ #test { width: 50%; height: 10em; background: #cdf; }
+ #nteb { font-size: 0.5em; text-align: right; margin-right: 50%; padding-right: 30px; }
+
+ .iat { font-style: oblique; color: #024; }
+ .warn { background: #fdd; }
+
+ pre { background: #def; color: black; margin: 1em; padding: 0 1em; }
+
+ #faq dl { margin-left: 1em; }
+ #faq dd { font-size: 0.8em; }
+ </style>
+ </head>
+ <body>
+ <h1>Welcome to <span class="iat">It's All Text!</span></h1>
+ <p id="ver"> version 0.7.3</p>
+
+ <p class="warn">
+ This software is ALMOST version 1.0! It's inches from being
+ ready to go. Please <a
+ href="http://docwhat.gerf.org/2007/03/its_all_text_v06">report</a>
+ any problems you have to help!
+ </p>
+
+ <p>
+ <span class="iat">It's All Text!</span> gives you a simple way to edit textareas, the large text boxes in forms, using your favorite editor.
+ </p>
+
+
+
+ <h3>Quick Start</h3>
+ <p>
+ Upon installation, go to the menu <tt>Tools -> It's All Text!
+ -> Preferences</tt> to set your preferences. Specifically,
+ you'll have to set the editor's full path.
+ </p>
+
+ <p>
+ There are three ways to use <span class="iat">It's All Text!</span>:
+ </p>
+ <ul>
+ <li>Right click on a textarea, select <tt>It's All Text!</tt>.
+ </li>
+ <li>Click on the edit buttons added for your convenience.
+ </li>
+ <li>Right click on the edit buttons for more options.
+ </li>
+ </ul>
+
+ <p>
+ Here is a test edit box for you to play with…
+ </p>
+ <form action="" style="margin-left: 2em;">
+ <p style="margin: 0; padding: 0;">
+ <textarea id="test" cols="10" rows="4">Click the edit button and have fun editing!</textarea>
+ </p>
+ <div id="nteb">Note the edit button.—> </div>
+ </form>
+
+ <div style="clear:both;"/>
+ <p id="signoff"> Ciao! </p>
+
+
+ <h3>A note to Mac OS X users</h3>
+
+ <p>
+ Out of the box, <span class="iat">It's All Text!</span> uses the <code>open</code> program. <code>open</code> behaves like double clicking on a file. It uses the type of the file to choose the correct application to run; for <tt>.txt</tt> files, that application is the built-in text editor.
+ </p>
+ <p>
+ If this behavior is fine for you, then leave the editor option alone and enjoy!
+ </p>
+ <p>
+ However, if you want to use a different editor or to force the same editor regardless of the file type, then you will need to do something a little more complicated.
+ </p>
+ <p>
+ Firefox <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=307463">cannot run .app applications directly</a>. To run a program in Mac OS X you need to do one of two things: If your editor comes with a non-<code>.app</code> version, then use that. Otherwise you have to write a shell script.
+ </p>
+ <p>
+ Check your editor's documentation; if it comes with a standalone program, usually located in the <code>/usr/bin/</code> directory, then you can enter that into the <span class="iat">It's All Text!</span> preferences and you're done.
+ </p>
+ <p>
+ Otherwise, you need to create a shell script. Here are the basic steps to create a shell script:
+ </p>
+ <ol>
+ <li> Open your favorite editor. </li>
+ <li> Create a file like the example below. </li>
+ <li> Save it to your home directory: <code>~/iat.sh</code> </li>
+ <li> Open a terminal window. </li>
+ <li> Type this command to make the shell script executable: <code>chmod +x ~/iat.sh</code> </li>
+ <li> In <span class="iat">It's All Text!</span> preferences, use the shell script as your editor. </li>
+ </ol>
+
+ <p>
+ The example shell script. Replace <code>/path/to/editor.app</code> with the actual path to your .app file. It'll probably be something like <code>/Applications/MyEditor.app</code>.
+ </p>
+ <pre lang="sh">#!/bin/sh
+# This is an example shell script for It's All Text!
+
+open -a /path/to/editor.app $*</pre>
+
+ <p>
+ Other alternative shell scripts are available at <a href="http://docwhat.gerf.org/2007/03/its_all_text_v06/#comment-2054">here</a>.
+ </p>
+
+
+
+ <h3>FAQ</h3>
+
+ <dl id="faq">
+ <dt>I want to do something more complicated than just running an editor with a file-name.</dt>
+ <dd>
+ <p>In UNIX systems, such as Mac OS X or Linux, you can create a shell script with your commands in it.</p>
+ <p>In windows, you can create a <tt>.cmd</tt> file instead.</p>
+ </dd>
+
+ <dt>I can't find the edit button for (gmail, blogger, etc.)</dt>
+ <dd>
+ <p>Gmail, blogger, and other sites has the option to use "rich text editors". The editors act similar to a word processor. Due to the way these work, it isn't possible for <span class="iat">It's All Text!</span> find the <tt>textarea</tt>, it is hidden or, in some cases, absent.
+ </p>
+ <p>
+ Workaround: Turn off the rich text editor, if possible.
+ </p>
+ </dd>
+
+ <dt>I use non-ASCII characters and they turn into blocks or question marks (?).</dt>
+ <dd>
+ <p>
+ The problem is that the encoding <span class="iat">It's All Text!</span> is using and your editor is using don't match. You can figure out what encoding your editor wants and change the encodings preference in <span class="iat">It's All Text!</span> or you can change the encoding your editor uses.
+ </p>
+ <p>
+ A common problem I get is that someone is using Notepad or WordPad in Windows. These both do not support sane encodings. I recommend getting something like <a href="http://notepad-plus.sourceforge.net/">Notepad++</a> for editing in UTF-8 instead.
+ </p>
+ </dd>
+ </dl>
+
+ </body>
+</html>
+
+<!-- LocalWords: gmail blogger WordPad UTF html abd fff moz ffa tt dt ver cdf
+-->
+<!-- LocalWords: nteb px iat faq dl cmd
+-->
--- /dev/null
+pref("extensions.itsalltext.charset", "UTF-8");
+pref("extensions.itsalltext.editor", "");
+pref("extensions.itsalltext.refresh", 3);
+pref("extensions.itsalltext.debug", false);
+pref("extensions.itsalltext.disable_gumdrops", false);
+pref("extensions.itsalltext.extensions", '.txt,.html,.css,.xml,.xsl,.js');
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+<?xml version="1.0"?>
+<RDF:RDF xmlns:em="http://www.mozilla.org/2004/em-rdf#"
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <RDF:Description RDF:about="rdf:#$firefox"
+ em:id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
+ em:minVersion="1.5"
+ em:maxVersion="3.0a6" />
+ <RDF:Description RDF:about="rdf:#$flock"
+ em:id="{a463f10c-3994-11da-9945-000d60ca027b}"
+ em:minVersion="0.4"
+ em:maxVersion="0.8" />
+ <RDF:Description RDF:about="urn:mozilla:install-manifest"
+ em:id="itsalltext@docwhat.gerf.org"
+ em:version="0.7.3"
+ em:type="2"
+ em:name="It's All Text!"
+ em:description="Edit text using your favorite editor!"
+ em:homepageURL="http://addons.mozilla.org/firefox/4125"
+ em:optionsURL="chrome://itsalltext/content/preferences.xul"
+ em:iconURL="chrome://itsalltext/content/icon.png"
+ em:aboutURL="chrome://itsalltext/content/about.xul"
+ em:creator="Christian Höltje">
+ <em:targetApplication RDF:resource="rdf:#$firefox"/>
+ <em:targetApplication RDF:resource="rdf:#$flock"/>
+ </RDF:Description>
+</RDF:RDF>