diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..075d642 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.lein-repl-history +target +.repl +out +.nrepl-port diff --git a/README.md b/README.md index df73c65..19101f0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ # Clojurifier A Chrome extension that replaces occurrences of the words "scala" and "haskell" to "Clojure" in web pages text nodes. + +## how to build +``` +lein chromebuild once +``` +_It requires JDK 1.7+_ +The unpacked extension will be in target/unpacked + +###[how to install a Chrome extension](http://superuser.com/questions/247651/how-does-one-install-an-extension-for-chrome-browser-from-the-local-file-system/247654#247654) diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..0dcbe01 --- /dev/null +++ b/project.clj @@ -0,0 +1,23 @@ +(defproject clojurifier "0.2.0-SNAPSHOT" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies [[org.clojure/clojure "1.5.1"] + [org.clojure/clojurescript "0.0-2156"] + [org.clojure/core.async "0.1.242.0-44b1e3-alpha"] + [khroma "0.0.2"] + [prismatic/dommy "0.1.2"]] + :source-paths ["src"] + :profiles {:dev {:plugins [[com.cemerick/austin "0.1.3"] + [lein-cljsbuild "1.0.1"] + [lein-resource "0.3.6"] + [lein-chromebuild "0.1.0"]] + :cljsbuild {:builds {:main + {:source-paths ["src"] + :compiler {:output-to "target/unpacked/clojurifier.js" + :output-dir "target/js" + :optimizations :whitespace + :pretty-print true}}}} + :chromebuild {:resource-paths ["resources/js" "resources/html" "resources/images"] + :target-path "target/unpacked"} + } + }) diff --git a/resources/html/popup.html b/resources/html/popup.html new file mode 100644 index 0000000..e69de29 diff --git a/resources/images/icon128.png b/resources/images/icon128.png new file mode 100644 index 0000000..6070217 Binary files /dev/null and b/resources/images/icon128.png differ diff --git a/resources/images/icon16.png b/resources/images/icon16.png new file mode 100644 index 0000000..4d0af37 Binary files /dev/null and b/resources/images/icon16.png differ diff --git a/resources/images/icon48.png b/resources/images/icon48.png new file mode 100644 index 0000000..d4f0dcf Binary files /dev/null and b/resources/images/icon48.png differ diff --git a/resources/js/background.js b/resources/js/background.js new file mode 100644 index 0000000..7158f1c --- /dev/null +++ b/resources/js/background.js @@ -0,0 +1 @@ +clojurifier.background.init(); diff --git a/resources/js/content.js b/resources/js/content.js new file mode 100644 index 0000000..a01ee14 --- /dev/null +++ b/resources/js/content.js @@ -0,0 +1 @@ +clojurifier.content.init(); diff --git a/resources/js/manifest.json b/resources/js/manifest.json new file mode 100644 index 0000000..6b25bf1 --- /dev/null +++ b/resources/js/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "Clojurifier", + "version": "0.2", + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "content_scripts": [ + { + "matches": ["*://*/*"], + "js": ["clojurifier.js", "content.js"], + "run_at": "document_end" + } + ], + "manifest_version": 2 +} + diff --git a/src/clojurifier/clojurifier.cljs b/src/clojurifier/clojurifier.cljs new file mode 100644 index 0000000..c154f5d --- /dev/null +++ b/src/clojurifier/clojurifier.cljs @@ -0,0 +1,33 @@ +(ns clojurifier.clojurifier + (:require [clojure.string :as s] + [khroma.log :as console])) + + +; the strings we want to replace +(def mapping {(js/RegExp. "haskell" "i") "Clojure" + (js/RegExp. "scala" "i") "Clojure"}) + + +(defn clojurize + "Replaces all matched strings by 'mapping' in the element" + [element] + (doseq [[k v] mapping] + (set! (.-textContent element) (.replace (.-textContent element) k v)))) + + +(defn- clojurable? + "Decides if we want to try finding matching strings in the element. + Script and textarea elements are out of our scope." + [node] + (and (= (.-nodeType node) 1) (not (#{"script" "textarea"} (.-nodeName node))))) + + +(defn clojurify + "Replaces all matched strings by 'mapping' in the element and its children if the element is a text element" + [element] + (let [nn (s/lower-case (.-nodeName element))] + (if (= nn "#text") + (clojurize element) + (when (clojurable? element) + (dotimes [i (.-length (.-childNodes element))] + (clojurify (.item (.-childNodes element) i) clojurize)))))) diff --git a/src/clojurifier/content.cljs b/src/clojurifier/content.cljs new file mode 100644 index 0000000..94ab0e8 --- /dev/null +++ b/src/clojurifier/content.cljs @@ -0,0 +1,12 @@ +(ns clojurifier.content + (:require [clojurifier.mutant :refer [observe-body]] + [clojurifier.clojurifier :refer [clojurify]]) + (:require-macros [dommy.macros :refer [sel1]])) + +(defn init + "Clojurifies everything on the page and starts observing the body element for changes" + [] + (clojurify (sel1 :body)) + (observe-body)) + + diff --git a/src/clojurifier/mutant.cljs b/src/clojurifier/mutant.cljs new file mode 100644 index 0000000..1adb2dc --- /dev/null +++ b/src/clojurifier/mutant.cljs @@ -0,0 +1,33 @@ +(ns clojurifier.mutant + (:require [khroma.log :as console] + [clojurifier.clojurifier :refer [clojurify]])) + + +; so we can use the circular dependency between observe-element and handle-mutation +(declare observe-element) + +(defn handle-mutation + "Handles the change of an element. Clojurifies it and starts observing its children." + [mutation] + (clojurify (.-target mutation)) + (doseq [child (-> mutation .-target .-childNodes (array-seq 0))] + (observe-element child))) + +(defn handle-mutations + "Handles the incoming mutations one by one" + [mutations] + (doseq [m (js->clj mutations)] + (handle-mutation m))) + +(defn observe-element + "Starts observing an element, calling handle-mutations on every attribute, child or text data change" + [elem] + (let [observer (js/MutationObserver. handle-mutations)] + (.observe observer elem + (clj->js {:attributes true :childList true :characterData true})))) + +(defn observe-body + "Observes the body element for changes, indirectly watches all of it's children too" + [] + (observe-element js/document.body)) +