├── .gitignore ├── CHANGELOG.md ├── README.md ├── build.boot └── src └── adzerk └── boot_bookmarklet.clj /.gitignore: -------------------------------------------------------------------------------- 1 | .nrepl* 2 | target/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 0.2.1 (11/30/16) 4 | 5 | * Fixed an [obscure bug](https://github.com/adzerk-oss/boot-bookmarklet/issues/2) related to some browsers not rendering links that are draggable if the link includes an `h1` element. 6 | 7 | Apparently the right way to make an `h1` containing a link is for the `a` element to be inside of the `h1` element, not the other way around. 8 | 9 | The HTML generated by boot-bookmarklet that goes into `bookmarklets.html` now does it the right way, so that links should be draggable in all browsers. 10 | 11 | ## 0.2.0 12 | 13 | * Added `external-bookmarklet` task. See README for more info. 14 | 15 | ## 0.1.1 16 | 17 | * URL-encode the JavaScript code in the `javascript:` link tag. 18 | 19 | ## 0.1.0 20 | 21 | * Initial release. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boot-bookmarklet 2 | 3 | [![Clojars Project](http://clojars.org/adzerk/boot-bookmarklet/latest-version.svg)](http://clojars.org/adzerk/boot-bookmarklet) 4 | 5 | A [Boot](http://boot-clj.com) task for generating [bookmarklets](https://en.wikipedia.org/wiki/Bookmarklet) from ClojureScript namespaces, allowing you to write bookmarklets in ClojureScript! :book: :bookmark: 6 | 7 | ## Usage 8 | 9 | Add `adzerk/boot-bookmarklet` to your `build.boot` dependencies and require/refer in the task: 10 | 11 | ```clojure 12 | (set-env! :dependencies '[[adzerk/boot-bookmarklet "X.Y.Z" :scope "test"]]) 13 | (require '[adzerk.boot-bookmarklet :refer (bookmarklet external-bookmarklet)]) 14 | ``` 15 | 16 | ### Tasks 17 | 18 | * `bookmarklet` compiles your ClojureScript into JavaScript and stuffs the code for each namespace into a `javascript:` bookmarklet link. 19 | 20 | `bookmarklet` is convenient if your bookmarklet happens to be small enough that the compiled, URL-encoded JavaScript can fit in the bookmarklet link. [The character limit for a bookmarklet link varies from browser to browser](http://subsimple.com/bookmarklets/rules.php#CharLimit). 21 | 22 | * `external-bookmarklet` takes any number of URLs to hosted .js files and generates a bookmarklet link for each file that sources and runs it. 23 | 24 | Once your code reaches a certain size, you will hit the character limit and your bookmarklet won't work anymore. To get around this, you can host your compiled .js file somewhere (e.g. Dropbox, S3) and use `external-bookmarklet` to generate a small bookmarklet that simply loads the hosted .js file. 25 | 26 | ### `bookmarklet` 27 | 28 | The `bookmarklet` task takes an (optional) option `:ids` which is a set of strings identifying cljs files on the source path: 29 | 30 | ```clojure 31 | (deftask build 32 | [] 33 | (comp 34 | (watch) 35 | (speak) 36 | (bookmarklet :ids #{"lobster"}) 37 | (target))) 38 | ``` 39 | 40 | This will match a file `lobster.cljs` if it exists in the source paths. 41 | 42 | When run, this task will compile the namespace of each ClojureScript file into a standalone JavaScript file (compiled with advanced optimizations), read the contents of the file, and generate an HTML file called `bookmarklets.html`, containing a bookmarklet link for each ClojureScript file identified in `:ids`. 43 | 44 | You can then open `target/bookmarklets.html` in your browser and drag the bookmarklet link into your bookmarks. 45 | 46 | ```clojure 47 | (deftask build 48 | [] 49 | (comp 50 | (watch) 51 | (speak) 52 | (bookmarklet) 53 | (target))) 54 | ``` 55 | 56 | If no `:ids` argument is provided, the `bookmarklets` task will generate a bookmarklet for every ClojureScript file found in the source paths. This may be more convenient if you want to create a project that is just a collection of bookmarklets, and you want `bookmarklets` to generate a single HTML page containing a bookmarklet link for each ClojureScript namespace. 57 | 58 | You can, of course, also run the `bookmarklet` task from the command-line: 59 | 60 | ``` 61 | boot bookmarklet -i foo -i bar target 62 | ``` 63 | 64 | ### `external-bookmarklet` 65 | 66 | In order to use `external-bookmarklet`, your bookmarklet code must already be compiled to JavaScript and hosted somewhere. Once you have the URL to your hosted .js file, you can provide it to `external-bookmarklet` via the `-u/--urls` option, and it will generate a `bookmarklets.html` file containing a bookmarklet link which, when clicked, will fetch and run your hosted .js file in the context of the current page. 67 | 68 | ``` 69 | boot external-bookmarklet -u http://path.to/my/hosted.js 70 | ``` 71 | 72 | ``` 73 | (deftask link 74 | (external-bookmarklet :urls #{"http://path.to/my/hosted.js"})) 75 | ``` 76 | 77 | If you provide multiple URLs, the generated HTML file will contain one bookmarklet link per .js file. 78 | 79 | *TODO: example build task that watches for changes, updates the hosted.js, and generates bookmarklets.html with an external bookmarklet* 80 | 81 | ## Example 82 | 83 | The simplest of ClojureScript bookmarklets might look something like this: 84 | 85 | ```clojure 86 | (ns example.bookmarklet) 87 | 88 | (js/alert "o hai!") 89 | ``` 90 | 91 | *A more complex example project is in the works. Stay tuned!* 92 | 93 | ## Contributing 94 | 95 | There are probably all kinds of ways `boot-bookmarklet` could be improved. Pull Requests welcome! 96 | 97 | ## License 98 | 99 | Copyright © 2016 Adzerk 100 | 101 | Distributed under the Eclipse Public License version 1.0. 102 | -------------------------------------------------------------------------------- /build.boot: -------------------------------------------------------------------------------- 1 | (set-env! 2 | :source-paths #{"src"} 3 | :dependencies '[[org.clojure/clojure "1.7.0"] 4 | [org.clojure/clojurescript "1.7.228"] 5 | [adzerk/bootlaces "0.1.13" :scope "test"] 6 | [adzerk/boot-cljs "1.7.228-1"] 7 | [com.cemerick/url "0.1.1"]]) 8 | 9 | (require '[adzerk.bootlaces :refer :all]) 10 | 11 | (def +version+ "0.2.1") 12 | 13 | (task-options! 14 | pom {:project 'adzerk/boot-bookmarklet 15 | :version +version+ 16 | :description "A Boot task for generating bookmarklets." 17 | :url "https://github.com/adzerk-oss/boot-bookmarklet" 18 | :scm {:url "https://github.com/adzerk-oss/boot-bookmarklet"} 19 | :license {"EPL" "http://www.eclipse.org/legal/epl-v10.html"}}) 20 | 21 | (bootlaces! +version+) 22 | 23 | (deftask deploy 24 | "Builds uberjar, installs it to local Maven repo, and deploys it to Clojars." 25 | [] 26 | (comp (build-jar) (push-release))) 27 | -------------------------------------------------------------------------------- /src/adzerk/boot_bookmarklet.clj: -------------------------------------------------------------------------------- 1 | (ns adzerk.boot-bookmarklet 2 | {:boot/export-tasks true} 3 | (:require [adzerk.boot-cljs :refer (cljs)] 4 | [boot.core :as core] 5 | [boot.util :as util] 6 | [clojure.java.io :as io] 7 | [clojure.string :as str] 8 | [cemerick.url :refer (url-encode)])) 9 | 10 | (defn- cljs-files-by-id 11 | "(stolen from boot-cljs, modified 'main-files')" 12 | [fileset ids] 13 | (let [re-pat #(re-pattern (str "\\Q" % "\\E\\.cljs$"))] 14 | (->> fileset 15 | core/input-files 16 | (core/by-re (map re-pat ids)) 17 | (sort-by :path)))) 18 | 19 | (defn- path->js 20 | "Given a path to a cljs namespace source file, returns the corresponding 21 | Google Closure namespace name for goog.provide() or goog.require(). 22 | 23 | (stolen from boot-cljs)" 24 | [path] 25 | (-> path 26 | (str/replace #"\.clj([s|c])?$" "") 27 | (str/replace #"[/\\]" "."))) 28 | 29 | (defn- path->ns 30 | "Given a path to a cljs namespace source file, returns the corresponding 31 | cljs namespace name. 32 | 33 | (stolen from boot-cljs)" 34 | [path] 35 | (-> (path->js path) (str/replace #"_" "-"))) 36 | 37 | (defn- all-cljs-src-files 38 | [fileset] 39 | (->> fileset core/input-files (core/by-ext ["cljs"]))) 40 | 41 | (defn- cljs-edn-for 42 | [cljs-file] 43 | {:require (mapv (comp symbol path->ns core/tmp-path) [cljs-file]) 44 | :compiler-options {:optimizations :advanced}}) 45 | 46 | (core/deftask ^:private generate-cljs-edn 47 | [i ids IDS #{str} "The cljs namespaces for which to generate .cljs.edn files."] 48 | (let [tmp-main (core/tmp-dir!)] 49 | (core/with-pre-wrap fileset 50 | (core/empty-dir! tmp-main) 51 | (let [cljs-files (if ids 52 | (cljs-files-by-id fileset ids) 53 | (->> fileset 54 | core/input-files 55 | (core/by-ext ["cljs"]) 56 | (sort-by :path)))] 57 | (doseq [cljs cljs-files 58 | :let [out-main (str (.getName (core/tmp-file cljs)) ".edn") 59 | out-file (io/file tmp-main out-main)]] 60 | (util/info "Writing %s...\n" (.getName out-file)) 61 | (doto out-file 62 | (io/make-parents) 63 | (spit (cljs-edn-for cljs)))) 64 | (-> fileset (core/add-source tmp-main) core/commit!))))) 65 | 66 | (defn- bookmarklet-link 67 | [js-file] 68 | (let [js-code (url-encode (slurp (core/tmp-file js-file)))] 69 | (format "
\n

\n\n%s\n\n

\n
\n" 70 | js-code 71 | (.getName (core/tmp-file js-file))))) 72 | 73 | (defn- bookmarklet-links 74 | [fileset] 75 | (let [cljs-edn-paths (->> fileset 76 | core/input-files 77 | (core/by-ext ["cljs.edn"]) 78 | (sort-by :path) 79 | (map (comp #(str/replace % #"\.cljs\.edn" ".js") 80 | core/tmp-path))) 81 | js-files (->> fileset 82 | core/output-files 83 | (filter (comp (set cljs-edn-paths) core/tmp-path)) 84 | (sort-by :path))] 85 | (apply str (map bookmarklet-link js-files)))) 86 | 87 | (defn- external-bookmarklet-link 88 | [js-url] 89 | (let [filename (->> js-url 90 | (re-matches #".*/(.*)") 91 | last) 92 | js-code (-> (str "(function () { " 93 | "var jsCode = document.createElement('script'); " 94 | "jsCode.setAttribute('src', '%s'); " 95 | "document.body.appendChild(jsCode); }());") 96 | (format js-url) 97 | url-encode)] 98 | (format "
\n

\n\n%s\n\n

\n
\n" 99 | js-code 100 | filename))) 101 | 102 | (defn- external-bookmarklet-links 103 | [urls] 104 | (apply str (map external-bookmarklet-link urls))) 105 | 106 | (defn- generate-html 107 | [fileset html-content] 108 | (let [tmp-main (core/tmp-dir!) 109 | _ (core/empty-dir! tmp-main) 110 | html-file (io/file tmp-main "bookmarklets.html")] 111 | (util/info "Writing %s...\n" (.getName html-file)) 112 | (doto html-file 113 | (io/make-parents) 114 | (spit "\n\nBookmarklets\n\n\n") 115 | (spit html-content :append true) 116 | (spit "\n" :append true)) 117 | (-> fileset (core/add-resource tmp-main) core/commit!))) 118 | 119 | (core/deftask bookmarklet 120 | "Compiles cljs -> js and generates bookmarklets.html, a simple page 121 | containing a link to each compiled .js file. 122 | 123 | If --ids are supplied (each a string representing a cljs namespace), each 124 | namespace is turned into a bookmarklet. 125 | 126 | If no --ids are supplied, makes a bookmarklet out of every .cljs input file." 127 | [i ids IDS #{str} "The cljs namespaces to turn into bookmarklets."] 128 | (comp 129 | (generate-cljs-edn :ids ids) 130 | (cljs) 131 | (core/with-pre-wrap fileset 132 | (generate-html fileset (bookmarklet-links fileset))))) 133 | 134 | (core/deftask external-bookmarklet 135 | "Generates bookmarklets.html, a simple page containing a bookmarklet link for 136 | each supplied URL to a hosted .js file." 137 | [u urls URLS #{str} "The URLs to hosted .js files to be loaded by bookmarklets."] 138 | (core/with-pre-wrap fileset 139 | (generate-html fileset (external-bookmarklet-links (or urls #{}))))) 140 | --------------------------------------------------------------------------------