From c8436ddca5160c736aaf67326ccbaa08ffb01ae5 Mon Sep 17 00:00:00 2001 From: Qeole Date: Sun, 20 Sep 2020 16:55:00 +0100 Subject: [PATCH] v2: Port to TB 78.4+/82+, rewrite add-on from scratch, use highlight.js Following Thunderbird's switch to MailExtensions, the add-on had to be ported as a "legacy" extension for v68. This proved to be a short relief, since the following stable, v78, does not support those anymore. The new version of the add-on is a pure MailExtension that should be compatible with new Thunderbird versions. It relies on the messagesModify API that appeared in Thunderbird 82, but has since been backported to version 78.4 [0]. Various parts of the add-on were referring to XUL overlays or chrome components, but none are available with MailExtensions. Instead of struggling to adapt these parts, the add-on was entirely rewritten from scratch, and with a different approach. We no longer parse and reformat the diffs: Instead, we embed and inject the highlight.js library that takes care of coloring the diff [1]. Consequences and main changes: - We loose some accuracy, because we do not rely on "what part of the diff we are in". Concretely, all lines starting with "+" or "-" will be colored, even if in the commit log. - We gain in stability, with less parsing susceptible to break. - We loose the different modes, unified / context / side-by-side. The diffs are not rewritten, we simply add colors. - We keep the ability to replace tabs and spaces, but loose the line numbers. - It is not possible at this time to specify custom colors for the diffs, but we can pick a style amongst the hundred or so that are supported by highlight.js. Bumping the major version number. [0] https://bugzilla.mozilla.org/show_bug.cgi?id=1504475 [1] https://highlightjs.org/ --- .gitignore | 3 + COPYRIGHT | 2 +- Makefile | 63 +- README.md | 116 +-- chrome.manifest | 6 - chrome/content/bindings/bindings.css | 7 - chrome/content/bindings/colorpicker.css | 9 - chrome/content/bindings/main-bindings.xbl | 97 --- chrome/content/callbacks.js | 38 - chrome/content/colorediffs.js | 157 ---- chrome/content/dom.js | 44 - chrome/content/globals.js | 154 ---- chrome/content/ilUtils.js | 28 - chrome/content/main-overlay.xul | 112 --- chrome/content/messageWindowOverlay.xul | 6 - chrome/content/messengerOverlay.xul | 6 - .../content/options/context-view-options.xul | 65 -- .../content/options/options-pref-callback.js | 32 - chrome/content/options/options-pref.js | 63 -- chrome/content/options/options.css | 17 - chrome/content/options/options.js | 204 ----- chrome/content/options/options.xul | 143 --- .../options/side-by-side-view-options.xul | 114 --- .../content/options/unified-view-options.xul | 65 -- chrome/content/overlay.js | 79 -- chrome/content/parsers/context-parser.js | 160 ---- chrome/content/parsers/main-parser.js | 8 - chrome/content/parsers/unified-parser.js | 547 ------------ chrome/content/prefs.js | 103 --- chrome/content/tests/prefHelper.js | 54 -- chrome/content/tests/testCallbacks.js | 63 -- chrome/content/tests/testDom.js | 31 - chrome/content/tests/testGlobals.js | 44 - chrome/content/tests/testOptionsPrefs.js | 35 - chrome/content/tests/testParsers.js | 822 ------------------ chrome/content/tests/testPrefs.js | 54 -- chrome/content/toolbar.js | 110 --- chrome/content/transformations/add-title.js | 14 - .../transformations/calc-chunk-size.js | 19 - .../transformations/collect-tab-sizes.js | 40 - .../content/transformations/composite-init.js | 134 --- .../content/transformations/composite-run.js | 89 -- .../composite-transformation.js | 15 - .../transformations/detect-old-new-files.js | 23 - .../transformations/find-common-name.js | 253 ------ .../transformations/main-transformation.js | 6 - .../make-lines-equal-length.js | 73 -- .../replace-file-names-transformation.js | 20 - .../content/transformations/replace-tabs.js | 22 - .../transformations/select-old-new-files.js | 27 - .../transformations/show-line-numbers.js | 87 -- .../show-whitespaces-transformation.js | 30 - .../transformations/truncate-file-names.js | 87 -- chrome/content/views/context-view.js | 254 ------ chrome/content/views/main-view.js | 3 - chrome/content/views/side-by-side-view.js | 351 -------- chrome/content/views/unified-view.js | 229 ----- chrome/skin/colorediffs.css | 17 - chrome/skin/line-numbers.png | Bin 342 -> 0 bytes chrome/skin/options.png | Bin 325 -> 0 bytes chrome/skin/white-space.png | Bin 289 -> 0 bytes defaults/preferences/colorediffs.js | 68 -- manifest.json | 27 +- misc/HowFiltersDependanciesGraphWorks.md | 39 - misc/HowTabsAreHandled.md | 8 - misc/HowToGetMessageTextInThunderbird.md | 124 --- misc/Screenshots.md | 61 -- misc/SupportedFormats.md | 168 ---- misc/context.png | Bin 81085 -> 0 bytes {chrome/content => misc}/icon.png | Bin misc/options-context.png | Bin 62861 -> 0 bytes misc/options-general.png | Bin 59626 -> 0 bytes misc/options-side-by-side.png | Bin 68091 -> 0 bytes misc/options-unified.png | Bin 60115 -> 0 bytes misc/plain.png | Bin 75638 -> 0 bytes misc/side-by-side-new.png | Bin 74332 -> 0 bytes misc/side-by-side-without-whitespaces.png | Bin 72744 -> 0 bytes misc/side-by-side.png | Bin 77285 -> 0 bytes misc/transformation.png | Bin 6740 -> 0 bytes misc/unified.png | Bin 80691 -> 0 bytes options/common.inc.css | 491 +++++++++++ options/defaults.js | 14 + options/fill-css-list.js | 107 +++ options/listeners.js | 18 + options/preview.js | 61 ++ options/save-restore.js | 39 + options/ui.html | 92 ++ scripts/background.js | 106 +++ scripts/coloring.js | 37 + scripts/content.js | 141 +++ scripts/transformations.js | 118 +++ 91 files changed, 1336 insertions(+), 5837 deletions(-) create mode 100644 .gitignore delete mode 100644 chrome.manifest delete mode 100644 chrome/content/bindings/bindings.css delete mode 100644 chrome/content/bindings/colorpicker.css delete mode 100644 chrome/content/bindings/main-bindings.xbl delete mode 100644 chrome/content/callbacks.js delete mode 100644 chrome/content/colorediffs.js delete mode 100644 chrome/content/dom.js delete mode 100644 chrome/content/globals.js delete mode 100644 chrome/content/ilUtils.js delete mode 100644 chrome/content/main-overlay.xul delete mode 100644 chrome/content/messageWindowOverlay.xul delete mode 100644 chrome/content/messengerOverlay.xul delete mode 100644 chrome/content/options/context-view-options.xul delete mode 100644 chrome/content/options/options-pref-callback.js delete mode 100644 chrome/content/options/options-pref.js delete mode 100644 chrome/content/options/options.css delete mode 100644 chrome/content/options/options.js delete mode 100644 chrome/content/options/options.xul delete mode 100644 chrome/content/options/side-by-side-view-options.xul delete mode 100644 chrome/content/options/unified-view-options.xul delete mode 100644 chrome/content/overlay.js delete mode 100644 chrome/content/parsers/context-parser.js delete mode 100644 chrome/content/parsers/main-parser.js delete mode 100644 chrome/content/parsers/unified-parser.js delete mode 100644 chrome/content/prefs.js delete mode 100644 chrome/content/tests/prefHelper.js delete mode 100644 chrome/content/tests/testCallbacks.js delete mode 100644 chrome/content/tests/testDom.js delete mode 100644 chrome/content/tests/testGlobals.js delete mode 100644 chrome/content/tests/testOptionsPrefs.js delete mode 100644 chrome/content/tests/testParsers.js delete mode 100644 chrome/content/tests/testPrefs.js delete mode 100644 chrome/content/toolbar.js delete mode 100644 chrome/content/transformations/add-title.js delete mode 100644 chrome/content/transformations/calc-chunk-size.js delete mode 100644 chrome/content/transformations/collect-tab-sizes.js delete mode 100644 chrome/content/transformations/composite-init.js delete mode 100644 chrome/content/transformations/composite-run.js delete mode 100644 chrome/content/transformations/composite-transformation.js delete mode 100644 chrome/content/transformations/detect-old-new-files.js delete mode 100644 chrome/content/transformations/find-common-name.js delete mode 100644 chrome/content/transformations/main-transformation.js delete mode 100644 chrome/content/transformations/make-lines-equal-length.js delete mode 100644 chrome/content/transformations/replace-file-names-transformation.js delete mode 100644 chrome/content/transformations/replace-tabs.js delete mode 100644 chrome/content/transformations/select-old-new-files.js delete mode 100644 chrome/content/transformations/show-line-numbers.js delete mode 100644 chrome/content/transformations/show-whitespaces-transformation.js delete mode 100644 chrome/content/transformations/truncate-file-names.js delete mode 100644 chrome/content/views/context-view.js delete mode 100644 chrome/content/views/main-view.js delete mode 100644 chrome/content/views/side-by-side-view.js delete mode 100644 chrome/content/views/unified-view.js delete mode 100644 chrome/skin/colorediffs.css delete mode 100644 chrome/skin/line-numbers.png delete mode 100644 chrome/skin/options.png delete mode 100644 chrome/skin/white-space.png delete mode 100644 defaults/preferences/colorediffs.js delete mode 100644 misc/HowFiltersDependanciesGraphWorks.md delete mode 100644 misc/HowTabsAreHandled.md delete mode 100644 misc/HowToGetMessageTextInThunderbird.md delete mode 100644 misc/Screenshots.md delete mode 100644 misc/SupportedFormats.md delete mode 100644 misc/context.png rename {chrome/content => misc}/icon.png (100%) delete mode 100644 misc/options-context.png delete mode 100644 misc/options-general.png delete mode 100644 misc/options-side-by-side.png delete mode 100644 misc/options-unified.png delete mode 100644 misc/plain.png delete mode 100644 misc/side-by-side-new.png delete mode 100644 misc/side-by-side-without-whitespaces.png delete mode 100644 misc/side-by-side.png delete mode 100644 misc/transformation.png delete mode 100644 misc/unified.png create mode 100644 options/common.inc.css create mode 100644 options/defaults.js create mode 100644 options/fill-css-list.js create mode 100644 options/listeners.js create mode 100644 options/preview.js create mode 100644 options/save-restore.js create mode 100644 options/ui.html create mode 100644 scripts/background.js create mode 100644 scripts/coloring.js create mode 100644 scripts/content.js create mode 100644 scripts/transformations.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b17e911 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.editorconfig +*.xpi +hljs/ diff --git a/COPYRIGHT b/COPYRIGHT index a498011..da95d68 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,6 +1,6 @@ Copyright 2006-2010 Vadim Atlygin Copyright 2011-2015 Jesse Glick -Copyright 2016-2018 Qeole +Copyright 2016-2020 Qeole This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/Makefile b/Makefile index 942a73c..a0b645d 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,52 @@ -ADDON=colorediffs -XPI=$(ADDON)-$(shell date -u "+%Y%m%d").xpi -SRC=$(shell find chrome defaults ! -path "chrome/content/tests*") -TOPFILES=manifest.json chrome.manifest LICENSE COPYRIGHT +# SPDX-License-Identifier: MPL-2.0 -.PHONY: clean test +VERSION=$(shell sed -n '/"version"/ s/.*: "\(.*\)",/\1/p' manifest.json) +ADDON=colorediffs-$(VERSION).xpi -xpi: $(XPI) +xpi: $(ADDON) check_css_list -%.xpi: $(SRC) $(TOPFILES) - zip -q $@ $^ +%.xpi: \ + manifest.json \ + README.md LICENSE COPYRIGHT \ + misc/icon.png \ + options/ \ + scripts/ \ + hljs/README.md hljs/LICENSE \ + hljs/highlight.pack.js \ + hljs/styles/ + zip -q -r $@ $^ -test: - @echo 'Wait, it does not work! :(' - @#java -cp test-framework/js.jar org.mozilla.javascript.tools.shell.Main \ - #-version 170 \ - #-debug test-framework/main.js \ - #--test-directory chrome/content/tests/ +JAR=/tmp/colorediffs.cookie +HLJS_DL='https://highlightjs.org/download/' +HLJS_ZIP=/tmp/highlight.zip +HLJS_SCRIPT=hljs/highlight.pack.js +HLJS_STYLES=hljs/styles/ -clean: - rm -f $(ADDON)-*.xpi +$(HLJS_SCRIPT): + TOKEN=$$(curl -sL --cookie-jar "$(JAR)" $(HLJS_DL) | sed -n '/csrfmiddlewaretoken/ s/.*value="\([^"]\+\)".*/\1/p') && \ + curl -sL --referer $(HLJS_DL) --cookie "$(JAR)" --request POST --data-urlencode "csrfmiddlewaretoken=$$TOKEN" --data-urlencode 'diff.js=on' $(HLJS_DL) --output "$(HLJS_ZIP)" + unzip -q -o "$(HLJS_ZIP)" -d hljs + rm -- "$(JAR)" "$(HLJS_ZIP)" + @VERSION=$$(sed -n 's/.*versionString="\([^\"]\+\)".*/\1/p' $@) && printf "\e[;32m[NOTE] Successfully downloaded highlight.js version $$VERSION.\e[0m\n" + +$(HLJS_STYLES) hljs/README.md hljs/LICENSE: $(HLJS_SCRIPT) + +check_css_list: $(HLJS_STYLES) + @printf 'Checking that all CSS styles from highlight.js are listed in the add-on...\t' + @error=0; for stylepath in $(HLJS_STYLES)*.css; do \ + stylesheet=$$(basename $$stylepath); \ + stylename=$${stylesheet%.css}; \ + if ! grep -q $$stylename options/fill-css-list.js; then \ + [ $$error = 0 ] && printf '\n'; \ + printf '\e[1;31m[WARNING] Found missing CSS style: "%s".\e[0m\n' $$stylename; \ + error=1; \ + fi; \ + done; [ $$error = 0 ] && printf 'All good.\n' || false + +clean-hljs: + rm -rf -- hljs + +clean: clean-hljs + rm -f -- $(ADDON) + +.PHONY: xpi clean clean-hljs check_css_list diff --git a/README.md b/README.md index c138021..2e8a157 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,64 @@ -# ![Add-on icon](chrome/content/icon.png) Colored diffs +# ![Add-on icon](misc/icon.png) Colorediffs -This is an extension that colors boring diffs sent by notifiers for Git, -Subversion, CVS, Mercurial, etc. - -The project was originally authored by Vadim Atlygin, and for a time it has -been maintained by Jesse Glick (jglick). Now the torch has passed to Qeole. - -## Presentation - -A lot of developers use Git or other version control systems. Most of them also -receive special notifications from the system about changes other people do. -They might be useful in various ways: someone wants to check if there are bugs -in the new code, someone just wants to keep knowledge of code base up-to-date. -But looking over that black-on-white letter is so boring. That's why we decided -to color it up a bit. +Color diffs in the emails you receive. This is typically helpful for reviewing +patches formatted with Git or other version control systems. ## Installation -### Get it from Mozilla add-ons platform… +### Get it from Thunderbird's Add-Ons Platform… -[Available here](https://addons.mozilla.org/en-US/thunderbird/addon/colored-diffs/) +[Available here](https://addons.thunderbird.net/en-US/thunderbird/addon/colored-diffs/) -### … Or install it manually +### … Or Install it Manually -[Install locally](https://developer.mozilla.org/en-US/Add-ons/Thunderbird/Building_a_Thunderbird_extension_7:_Installation) -or -[package it into a .xpi file](https://developer.mozilla.org/en-US/Add-ons/Thunderbird/Building_a_Thunderbird_extension_8:_packaging). +Pack the add-on as an .xpi file and install it from the “gear” menu in +Thunderbird's add-on manager. -Under UNIX-like systems you can create the .xpi file by simply running: +On UNIX-like systems, you can create the .xpi file by simply running: $ cd /path/to/colorediffs/ $ make -## What the add-on can do - -![Transformation](misc/transformation.png) - -Well, not so much… It can color your diffs, it can show them in side-by-side -mode if you like. Also it converts all the filenames in message log into links -so you can quickly jump to the file you want to review. Ah, and it can make -space and tabs chars visible. You can look through list of SupportedFormats to -see what are the supported VCSs. - -## Ideas for future contribution - -The original author of the project wanted this add-on to include the following -features: +Note that this will download (with curl) the latest version of the highlight.js +library, which is not included in this repository. - 1. Highlighting the actual difference between the lines. - 2. Coloring the syntax of the language used. - 3. Checking new code against few rules in order to estimate the quality of - it. +## Usage -But these are pretty big tasks, and development of the add-on is not so active -by now. If you want to help, though, do not hesitate to contribute by dropping -an issue or a PR! +Once installed, the add-on should automatically detect diffs in your plain-text +messages and color them with the selected theme. Some options are available in +the add-on preference page: -## Contribution +- You can select the color scheme amongst all the styles supported by + highlight.js. +- You can have tabs and white space characters replaced by visible characters. +- You can set the length for tab characters (defaults to 8). +- You can choose to color all plain-text messages (even with no diffs), which is + mostly useful to avoid visual discomfort when using a style with a dark + background and browsing a mailing list. -If you know how to do something better, whether it's code, icons, default color -scheme, just contact me. Also please visit the [issues -list](https://github.com/jglick/colorediffs/issues) and comment on issues you'd -really like to be done first. +## Versions -## Firefox extension +**Version 2+ of the add-on is compatible with Thunderbird stable version 78.4 +and onward.** It uses the `messageDisplayScripts` API which was added in +Thunderbird 82, and backported to 78.4. -There isn't one and probably never will be, sorry. I could make it work for -diff in `
` sections (most mail lists archives format them like this) but
-it would never work in GMail and other Web mail systems where it would be
-actually useful. It's just plain hard to find the code between the lines of
-normal text. But you could ease you pain with [Bookmarklet](Bookmarklet.md) I
-wrote when your needs is simple (like mail list archives) and [GreaseMonkey
-script](http://userscripts.org/scripts/show/26684) written by Fabrice
-Bellingard for GMail. Thanks for understanding.
+Older versions of the add-on work with Thunderbird
+[up to the version 68](https://github.com/Qeole/colorediffs/tree/e51d1aab6390d11a5ee2ec84e1cf42fd08564a41#version-notes).
 
-## Version notes
+The distinction is due to Thunderbird's move to MailExtensions. As a
+consequence of this change, version 2.0.0 of the add-on is a complete rewrite
+(by Qeole) and works differently from the previous versions. Instead of parsing
+the diffs and rebuilding the messages itself, the add-on embeds and injects the
+[highlight.js library](https://highlightjs.org/) which takes care of the
+colors, without reformatting the content of the message.
 
-* Version 0.5 is the last one with support for Thunderbird 2. I'm too tired of
-  more than two years old JavaScript engine.
+## Status
 
-* Version 0.7 does not support SeaMonkey anymore, because the new maintainer
-  does not use it and does not wish to test add-on compatibility. But the code
-  has not changed much, and this is probably just a matter of enabling support
-  again in install.rdf file. So if some tech-savvy SeaMonkey user tests and
-  confirms compatibility, I will enable it again.
+The project was originally authored by Vadim Atlygin. For a time it has
+been maintained by Jesse Glick (jglick), and it has now passed to Qeole.
 
-* Versions 0.8 and 0.9 should work with Thunderbird 60 (although I did not
-  manage to fix preferences settings, so it comes with fixed color choices,
-  unless users resort to the config editor). Later Thunderbird versions are
-  expected to deprecate XUL-based extensions, which means the add-on will not
-  remain compatible unless it undergoes major rework.
+This add-on is mostly in maintenance mode, do not expect new features. Several
+people are using it to review patches for their daily jobs, so the objective is
+essentially to keep something basic, but that works.
 
-* Version 1.9 works with Thunderbird 68. Note that the preference menu remains
-  completely broken (I even removed the button to display it), therefore you'll
-  have to work with the default options or to use Thunderbird's config editor.
-  This version has not been tested on older Thunderbird versions. Conversely,
-  version 0.9 and earlier of the add-on cannot be loaded on Thunderbird 68+.
+Nonetheless, you are welcome to report issues or to submit pull requests.
diff --git a/chrome.manifest b/chrome.manifest
deleted file mode 100644
index 4ea570c..0000000
--- a/chrome.manifest
+++ /dev/null
@@ -1,6 +0,0 @@
-content colorediffs                     chrome/content/
-
-skin    colorediffs     classic/1.0     chrome/skin/
-
-overlay chrome://messenger/content/messenger.xul        chrome://colorediffs/content/messengerOverlay.xul
-overlay chrome://messenger/content/messageWindow.xul    chrome://colorediffs/content/messageWindowOverlay.xul
diff --git a/chrome/content/bindings/bindings.css b/chrome/content/bindings/bindings.css
deleted file mode 100644
index f7a97d9..0000000
--- a/chrome/content/bindings/bindings.css
+++ /dev/null
@@ -1,7 +0,0 @@
-viewsmenu {
-	-moz-binding: url('chrome://colorediffs/content/bindings/main-bindings.xbl#views');
-}
-
-colorpicker {
-	-moz-binding: url('chrome://colorediffs/content/bindings/main-bindings.xbl#color-picker');
-}
diff --git a/chrome/content/bindings/colorpicker.css b/chrome/content/bindings/colorpicker.css
deleted file mode 100644
index 386b760..0000000
--- a/chrome/content/bindings/colorpicker.css
+++ /dev/null
@@ -1,9 +0,0 @@
-.colorpickerspacer {
-	height: 12px;
-	border: 1px inset #CCCCCC;
-}
-
-.colorpickerbutton {
-	min-width: 60px;
-	margin: 1px;
-}
diff --git a/chrome/content/bindings/main-bindings.xbl b/chrome/content/bindings/main-bindings.xbl
deleted file mode 100644
index 9cbc448..0000000
--- a/chrome/content/bindings/main-bindings.xbl
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-	
-		
-			
-				
-					
-				
-			
-		
-		
-			
-			
-				
-				
-			
-			
-				
-				
-			
-		
-	
-
-	
-		
-			
-		
-		
-			
-				
-			
-		
-		
-			
-			
-				
-				
-			
-			
-				
-			
-		
-		
-			
-		
-	
-
diff --git a/chrome/content/callbacks.js b/chrome/content/callbacks.js
deleted file mode 100644
index dfc164a..0000000
--- a/chrome/content/callbacks.js
+++ /dev/null
@@ -1,38 +0,0 @@
-if (!colorediffsGlobal) {
-    var colorediffsGlobal = {};
-}
-
-colorediffsGlobal.tooltipCallback = function(element) {
-    var me = colorediffsGlobal;
-
-    function getTooltip() {
-	var elem = element;
-
-	while( elem && elem.nodeName.toLowerCase() != "body" && elem.nodeName.toLowerCase() != "browser" && elem.nodeName.toLowerCase() != "html" && (elem.getAttribute('title') == null || elem.getAttribute('title') == "")) {
-	    elem = elem.parentNode;
-	}
-	return (elem != null)?elem.getAttribute('title'):null;
-    };
-
-    if ( me.isActive() ) {
-	var title = getTooltip();
-	if (title == "") {
-	    title = null;
-	}
-
-	me.$("colorediff-tooltip").value = title;
-	return title != null;
-    } else {
-	return false;
-    }
-
-};
-
-colorediffsGlobal.scrollCallback = function(evt) {
-    var ourclass = evt.target.getAttribute('class');
-    var opositeClass = (ourclass == "left")?"right":"left";
-
-    var otherSide = evt.target.parentNode.parentNode.getElementsByClassName(opositeClass)[0];
-    otherSide.scrollLeft = evt.target.scrollLeft;
-};
-
diff --git a/chrome/content/colorediffs.js b/chrome/content/colorediffs.js
deleted file mode 100644
index 6d43493..0000000
--- a/chrome/content/colorediffs.js
+++ /dev/null
@@ -1,157 +0,0 @@
-if (!colorediffsGlobal) {
-	var colorediffsGlobal = {};
-}
-
-colorediffsGlobal.isMessageDiff = function() {
-	var content = this.getMessagePane();
-	if (!content) {
-		return false;
-	}
-
-	var messagePrefix = /^mailbox-message:|^imap-message:|^news-message:|^exquilla-message:/i;
-	if (! gMessageDisplay.folderDisplay.selectedMessageUris ||
-			! messagePrefix.test(
-				gMessageDisplay.folderDisplay.selectedMessageUris[0]) ) {
-		return false;
-	}
-
-	var message = content.contentDocument;
-	var body = message.body;
-
-	if ( !body ) {
-		return false;
-	}
-
-	var text = colorediffsGlobal.htmlToPlainText(body.innerHTML);
-
-	for (var parserName in colorediffsGlobal.parsers) {
-		if (colorediffsGlobal.parsers[parserName].couldParse(text)) {
-			return true;
-		}
-	}
-	return false;
-};
-
-colorediffsGlobal.writeDebugFile = function(filename, html, pref) {
-	if (pref.debugDir.has()) {
-		var debugDir = pref.debugDir.get();
-		if ( debugDir != "" ) {
-			var file = Components.classes["@mozilla.org/file/local;1"]
-				.createInstance(Components.interfaces.nsILocalFile);
-			file.initWithPath(debugDir);
-			file.append(filename);
-
-			var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
-				.createInstance(Components.interfaces.nsIFileOutputStream);
-
-			foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
-			foStream.write(html, html.length);
-			foStream.close();
-		}
-	}
-};
-
-colorediffsGlobal.onLoadMessage = function() {
-	var me = colorediffsGlobal;
-
-	var message = me.getMessagePane().contentDocument;
-	var pref = new colorediffsGlobal.Pref(colorediffsGlobal.getPrefs());
-	me.writeDebugFile("before1.html", message.documentElement.innerHTML, pref);
-
-	if (!me.isMessageDiff()) {
-	me.setActive(false);
-	me.colorediffsToolbar.initToolbar();
-	return;
-	}
-
-	me.setActive(true);
-	me.colorediffsToolbar.initToolbar();
-
-	//don't do anything if user wants plain
-	if (pref.mode.get() == 'none') {
-	return;
-	}
-
-	var body = message.body;
-
-	me.writeDebugFile("before.html", message.documentElement.innerHTML, pref);
-
-	var divs = body.getElementsByTagName("div");
-	if ( !divs ) {
-		return;
-	}
-
-	var reloadPlanned = false;
-
-	var text = me.fold(divs, function(div, text) {
-		switch(div.getAttribute("class")) {
-			case "moz-text-plain":
-			case "moz-text-flowed":
-				return text + colorediffsGlobal.stripHtml(div) + "\n\n\n";
-			case "moz-text-html":
-				//that means we're looking at HTML part of multipart mail
-				//Check if we're after reload
-				if (colorediffsGlobal.restorePreferHtmlTo === undefined) {
-					//Will try to make Thunderird reload it with text part in use.
-					colorediffsGlobal.restorePreferHtmlTo = pref.preferHtml.get();
-					pref.preferHtml.set(true);
-					ReloadMessage();
-					reloadPlanned = true;
-				} else { //ok, reloading was bad idea, but maybe html part is only log and diff is actually in attached file
-					// so let's give it a try
-					return text + colorediffsGlobal.stripHtml(div) + "\n\n\n";
-				}
-			default:
-				return text;
-		}
-	}, "");
-
-	if (reloadPlanned) {
-		return; //got to reload
-	}
-
-	//Should do it here so we can check whether we planned reload or not in the code that actually plan it.
-	if (colorediffsGlobal.restorePreferHtmlTo !== undefined) {
-		pref.preferHtml.set(colorediffsGlobal.restorePreferHtmlTo);
-		delete colorediffsGlobal.restorePreferHtmlTo;
-	}
-
-	//no luck finding a moz styled divs,
-	//	let's try just stripping all html out
-	if (text == "") {
-		text = colorediffsGlobal.stripHtml(body);
-	}
-
-	me.writeDebugFile("text.html", text, pref);
-	//Choose parser
-	var il = colorediffsGlobal.parse(text, pref);
-	if (il == null) {
-		me.setActive(false);
-		me.colorediffsToolbar.initToolbar();
-		return;
-	}
-
-	//Apply filters
-	il = colorediffsGlobal.transform(il, pref);
-
-	var dom = new colorediffsGlobal.domHelper(message);
-
-	//Generate view
-	var renderedStyleBody = colorediffsGlobal.render(il, pref, dom);
-
-	var head = message.getElementsByTagName("head")[0];
-	head.appendChild(renderedStyleBody[0]);
-
-	body.innerHTML = "";
-	body.appendChild(renderedStyleBody[1]);
-
-	me.writeDebugFile("after.html", message.documentElement.innerHTML, pref);
-
-
-	//inner functions
-	//Strip 



tags from every div - function stripThunderbirdGeneratedHtml(html) { - return html.replace(/^
(?:
(?:
|
.*?<\/fieldset>)
)?((?:.|\n)*)<\/pre>$/i, "$1"); - } -}; - diff --git a/chrome/content/dom.js b/chrome/content/dom.js deleted file mode 100644 index e80e419..0000000 --- a/chrome/content/dom.js +++ /dev/null @@ -1,44 +0,0 @@ -colorediffsGlobal.domHelper = function(doc) { - - function addElements(element, start_index, array) { - var length = array.length; - for(var i = start_index; i < length; ++i) { - var arg = array[i]; - switch(typeof arg) { - case "string": - element.innerHTML += arg; - break; - case "object": - if (arg instanceof Element) { - element.appendChild(arg); - } else if (arg instanceof Array) { - arguments.callee(element, 0, arg); - } - break; - case "function": - var res = arg(); - if ( res != null ) { - element.appendChild(res); - } - break; - } - } - } - - this.createElement = function(tag, attributes) { - var element = doc.createElement(tag); - for (var attribute in attributes) { - element.setAttribute(attribute, attributes[attribute]); - } - - addElements(element, 2, arguments); - - return element; - }; - - this.createDocumentFragment = function() { - var element = doc.createDocumentFragment(); - addElements(element, 0, arguments); - return element; - }; -}; diff --git a/chrome/content/globals.js b/chrome/content/globals.js deleted file mode 100644 index e2e411c..0000000 --- a/chrome/content/globals.js +++ /dev/null @@ -1,154 +0,0 @@ -if (!colorediffsGlobal) { - var colorediffsGlobal = { - parsers:{}, - transformations:{}, - views:{} - }; -} - -colorediffsGlobal.$ = function(id) { - return document.getElementById(id); -}; - -colorediffsGlobal.$R = function(f) { - return f(); -}; - -colorediffsGlobal.getMessagePane = function() { - if (!this.gmessagePane) { - this.gmessagePane = this.$("messagepane"); - } - - return this.gmessagePane; -}; - -colorediffsGlobal.isActive = function(m) { - var node = colorediffsGlobal.$("colorediff-mode"); - if ( node ) { - return node.value; - } else { - return false; - } -}; - -colorediffsGlobal.setActive = function(m) { - colorediffsGlobal.$("colorediff-mode").value = m; -}; - -colorediffsGlobal.pad = function(string, l, s) { - if (!s) s = " "; - - if ( string.length < l ) { - var padding = new Array(Math.ceil((l - string.length)/s.length) + 1); - - return string.concat(padding.join(s)); - } else { - return string; - } -}; - -colorediffsGlobal.isUpperCaseLetter = function(c) { - return /^[A-Z]$/.test(c); -}; - -colorediffsGlobal.getPrefs = function() { - return Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch); -}; - -colorediffsGlobal.htmlToPlainText = function(html) { - var texts = html.split(/(<\/?[^>]+>)/); - var text = texts.map(function(string) { - if (string.length > 0 && string[0] == '<') { - //replace smileys - var regExpRes = string.match(/^"; - case "quot": return '"'; - } - return " "; - }); - } - }).join(""); - - return text; -}; - -colorediffsGlobal.outerHtml = function(dom_node, innerHtml) { - var html = "<" + dom_node.tagName; - if (dom_node.hasAttributes()) { - var attributes = dom_node.attributes; - var attributes_length = attributes.length; - for (let i = 0; i < attributes_length; i++) { - var attribute = attributes[i]; - html += ' ' + attribute.name + '="' + attribute.value.replace('"', '\\"') + '"'; - } - } - html += '>' + innerHtml + ''; - return html; -}; - -colorediffsGlobal.stripHtml = function(dom_node) { - if (dom_node.nodeName == "#text") { - return colorediffsGlobal.escapeHTML(dom_node.textContent); - } else if (dom_node.nodeType == 1) { - if (dom_node.tagName == "BLOCKQUOTE") { - return dom_node.outerHTML; - } - var klass = dom_node.getAttribute("class"); - var text = colorediffsGlobal.stripHtmlList(dom_node.childNodes); - if (klass != null && (klass.indexOf('moz-txt-link') == 0 || klass.indexOf('moz-smiley') == 0)) { - text = colorediffsGlobal.outerHtml(dom_node, text); - } - return text; - } else { - //it's not a real dom node, comment of something similar. - // better to ignore it - return ""; - } -}; - -colorediffsGlobal.stripHtmlList = function(dom_node_list) { - var text = ""; - var nodes_length = dom_node_list.length; - for (let i = 0; i < nodes_length; i++) { - text += colorediffsGlobal.stripHtml(dom_node_list[i]); - } - return text; -}; - -colorediffsGlobal.escapeHTML = function(text) { - text = text.replace(/&/g, "&"); - text = text.replace(//g, ">"); - text = text.replace(/"/g, """); - return text; -}; - -colorediffsGlobal.unescapeHTML = function(text) { - text = text.replace(/&/g, "&"); - text = text.replace(/</g, "<"); - text = text.replace(/>/g, ">"); - text = text.replace(/"/g, '"'); - text = text.replace(/ /g, ' '); - return text; -}; - -colorediffsGlobal.fold = function(a, fun, o) { - var l = a.length; - for (var i=0; i < l; ++i) { - o = fun(a[i], o); - } - return o; -}; - diff --git a/chrome/content/ilUtils.js b/chrome/content/ilUtils.js deleted file mode 100644 index 3de8de5..0000000 --- a/chrome/content/ilUtils.js +++ /dev/null @@ -1,28 +0,0 @@ -colorediffsGlobal.ilUtils = { - chunksMap: function (file, func) { - var result = []; - var l = 0; - if ( file['new'] && file['new'].chunks ) { - l = Math.max(l, file['new'].chunks.length); - } else if ( file['old'] && file['old'].chunks ) { - l = Math.max(l, file['old'].chunks.length); - } - - for (var i = 0; i < l; i++) { - var old_chunk = null; - if ( file['old'] && file['old'].chunks && file['old'].chunks[i] ) { - old_chunk = file['old'].chunks[i]; - } - - var new_chunk = null; - if ( file['new'] && file['new'].chunks && file['new'].chunks[i] ) { - new_chunk = file['new'].chunks[i]; - } - - - result.push(func(old_chunk, new_chunk)); - } - - return result; - } -}; diff --git a/chrome/content/main-overlay.xul b/chrome/content/main-overlay.xul deleted file mode 100644 index 4274500..0000000 --- a/chrome/content/main-overlay.xul +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - + + + + + + + + diff --git a/scripts/background.js b/scripts/background.js new file mode 100644 index 0000000..e0cb929 --- /dev/null +++ b/scripts/background.js @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: MPL-2.0 */ + +var options = {}; +var wantColors = true; + +var port; +var scriptsPromise; +var cssPromise; + +function reloadOption(id) { + return browser.storage.local.get(id).then((res) => { + if (res[id] != undefined) + options[id] = res[id]; + else + options[id] = DefaultOptions[id]; + }, defaultError); +} + +async function reloadAllOptions() { + await reloadOption("style"); + await reloadOption("tabsize"); + await reloadOption("spaces"); + await reloadOption("colorall"); +} + +function processCommand(msg) { + switch (msg.command) { + case "toggleSpaces": + options.spaces = !options.spaces; + unregisterScripts().then(registerScripts); + break; + case "toggleColor": + wantColors = !wantColors; + unregisterScripts().then(registerScripts); + if (wantColors) + registerCss(); + else + unregisterCss(); + break; + default: + break; + } +} + +function registerCss() { + cssPromise = browser.messageDisplayScripts.register({ + css: [{ + file: "/hljs/styles/" + options.style + ".css", + }], + }); +} + +function registerScripts() { + let contentScripts = { + js: [ + { + code: "var options = " + JSON.stringify(options) + ";", + }, + ], + }; + if (wantColors) { + contentScripts.js.push( + { + file: "/hljs/highlight.pack.js", + }, + { + file: "/scripts/transformations.js", + }, + { + file: "/scripts/coloring.js", + }, + ); + } + contentScripts.js.push( + { + file: "/scripts/content.js", + }, + ); + scriptsPromise = browser.messageDisplayScripts.register(contentScripts); +} + +async function unregisterCss() { + await cssPromise.then(css => css.unregister()); +} + +async function unregisterScripts() { + await scriptsPromise.then(script => script.unregister()); +} + +async function reset() { + await unregisterScripts(); + await unregisterCss(); + await init(); +} + +function init() { + reloadAllOptions().then(registerCss).then(registerScripts); +} + +browser.storage.onChanged.addListener(reset); +browser.runtime.onConnect.addListener((p) => { + port = p; + port.onMessage.addListener(processCommand); +}); + +init(); diff --git a/scripts/coloring.js b/scripts/coloring.js new file mode 100644 index 0000000..2845d4c --- /dev/null +++ b/scripts/coloring.js @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: MPL-2.0 */ + +function colorizeBody() { + document.body.setAttribute("class", "hljs"); +} + +function colorizeDiff() { + /* + * With plain text, content is under
 nodes. We want to insert 
+     * nodes with .hljs class between these 
 node and their children.
+     */
+    let preNodes = document.querySelectorAll("body > div > pre"); 
+    for (let pre of preNodes) {
+        let code = document.createElement("CODE");
+
+        code.style.padding = "0";
+        setTabStyle(code, options.tabsize);
+        code.setAttribute("class", "hljs diff");
+
+        while (pre.childNodes.length > 0)
+            code.appendChild(pre.childNodes[0]);
+        pre.appendChild(code);
+    }
+
+    /* Call library function, trigger highlighting */
+    hljs.initHighlighting();
+
+    /* Git-send signature is considered as a deletion, patch it */
+    patchGitSendSignature(preNodes[preNodes.length - 1].lastChild.lastChild);
+    /* Replace spaces and tabs if required */
+    if (options.spaces) {
+        for (let pre of preNodes)
+            replaceSpaces(pre.firstChild, options.tabsize);
+    } else if (document.getElementsByClassName("cd-s").length) {
+            restoreSpaces();
+    }
+}
diff --git a/scripts/content.js b/scripts/content.js
new file mode 100644
index 0000000..44272a5
--- /dev/null
+++ b/scripts/content.js
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: MPL-2.0 */
+
+var port;
+
+function connectPort() {
+    if (port)
+        return;
+    port = browser.runtime.connect({
+        name: "contentToBackground",
+    });
+}
+
+function addToolbar() {
+    let div = document.getElementById("colorediffsToolbar");
+    if (div)
+        return;
+
+    let toolbarHeight = "5ex";
+    document.body.childNodes[1].style.margin = "8px 8px calc(" + toolbarHeight + " + 8px) 8px";
+    document.body.style.margin = "0";
+    document.body.style.padding = "0";
+
+    div = document.createElement("DIV");
+    div.id = "colorediffsToolbar";
+
+    colorButton = document.createElement("BUTTON");
+    spaceButton = document.createElement("BUTTON");
+    colorButton.id = "toggleColor";
+    spaceButton.id = "toggleSpaces";
+    colorButton.className = "toolbarButton";
+    spaceButton.className = "toolbarButton";
+    colorButton.innerHTML = "+-";
+    spaceButton.textContent = "›·";
+    div.appendChild(colorButton);
+    div.appendChild(spaceButton);
+
+    let toolbarCss = `
+    #colorediffsToolbar {
+        position: fixed;
+        bottom: 0;
+        right: 0;
+        height: ` + toolbarHeight + `;
+        margin: 0;
+        opacity: .3;
+        transition: opacity .2s;
+    }
+    #colorediffsToolbar:hover {
+        opacity: 1;
+    }
+    .toolbarButton {
+        padding: 0;
+        margin-right: 8px;
+        font-weight: 800;
+        min-width: 3em;
+    }`;
+    let style = document.createElement("style");
+    style.setAttribute("type", "text/css");
+    let styleTextNode = document.createTextNode(toolbarCss);
+    style.appendChild(styleTextNode);
+    document.head.appendChild(style);
+
+    document.body.appendChild(div);
+
+    document.getElementById("toggleSpaces").addEventListener("click", () => {
+        toggleSpaces();
+        connectPort();
+        port.postMessage({
+            command: "toggleSpaces",
+        });
+    });
+    document.getElementById("toggleColor").addEventListener("click", () => {
+        connectPort();
+        port.postMessage({
+            command: "toggleColor",
+        });
+    });
+}
+
+/* Check that this is a plain text email. We don't do HTML. */
+function isPlainText() {
+    let bodyNodes = document.body.childNodes;
+    let bodyLength = bodyNodes.length;
+    if (!(bodyLength == 3 ||
+          (bodyLength == 4 && bodyNodes[bodyLength - 1].id == "colorediffsToolbar")))
+        return false;
+    if (bodyNodes[1].tagName != "DIV")
+        return false;
+    let firstChildClass = bodyNodes[1].className;
+    if (firstChildClass != "moz-text-plain" && firstChildClass != "moz-text-flowed")
+        return false;
+
+    return true;
+}
+
+/* Check whether this message contains diff snippets */
+function hasDiff() {
+    /* These patterns were kept from the old version of the add-on */
+    let contextLineTag = /^(?:\*|\-){3}\s+(\d+)\,\d+\s+(?:\*|\-){4}$/m;
+    let unifiedLineTag = /^@@\s+\-\d+(?:\,\d+)?\s\+\d+(?:\,\d+)?\s+@@/m;
+    let unifiedNewTag = /^--- NEW FILE:\s.* ---$/m;
+    let unifiedBinaryTag = /^---\s(?:new\s)?BINARY FILE:\s.*\s---$/m;
+    let textBody = document.body.textContent;
+
+    for (tag of
+         [contextLineTag, unifiedLineTag, unifiedNewTag, unifiedBinaryTag]) {
+        if (tag.test(textBody))
+            return true;
+    }
+
+    return false;
+}
+
+/* Trigger checks, toolbar, and coloring */
+(function () {
+    if (!isPlainText())
+        return;
+
+    /* Do not colorize twice, but do update toolbar color */
+    if (document.getElementsByClassName("hljs").length)
+        return addToolbar();
+
+    /* Check whether coloring code was injected by background script */
+    let doColors = typeof(colorizeDiff) != 'undefined';
+
+    /*
+     * If options.colorall is enabled, color as fast as we can to avoid white
+     * flashes with dark backgrounds
+     */
+    if (options.colorall && doColors)
+        colorizeBody();
+
+    let msgWithDiff = hasDiff();
+    if (!options.colorall && msgWithDiff && doColors)
+        colorizeBody();
+
+    if (options.colorall || msgWithDiff)
+        addToolbar();
+
+    if (msgWithDiff && doColors)
+        colorizeDiff();
+})();
diff --git a/scripts/transformations.js b/scripts/transformations.js
new file mode 100644
index 0000000..6d9c469
--- /dev/null
+++ b/scripts/transformations.js
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: MPL-2.0 */
+
+function setTabStyle(node, tabsize) {
+    if (!tabsize)
+        return;
+
+    node.style["-moz-tab-size"] = tabsize;
+}
+
+function replaceSpaces(root, tabsize) {
+    function replacer(match, spaces, offset, string) {
+        /* Space on first column, likely generated for the diff, abort */
+        if (match == " " && (offset == 0 || string[offset - 1] == '\n'))
+            return " ";
+
+        let content = "";
+        if (match[0] == '\t') {
+            content += "›";
+            let lastNewLine = string.slice(0, offset).lastIndexOf("\n");
+            let lastTab = string.slice(0, offset).lastIndexOf("\t");
+            let offsetInLine = offset - Math.max(lastNewLine + 1, lastTab + 1);
+            if (offsetInLine % tabsize != tabsize - 1)
+                content += "\t";
+            content += "›\t".repeat(match.length - 1);
+        } else {
+            content += (offset == 0 || string[offset - 1] == '\n' ? " " : "·")
+            content += "·".repeat(match.length - 1);
+        }
+        content += "";
+        return content;
+    };
+
+    function doReplace(node) {
+        node.innerHTML = node.textContent.replace(/( +|\t+)/g, replacer);
+    };
+
+    function spanify(node) {
+        let span = document.createElement("SPAN");
+        span.className = 'cd-s';
+        span.textContent = node.textContent;
+        node.parentNode.insertBefore(span, node);
+        node.remove();
+        return span;
+    };
+
+    function processNeighbour(neighbour) {
+        if (neighbour && neighbour.nodeType == Node.TEXT_NODE)
+            doReplace(spanify(neighbour));
+    };
+
+    function processSpan(span) {
+        doReplace(span);
+
+        /* For context: replace in closest siblings if they are text nodes */
+        processNeighbour(span.previousSibling);
+        processNeighbour(span.nextSibling);
+    };
+
+    let addSpans = root.getElementsByClassName("hljs-addition");
+    for (let span of addSpans)
+        processSpan(span);
+
+    let delSpans = root.getElementsByClassName("hljs-deletion");
+    for (let span of delSpans)
+        processSpan(span);
+}
+
+function restoreSpaces() {
+    let spans = document.getElementsByClassName("cd-s");
+    while (spans.length) {
+        let span = spans[0];
+        let content = span.textContent.replace(/›\t?/g, '\t').replace(/·/g, ' ');
+        let spaces = document.createTextNode(content);
+        span.parentNode.insertBefore(spaces, span);
+        span.remove();
+    }
+}
+
+function toggleSpaces(pre) {
+    if (document.getElementsByClassName("cd-s").length) {
+        restoreSpaces();
+    } else {
+        for (let pre of document.querySelectorAll("body > div > pre"))
+            replaceSpaces(pre.firstChild, options.tabsize);
+    }
+}
+
+/* Do not consider signature marker as a deleted line */
+function patchGitSendSignature(node) {
+    if (node.className != "moz-txt-sig")
+        return;
+    if (node.childNodes.length != 2)
+        return;
+
+    let marker = node.childNodes[0];
+    let version = node.childNodes[1];
+    let versionPattern = /^\n\d+\.\d+\.\S+\n+$/;
+
+    if (marker.textContent != "-- ")
+        return;
+    if (!versionPattern.test(version.textContent))
+        return;
+
+    marker.className = "hljs-comment";
+    marker.textContent += version.textContent;
+    version.remove();
+}
+
+/* Wait for the hljs object from the highlight.js script to be loaded */
+function waitForHljs() {
+    return new Promise((resolve, reject) => {
+        (function pollHljs() {
+            if (typeof(hljs) !== 'undefined')
+                return resolve();
+            setTimeout(pollHljs, 10);
+        })();
+    });
+}