├── .github └── FUNDING.yml ├── .gitignore ├── CHANGES.md ├── README.md ├── doc └── potential_features.md ├── example-resources └── public │ └── devcards │ ├── css │ ├── devcard-api.css │ └── two-zero.css │ └── index.html ├── example_src └── devdemos │ ├── core.cljs │ ├── css_opt_out.cljs │ ├── custom_cards.cljs │ ├── defcard_api.cljs │ ├── edn_render.cljs │ ├── errors.cljs │ ├── extentions.cljs │ ├── maintain_state.cljs │ ├── om_next.cljs │ ├── reagent.cljs │ ├── source_code_display.cljs │ ├── start_ui.cljs │ ├── testing.cljs │ └── two_zero.cljs ├── index.html ├── project.clj ├── resources └── public │ └── devcards │ └── css │ ├── com_rigsomelight_devcards.css │ ├── com_rigsomelight_devcards_addons.css │ ├── com_rigsomelight_edn.css │ ├── com_rigsomelight_edn_flex.css │ ├── com_rigsomelight_github_highlight.css │ ├── default.css │ └── zenburn.css ├── site ├── devdemos.js └── two-zero.css └── src ├── deps.cljs └── devcards ├── core.clj ├── core.cljs ├── js_libs ├── highlight.ext.js ├── highlight.pack.js ├── marked.ext.js └── marked.min.js ├── system.clj ├── system.cljs └── util ├── edn_renderer.cljs ├── markdown.cljs ├── utils.clj └── utils.cljs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [bhauman] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml.asc 2 | .idea 3 | *.iml 4 | pom.xml 5 | *jar 6 | /lib/ 7 | /classes/ 8 | /out/ 9 | /target/ 10 | /site/out 11 | .lein-deps-sum 12 | .lein-repl-history 13 | .lein-plugins/ 14 | checkouts/ 15 | .nrepl-port 16 | .lein-classpath 17 | .rbenv-version 18 | config.ru 19 | example-resources/public/js/compiled/* 20 | example-resources/public/devcards/js/compiled/* 21 | .\#* 22 | \#* 23 | 24 | *-init.clj 25 | figwheel_server.log 26 | .rebel_readline_history 27 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 0.2.6 Updated for latest React 16.4 2 | 3 | * requires `cljsjs/create-react-class` - you may need to exclude this 4 | along with `cljsjs/react` and `cljsjs/react-dom` if you are using 5 | npm dependencies 6 | * should effortlessly support the latest reagent 7 | * fixed outstanding issue that made it difficult to work with npm dependencies 8 | * moved to `cljsjs/marked` instead of using showdown - I'm considering 9 | injecting a wrapped version of this lib so that it doesn't conflict 10 | if you happen to be using it from npm. 11 | * fixed an edn rendering error caused by MapEntry changes 12 | * fixed the 1024 buffer limitation, can be configured via `:closure-defines {devcards.core/card-buffer-size 10000}` 13 | * added tool friendly way to opt-in to devcards via `:closure-defines {devcards.core/active true}` 14 | * discoverd that `(set! (.-createClass (.-React goog.global)) create-react-class)` is a sweet 15 | hack to get `omcljs/om` to work with latest React 16 | 17 | ## 0.2.1-7 Some reagent fixes 18 | 19 | * Improved Reagent reloading PR #100 20 | * removed woarnings on duplicate "is" tests PR #101 21 | 22 | ## 0.2.1-6 23 | 24 | * Fixed a regression where Component local state was lost during reload 25 | * refactored to accommodate Om Next extension 26 | * fixed some React missing key warnings 27 | 28 | ## 0.2.1-5 Fixing minor dependency conflicts 29 | 30 | * getting rid of dep conflicts 31 | 32 | ## 0.2.1-4 Om Next Helpers 33 | 34 | * bumping sablono deps 35 | * Om Next helpers added by @anmonteiro 36 | * more react warnings killed by @tristanstraub 37 | 38 | ## 0.2.1-3 39 | 40 | * markdown bullet indentation fix 41 | * got rid of a bunch of React warnings 42 | * fix printing JavaScript Symbols 43 | * various documentation updates 44 | 45 | ## 0.2.1 React 14 46 | 47 | * now depends on React 14 48 | * fixing the `:watch-atom false` option 49 | * added the `:actual` output for failing tests 50 | * allowing the config of default card options 51 | 52 | ## 0.2.0-8 Improved Reagent support 53 | 54 | * **added `devcards.core/devcard-rg`** as a shortcut to defining 55 | `(defcard title (dc/reagent [:div "hey"]))` which can now be 56 | expressed as `(defcard-rg title [:div "hey"])` 57 | * improved Reagent documentation 58 | see: http://rigsomelight.com/devcards/#!/devdemos.reagent 59 | - I could really use some more help with Reagent docs 60 | * **Reagent breaking change** you can no longer supply bare reagent components to 61 | `devcards.core/reagent` or `devcards.core/defcard-rg` You always 62 | need to pass a Reagent element of the form `[component ... args]` 63 | This makes reagent support consistent with the `defcard` api where you have to 64 | supply a ReactElement and not a component 65 | 66 | ## 0.2.0-7 Reagent support with bad bug 67 | 68 | ## 0.2.0-5 69 | 70 | * isolated more css into the addons css - thanks to magnars 71 | * a new `:classname` option to the devcard options that will be added 72 | to the card body - thanks to codebeige 73 | * bumped React version to 0.13.3-1 74 | * added base support for React 14 - thanks to minimal 75 | * made `start-devcard-ui*` public 76 | * a bunch of documentation has been added to rigsomelight.com/devcards 77 | 78 | ## 0.2.0-2 First documented release 79 | 80 | * Support for `cljs.test` async testing 81 | 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Devcards 2 | 3 | ### Current release: 4 | 5 | [![Clojars Project](https://clojars.org/devcards/latest-version.svg)](https://clojars.org/devcards) 6 | 7 | Devcards aims to provide ClojureScript developers with an interactive 8 | visual REPL. Devcards makes it simple to interactively surface code 9 | examples that have a visual aspect into a browser interface. 10 | 11 | Devcards is **not** a REPL, as it is driven by code that exists in 12 | your source files, but it attempts to provide a REPL-like experience 13 | by allowing developers to quickly try different code examples and 14 | see how they behave in an actual DOM. 15 | 16 | Devcards is centered around a notion of a *card*. Every card 17 | represents some code to be displayed. Devcards provides an interface 18 | which allows the developer to navigate to different namespaces and view 19 | the *cards* that have been defined in that namespace. 20 | 21 | When used in conjunction with [lein figwheel][leinfigwheel] the cards 22 | can be created and edited **"live"** in one's ClojureScript source 23 | files. Essentially lifting the code example out of the file into the 24 | browser for you to try out immediately. 25 | 26 | 27 | 28 | For example, the following code will create a *card* for a Sablono 29 | template that you might be working on: 30 | 31 | ```clojure 32 | (defcard two-zero-48-view 33 | (sab/html 34 | [:div.board 35 | [:div.cells 36 | [:div {:class "cell xpos-1 ypos-1"} 4] 37 | [:div {:class "cell xpos-1 ypos-2"} 2] 38 | [:div {:class "cell xpos-1 ypos-3"} 8]]])) 39 | ``` 40 | 41 | When used with [lein-figwheel][leinfigwheel], saving the file that 42 | contains this definition will cause this Sablono template to be 43 | rendered into the Devcards interface. 44 | 45 | Read: [The Hard Sell](http://rigsomelight.com/devcards/#!/devdemos.core) 46 | 47 | [See the introduction video.](https://vimeo.com/97078905) 48 | 49 | [See the Strange Loop talk.](https://www.youtube.com/watch?v=G7Z_g2fnEDg) 50 | 51 | # Why??? 52 | 53 | We primarily design and iterate on our front end applications *inside* 54 | the main application itself. In other words, our execution environment 55 | is constrained by the shape and demands of the application we are 56 | working on. This is extremely limiting. 57 | 58 | This doesn't seem like a problem, eh? 59 | 60 | Well think of it this way: the main application and its many 61 | subcomponents can potentially embody a tremendous number of states. But 62 | working against a single instance of the application only lets you 63 | look at one state at a time. What if you could work on the application 64 | or component in several states at the same time? This is a powerful 65 | multiplier. You are **increasing the bandwidth of the feedback** you are 66 | getting while working on your code. 67 | 68 | Another problem is that we often manually place our components into 69 | different **important** states to run them through their paces as we 70 | develop them. But ... these test states are **ephemeral**. Wouldn't 71 | it be better to **keep** a development "page" as a permanent asset 72 | where these components are displayed in these various states as a 73 | 74 | * a lab space for future development 75 | * a code reference for new developers, and your future self 76 | * a tool for QA and application testers 77 | 78 | Developing your components in a different context than your main 79 | application **starkly reveals environmental coupling**, in the same 80 | way that unit tests often do. This can lead to developing components 81 | that are more independent than the ones that are developed inside the 82 | main app. 83 | 84 | One more thing: developing your components in a SPA that isn't your 85 | main application provides you a space to create and use visual 86 | components that are intended to help you understand the code you are 87 | working on. We are UI programmers after all, why wait for IDEs to 88 | create the tools we need? Most problems are unique and can benefit 89 | tremendously from the creation of a very thin layer of custom tooling. 90 | 91 | Developing inside the main application is constraining and it isn't 92 | until you develop inside a **meta application** that you can see this 93 | more clearly. With a meta application, you now have a space to try 94 | things out that **do not have to interface or fit into the main 95 | application**. This is extremely important as it gives you space to 96 | try new things without the cost that is currently associated with 97 | experiments (branching, new html host file, etc). 98 | 99 | ## Examples 100 | 101 | Regardless of which path you take to get started with Devcards please 102 | see the following examples: 103 | 104 | [Introduction examples](http://rigsomelight.com/devcards/#!/devdemos.core) ([src](https://github.com/bhauman/devcards/blob/master/example_src/devdemos/core.cljs)) 105 | 106 | [An example implementation of 2048](http://rigsomelight.com/devcards/#!/devdemos.two_zero) ([src](https://github.com/bhauman/devcards/blob/master/example_src/devdemos/two_zero.cljs)) 107 | 108 | [An introduction to the `defcard` api](http://rigsomelight.com/devcards/#!/devdemos.defcard_api) ([src](https://github.com/bhauman/devcards/blob/master/example_src/devdemos/defcard_api.cljs)) 109 | 110 | ## Super Quick Start 111 | 112 | There is a Devcards Leiningen template to get you up an running quickly. 113 | 114 | Make sure you have the [latest version of leiningen installed](https://github.com/technomancy/leiningen#installation). 115 | 116 | Type the following to create a fresh project with devcards setup for you: 117 | 118 | ``` 119 | lein new devcards hello-world 120 | ``` 121 | 122 | Then 123 | 124 | ``` 125 | cd hello-world 126 | 127 | lein figwheel 128 | ``` 129 | 130 | to start the figwheel interactive devserver. 131 | 132 | Then visit `http://localhost:3449/cards.html` 133 | 134 | ## Quick Trial 135 | 136 | If you want to quickly interact with a bunch of devcards demos: 137 | 138 | ``` 139 | git clone https://github.com/bhauman/devcards.git 140 | 141 | cd devcards 142 | 143 | lein figwheel 144 | ``` 145 | 146 | Then visit `http://localhost:3449/devcards/index.html` 147 | 148 | The code for the cards you are viewing in the devcards interface is 149 | located in the `example_src` directory. 150 | 151 | Go ahead and edit the code in the examples and see how the devcards 152 | interface responds. 153 | 154 | ## Usage 155 | 156 | First make sure you include the following `:dependencies` in your `project.clj` file. 157 | 158 | ```clojure 159 | [org.clojure/clojurescript "1.10.238"] 160 | [devcards "0.2.5"] 161 | ``` 162 | 163 | You will need an HTML file to host the devcards interface. It makes 164 | sense to have a separate file to host devcards. I would 165 | create the following `resources/public/cards.html` file (this is the same 166 | file as in the leiningen template). 167 | 168 | 169 | ```html 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | ``` 182 | 183 | ## Usage With Figwheel 184 | 185 | [lein-figwheel](https://github.com/bhauman/lein-figwheel) 186 | is not required to use Devcards but it is definitely recommended 187 | if you want to experience interactive coding with Devcards. 188 | See the [lein-figwheel repo](https://github.com/bhauman/lein-figwheel) 189 | for instructions on how to do that. 190 | 191 | Configure your devcards build: 192 | 193 | ```clojure 194 | :cljsbuild { 195 | :builds [ 196 | {:id "devcards" 197 | :source-paths ["src"] 198 | :figwheel { :devcards true } ;; <- note this 199 | :compiler { :main "{{your lib name}}.core" 200 | :asset-path "js/compiled/devcards_out" 201 | :output-to "resources/public/js/{{your lib name}}_devcards.js" 202 | :output-dir "resources/public/js/devcards_out" 203 | :source-map-timestamp true }}] 204 | } 205 | ``` 206 | 207 | 208 | Next you will need to include the Devcards macros into your file: 209 | 210 | ```clojure 211 | (ns example.core 212 | (:require 213 | [sablono.core :as sab]) ; just for example 214 | (:require-macros 215 | [devcards.core :refer [defcard]])) 216 | 217 | (defcard my-first-card 218 | (sab/html [:h1 "Devcards is freaking awesome!"])) 219 | ``` 220 | 221 | This will create a card in the devcards interface. 222 | 223 | Take a look at [the `defcard` api](http://rigsomelight.com/devcards/#!/devdemos.defcard_api) ([src](https://github.com/bhauman/devcards/blob/master/example_src/devdemos/defcard_api.cljs)) 224 | 225 | ## Usage without Figwheel 226 | 227 | Figwheel does some magic so that Devcards can be included or excluded 228 | from your code easily. You can certainly use Devcards without Figwheel, 229 | but there are three things that you will need to do. 230 | 231 | #### You need to specify `:devcards true` **in the build-options** of your ClojureScript build 232 | 233 | ```clojure 234 | { :main "{{name}}.core" 235 | :devcards true ; <- note this 236 | :asset-path "js/compiled/devcards_out" 237 | :output-to "resources/public/js/{{sanitized}}_devcards.js" 238 | :output-dir "resources/public/js/devcards_out" 239 | :source-map-timestamp true } 240 | ``` 241 | 242 | This is important as it is a signal to the `defcard` macro to render 243 | the cards. This is equivalent to adding `:figwheel { :devcards true }` 244 | in our figwheel based build above, but since we aren't using figwheel 245 | in this build adding the figwheel options doesn't help. 246 | 247 | #### You will need to require `devcards.core` in the files that use devcards as such: 248 | 249 | ```clojure 250 | (ns example.core 251 | (:require 252 | [devcards.core :as dc] ; <-- here 253 | [sablono.core :as sab]) ; just for this example 254 | (:require-macros 255 | [devcards.core :refer [defcard]])) ; <-- and here 256 | 257 | (defcard my-first-card 258 | (sab/html [:h1 "Devcards is freaking awesome!"])) 259 | ``` 260 | 261 | This isn't required with Figwheel because it puts `devcards.core` into the 262 | build automatically. 263 | 264 | #### You will need to start the Devcards UI 265 | 266 | ``` 267 | (devcards.core/start-devcard-ui!) 268 | ``` 269 | 270 | Make sure this is included in the file you have specified as `:main` 271 | in your build options. As mentioned above, you don't want the Devcards UI to compete with 272 | your application's UI so you will want to make sure it isn't getting 273 | launched. 274 | 275 | 276 | ## Devcards as a Standalone Website 277 | 278 | Devcards can easily be hosted as a standalone website by following 279 | steps similar to those needed to use it locally without figwheel. 280 | In this example, we will be adding a `hostedcards` profile to build 281 | our site. 282 | 283 | #### Add `:devcards true` **to the build-options** of our ClojureScript build profile 284 | 285 | ```clojure 286 | {:id "hostedcards" 287 | :source-paths ["src"] 288 | :compiler {:main "{{your lib name}}.core" 289 | :devcards true ; <- note this 290 | :asset-path "js/compiled/out" 291 | :output-to "resources/public/js/compiled/{{your lib name}}.js" 292 | :optimizations :advanced}} 293 | ``` 294 | 295 | #### Require `devcards.core`in the files that use devcards 296 | 297 | ```clojure 298 | (ns {{your lib name}}.core 299 | (:require 300 | [devcards.core :as dc]) 301 | (:require-macros 302 | [devcards.core :refer [defcard]])) 303 | ``` 304 | 305 | ### Start the Devcards UI in {{your lib name}}.core 306 | ```clojure 307 | (devcards.core/start-devcard-ui!) 308 | ``` 309 | 310 | ### Include the compiled JS in our HTML 311 | 312 | ```html 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 |
322 | 323 | 324 | 325 | ``` 326 | 327 | 328 | ### Run our Build 329 | 330 | `lein cljsbuild once hostedcards` 331 | 332 | Once the build is complete, simply copy the contents of `resources\public` 333 | to your webserver and serve it up as you would any other page. 334 | 335 | ## FAQ 336 | 337 | #### Does Devcards only work with React or Om? 338 | 339 | Nope, it can work with arbitrary CLJS code examples as well. Devcards 340 | provides a `dom-node` helper that will give you a node in the DOM to 341 | display stuff in. 342 | 343 | #### Does Devcards require Figwheel? 344 | 345 | Devcards will work automatically with REPL workflow or boot-reload. 346 | 347 | You can also just reload the browser after making a change. 348 | 349 | #### What do I do for deployment? 350 | 351 | Devcards has been rewritten so that you can write Devcards alongside 352 | your code with no impact on your production code. 353 | 354 | That being said it is often helpful to move the bulk of your cards to 355 | a different buildpath that is only built when working on the **devcards** 356 | build. 357 | 358 | When working with devcards I often have three builds "devcards", 359 | "dev", "prod". 360 | 361 | 362 | 363 | 364 | [leinfigwheel]: https://github.com/bhauman/lein-figwheel 365 | 366 | -------------------------------------------------------------------------------- /doc/potential_features.md: -------------------------------------------------------------------------------- 1 | ## Todo 2 | 3 | Make edn renderer render differences. 4 | -------------------------------------------------------------------------------- /example-resources/public/devcards/css/devcard-api.css: -------------------------------------------------------------------------------- 1 | .red-box { 2 | border: 5px solid red; 3 | } 4 | -------------------------------------------------------------------------------- /example-resources/public/devcards/css/two-zero.css: -------------------------------------------------------------------------------- 1 | .board-area { 2 | background-color: rgb(187,173,160); 3 | width: 499px; 4 | height: 499px; 5 | border-radius: 6px; 6 | position: relative; 7 | 8 | /* initialize as 3d */ 9 | -webkit-transform: translate3d(0,0,0); 10 | -moz-transform: translate3d(0,0,0); 11 | transform: translate3d(0,0,0); 12 | } 13 | 14 | .board-area-one-row { 15 | height: 136px; 16 | } 17 | 18 | .one-row-board .board-area { 19 | height: 136px; 20 | overflow: hidden; 21 | } 22 | 23 | .cell-pos { 24 | height: 106px; 25 | width: 106px; 26 | position:absolute; 27 | border-radius: 4px; 28 | 29 | -webkit-transition: all 0.12s; 30 | transition: all 0.12s; 31 | 32 | /* initialize as 3d */ 33 | -webkit-transform: translate3d(0,0,0); 34 | -moz-transform: translate3d(0,0,0); 35 | transform: translate3d(0,0,0); 36 | } 37 | 38 | .cell-empty { 39 | background-color: rgb(205,193,180); 40 | } 41 | 42 | .cell { 43 | background-color: rgb(205,193,180); 44 | height: 106px; 45 | width: 106px; 46 | /* position: relative; */ 47 | border-radius: 4px; 48 | font-family: "Helvetica Neue", Arial, sans-serif; 49 | font-size: 55px; 50 | line-height: 102px; 51 | font-weight: bold; 52 | text-align: center; 53 | vertical-align: middle; 54 | 55 | /* initialize as 3d */ 56 | -webkit-transform: translate3d(0,0,0); 57 | -moz-transform: translate3d(0,0,0); 58 | transform: translate3d(0,0,0); 59 | } 60 | 61 | .cell.highlight { 62 | -webkit-animation: highlight 0.1s; 63 | } 64 | 65 | @-webkit-keyframes highlight { 66 | 0% { -webkit-transform: scale3d(1.2,1.2,1.0); 67 | opacity: 0.7;} 68 | 100% { -webkit-transform: scale3d(1.0,1.0,1.0); 69 | opacity: 1.0;} 70 | } 71 | 72 | .cell.reveal { 73 | -webkit-animation: reveal 0.1s; 74 | } 75 | 76 | @-webkit-keyframes reveal { 77 | 0% { -webkit-transform: scale3d(0.1,0.1,1.0); 78 | opacity: 0.1; 79 | } 80 | 100% { -webkit-transform: scale3d(1.0,1.0,1.0); 81 | opacity: 1.0;} 82 | } 83 | 84 | .pos-top-0 { top: 15px; } 85 | .pos-top-1 { top: 136px; } 86 | .pos-top-2 { top: 257px; } 87 | .pos-top-3 { top: 378px; } 88 | 89 | .pos-left-0 { left: 15px; } 90 | .pos-left-1 { left: 136px; } 91 | .pos-left-2 { left: 257px; } 92 | .pos-left-3 { left: 378px; } 93 | 94 | 95 | 96 | .cell-num-2 { 97 | background-color: rgb(238, 228,218); 98 | color: rgb(110,102,93); 99 | } 100 | 101 | .cell-num-4 { 102 | background-color: rgb(237, 224,200); 103 | color: rgb(119,110,101); 104 | } 105 | 106 | .cell-num-8 { 107 | background-color: rgb(242, 177, 121); 108 | color: rgb(249,246,242); 109 | } 110 | 111 | .cell-num-16 { 112 | background-color: rgb(245, 149, 99); 113 | color: rgb(249,246,242); 114 | } 115 | 116 | .cell-num-32 { 117 | background-color: rgb(245, 124, 95); 118 | color: rgb(249,246,242); 119 | } 120 | 121 | .cell-num-64 { 122 | background-color: rgb(246, 94, 59); 123 | color: rgb(249,246,242); 124 | } 125 | 126 | .cell-num-128 { 127 | background-color: rgb(237, 207,114); 128 | color: rgb(249,246,242); 129 | font-size: 48px; 130 | } 131 | 132 | .cell-num-256 { 133 | background-color: rgb(237, 204, 97); 134 | color: rgb(249,246,242); 135 | font-size: 48px; 136 | border: 1px solid rgba(238, 228, 218, 0.5); 137 | box-shadow: 0 0 25px 5px rgb(237, 204, 97); 138 | } 139 | 140 | .cell-num-512 { 141 | background-color: rgb(237, 204, 97); 142 | color: rgb(249,246,242); 143 | font-size: 48px; 144 | border: 1px solid rgba(238, 228, 218, 0.5); 145 | box-shadow: 0 0 25px 5px rgb(237, 204, 97); 146 | } 147 | 148 | .cell-num-1024 { 149 | background-color: rgb(237, 204, 97); 150 | color: rgb(249,246,242); 151 | font-size: 40px; 152 | border: 1px solid rgba(238, 228, 218, 0.5); 153 | box-shadow: 0 0 25px 5px rgb(237, 204, 97); 154 | } 155 | 156 | .cell-num-2048 { 157 | background-color: rgb(237, 204, 97); 158 | color: rgb(249,246,242); 159 | font-size: 40px; 160 | border: 1px solid rgba(238, 228, 218, 0.5); 161 | box-shadow: 0 0 25px 5px rgb(237, 204, 97); 162 | } 163 | 164 | 165 | @media (max-width: 480px){ 166 | .board-area { 167 | width: 280px; 168 | height: 280px; 169 | } 170 | .cell-pos, .cell { 171 | width: 60px; 172 | height: 60px; 173 | } 174 | 175 | .cell { 176 | font-size: 25px; 177 | line-height: 60px; 178 | } 179 | 180 | .cell-num-128, .cell-num-256, .cell-num-512 { 181 | font-size: 26px; 182 | } 183 | 184 | .cell-num-1024, .cell-num-2048 { 185 | font-size: 21px; 186 | } 187 | 188 | .pos-top-0 { top: 8px; } 189 | .pos-top-1 { top: 76px; } 190 | .pos-top-2 { top: 144px; } 191 | .pos-top-3 { top: 212px; } 192 | 193 | .pos-left-0 { left: 8px; } 194 | .pos-left-1 { left: 76px; } 195 | .pos-left-2 { left: 144px; } 196 | .pos-left-3 { left: 212px; } 197 | 198 | } 199 | 200 | 201 | -------------------------------------------------------------------------------- /example-resources/public/devcards/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /example_src/devdemos/css_opt_out.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.css-opt-out 2 | (:require 3 | [sablono.core :as sab :include-macros true] 4 | [devcards.core :as dc] 5 | [clojure.string :as string]) 6 | (:require-macros 7 | [devcards.core :refer [defcard]])) 8 | 9 | (defcard 10 | "# Devcards CSS 11 | 12 | Devcards inlines its own CSS into the `` of the HTML document that 13 | hosts your cards. This is done because it is awkward to get and 14 | include CSS and other assets from a CLJS library (in a jar file) into an HTML file. 15 | 16 | Inlining CSS into the document makes the initial setup of Devcards much easier. 17 | 18 | There are four CSS files that are included: 19 | 20 | * Devcards main CSS (for card headings, ui and navigation) 21 | * Devcards addons CSS (adding default typography styles to the card body, etc) 22 | * EDN highlighting CSS (for the built in EDN renderer) 23 | * Code highlighting CSS (for highlight.js) 24 | 25 | If you inspect `` tag of this document or of your Devcards UI you 26 | will see four ` 54 | ``` 55 | 56 | _I'm trying to keep extraneous CSS in the addons_ 57 | 58 | 59 | You can find all the orginal CSS files here: 60 | [https://github.com/bhauman/devcards/blob/master/resources/public/devcards/css](https://github.com/bhauman/devcards/blob/master/resources/public/devcards/css) 61 | 62 | It's probably best to copy and edit the original CSS if you have any 63 | tricky CSS issues." 64 | ) 65 | 66 | -------------------------------------------------------------------------------- /example_src/devdemos/custom_cards.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.custom-cards 2 | (:require 3 | [devcards.core :as dc] 4 | [sablono.core :as sab :include-macros true]) 5 | (:require-macros 6 | [devcards.core :refer [defcard defcard-doc]])) 7 | 8 | (defcard 9 | "# Extending Devcards 10 | 11 | There are several ways to get custom behavior out of devcards. 12 | 13 | * create a React Component 14 | * create an instance of `devcards.core/IDevcardOptions` 15 | * create an instance of `devcards.core/IDevcard` 16 | 17 | Implementing a React Component is the most straightforward way to 18 | create some tooling on top of Devcards. 19 | 20 | Here is a sketch of such usage: 21 | 22 | ```clojure 23 | (defn graph-state-overtime [state-atom state-filter] 24 | ... 25 | code that does some cool graphing 26 | ...) 27 | 28 | (defcard state-over-time-view 29 | (fn [state _] 30 | ;; return a ReactElement 31 | (sab/html 32 | [:div 33 | (your-component state) 34 | (graph-state-overtime state :x)])) 35 | ;; initial state 36 | {:x 0}) 37 | ``` 38 | 39 | You can create a macro to make the above composition much more 40 | convenient to use. 41 | 42 | ```clojure 43 | ;; in a clj namespace 44 | (defmacro def-state-plot-card [vname component init-state filter-fn] 45 | `(defcard ~vname 46 | (fn [state# _] 47 | (sab/html 48 | [:div 49 | (~component state) 50 | (graph-state-overtime state# ~filter-fn)]]) 51 | ~init-state)) 52 | 53 | ;; use this in a cljs namespace 54 | (def-state-plot-card cool-state-plot 55 | my-component 56 | {:x 0} 57 | :x) 58 | ``` 59 | 60 | ## Reaching into Devcards arguments with IDevcardOptions 61 | 62 | Using React components works great if you are focusing on the content 63 | area of a card. But sometimes you want to leverage the argument 64 | parsing of the `defcard` macro and the functionality of the base 65 | devcard system itself. 66 | 67 | If you want to intercept the arguments that the `defcard` macro has 68 | parsed and alter them. You can do this with 69 | `devcards.core/IDevcardOptions`. 70 | 71 | Let's use `IDevcardOptions` to discover the options that are passed 72 | to its protocol method `-devcard-options`. 73 | 74 | ```clojure 75 | (defcard devcard-options-example-name 76 | \"Devcard options documentation.\" 77 | (reify dc/IDevcardOptions 78 | (-devcard-options [_ opts] 79 | (assoc opts :main-obj opts))) ;; <-- alter :main-obj to be the passed in opts 80 | {:devcards-options-init-state true}) 81 | ``` 82 | 83 | This is a bit subtle but implementing `IDevcardOptions` allows us to 84 | intercept the arguments that have been passed to the original 85 | `defcard`. You can see resulting card rendered below: 86 | ") 87 | 88 | (defcard devcard-options-example-namee 89 | "Devcard options documentation." 90 | (reify dc/IDevcardOptions 91 | (-devcard-options [_ opts] 92 | (assoc opts :main-obj opts))) 93 | {:devcards-options-init-state true}) 94 | 95 | (defcard 96 | "As you can see above, we are intercepting the options that were 97 | parsed out by the `defcard` macro and we have a chance to operate on 98 | them before they are sent to the underlying system. 99 | 100 | These options are: 101 | 102 | * `:name` - the name of the card (changing this affects nothing) 103 | * `:documentation` - the docs associated with the card 104 | * `:main-obj` - the main object that is subject of display 105 | * `:initial-data` - the initial data for the card state atom 106 | * `:options` - the options for the devcard system (like `:inspect-data true`) 107 | * `:path` - the path to the card in the devcards interface (normally `[ns var]`) 108 | 109 | You can alter any of these args before returning the `opts`. 110 | 111 | Here is an example where we change the path name of a card. 112 | 113 | ```clojure 114 | (defcard example-2 115 | (reify dc/IDevcardOptions 116 | (-devcard-options [_ opts] 117 | (assoc opts :path 118 | [:devdemos.custom_cards 119 | :this-is-a-changed-path-name])))) 120 | ``` 121 | 122 | And you can see this card rendered below: 123 | 124 | ") 125 | 126 | 127 | (defcard example-2 128 | (reify dc/IDevcardOptions 129 | (-devcard-options [_ opts] 130 | (assoc opts 131 | :path 132 | [:devdemos.custom_cards 133 | :this-is-a-changed-path-name])))) 134 | 135 | (defcard 136 | "The above card's heading has been altered from `example-2` to 137 | `this-is-a-changed-path-name`. We could have changed the `ns` part 138 | of the path name but then the card would no longer be on this page! 139 | 140 | You may have noticed that we are getting a JavaScript Object of some sort 141 | rendered in the body of the card. This is because the `:main-obj` is 142 | the reified instance of IDevcardOptions. This is where the magic comes in, you can 143 | specify any `:main-obj` you like. 144 | 145 | Here's an example where we create a `state-reset` control that we can 146 | use in our cards. 147 | 148 | ```clojure 149 | (defn state-reset [component] 150 | (reify dc/IDevcardOptions 151 | (-devcard-options [_ opts] 152 | (assoc opts 153 | :main-obj 154 | (fn [state owner] 155 | (sab/html 156 | [:div 157 | [:button 158 | {:onClick 159 | (fn [] 160 | (reset! state (:initial-data opts)))} 161 | \"reset state\"] 162 | (if (fn? component) 163 | (component state owner) 164 | component)])))))) 165 | 166 | (defn counter [state] 167 | (sab/html 168 | [:div 169 | [:h3 \"Counter : \" (:count @state)] 170 | [:button {:onClick #(swap! state (fn [s] (update-in s [:count] inc)))} 171 | \"inc\"]])) 172 | 173 | (defcard counter-example 174 | (state-reset (fn [state _] (counter state))) 175 | {:count 0} 176 | {:inspect-data true}) 177 | ``` 178 | 179 | You can see the resulting card below. You can increment the example 180 | counter and then easily reset the state of the counter to the initial 181 | value. 182 | ") 183 | 184 | (defn state-reset [component] 185 | (reify dc/IDevcardOptions 186 | (-devcard-options [_ opts] 187 | (assoc opts 188 | :main-obj 189 | (fn [state owner] 190 | (sab/html 191 | [:div 192 | [:button 193 | {:onClick 194 | (fn [] 195 | (reset! state (:initial-data opts)))} 196 | "reset state"] 197 | (if (fn? component) 198 | (component state owner) 199 | component)])))))) 200 | 201 | (defn counter [state] 202 | (sab/html 203 | [:div 204 | [:h3 "Counter : " (:count @state)] 205 | [:button {:onClick #(swap! state (fn [s] (update-in s [:count] inc)))} 206 | "inc"]])) 207 | 208 | (defcard counter-example 209 | (state-reset (fn [state _] (counter state))) 210 | {:count 0} 211 | {:inspect-data true}) 212 | 213 | (defcard 214 | "There are other ways to accomplish this same goal. I just wanted to 215 | demonstrate that `IDevcardOptions` can offer a bit of flexibility 216 | when creating tools for devcards. 217 | 218 | Don't forget you can wrap all of this in a macro to make expressive 219 | consice tools for your workflow.") 220 | 221 | (defcard 222 | "## The `IDevcard` protocol 223 | 224 | If you want to escape the base functionality of Devcards and render 225 | your own card with it's own functionality. The `devcards.core/IDevcard` 226 | protocol can help. 227 | 228 | The protocal is simple you just need to implement a function called 229 | `devcards.core/-devcard` and return a `ReactElement`. 230 | 231 | ```clojure 232 | (defcard 233 | (reify dc/IDevcard 234 | (-devcard [_ opts] 235 | (sab/html [:h3 \"This is a card without any base Devcard functionality\"])))) 236 | ``` 237 | ") 238 | 239 | (defcard idevcard-example 240 | (reify dc/IDevcard 241 | (-devcard [_ opts] 242 | (sab/html [:h3 "This is a card without any base Devcard functionality"])))) 243 | 244 | (defcard 245 | "## Low level api `devcards.core/register-card` 246 | 247 | You can create a card with the low level api 248 | `devcards.core/register-card`. This function takes a map with two keys: 249 | 250 | * `:path` - the path to the card in the devcards interface (normally [ns var]) 251 | * `:func` - a thunk (function of no args) that returns a ReactElement 252 | 253 | Here is an example of using `register-card`: 254 | 255 | ```clojure 256 | (dc/register-card 257 | {:path [:devdemos.custom_cards 258 | :registered-card] 259 | :func (fn [] (sab/html [:h1 \"** Registered card **\"]))}) 260 | ``` 261 | 262 | You can see this in action below 263 | 264 | ") 265 | 266 | (dc/register-card 267 | {:path [:devdemos.custom_cards 268 | :registered-card] 269 | :func (fn [] (sab/html [:h1 "** Registered card **"]))}) 270 | 271 | (defcard 272 | "## The `devcards.core/defcard*` macro 273 | 274 | The `devcard*` macro makes it easy to bypass base `devcard` 275 | functionality. 276 | 277 | It is defined as so: 278 | 279 | ```clojure 280 | (defmacro defcard* 281 | ([vname expr] 282 | (when (utils/devcards-active?) 283 | `(devcards.core/register-card ~{:path (name->path &env vname) 284 | :func `(fn [] ~expr)})))) 285 | ``` 286 | 287 | As you can see it uses the `register-card` function but also captures 288 | path information. The `defcard*` macro can be helpful when composing 289 | your own macros for Devcards. 290 | 291 | 292 | ") 293 | -------------------------------------------------------------------------------- /example_src/devdemos/defcard_api.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.defcard-api 2 | (:require 3 | [devcards.core] 4 | [om.core :as om :include-macros true] 5 | [om.dom :as dom :include-macros true] 6 | [reagent.core :as reagent] 7 | [clojure.string :as string] 8 | [sablono.core :as sab :include-macros true] 9 | [cljs.test :as t :include-macros true :refer-macros [testing is]]) 10 | (:require-macros 11 | ;; Notice that I am not including the 'devcards.core namespace 12 | ;; but only the macros. This helps ensure that devcards will only 13 | ;; be created when the :devcards is set to true in the build config. 14 | [devcards.core :as dc :refer [defcard defcard-doc noframe-doc deftest dom-node]])) 15 | 16 | (defcard-doc 17 | "#It all starts with `defcard` 18 | 19 | Once you have Devcards setup and have required the devcards macros as below 20 | ```clojure 21 | (:require-macros 22 | [devcards.core :as dc :refer [defcard]]) 23 | ``` 24 | You can then use the `defcard` macro. `defcard` is a multipurpose 25 | macro which is designed to take what you are working on elevate 26 | live into the browser. It can handle many types of data but 27 | primarily takes any type of ReactElement. 28 | 29 | So this would be the \"Hello World\" of Devcards," 30 | 31 | '(defcard (sab/html [:h3 "Hello world"])) 32 | 33 | "You can see this devcard rendered below:") 34 | 35 | (defcard (sab/html [:h3 "Hello World!"])) 36 | 37 | (defcard 38 | "# These cards are hot ... loaded 39 | 40 | One thing that isn't easy to see from reading this page is that when 41 | you define a Devcard in your code and save it, a card instantly 42 | appears in the Devcards interface. It shows up on the page in the 43 | order of its definition, and when you comment out or delete the 44 | card from your code it dissapears from the interface. 45 | 46 | ## `defcard` takes 5 arguments 47 | 48 | * **name** an optional symbol name for the card to be used as a heading and to 49 | locate it in the Devcards interface 50 | * **documentation** an optional string literal of markdown documentation 51 | * **main object** a required object for the card to display 52 | * **initial data** an optional Atom, RAtom or Clojure data structure (normally 53 | a Map), used to initialize the a state that the devcard will pass back to 54 | your code examples. More on this later ... 55 | * **devcard options** an optional map of options for the underlying devcard 56 | 57 | ``` 58 | (defcard hello-world ;; optional symbol name 59 | \"**Optional Mardown documentation**\" ;; optional literal string doc 60 | {:object \"of focus\"} ;; required object of focus 61 | {} ;; optional intial data 62 | {} ;; optional devcard config 63 | ) 64 | ``` 65 | 66 | We are going to explore these arguments and examples of how they work 67 | below. 68 | 69 | This is pretty *meta* as I am using Devcards to document Devcards. 70 | So please take this page as an example of **interactive literate 71 | programming**. Where you create a story about your code, supported 72 | by live examples of how it works. 73 | 74 | ## What's in a name? 75 | 76 | The first optional arg to `defcard` is the **name**. This is a 77 | symbol and it is used to provide a distinct key for the card 78 | you are creating. 79 | 80 | For cards that aren't stateful like documentation and such the name 81 | really isn't necessary but when you create cards that are displaying 82 | stateful running widgets then this key will help ensure that the 83 | underlying state is mapped back to the correct card. 84 | 85 | The name will be used to create a header on the card. The header can 86 | be clicked to display and work on the card by itself. 87 | 88 | For instance here is a card with a name: 89 | 90 | ``` 91 | (defcard first-example) 92 | ``` 93 | 94 | You can see this card with its header just below. If you click on 95 | the `first-example` card header, you will be presented with the card 96 | by itself, so that you can work on the card in isolation. 97 | 98 | 99 | ") 100 | 101 | (defcard first-example 102 | (sab/html [:div]) 103 | {} 104 | {:heading true}) 105 | 106 | (defcard-doc 107 | "## Name absentia 108 | 109 | In the absense of a name, the heading of the card will not be displayed. 110 | 111 | Devcards generate's a card name in the order that it shows up on 112 | the page. You can see this autogenerated name by setting the 113 | `:heading` option to `true`. 114 | 115 | ``` 116 | (defcard {} ; main obj 117 | {} ; initial data 118 | {:heading true}) ; devcard options: forces header to show 119 | ``` 120 | 121 | Which is displayed as so:") 122 | 123 | (defcard {} {} {:heading true}) 124 | 125 | (defcard 126 | "The generated card name above is *card-4*. This makes sense 127 | because it's the fouth card on the page that has no name. 128 | 129 | The generated card name will work in many cases but not for all. 130 | It's best to have a name for cards with state.") 131 | 132 | (defcard 133 | "## Optional markdown doc 134 | 135 | You can also add an optional markdown documentation to your card like this: 136 | ``` 137 | (defcard example-2 \"## Example: This is optional markdown\") 138 | ``` 139 | ") 140 | 141 | (defcard example-2 "## Example: This is optional markdown") 142 | 143 | (defcard 144 | "Since the name `example-2` is optional you can write docs just like this: 145 | 146 | ``` 147 | (defcard 148 | \"## Example: writing markdown docs is intended to be easy. 149 | 150 | You should be able to add docs to your code examples easily.\") 151 | ```") 152 | 153 | (defcard-doc 154 | "# The object of our attention 155 | 156 | The main object that we are displaying comes after the optional 157 | **name** and **documentation** and it will be displayed in the 158 | **body** of the card. 159 | 160 | As mentioned before this object can be many things but perhaps most 161 | importantly it can be a ReactElement. 162 | 163 | For example this is valid:" 164 | 165 | (dc/mkdn-pprint-code 166 | '(defcard react-example (sab/html [:h3 "Example: Rendering a ReactElement"]))) 167 | 168 | "Above we simply passed a ReactElement created by `sablono` to `defcard` 169 | and it gets rendered as the following card:") 170 | 171 | (defcard react-example (sab/html [:h3 "Example: Rendering a ReactElement"])) 172 | 173 | (defcard 174 | "## A string is interpreted as markdown 175 | 176 | In the example below we are not using a string literal so the first 177 | arg is really the main object and because it is of type `string` it 178 | will be interpreted as markdown. 179 | 180 | ``` 181 | (defcard (str \"This is the main object and it **will** be interpreted as markdown\")) 182 | ``` 183 | ") 184 | 185 | 186 | (defcard (str "This is the main object and **will** be interpreted as markdown")) 187 | 188 | (defcard 189 | "## Many types are displayed as edn 190 | 191 | As we are programming we often want to see the result of an 192 | evaluation, for this reason `defcard` will display many types of 193 | data as edn. 194 | 195 | This is a growing list of items but right now look at the following examples:") 196 | 197 | (defcard 198 | "**Map**s are displayed as edn: 199 | ``` 200 | (defcard {:this \"is a map\"}) 201 | ```" 202 | {:this "is a map"}) 203 | 204 | (defcard 205 | "**Vector**s are displayed as edn: 206 | 207 | ``` 208 | (defcard [\"This\" \"is\" \"a\" \"vector\"]) 209 | ```" 210 | (string/split "This is a vector" #"\s" )) 211 | 212 | (defcard 213 | "**Set**s are displayed as edn 214 | 215 | ``` 216 | (defcard #{1 2 3}) 217 | ```" 218 | #{1 2 3}) 219 | 220 | (defcard 221 | "**List**s are displayed as edn 222 | 223 | ``` 224 | (defcard (list 1 2 3)) 225 | ```" 226 | (list 1 2 3)) 227 | 228 | (defcard 229 | "## Atoms are displayed as observed edn 230 | 231 | When you pass an atom to `defcard` as the main object its contents 232 | will be rendered as edn. And when the atom changes so will the 233 | displayed edn. 234 | 235 | ``` 236 | (defonce observed-atom 237 | (let [a (atom 0)] 238 | (js/setInterval (fn [] (swap! observed-atom inc)) 1000) 239 | a)) 240 | 241 | (defcard atom-observing-card observed-atom) 242 | ``` 243 | 244 | This will produce the timer card that you can see below:") 245 | 246 | (defonce observed-atom 247 | (let [a (atom {:time 0})] 248 | (js/setInterval (fn [] (swap! observed-atom update-in [:time] inc)) 1000) 249 | a)) 250 | 251 | (defcard atom-observing-card observed-atom {} {:history false}) 252 | 253 | (defcard-doc 254 | "## A function as a main object 255 | 256 | The main point of devcards is to get your code out of the source 257 | file and up and running in front of you as soon as possible. To this 258 | end devcards tries to provide several generic ways for you to run 259 | your code in the devcards interface. The main way is to pass a 260 | function to the `defcard` macro as the main object. 261 | 262 | Instead of a ReactElement you can provide a function the takes two 263 | parameters and returns a ReactElement like so:" 264 | 265 | (dc/mkdn-pprint-code 266 | '(defcard (fn [data-atom owner] 267 | (sab/html [:div [:h2 "Example: fn that returns React"] 268 | (prn-str data-atom)])))) 269 | "In this example the `data-atom` is a ClojureScript Atom and 270 | the`owner` is the enclosing cards ReactElement.") 271 | 272 | (defcard 273 | (fn [data-atom owner] 274 | (sab/html [:div [:h3 "Example: fn that returns React"] 275 | (prn-str data-atom)]))) 276 | 277 | (defcard-doc 278 | "If `data-atom` in the above example changes then the card will be re-rendered. 279 | 280 | Let's make a quick example counter:" 281 | (dc/mkdn-pprint-code 282 | '(defcard 283 | (fn [data-atom owner] 284 | (sab/html [:div [:h3 "Example Counter: " (:count @data-atom)] 285 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]]))))) 286 | 287 | (defcard 288 | (fn [data-atom owner] 289 | (sab/html [:div [:h3 "Example Counter: " (:count @data-atom)] 290 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]]))) 291 | 292 | (defcard-doc 293 | "## Initial state 294 | 295 | The counter example above was very interesting but what if you want 296 | to introduce some initial state? 297 | 298 | Well the next option after the main object is the **initial-data** 299 | parameter. You can use it like so:" 300 | (dc/mkdn-pprint-code 301 | '(defcard 302 | (fn [data-atom owner] 303 | (sab/html [:div [:h3 "Example Counter w/Initial Data: " (:count @data-atom)] 304 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]])) 305 | {:count 50}))) 306 | 307 | (defcard 308 | (fn [data-atom owner] 309 | (sab/html [:div [:h3 "Example Counter w/Initial Data: " (:count @data-atom)] 310 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]])) 311 | {:count 50}) 312 | 313 | (defcard-doc 314 | "## Initial state can be an Atom 315 | 316 | You can also pass an Atom as the initial state. This is a very 317 | important feature of devcards as it allows you to share state 318 | between cards. 319 | 320 | The following examples share state:" 321 | 322 | (dc/mkdn-pprint-code 323 | '(defonce first-example-state (atom {:count 55}))) 324 | 325 | (dc/mkdn-pprint-code 326 | '(defcard example-counter 327 | (fn [data-atom owner] 328 | (sab/html [:h3 "Example Counter w/Shared Initial Atom: " (:count @data-atom)])) 329 | first-example-state)) 330 | 331 | (dc/mkdn-pprint-code 332 | '(defcard example-incrementer 333 | (fn [data-atom owner] 334 | (sab/html [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "increment"])) 335 | first-example-state)) 336 | 337 | (dc/mkdn-pprint-code 338 | '(defcard example-decrementer 339 | (fn [data-atom owner] 340 | (sab/html [:button {:onClick (fn [] (swap! data-atom update-in [:count] dec))} "decrement"])) 341 | first-example-state)) 342 | "As you can see, we created three cards that all share the same state. 343 | 344 | If you try these example cards below you will see that they are all wired together:") 345 | 346 | (defonce first-example-state (atom {:count 55})) 347 | 348 | (defcard example-counter 349 | (fn [data-atom owner] 350 | (sab/html [:h3 "Example Counter w/Shared Initial Atom: " (:count @data-atom)])) 351 | first-example-state) 352 | 353 | (defcard example-incrementer 354 | (fn [data-atom owner] 355 | (sab/html [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc)) } "increment"])) 356 | first-example-state) 357 | 358 | (defcard example-decrementer 359 | (fn [data-atom owner] 360 | (sab/html [:button {:onClick (fn [] (swap! data-atom update-in [:count] dec)) } "decrement"])) 361 | first-example-state) 362 | 363 | (defcard 364 | "# Reseting the state of a card 365 | 366 | The **initial state** is just the initial state of the card. What if 367 | you want to reset the card and start from the initial state or some 368 | new initial state? 369 | 370 | There is a simple trick: you just change the name of the card. I 371 | often add and remove a `*` at the end of a card name to bump the 372 | state out of the card in read in a new initial state. 373 | 374 | I am debating adding knobs for these things to the heading panel of 375 | the card. A knob to reset the state, a knob to turn on history, a 376 | knob to display the data in the atom. Let me know if you think this 377 | is a good idea.") 378 | 379 | (defcard 380 | "# Devcard options 381 | 382 | The last argument to `defcard` is an optional map of options. 383 | 384 | Here are the available options with their defaults: 385 | 386 | ``` 387 | { 388 | :frame true ;; whether to enclose the card in a padded frame 389 | :heading true ;; whether to add a heading panel to the card 390 | :padding true ;; whether to have padding around the body of the card 391 | :hidden false ;; whether to diplay the card or not 392 | :inspect-data false ;; whether to display the data in the card atom 393 | :watch-atom true ;; whether to watch the atom and render on change 394 | :history false ;; whether to record a change history of the atom 395 | :classname \"\" ;; provide card with a custom classname 396 | :projection identity ;; provide a projection function for card state 397 | } 398 | ``` 399 | 400 | Most of these are fairly straight forward. Whats important to know 401 | is that you can change any of these live and the card will respond 402 | with the new behavior. 403 | 404 | Here are some cards that exercise these options:") 405 | 406 | (defcard no-framed 407 | (str "## This is a devcard 408 | 409 | And it doesn't have a frame") 410 | {} 411 | {:frame false}) 412 | 413 | (defcard no-heading 414 | (str "# this card is hiding its heading") 415 | {} 416 | {:heading false}) 417 | 418 | (defcard no-padding 419 | (str " this card is has no padding on its body") 420 | {} 421 | {:padding false}) 422 | 423 | (defcard custom-classname 424 | (str " this card has a custom class `.red-box`") 425 | {} 426 | {:classname "red-box"}) 427 | 428 | (defcard inspect-data 429 | (fn [data-atom owner] 430 | (sab/html [:div [:h3 "Inspecting data on this Counter: " (:count @data-atom)] 431 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]])) 432 | {:count 50} 433 | {:inspect-data true}) 434 | 435 | (defcard inspect-data-and-record-history 436 | (fn [data-atom owner] 437 | (sab/html [:div [:h3 "Inspecting data and recording history this Counter: " (:count @data-atom)] 438 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"]])) 439 | {:count 50} 440 | {:inspect-data true :history true}) 441 | 442 | (defcard project-data 443 | (fn [data-atom _] 444 | (sab/html [:div [:h3 "Inspecting data but only some of the data: Counter" (:count @data-atom)] 445 | [:button {:onClick (fn [] (swap! data-atom update-in [:count] inc))} "inc"] 446 | [:div "Random other state that is not important: " (:whatever @data-atom)]])) 447 | {:count 50 448 | :whatever "this state is present but not shown in `inspect-data` part."} 449 | {:inspect-data true 450 | :projection (fn [state] (select-keys state [:count]))}) 451 | 452 | 453 | (defcard-doc 454 | "## Accessing the DOM with `dom-node` 455 | 456 | While Devcards was written in and are very easy to use in 457 | conjunction with React. You may want to write something that writes 458 | directly to the DOM. 459 | 460 | The helper macro `dom-node` takes a function that accepts a DOM 461 | node and ClojureScript Atom and returns a ReactElement." 462 | 463 | (dc/mkdn-pprint-code 464 | '(defcard example-dom-node 465 | (dom-node (fn [data-atom node] 466 | (set! (.-innerHTML node) "

Example Dom Node

")))))) 467 | 468 | (defcard example-dom-node 469 | (dom-node 470 | (fn [data-atom node] 471 | (set! (.-innerHTML node) "

Example Dom Node

")))) 472 | -------------------------------------------------------------------------------- /example_src/devdemos/edn_render.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.edn-render 2 | (:require [devcards.core]) 3 | (:require-macros 4 | [devcards.core :as dc :refer [defcard edn]])) 5 | 6 | 7 | (defcard some-typical-nested-edn 8 | {:first (range 50) 9 | :sets (set (range 1000 1100)) 10 | :vector (vec (range 1000 1100)) 11 | :second (take 6 (repeat {:first-name "Bruce" 12 | :last-name "Hauman" 13 | :date (js/Date.) 14 | :children (take 6 (repeat {:first-name "Bruce" 15 | :last-name "Hauman" 16 | :date (js/Date.) 17 | :children (take 6 (repeat {:first-name "Bruce" 18 | :last-name "Hauman" 19 | :date (js/Date.)}))}))}))}) 20 | -------------------------------------------------------------------------------- /example_src/devdemos/errors.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.errors 2 | (:require 3 | [devcards.core :as devcards] 4 | [sablono.core :as sab :include-macros true] 5 | [cljs.test :as t :include-macros true]) 6 | (:require-macros 7 | [devcards.core :as dc :refer [defcard defcard-doc deftest dom-node]])) 8 | 9 | (defcard 10 | "#Errors 11 | 12 | Below are examples of various failure scenarios for devcards. 13 | 14 | I use this to put various cards in bad states to see how the system 15 | responds. 16 | 17 | This is a good example of how to use devcards to great benefit. 18 | Make an **errors** page of devcards and put your components 19 | through the races.") 20 | 21 | (defcard-doc (dc/doc "#this isn't is an error")) 22 | 23 | (defcard hello) 24 | 25 | (defcard "This should fall back to **pprint**" 26 | #js {:beetle "juice"}) 27 | 28 | (defcard 29 | "This should fall back to **pprint**" 30 | (to-array ["asdf" "asd"])) 31 | 32 | (defcard #js {} #js {}) 33 | 34 | (defcard #js {} #js {} #js {}) 35 | 36 | (defcard (sab/html [:div "hello"]) {} 37 | { :frame 5 38 | :heading 5 39 | :padding 5 40 | :inspect-data 5 41 | :watch-atom 5 42 | :history 5 }) 43 | -------------------------------------------------------------------------------- /example_src/devdemos/extentions.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.extentions 2 | (:require 3 | [devcards.core] 4 | [sablono.core :as sab :include-macros true] 5 | [cljs.test :as t :include-macros true] 6 | #_[om.core :as om] 7 | [reagent.core :as rg]) 8 | (:require-macros 9 | [devcards.core :as dc :refer [defcard defcard-doc deftest dom-node]])) 10 | 11 | (defcard string 12 | (str "## **string** type will render as markdown.")) 13 | 14 | (defcard persitent-array-map 15 | "**PersitentArrayMap** will be rendered as edn" 16 | {:hey "there"}) 17 | 18 | (defcard persistent-vector 19 | "**PersitentArrayMap** will be rendered as edn" 20 | [:hi]) 21 | 22 | (defcard persistent-hash-set 23 | "**PersitentHashSet** will be rendered as edn" 24 | #{ 1 2 3 }) 25 | 26 | (defcard list 27 | "**List** will be rendered as edn" 28 | '(1 2 3)) 29 | 30 | (defcard empty-list 31 | "**EmptyList** will be rendered as edn" 32 | '()) 33 | 34 | (defonce sample-atom (atom 25)) 35 | 36 | (defcard atom-card 37 | "Atom will be rendered as edn and will watch and rerender the atom when it changes. 38 | 39 | It will also set `:history true`" 40 | sample-atom) 41 | 42 | (swap! sample-atom inc) 43 | 44 | (defcard checking-meta 45 | ^{:type :ommer} (fn [a b] (sab/html [:div "ommer"])) 46 | {} 47 | {:heading 5}) 48 | 49 | #_(defcard om-root 50 | (dc/om-root 51 | (fn [data owner] 52 | (reify om/IRender 53 | (render [_] 54 | (sab/html [:h1 "This is om now!!!"])))))) 55 | 56 | (defn simple-component [] 57 | [:div 58 | [:p "I am a component!"] 59 | [:p.someclass 60 | "I have " [:strong "bold"] 61 | [:span {:style {:color "red"}} " and red "] "text."]]) 62 | 63 | (defcard reagent 64 | (rg/as-element [simple-component])) 65 | 66 | (defonce click-count (rg/atom 0)) 67 | 68 | (defn counting-component [] 69 | [:div 70 | "The atom " [:code "click-count"] " has valuer: " 71 | @click-count ". " 72 | [:input {:type "button" :value "Click me!" 73 | :on-click #(swap! click-count inc)}]]) 74 | 75 | (defcard reagent-counter 76 | (dc/reagent [counting-component])) 77 | 78 | ;; experimenting with reloadable local state 79 | (defn elapsed-template [seconds-elapsed props] 80 | [:div 81 | [:h1 (:name props)] 82 | "Seconds Elapsed yeppers now: " @seconds-elapsed]) 83 | 84 | (defn timer-component [props] 85 | (let [seconds-elapsed (rg/atom 0)] 86 | (js/setInterval #(swap! seconds-elapsed inc) 1000) 87 | (fn [props1] 88 | (elapsed-template seconds-elapsed props)))) 89 | 90 | ;; trick to capture local state through reloads 91 | (defonce timer (rg/reactify-component timer-component)) 92 | 93 | (defn timer-app [_] 94 | [:div [:h1 "I'm a timer app"] 95 | (rg/create-element timer #js {:name "George" })]) 96 | 97 | (defonce timer-apper (rg/reactify-component timer-app)) 98 | 99 | (defcard reagent-counter-3 100 | (rg/create-element timer-apper)) 101 | 102 | (defcard reagent-locals-try 103 | "A quick way to create some stable local RAtoms" 104 | (dc/reagent 105 | (fn [data-atom _] 106 | (let [{:keys [name age]} @data-atom] 107 | [:div [:h3 "Hi there " @name] 108 | [:p "You are " @age " years old!"]]))) 109 | ;; store the needed locals in the data atom 110 | {:age (rg/atom 55) 111 | :name (rg/atom "George")}) 112 | 113 | (defn counting-component-passing-ratom [ratom] 114 | [:div 115 | "The atom " [:code "click-count"] " has valuer: " 116 | @click-count ". " 117 | [:input {:type "button" :value "Click me!" 118 | :on-click #(swap! click-count inc)}]]) 119 | 120 | (defonce temp-atom (rg/atom {:count 12})) 121 | 122 | (defcard reagent-atom-support 123 | "## We should support anything with the IAtom interface 124 | 125 | This will allow folks to use Reagent's rAtom." 126 | (dc/reagent 127 | (fn [counter-atom _] 128 | [:div "counting away " 129 | [:button {:on-click #(swap! counter-atom update-in [:count] inc)} "inc"] " " (:count @counter-atom)])) 130 | temp-atom 131 | {:inspect-data true 132 | :history true}) 133 | 134 | (defcard direct-ratom-support 135 | temp-atom) 136 | 137 | ;; tried to support reagent cursor but updates are firing during render 138 | 139 | ;; hmmm need to be smarter about watching things for cursors sake 140 | 141 | #_(defonce c (rg/cursor temp-atom [])) 142 | 143 | #_(defcard reagent-cursor c {} {:heading 5}) 144 | -------------------------------------------------------------------------------- /example_src/devdemos/maintain_state.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.maintain_state 2 | (:require 3 | [sablono.core :as sab :include-macros true] 4 | [goog.object :as gobj] 5 | [devcards.util.utils :refer-macros [define-react-class-once]] 6 | [devcards.core] 7 | [react]) 8 | (:require-macros 9 | ;; Notice that I am not including the 'devcards.core namespace 10 | ;; but only the macros. This helps ensure that devcards will only 11 | ;; be created when the :devcards is set to true in the build config. 12 | [devcards.core :as dc :refer [defcard defcard-doc deftest dom-node]])) 13 | 14 | (defcard 15 | "# Maintain local React state 16 | 17 | This is just a document to hold examples of maintaining React local state. 18 | 19 | This is mainly intended for verification of behavior.") 20 | 21 | (define-react-class-once County 22 | (constructor 23 | [props] 24 | (this-as this 25 | (set! (.-state this) #js {:count 0}))) 26 | (render 27 | [this] 28 | (let [count (gobj/get (.-state this) "count")] 29 | (sab/html 30 | [:div [:h1 "Counter: " count] 31 | [:button {:onClick (fn [e] (.setState this #js {:count (inc count)}) )} "inc"]])))) 32 | 33 | (defcard counter-with-local-state 34 | " 35 | ## Counter with local state. 36 | 37 | The state of this counter is stored in the local state of the component. 38 | 39 | To test: increment the counter then save this file and verify that the counter state hasn't changed." 40 | (react/createElement County)) 41 | 42 | -------------------------------------------------------------------------------- /example_src/devdemos/om_next.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.om-next 2 | (:require 3 | [devcards.core] 4 | [om.next :as om :refer-macros [defui ui]] 5 | [om.next.protocols :as p] 6 | [om.dom :as dom :include-macros true] 7 | [clojure.string :as string] 8 | [sablono.core :as sab :include-macros true] 9 | [cljs.test :as t :include-macros true :refer-macros [testing is]]) 10 | (:require-macros 11 | ;; Notice that I am not including the 'devcards.core namespace 12 | ;; but only the macros. This helps ensure that devcards will only 13 | ;; be created when the :devcards is set to true in the build config. 14 | [devcards.core :as dc :refer [defcard defcard-doc defcard-om-next noframe-doc deftest dom-node]])) 15 | 16 | (defcard-doc 17 | "## Rendering Om Next components with `om-next-root` and `defcard-om-next` 18 | 19 | The `om-next-root` will render Om Next components, much the way `om.core/add-root!` does. 20 | It takes one or two arguments. The first argument is the Om Next component. The second (optional) 21 | argument is either a map with the state to pass to the component, or an Om Next reconciler. 22 | 23 | The `defcard-om-next` is a shortcut to `(defcard (om-next-root ...))`. 24 | Its arguments are the same of a normal `defcard`, with the following exception: 25 | after the optional name and documentation, there must be an Om Next component. The argument after that 26 | is optional, and may either the initial state map, or an Om Next reconciler. 27 | 28 | Please refer to code of this file to see how these Om Next examples are 29 | built. 30 | 31 | ### One more thing 32 | - If you want to experience the best of a live-programming environment, don't forget to write reloadable code: 33 | - `defui ^:once` your components 34 | - `defonce` your reconcilers! 35 | ") 36 | 37 | (defui ^:once Widget 38 | Object 39 | (render [this] 40 | (sab/html [:h2 "This is an Om Next card, " (:text (om/props this))]))) 41 | 42 | (defonce om-next-root-data {:text "yep"}) 43 | 44 | (defcard om-next-card-ex 45 | "This card calls `om-next-root` with one argument, the component" 46 | (dc/om-next-root Widget) 47 | {:text "yep"}) 48 | 49 | (defcard om-next-card-reconciler-ex 50 | "This card calls `om-next-root` with 2 args, the component and the Om Next reconciler" 51 | (dc/om-next-root Widget 52 | (om/reconciler {:state om-next-root-data 53 | :parser (om/parser {:read (fn [] {:value om-next-root-data})})}))) 54 | 55 | (defcard-om-next om-next-no-reconciler 56 | "This `defcard-om-next` card takes the initial state map as its last arg" 57 | Widget 58 | om-next-root-data) 59 | 60 | (defcard om-next-share-atoms 61 | (dc/doc 62 | "#### You can share an Atom between `om-next-root`/`defcard-om-next` cards. 63 | 64 | Interact with the counters below.")) 65 | 66 | (defonce om-test-atom (atom {:count 20})) 67 | 68 | (defn counter-mutate 69 | [{:keys [state]} _ {:keys [f]}] 70 | {:value {:keys [:count]} 71 | :action #(swap! state update :count f)}) 72 | 73 | (defn counter-read 74 | [{:keys [state]} _ _] 75 | {:value (:count @state)}) 76 | 77 | (defn counter [f s] 78 | (ui 79 | static om/IQuery 80 | (query [this] 81 | [:count]) 82 | Object 83 | (render [this] 84 | (let [{:keys [count] :as props} (om/props this)] 85 | (sab/html 86 | [:div 87 | [:h1 (om/shared this :title) count] 88 | [:div [:a {:onClick #(om/transact! this `[(counter-mutate! {:f ~f})])} s]] 89 | (dc/edn props)]))))) 90 | 91 | (def om-next-counter-inc (counter inc "inc")) 92 | 93 | (defonce rec1 94 | (om/reconciler {:state om-test-atom 95 | :parser (om/parser {:read counter-read 96 | :mutate counter-mutate}) 97 | :shared {:title "First counter "}})) 98 | 99 | (defcard-om-next om-next-card-shared-ex-1 100 | om-next-counter-inc 101 | rec1) 102 | 103 | (def om-next-counter-dec (counter dec "dec")) 104 | 105 | (defonce rec2 106 | (om/reconciler {:state om-test-atom 107 | :parser (om/parser {:read counter-read 108 | :mutate counter-mutate}) 109 | :shared {:title "Second counter "}})) 110 | 111 | (defcard-om-next om-next-card-shared-ex-2 112 | om-next-counter-dec 113 | rec2) 114 | 115 | (defcard om-test-atom-data 116 | "### You can share an Atom with an `edn-card` too:" 117 | om-test-atom) 118 | 119 | (defui ^:once UnmountSample 120 | Object 121 | (componentDidMount [_] 122 | (println "mounting")) 123 | (componentWillUnmount [_] 124 | (println "unmounting this")) 125 | (render [_] 126 | (dom/div nil "unmount sample"))) 127 | 128 | (defcard-om-next sample-om-next-card 129 | UnmountSample) 130 | -------------------------------------------------------------------------------- /example_src/devdemos/reagent.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.reagent 2 | (:require 3 | [devcards.core] 4 | [reagent.core :as reagent]) 5 | (:require-macros 6 | [devcards.core :as dc :refer [defcard defcard-rg defcard-doc]])) 7 | 8 | ;; util 9 | 10 | (defn on-click [ratom] 11 | (swap! ratom update-in [:count] inc)) 12 | 13 | (defcard-doc 14 | " 15 | ## Rendering Reagent components 16 | 17 | Note: The following examples assume a namespace that looks like this: 18 | 19 | ```clojure 20 | (ns xxx 21 | (:require [devcards.core] 22 | [reagent.core :as reagent]) 23 | (:require-macros [devcards.core :as dc 24 | :refer [defcard defcard-rg]])) 25 | ``` 26 | ") 27 | 28 | (defcard 29 | "## Devcard Reagent basics 30 | 31 | First of all, you don't need to use any of Devcards helpers to use Reagent. 32 | 33 | You can use the `reagent.core/as-element` function to turn your 34 | reagent code into a ReactElement. 35 | 36 | The following works great: 37 | 38 | ```clojure 39 | (defcard reagent-no-help 40 | (reagent/as-element [:h1 \"Reagent example\"])) 41 | ``` 42 | 43 | You can see the example rendered below: 44 | ") 45 | 46 | (defcard reagent-no-help 47 | 48 | (reagent/as-element [:h1 "Reagent example"])) 49 | 50 | (defcard 51 | "This will also work with Reagent components 52 | 53 | ```clojure 54 | (defn reagent-component-example [] 55 | [:div \"hey there\"]) 56 | 57 | (defcard reagent-no-help 58 | (reagent/as-element [reagent-component-example])) 59 | ``` 60 | And you can see this rendered below: 61 | ") 62 | 63 | (defn reagent-component-example [] 64 | [:div "hey there"]) 65 | 66 | (defcard reagent-component 67 | (reagent/as-element [reagent-component-example])) 68 | 69 | (defcard 70 | "# Devcards helpers 71 | 72 | Devcards provides two macros to help you use reagent. 73 | 74 | * `devcards.core/reagent` 75 | * `devcards.core/defcard-rg` 76 | 77 | The `devcards.core/reagent` macro just calls `as-element` so the 78 | following works just like above. 79 | 80 | ```clojure 81 | (defcard reagent-macro-1 82 | (dc/reagent [:div \"This works fine\"])) 83 | ``` 84 | 85 | If you pass a function of 2 arguments to the `devcards.core/reagent` 86 | macro it will behave just like passing a function as the main object to `defcard`. 87 | 88 | ```clojure 89 | (defcard reagent-macro-2 90 | (dc/reagent (fn [data-atom _] [:div \"this works as well\"]))) 91 | ``` 92 | 93 | I'll explore uses for this in just a bit. 94 | 95 | Since typing `(defcard reagent-macro-1 (dc/reagent ...))` is a tad 96 | verbose Devcards provides the `devcards.core/defcard-rg` macro. 97 | 98 | With `defcard-rg` you can create all the above examples without 99 | explicitly calling `dc/reagent`. 100 | 101 | The following two examples work and are a bit cleaner: 102 | 103 | 104 | ```clojure 105 | (defcard-rg rg-example-2 106 | \"some docs\" 107 | [:div \"this works\"]) 108 | 109 | (defcard-rg rg-example 110 | \"some docs\" 111 | (fn [data-atom _] [:div \"this works as well\"]) 112 | (reagent/atom {:counter 5})) 113 | ``` 114 | 115 | Alright let's look at some more examples: 116 | 117 | 118 | ") 119 | 120 | (defcard-rg jamming 121 | "You can pass straight Reagent hiccup markup to `devcard-rg` like so: 122 | 123 | ```clojure 124 | (defcard-rg jamming 125 | [:div {:style {:border \"10px solid blue\" :padding \"20px\"}} 126 | [:h1 \"Composing Reagent Hiccup on the fly\"] 127 | [:p \"adding arbitrary hiccup\"]]) 128 | ``` 129 | " 130 | [:div {:style {:border "10px solid blue" :padding "20px"}} 131 | [:h1 "Composing Reagent Hiccup on the fly"] 132 | [:p "adding arbitrary hiccup"]]) 133 | 134 | 135 | ;; counter 1 136 | 137 | (defonce counter1-state (reagent/atom {:count 0})) 138 | 139 | (defn counter1 [] 140 | [:div "Current count: " (@counter1-state :count) 141 | [:div 142 | [:button {:on-click #(on-click counter1-state)} 143 | "Increment"]]]) 144 | 145 | (defcard-rg counter1 146 | " 147 | ## Counter1 (Basic) 148 | 149 | The simplest way to create a reagent devcard is to pass the `defcard-rg` macro a single argument: 150 | 151 | 1) a reagent component (i.e., `counter1`) wrapped by square brackets: 152 | 153 | ```clojure 154 | (defn on-click [ratom] 155 | (swap! ratom update-in [:count] inc)) 156 | 157 | (defonce counter1-state (reagent/atom {:count 0})) 158 | 159 | (defn counter1 [] 160 | [:div \"Current count: \" (@counter1-state :count) 161 | [:div 162 | [:button {:on-click #(on-click counter1-state)} 163 | \"Increment\"]]]) 164 | 165 | (defcard-rg counter1 166 | [counter1]) ;; <--1 167 | ``` 168 | " 169 | [counter1]) 170 | 171 | ;; counter 2 172 | 173 | (defonce counter2-state (reagent/atom {:count 0})) 174 | 175 | (defn counter2 [] 176 | [:div "Current count: " (@counter2-state :count) 177 | [:div 178 | [:button {:on-click #(on-click counter2-state)} 179 | "Increment"]]]) 180 | 181 | (defcard-rg counter2 182 | " 183 | ## Counter 2 (Displaying the state of reagent atom/cursor) 184 | 185 | However, wouldn't it be nice to see the state of our component? To 186 | accomplish this, we can pass the following arguments to the defcard 187 | macro: 188 | 189 | 1) a reagent component (i.e., `counter2`) wrapped by square brackets 190 | 191 | 2) the reagent atom (or cursor) that holds the state of our component (i.e., `counter2-state`) 192 | 193 | 3) a hash-map of options, where we set inspect-data to true (i.e., `{:inspect-data :true}`} 194 | 195 | ```clojure 196 | (defn on-click [ratom] 197 | (swap! ratom update-in [:count] inc)) 198 | 199 | (defonce counter2-state (reagent/atom {:count 0})) 200 | 201 | (defn counter2 [] 202 | [:div \"Current count: \" (@counter2-state :count) 203 | [:div 204 | [:button {:on-click #(on-click counter2-state)} 205 | \"Increment\"]]]) 206 | 207 | (defcard-rg counter2 208 | [counter2] ;; <-- 1 209 | counter2-state ;; <-- 2 210 | {:inspect-data true} ;; <-- 3 211 | ) 212 | ``` 213 | " 214 | [counter2] ;; <-- 1 215 | counter2-state ;; <-- 2 216 | {:inspect-data true} ;; <-- 3 217 | ) 218 | 219 | ;; counter 3 220 | 221 | 222 | (defonce counter3-state (reagent/atom {:count 0})) 223 | 224 | (defn counter3 [ratom] 225 | [:div "Current count: " (@ratom :count) 226 | [:div 227 | [:button {:on-click #(on-click ratom)} 228 | "Increment"]]]) 229 | 230 | (defcard-rg counter3 231 | " 232 | ## Counter 3 (Passing in an argument to the reagent component) 233 | 234 | At this point, you may be wondering, *how do we pass in arguments to 235 | the reagent component itself?* All you have to do is pass in your 236 | reagent component along with it's args. 237 | 238 | ```clojure 239 | (defn on-click [ratom] 240 | (swap! ratom update-in [:count] inc)) 241 | 242 | (defonce counter3-state (reagent/atom {:count 0})) 243 | 244 | (defn counter3 [ratom] ;; <-- counter2 expects one argument 245 | [:div \"Current count: \" (@ratom :count) 246 | [:div 247 | [:button {:on-click #(on-click ratom)} 248 | \"Increment\"]]]) 249 | 250 | (defcard-rg counter3 251 | [counter3 counter3-state] ;; <-- passing in a ratom (counter3-state) to our reagent component (counter3) 252 | counter3-state ;; <-- notice that we are *still* passing in a 2nd argument to defcard! 253 | {:inspect-data true} 254 | ) 255 | ``` 256 | " 257 | [counter3 counter3-state] 258 | counter3-state 259 | {:inspect-data true} 260 | ) 261 | 262 | 263 | ;; counter 4 264 | 265 | (defonce counter4-state (reagent/atom {:count 0})) 266 | 267 | (defn counter4 [ratom 268 | {:keys [title button-text]}] 269 | [:div [:h3 title] 270 | [:div "Current count: " (@ratom :count)] 271 | [:div [:button {:on-click #(on-click ratom)} 272 | button-text]]]) 273 | 274 | (defcard-rg counter4 275 | " 276 | ## Counter 4 (Passing in multiple arguments to the reagent component) 277 | 278 | We can pass in an arbitray number of arguments to our reagent component if we wrap it in square brackets. 279 | 280 | ```clojure 281 | (defn on-click [ratom] 282 | (swap! ratom update-in [:count] inc)) 283 | 284 | (defonce counter4-state (reagent/atom {:count 0})) 285 | 286 | (defn counter4 [ratom 287 | {:keys [title button-text]}] ;; <-- counter4 expects two arguments: a ratom, and a hash-map 288 | [:div [:h3 title] 289 | [:div \"Current count: \" (@ratom :count)] 290 | [:div [:button {:on-click #(on-click ratom)} 291 | button-text]]]) 292 | 293 | (defcard-rg counter4 294 | [counter4 counter4-state 295 | {:title \"Counter 4\" 296 | :button-text \"INCREMENT\"}] ;; <-- passing in two arguments to our reagent component 297 | counter4-state ;; <-- notice that we are *still* passing in a 2nd argument to defcard! 298 | {:inspect-data true} 299 | ) 300 | ``` 301 | " 302 | [counter4 counter4-state {:title "Counter 4" 303 | :button-text "INCREMENT"}] 304 | counter4-state 305 | {:inspect-data true} 306 | ) 307 | 308 | (defcard-rg isolating-state 309 | "## Isolating state 310 | 311 | You may only want to have state available to your example code. This is accomplished in the following example: 312 | 313 | ```clojure 314 | (defcard-rg isolating-state 315 | (fn [data-atom _] 316 | [counter4 data-atom {:title \"Counter 5\" 317 | :button-text \"increment isolated state\"}]) 318 | (reagent/atom {:count 0}) ; <-- intial ratom 319 | {:inspect-data true}) 320 | ``` 321 | 322 | " 323 | (fn [data-atom _] 324 | [counter4 data-atom {:title "Counter 5" 325 | :button-text "increment isolated state"}]) 326 | (reagent/atom {:count 0}) ; <-- intial ratom 327 | {:inspect-data true}) 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | -------------------------------------------------------------------------------- /example_src/devdemos/source_code_display.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.source-code-display 2 | (:require 3 | [cljs.repl] 4 | [sablono.core :as sab :include-macros true]) 5 | (:require-macros 6 | [devcards.core :as dc :refer [defcard defcard-doc mkdn-pprint-source]])) 7 | 8 | (enable-console-print!) 9 | 10 | (defn foo [x y z] 11 | "Returns the product of x y and z." 12 | (* x y z)) 13 | 14 | (defcard 15 | "# Source Code Display 16 | 17 | There are some situations, such as tutorials like this one, where you want 18 | to display some source code. And while you can certainly cut-and-paste that 19 | code into the `defcard` docstring, there is a better approach. 20 | 21 | Assume that you have defined a function named `foo`, and you would now like 22 | to display the source code for that function. How do you get the code into 23 | your card?") 24 | 25 | (defcard 26 | "## Cut-And-Paste 27 | 28 | Here is an example where we just pasted the code directly into the docstring 29 | as markdown text. 30 | 31 | ``` 32 | (defn foo [x y z] 33 | \"Returns the product of x y and z.\" 34 | (* x y z)) 35 | ``` 36 | 37 | The problem with this approach is that it is extra work and, more 38 | importantly, the code that we have pasted into the docstring will not stay in 39 | sync with any changes we make to the actual function definition.") 40 | 41 | (defcard-doc 42 | "## mkdn-pprint-source 43 | 44 | Here we have used a cool macro to get the source for us." 45 | 46 | (dc/mkdn-pprint-source foo) 47 | 48 | "To get this same result we simply passed the `foo` function to the 49 | `dc/mkdn-pprint-source` macro. This only works inside of a `defcard-doc`, 50 | not a regular `defcard`. But it does solve our problem in that it will 51 | dynamically retrieve the source code for the `foo` function for us. The card 52 | looks something like this: 53 | ```clojure 54 | (defcard-doc 55 | \"## mkdn-pprint-source 56 | 57 | Here we have used a cool macro to get the source for us.\" 58 | 59 | (dc/mkdn-pprint-source foo) 60 | 61 | \"To get this same result ...\" 62 | ``` 63 | 64 | Now we can continue to develop and refine our `foo` function without having 65 | to worry about making corresponding changes in our devcards.") 66 | 67 | (defcard-doc 68 | "## Error Handling for mkdn-pprint-source 69 | 70 | Here we have tried to get the source for a function that does not exist." 71 | 72 | (dc/mkdn-pprint-source bar) 73 | 74 | "Above we simply passed the `bar` function to `dc/mkdn-pprint-source`. But 75 | `bar` is not recognized in this namespace.") 76 | 77 | (defcard-doc 78 | "## Any Available Source 79 | 80 | Because the `mkdn-pprint-source` makes use of the `cljs.repl` to get the 81 | source code for an object, we can use it to display the source code for any 82 | object accessible to our current namespace. For example:" 83 | 84 | (dc/mkdn-pprint-source mkdn-pprint-source) 85 | 86 | "How's that for introspection? Or this:" 87 | 88 | (dc/mkdn-pprint-source defcard) 89 | 90 | "And one final example:" 91 | 92 | (dc/mkdn-pprint-source defcard-doc)) 93 | 94 | (defcard-doc 95 | "## Almost..." 96 | 97 | "For some reason it can find this (`cljs.repl/source`):" 98 | 99 | (dc/mkdn-pprint-source cljs.repl/source) 100 | 101 | "But not this (`cljs.repl/source-fn`):" 102 | 103 | (dc/mkdn-pprint-source cljs.repl/source-fn)) 104 | -------------------------------------------------------------------------------- /example_src/devdemos/start_ui.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.start-ui 2 | (:require 3 | [devcards.core] 4 | [devdemos.defcard-api] 5 | [devdemos.om-next] 6 | [devdemos.reagent] 7 | [devdemos.source-code-display] 8 | [devdemos.two-zero] 9 | [devdemos.testing] 10 | [devdemos.errors] 11 | [devdemos.extentions] 12 | [devdemos.edn-render] 13 | [devdemos.css-opt-out] 14 | [devdemos.custom-cards] 15 | [devdemos.maintain_state] 16 | [devdemos.core])) 17 | 18 | ;; The main function here is actually used in a documentation 19 | ;; generator that I'm experimenting with. This is not needed 20 | ;; with a standard Devcards setup!! 21 | 22 | (defn ^:export main [] 23 | (devcards.core/start-devcard-ui!)) 24 | -------------------------------------------------------------------------------- /example_src/devdemos/testing.cljs: -------------------------------------------------------------------------------- 1 | (ns devdemos.testing 2 | (:require 3 | [devcards.core :as devcards] 4 | [cljs.test :as t :refer [report] :include-macros true] 5 | [sablono.core :as sab] 6 | [cljs.core.async :refer [ You do not want to interleave testing runs! 35 | 36 | Tests defined with `defcards.core/deftest`run asynchronously and rely 37 | on a scheduler. If you make a call to `cljs.test/run-tests` while the 38 | tests in the cards are running there is a real possibility that your 39 | test runs will trample all over eachother. 40 | 41 | If you want to schedule a standard `cljs.test` test run in the same 42 | process as the Devcards tests are running in, you will need to have 43 | them run on after a safe time interval has passed to prevent 44 | interleaving test executions. (I might include a way to schedule the 45 | running of some standard tests on the scheduler in the future ...) 46 | 47 | The following is an example of using `devcards.core/deftest` 48 | " 49 | (dc/mkdn-pprint-code 50 | '(deftest first-testers 51 | "## This is documentation 52 | It should work well" 53 | (testing "good stuff" 54 | (is (= (+ 3 4 55555) 5) "Testing the adding") 55 | (is (= (+ 1 0 0 0) 1) "This should work") 56 | (is (= 1 3)) 57 | (is false) 58 | (is (throw "heck")) 59 | (is (js/asdf))) 60 | "## And here is more documentation" 61 | (testing "bad stuff" 62 | (is (= (+ 1 0 0 0) 1)) 63 | (is (= (+ 3 4 55555) 4)) 64 | (is false) 65 | (testing "mad stuff" 66 | (is (= (+ 1 0 0 0) 1)) 67 | (is (= (+ 3 4 55555) 4)) 68 | (is false))))) 69 | 70 | "And you can see this rendered below:") 71 | 72 | (dc/deftest first-testers 73 | "## This is documentation 74 | It should work well" 75 | (testing "good stuff" 76 | (is (= (+ 3 4 55555) 5) "Testing the adding") 77 | (is (= (+ 1 0 0 0) 1) "This should work") 78 | (is (= 1 3)) 79 | (is false) 80 | (is (throw "heck")) 81 | (is (js/asdf))) 82 | "## And here is more documentation" 83 | (testing "bad stuff" 84 | (is (= (+ 1 0 0 0) 1)) 85 | (is (= (+ 3 4 55555) 4)) 86 | (is false) 87 | (testing "mad stuff" 88 | (is (= (+ 1 0 0 0) 1)) 89 | (is (= (+ 3 4 55555) 4)) 90 | (is false)))) 91 | 92 | (defcard "## Checking the case where there are no tests 93 | 94 | Just creating an exmple to display the empty case where no tests are 95 | supplied to `devcards.core/deftest`. 96 | 97 | ```clojure 98 | (devcards.core/deftest no-tests) 99 | ``` 100 | 101 | When you pass 0 tests to `devcards.core/deftest` 102 | it should just render a heading with a counter of zero. 103 | ") 104 | 105 | (dc/deftest no-tests) 106 | 107 | (defcard 108 | "# Async testing 109 | 110 | Devcards supports standard `cljs.test` async testing. 111 | 112 | If you look at the source for the examples below you will see async 113 | testing in use. 114 | 115 | During async tests exceptions and errors are much more difficult to 116 | catch in the testing system each. For this reason 117 | `devcards.core/deftest` has an execution timeout, that will add an 118 | error indicating that the execution of the tests did not complete in 119 | time. This allows us to continue on with the rest of the tests even 120 | when an exception interupts the process. There is a chance that the 121 | timeout is too small for your tests, this can cause test run 122 | interleaving which can corrupt your test results. 123 | 124 | In this case you will want to increase the timeout. 125 | 126 | You can set the timeout in milliseconds as so: 127 | 128 | ```clojure 129 | (set! devcards.core/test-timeout 800) ;; 800 is the default value 130 | ``` 131 | 132 | You can see an example of this below. 133 | 134 | Notice that the last test says `Error: Tests timed out.` This normally 135 | indicates that your async tests threw an exception. 136 | 137 | All the tests after that exception will not be run. 138 | ") 139 | 140 | (set! devcards.core/test-timeout 800) 141 | 142 | (dc/deftest async-tester 143 | "## This is an async test 144 | You should see some tests here" 145 | 146 | (t/testing "Let's run async tests!" 147 | (is (= (+ 3 4 55555) 4) "Testing the adding") 148 | (is (= (+ 1 0 0 0) 1) "This should work") 149 | (is (= 1 3)) 150 | (is true) 151 | (async done 152 | (go 153 | (board [tiles] 315 | (reduce (fn [accum {:keys [top left] :as x}] 316 | (assoc-in accum [top left] 317 | (dissoc x :top :left))) 318 | base-board (vals tiles))) 319 | 320 | (defn get-replaced-tiles [new-tiles] 321 | (keep (fn [x] 322 | (when (:replaces x) 323 | (-> x 324 | (dissoc :replaces :double) 325 | (assoc :id (:replaces x)) 326 | (assoc :remove true)))) 327 | new-tiles)) 328 | 329 | (defn transform-board [direction tiles] 330 | (let [new-tiles (->> tiles 331 | tiles->board 332 | (transform-rows direction) 333 | convert-to-visible-tiles)] 334 | (merge tiles 335 | (into {} (map (juxt :id #(dissoc % :replaces)) new-tiles)) 336 | (into {} (map (juxt :id identity) (get-replaced-tiles new-tiles)))))) 337 | 338 | (defn double-tiles [tiles] 339 | (let [tiles' (filter (fn [[_ v]] (not (:remove v))) tiles)] 340 | (into {} 341 | (map (fn [[k x]] 342 | [k (if (:double x) 343 | (-> x 344 | (assoc :v (* 2 (:v x)) 345 | :highlight true) 346 | (dissoc :double)) 347 | x)]) 348 | tiles')))) 349 | 350 | (defn remove-highlight-and-reveal [tiles] 351 | (into {} 352 | (map (fn [[k v]] [k (dissoc v :highlight :reveal)]) tiles))) 353 | 354 | (defn create-tile [opts] 355 | (merge 356 | { :v (if (< (rand) 0.9) 2 4) 357 | :top (rand-int 4) 358 | :left (rand-int 4) 359 | :id (keyword (gensym "t")) 360 | :reveal true} 361 | opts)) 362 | 363 | (defn empty-slots [board] 364 | (vec (keep 365 | identity 366 | (for [top (range 4) 367 | left (range 4)] 368 | (when (= :_ (get-in board [top left])) 369 | {:top top :left left}))))) 370 | 371 | (defn add-random-tile [tiles] 372 | (->> tiles 373 | tiles->board 374 | empty-slots 375 | rand-nth 376 | create-tile 377 | ((juxt :id identity)) 378 | (apply assoc tiles))) 379 | 380 | (deftest transform-row-left-test 381 | (t/is (= (transform-row [{:v 2 :id "t1"} :_ :_ {:v 2 :id "t2"}]) 382 | [{:v 2 :double true :id "t2" :replaces "t1"} :_ :_ :_])) 383 | (t/is (= (transform-rows :left [[{:v 2 :id "t6"} :_ :_ {:v 2 :id "t1"}] 384 | [:_ :_ :_ {:v 4 :id "t2"}] 385 | [:_ :_ :_ {:v 8 :id "t3"}] 386 | [{:v 16 :id "t7"} :_ :_ {:v 16 :id "t4"}]]) 387 | [[{:v 2 :id "t1" :double true :replaces "t6"} :_ :_ :_] 388 | [{:v 4 :id "t2"} :_ :_ :_] 389 | [{:v 8 :id "t3"} :_ :_ :_] 390 | [{:v 16 :id "t4" :double true :replaces "t7"} :_ :_ :_]])) 391 | (t/is (= (transform-board :left {:t1 {:id :t1 :top 0 :left 0 :v 2} 392 | :t2 {:id :t2 :top 0 :left 3 :v 2}}) 393 | {:t1 {:id :t1, :v 2, :top 0, :left 0, :remove true} 394 | :t2 {:id :t2, :v 2, :double true, :top 0, :left 0}} 395 | )) 396 | (t/is (= (transform-board :right {:t1 {:id :t1 :top 0 :left 0 :v 2} 397 | :t2 {:id :t2 :top 0 :left 3 :v 2}}) 398 | {:t1 {:id :t1, :v 2, :double true, :top 0, :left 3} 399 | :t2 {:id :t2, :v 2, :top 0, :left 3, :remove true}} 400 | )) 401 | (t/is (= (transform-board :up {:t1 {:id :t1 :top 0 :left 0 :v 2} 402 | :t2 {:id :t2 :top 3 :left 0 :v 2}}) 403 | {:t2 {:id :t2, :v 2, :double true, :top 0, :left 0} 404 | :t1 {:id :t1, :v 2, :top 0, :left 0, :remove true}} 405 | )) 406 | (t/is (= (transform-board :down {:t1 {:id :t1 :top 0 :left 0 :v 2} 407 | :t2 {:id :t2 :top 3 :left 0 :v 2}}) 408 | {:t1 {:id :t1, :v 2, :double true, :top 3, :left 0} 409 | :t2 {:id :t2, :v 2, :top 3, :left 0, :remove true}}))) 410 | 411 | (defn move [dir data] 412 | (let [prev @data] 413 | (swap! data (partial transform-board dir)) 414 | (when (not= prev @data) 415 | (go 416 | ( 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject devcards "0.2.8-SNAPSHOT" 2 | :description "Devcards is a ClojureScript library that provides a lab space in which you can develop your UI components independently and interactively." 3 | :url "http://github.com/bhauman/devcards" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :dependencies [[org.clojure/clojure "1.8.0"] 8 | [org.clojure/clojurescript "1.10.339"] 9 | [org.clojure/core.async "0.4.474"] 10 | [cljsjs/react "16.4.1-0"] 11 | [cljsjs/react-dom "16.4.1-0"] 12 | [cljsjs/create-react-class "15.6.3-1"] 13 | [sablono "0.8.6"]] 14 | 15 | :source-paths ["src"] 16 | 17 | :clean-targets ^{:protect false} ["example-resources/public/devcards/js/compiled" 18 | :target-path] 19 | 20 | :scm { :name "git" 21 | :url "https://github.com/bhauman/devcards" } 22 | 23 | :cljsbuild { 24 | :builds [{:id "devcards-demos" 25 | :source-paths ["example_src" "src"] 26 | :figwheel { 27 | :devcards true 28 | :open-urls ["http://localhost:3449/devcards/index.html"] 29 | } 30 | :compiler { 31 | :main "devdemos.start-ui" 32 | :asset-path "js/compiled/out" 33 | :output-to "example-resources/public/devcards/js/compiled/devdemos.js" 34 | :output-dir "example-resources/public/devcards/js/compiled/out" 35 | :recompile-dependents false 36 | :preloads [devtools.preload] 37 | :optimizations :none 38 | :source-map-timestamp true}} 39 | {:id "website" 40 | :source-paths ["example_src" "src"] 41 | ;; :figwheel { :devcards true } 42 | :compiler { 43 | :main "devdemos.start-ui" 44 | :asset-path "site/out" 45 | :output-to "site/devdemos.js" 46 | :output-dir "site/out" 47 | :devcards true 48 | ;; :pseudo-names true 49 | :recompile-dependents true 50 | ;; :optimizations :simple 51 | :optimizations :advanced 52 | }} 53 | ]} 54 | 55 | :figwheel { :css-dirs ["resources/public/devcards/css"] 56 | :open-file-command "emacsclient" 57 | ;;:nrepl-port 7888 58 | } 59 | 60 | :profiles { 61 | :repl { ;:plugins [[cider/cider-nrepl "0.11.1"]] 62 | :repl-options {:init (set! *print-length* 50)}} 63 | :dev { 64 | :dependencies [;[org.omcljs/om "0.9.0"] 65 | [org.omcljs/om "1.0.0-beta4"] 66 | [reagent "0.8.1"] 67 | [figwheel-sidecar "0.5.16"] 68 | [com.cemerick/piggieback "0.2.1"] 69 | [binaryage/devtools "0.9.10"] 70 | [org.clojure/tools.nrepl "0.2.12"]] 71 | :plugins [[lein-cljsbuild "1.1.4" :exclusions [org.clojure/clojure]] 72 | [lein-figwheel "0.5.16"]] 73 | :resource-paths ["resources" "example-resources"] 74 | 75 | }}) 76 | 77 | -------------------------------------------------------------------------------- /resources/public/devcards/css/com_rigsomelight_devcards.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0px; 3 | } 4 | 5 | body .hljs { 6 | padding: 0px; 7 | color: #333; 8 | background: transparent; 9 | } 10 | 11 | #com-rigsomelight-devcards-main { 12 | padding-bottom: 10em; 13 | } 14 | 15 | .com-rigsomelight-devcards_rendered-card { 16 | position: relative; 17 | } 18 | 19 | .com-rigsomelight-devcards-body { 20 | background-color: rgb(233,234,237); 21 | } 22 | 23 | .com-rigsomelight-devcards-markdown pre, 24 | .com-rigsomelight-devcards-test-line.com-rigsomelight-devcards-test-doc .com-rigsomelight-devcards-markdown pre 25 | { 26 | display: block; 27 | padding: 9.5px 14px; 28 | margin: 0px 0px 10px; 29 | font-size: 13px; 30 | line-height: 1.42857143; 31 | word-break: normal; 32 | word-wrap: normal; 33 | overflow-x: scroll; 34 | color: #333; 35 | background-color: rgb(250,250,250); 36 | border: 1px solid #e1e1e1; 37 | margin-left: -14px; 38 | margin-right: -14px; 39 | border-left: 0px; 40 | border-right: 0px; 41 | } 42 | 43 | .com-rigsomelight-devcards-test-line.com-rigsomelight-devcards-test-doc .com-rigsomelight-devcards-markdown pre { 44 | margin-left: -15px; 45 | margin-right: -15px; 46 | } 47 | 48 | /* frameless style for markdown */ 49 | .com-rigsomelight-devcards-framelesss .com-rigsomelight-devcards-markdown { 50 | padding-top: 14px; 51 | padding-left: 14px; 52 | padding-right: 14px; 53 | } 54 | 55 | /* end fremless markdown style */ 56 | 57 | .com-rigsomelight-devcards-padding-top-border { 58 | margin-top: 14px; 59 | padding-top: 14px; 60 | } 61 | 62 | .com-rigsomelight-devcards-markdown code { 63 | padding: 2px 4px; 64 | font-size: 90%; 65 | color: #990073; 66 | background-color: #fafafa; 67 | white-space: nowrap; 68 | border-radius: 4px; 69 | } 70 | 71 | .com-rigsomelight-devcards-markdown pre code { 72 | padding: 0; 73 | font-size: 1em; 74 | color: inherit; 75 | white-space: pre; 76 | background-color: transparent; 77 | border-radius: 0; 78 | } 79 | 80 | .com-rigsomelight-devcards-base, 81 | .com-rigsomelight-devcards-markdown { 82 | 83 | } 84 | 85 | 86 | .com-rigsomelight-devcards-typog { 87 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 88 | font-size: 16px; 89 | line-height: 1.42857143; 90 | } 91 | 92 | .com-rigsomelight-devcards-typog h1, 93 | .com-rigsomelight-devcards-typog h2, 94 | .com-rigsomelight-devcards-typog h3, 95 | .com-rigsomelight-devcards-typog h4, 96 | .com-rigsomelight-devcards-typog h5, 97 | .com-rigsomelight-devcards-markdown h1, 98 | .com-rigsomelight-devcards-markdown h2, 99 | .com-rigsomelight-devcards-markdown h3, 100 | .com-rigsomelight-devcards-markdown h4, 101 | .com-rigsomelight-devcards-markdown h5 { 102 | font-weight: 500; 103 | } 104 | 105 | .com-rigsomelight-devcards-typog a { 106 | color: #428bca; 107 | text-decoration: none; 108 | } 109 | 110 | .com-rigsomelight-devcards-markdown h1:first-child, 111 | .com-rigsomelight-devcards-markdown h2:first-child, 112 | .com-rigsomelight-devcards-markdown h3:first-child, 113 | .com-rigsomelight-devcards-markdown h4:first-child, 114 | .com-rigsomelight-devcards-markdown h5:first-child { 115 | margin-top: 14px; 116 | } 117 | 118 | .com-rigsomelight-devcards-markdown code, 119 | .com-rigsomelight-devcards-markdown kbd, 120 | .com-rigsomelight-devcards-markdown pre, 121 | .com-rigsomelight-devcards-markdown samp { 122 | font-family: Menlo,Monaco,Consolas,"Courier New",monospace; 123 | } 124 | 125 | .com-rigsomelight-devcards-navbar { 126 | background-color: rgb(60,90,153); 127 | border-color: rgb(50,80,153); 128 | color: #fff; 129 | height: 50px; 130 | } 131 | 132 | .com-rigsomelight-devcards-brand { 133 | color: #ccc; 134 | font-size: 18px; 135 | line-height: 50px; 136 | display: block; 137 | margin-left: 14px; 138 | } 139 | 140 | .com-rigsomelight-devcards-container { 141 | /* margin: auto; 142 | width: 80%;*/ 143 | } 144 | 145 | .com-rigsomelight-devcards-card-base { 146 | background: #fff; 147 | padding: 8px 14px; 148 | margin-top: 20px; 149 | } 150 | 151 | .com-rigsomelight-devcards-card-base-no-pad { 152 | background: #fff; 153 | border: 1px solid rgb(231,234,242); 154 | margin-top: 20px; 155 | border-left: 0px; 156 | border-right: 0px; 157 | } 158 | 159 | .com-rigsomelight-devcards-card-base-no-pad.com-rigsomelight-devcards-card-hide-border { 160 | border: 1px solid transparent; 161 | } 162 | 163 | 164 | .com-rigsomelight-devcards-breadcrumbs { 165 | font-size: 16px; 166 | line-height: 1.5em; 167 | border: none !important; 168 | } 169 | 170 | .com-rigsomelight-devcards-breadcrumb-sep { 171 | display: inline-block; 172 | padding: 0px 5px; 173 | color: #ccc; 174 | } 175 | 176 | .com-rigsomelight-devcards-list-group { 177 | margin-top: 30px; 178 | } 179 | 180 | .com-rigsomelight-devcards-list-group-item { 181 | color: #555; 182 | position: relative; 183 | display: block; 184 | padding: 10px 14px; 185 | margin-bottom: -1px; 186 | border-bottom: 1px solid #eee; 187 | } 188 | 189 | .com-rigsomelight-devcards-badge { 190 | display: inline-block; 191 | min-width: 10px; 192 | padding: 3px 7px; 193 | font-size: 12px; 194 | font-weight: 700; 195 | color: #fff; 196 | line-height: 1; 197 | vertical-align: baseline; 198 | white-space: nowrap; 199 | text-align: center; 200 | background-color: #999; 201 | border-radius: 10px; 202 | } 203 | 204 | button.com-rigsomelight-devcards-badge { 205 | border: none; 206 | padding: 3px 19px; 207 | } 208 | 209 | 210 | .com-rigsomelight-devcards-panel-heading { 211 | padding: 8px 15px; 212 | font-size: 16px; 213 | line-height: 1.5em; 214 | background-color: rgb(142,162,206); 215 | background-color: rgb(239, 237, 237); 216 | } 217 | 218 | .com-rigsomelight-devcards-panel-heading a { 219 | color: #666; 220 | } 221 | 222 | .com-rigsomelight-devcards-devcard-padding { 223 | margin-top: 14px; 224 | padding-left: 14px; 225 | padding-right: 14px; 226 | padding-bottom: 14px; 227 | } 228 | 229 | .com-rigsomelight-devcards-test-line { 230 | position: relative; 231 | display: block; 232 | padding: 10px 14px; 233 | border: none; 234 | border-top: 1px solid #fafafa; 235 | } 236 | 237 | 238 | 239 | 240 | .com-rigsomelight-devcards-test-line.com-rigsomelight-devcards-context { 241 | background-color: #fcfcfc; 242 | border-left: 1px solid #f1f1f1; 243 | border-right: 1px solid #f1f1f1; 244 | } 245 | 246 | .com-rigsomelight-devcards-test-line pre { 247 | margin: 0px; 248 | 249 | word-break: normal; 250 | word-wrap: normal; 251 | overflow-x: scroll; 252 | } 253 | 254 | 255 | .com-rigsomelight-devcards-test-line pre code { 256 | font-size: 80%; 257 | padding: 0px; 258 | background-color:transparent; 259 | } 260 | 261 | .com-rigsomelight-devcards-pass { 262 | color: #3c763d; 263 | border: 1px solid rgb(199, 225, 160); 264 | border-left: 10px solid rgb(199, 225, 160); 265 | } 266 | 267 | .com-rigsomelight-devcards-fail, .com-rigsomelight-devcards-error { 268 | color: #a94442; 269 | border: 1px solid rgb(236, 196, 196); 270 | border-left: 10px solid rgb(236, 196, 196); 271 | } 272 | 273 | .com-rigsomelight-devcards-fail { 274 | background-color: rgb(254, 254, 244); 275 | } 276 | 277 | 278 | .com-rigsomelight-devcards-error { 279 | background-color: rgb(254, 245, 245); 280 | } 281 | 282 | 283 | 284 | .com-rigsomelight-devcards-test-message { 285 | display: block; 286 | margin-top: 2px; 287 | margin-bottom: 8px; 288 | } 289 | 290 | .com-rigsomelight-devcards-pass .com-rigsomelight-devcards-test-message { 291 | color: #386739; 292 | } 293 | 294 | .com-rigsomelight-devcards-fail .com-rigsomelight-devcards-test-message { 295 | color: #994745; 296 | } 297 | 298 | .com-rigsomelight-devcards-history-control-small-arrow { 299 | display: inline-block; 300 | height: 0px; 301 | width: 0px; 302 | border: 8px solid transparent; 303 | border-left-width: 9px; 304 | border-left-color: #666; 305 | margin-right: -10px; 306 | } 307 | 308 | .com-rigsomelight-devcards-history-control-block { 309 | display: inline-block; 310 | height: 16px; 311 | width: 3px; 312 | background-color: #666; 313 | } 314 | 315 | .com-rigsomelight-devcards-history-control-right { 316 | display: inline-block; 317 | height: 0px; 318 | width: 0px; 319 | border: 8px solid transparent; 320 | border-left-width: 16px; 321 | border-left-color: #666; 322 | margin-right: -10px; 323 | } 324 | 325 | .com-rigsomelight-devcards-history-control-left { 326 | display: inline-block; 327 | height: 0px; 328 | width: 0px; 329 | border: 8px solid transparent; 330 | border-right-width: 16px; 331 | border-right-color: #666; 332 | margin-left: -10px; 333 | } 334 | 335 | .com-rigsomelight-devcards-history-stop { 336 | display: inline-block; 337 | height: 17px; 338 | width: 17px; 339 | background-color: #D88282; 340 | border-radius: 3px; 341 | } 342 | 343 | .com-rigsomelight-devcards-history-control-bar { 344 | background-color: rgb(255,252,234); 345 | padding-top: 5px; 346 | padding-bottom: 3px; 347 | margin: 14px 0px; 348 | padding-left: 14px; 349 | padding-right: 14px; 350 | text-align: right; 351 | /* position: absolute; 352 | top: 0px; 353 | right: 0px; */ 354 | } 355 | 356 | .com-rigsomelight-devcards-history-control-bar button { 357 | background: transparent; 358 | border: none; 359 | margin: 0px 4px; 360 | height: 20px; 361 | padding: 1px 28px; 362 | } 363 | 364 | .com-rigsomelight-devcards-history-control-bar + .com-rigsomelight-devcards-padding-top-border { 365 | border: none; 366 | padding-top: 0px; 367 | } 368 | 369 | .com-rigsomelight-devcards-devcard-padding .com-rigsomelight-devcards-history-control-bar { 370 | /* margin-top: -14px; */ 371 | margin: 14px -30px; 372 | } 373 | 374 | 375 | 376 | 377 | @media (min-width: 768px) { 378 | 379 | 380 | .com-rigsomelight-devcards-markdown pre, 381 | .com-rigsomelight-devcards-test-line.com-rigsomelight-devcards-test-doc .com-rigsomelight-devcards-markdown pre { 382 | padding: 9.5px 30px; 383 | margin-left: -30px; 384 | margin-right: -30px; 385 | } 386 | 387 | .com-rigsomelight-devcards-panel-heading { 388 | padding: 8px 30px; 389 | } 390 | 391 | .com-rigsomelight-devcards-brand { 392 | margin-left: 0px; 393 | } 394 | 395 | .com-rigsomelight-devcards-devcard-padding { 396 | padding-left: 30px; 397 | padding-right: 30px; 398 | } 399 | 400 | .com-rigsomelight-devcards-card-hide-border .com-rigsomelight-devcards-devcard-padding { 401 | padding-left: 0px; 402 | padding-right: 0px; 403 | } 404 | 405 | .com-rigsomelight-devcards-breadcrumbs { 406 | padding: 0px 0px; 407 | } 408 | 409 | .com-rigsomelight-devcards-list-group { 410 | margin-top: 30px; 411 | } 412 | 413 | .com-rigsomelight-devcards-list-group-item { 414 | padding-left: 0px; 415 | padding-right: 0px; 416 | } 417 | 418 | .com-rigsomelight-devcards-container { 419 | margin: auto; 420 | width: 750px; 421 | } 422 | 423 | button.com-rigsomelight-devcards-badge { 424 | border: 1px solid #999; 425 | padding: 3px 9px; 426 | background-color: #ccc; 427 | } 428 | 429 | .com-rigsomelight-devcards-history-control-bar button { 430 | padding: 1px 6px; 431 | } 432 | 433 | .com-rigsomelight-devcards-card-base, 434 | .com-rigsomelight-devcards-card-base-no-pad { 435 | border-radius: 3px; 436 | border: 1px solid rgb(231,234,242); 437 | } 438 | 439 | .com-rigsomelight-devcards-test-line { 440 | padding: 10px 30px; 441 | } 442 | 443 | .com-rigsomelight-devcards-pass { 444 | border-left: 25px solid rgb(199, 225, 160); 445 | } 446 | 447 | .com-rigsomelight-devcards-fail { 448 | border-left: 25px solid rgb(236, 196, 196); 449 | } 450 | 451 | .com-rigsomelight-devcards-error { 452 | border-left: 25px solid rgb(236, 196, 196); 453 | } 454 | 455 | } 456 | 457 | @media (min-width: 800px) { 458 | .com-rigsomelight-devcards-card-hide-border .com-rigsomelight-devcards-markdown pre { 459 | border: 1px solid #e1e1e1; 460 | border-radius: 4px; 461 | padding-left: 14px; 462 | padding-right: 14px; 463 | 464 | margin-left: 0px; 465 | margin-right: 0px; 466 | } 467 | } 468 | 469 | @media (min-width: 1200px) { 470 | .com-rigsomelight-devcards-card-hide-border .com-rigsomelight-devcards-devcard-padding { 471 | padding-left: 30px; 472 | padding-right: 30px; 473 | } 474 | .com-rigsomelight-devcards-brand { 475 | margin-left: 30px; 476 | } 477 | .com-rigsomelight-devcards-list-group-item { 478 | margin-left: 30px; 479 | margin-right: 30px; 480 | } 481 | 482 | .com-rigsomelight-devcards-breadcrumbs { 483 | padding: 0px 30px; 484 | } 485 | 486 | .com-rigsomelight-devcards-container { 487 | margin: auto; 488 | width: 970px; 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /resources/public/devcards/css/com_rigsomelight_devcards_addons.css: -------------------------------------------------------------------------------- 1 | /* full width code examples */ 2 | 3 | body { 4 | overflow-x: hidden; 5 | } 6 | 7 | @media (max-width: 1000px) { 8 | .com-rigsomelight-devcards-card-hide-border 9 | .com-rigsomelight-devcards_rendered-card 10 | .com-rigsomelight-devcards-markdown pre { 11 | margin-right: -3000px; 12 | margin-left: -3000px; 13 | padding-right: 3000px; 14 | padding-left: 3000px; 15 | } 16 | } 17 | 18 | /* default typography */ 19 | .com-rigsomelight-devcards_rendered-card { 20 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 21 | font-size: 16px; 22 | line-height: 1.42857143; 23 | } 24 | 25 | .com-rigsomelight-devcards_rendered-card code { 26 | font-size: 90%; 27 | } 28 | 29 | .com-rigsomelight-devcards_rendered-card h1, 30 | .com-rigsomelight-devcards_rendered-card h2, 31 | .com-rigsomelight-devcards_rendered-card h3, 32 | .com-rigsomelight-devcards_rendered-card h4, 33 | .com-rigsomelight-devcards_rendered-card h5 { 34 | font-weight: 500; 35 | } 36 | 37 | .com-rigsomelight-devcards_rendered-card a { 38 | color: #428bca; 39 | text-decoration: none; 40 | } 41 | -------------------------------------------------------------------------------- /resources/public/devcards/css/com_rigsomelight_edn.css: -------------------------------------------------------------------------------- 1 | .com-rigsomelight-rendered-edn .keyval > .keyword { 2 | color: rgb(196,33,0); 3 | padding-right: 10px; 4 | } 5 | 6 | .com-rigsomelight-rendered-edn .collection { 7 | position: relative; 8 | } 9 | 10 | .com-rigsomelight-rendered-edn .vector, 11 | .com-rigsomelight-rendered-edn .set, 12 | .com-rigsomelight-rendered-edn .seq { 13 | padding-left: 0.9em; 14 | padding-right: 0.9em; 15 | } 16 | 17 | .com-rigsomelight-rendered-edn .set { 18 | padding-left: 1.2em; 19 | } 20 | 21 | 22 | .com-rigsomelight-rendered-edn .collection.map { 23 | padding: 1.8em; 24 | display: inline-block; 25 | vertical-align: top; 26 | } 27 | 28 | .com-rigsomelight-rendered-edn .vector, 29 | .com-rigsomelight-rendered-edn .set, 30 | .com-rigsomelight-rendered-edn .seq { 31 | display: inline-block; 32 | vertical-align: top; 33 | } 34 | 35 | .com-rigsomelight-rendered-edn .vector > .contents { 36 | background-color: rgba(0,0,0,0.01); 37 | } 38 | 39 | .com-rigsomelight-rendered-edn .keyval { 40 | display: inline-block; 41 | } 42 | 43 | .com-rigsomelight-rendered-edn .collection.map > .contents > .separator { 44 | padding-right: 10px; 45 | } 46 | 47 | .com-rigsomelight-rendered-edn .collection .collection > .contents { 48 | /* background-color: rgba(0,0,0,0.02); */ 49 | } 50 | 51 | .com-rigsomelight-rendered-edn .collection .contents > .collection:nth-child(even) { 52 | background-color: rgba(0,0,0,0.04); 53 | } 54 | 55 | .com-rigsomelight-rendered-edn .contents { 56 | display: inline-block; 57 | } 58 | 59 | .com-rigsomelight-rendered-edn .opener, 60 | .com-rigsomelight-rendered-edn .closer { 61 | color: #999; 62 | } 63 | 64 | .com-rigsomelight-rendered-edn .collection.map > .opener, 65 | .com-rigsomelight-rendered-edn .collection.vector > .opener, 66 | .com-rigsomelight-rendered-edn .collection.seq > .opener, 67 | .com-rigsomelight-rendered-edn .collection.set > .opener { 68 | position: absolute; 69 | top: 0px; 70 | left: 3px; 71 | } 72 | .com-rigsomelight-rendered-edn .collection.map > .closer { 73 | position: absolute; 74 | bottom: 0px; 75 | left: 3px; 76 | display: block; 77 | } 78 | 79 | .com-rigsomelight-rendered-edn .collection.vector > .closer, 80 | .com-rigsomelight-rendered-edn .collection.seq > .closer, 81 | .com-rigsomelight-rendered-edn .collection.set > .closer { 82 | position: absolute; 83 | bottom: 0px; 84 | right: 0px; 85 | display: block; 86 | } -------------------------------------------------------------------------------- /resources/public/devcards/css/com_rigsomelight_edn_flex.css: -------------------------------------------------------------------------------- 1 | .com-rigsomelight-rendered-edn .collection { 2 | display: flex; 3 | display: -webkit-flex; 4 | } 5 | 6 | .com-rigsomelight-rendered-edn .keyval { 7 | display: flex; 8 | display: -webkit-flex; 9 | flex-wrap: wrap; 10 | -webkit-flex-wrap: wrap; 11 | } 12 | 13 | .com-rigsomelight-rendered-edn .keyval > .keyword { 14 | color: #a94442; 15 | } 16 | 17 | .com-rigsomelight-rendered-edn .keyval > *:first-child { 18 | margin: 0px 3px; 19 | flex-shrink: 0; 20 | -webkit-flex-shrink: 0; 21 | } 22 | 23 | .com-rigsomelight-rendered-edn .keyval > *:last-child { 24 | margin: 0px 3px; 25 | } 26 | 27 | .com-rigsomelight-rendered-edn .opener { 28 | color: #999; 29 | margin: 0px 4px; 30 | flex-shrink: 0; 31 | -webkit-flex-shrink: 0; 32 | } 33 | 34 | .com-rigsomelight-rendered-edn .closer { 35 | display: flex; 36 | display: -webkit-flex; 37 | flex-direction: column-reverse; 38 | -webkit-flex-direction: column-reverse; 39 | margin: 0px 3px; 40 | color: #999; 41 | } 42 | 43 | .com-rigsomelight-rendered-edn .string { 44 | color: #428bca; 45 | } 46 | 47 | .com-rigsomelight-rendered-edn .string .opener, 48 | .com-rigsomelight-rendered-edn .string .closer { 49 | display: inline; 50 | margin: 0px; 51 | color: #428bca; 52 | } 53 | -------------------------------------------------------------------------------- /resources/public/devcards/css/com_rigsomelight_github_highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header { 18 | color: #998; 19 | font-style: italic; 20 | } 21 | 22 | .hljs-keyword, 23 | .css .rule .hljs-keyword, 24 | .hljs-winutils, 25 | .nginx .hljs-title, 26 | .hljs-subst, 27 | .hljs-request, 28 | .hljs-status { 29 | color: #333; 30 | font-weight: bold; 31 | } 32 | 33 | .hljs-number, 34 | .hljs-hexcolor, 35 | .ruby .hljs-constant { 36 | color: #008080; 37 | } 38 | 39 | .hljs-string, 40 | .hljs-tag .hljs-value, 41 | .hljs-doctag, 42 | .tex .hljs-formula { 43 | color: #d14; 44 | } 45 | 46 | .hljs-title, 47 | .hljs-id, 48 | .scss .hljs-preprocessor { 49 | color: #900; 50 | font-weight: bold; 51 | } 52 | 53 | .hljs-list .hljs-keyword, 54 | .hljs-subst { 55 | font-weight: normal; 56 | } 57 | 58 | .hljs-class .hljs-title, 59 | .hljs-type, 60 | .vhdl .hljs-literal, 61 | .tex .hljs-command { 62 | color: #458; 63 | font-weight: bold; 64 | } 65 | 66 | .hljs-tag, 67 | .hljs-tag .hljs-title, 68 | .hljs-rule .hljs-property, 69 | .django .hljs-tag .hljs-keyword { 70 | color: #000080; 71 | font-weight: normal; 72 | } 73 | 74 | .hljs-attribute, 75 | .hljs-variable, 76 | .lisp .hljs-body, 77 | .hljs-name { 78 | color: #008080; 79 | } 80 | 81 | .hljs-regexp { 82 | color: #009926; 83 | } 84 | 85 | .hljs-symbol, 86 | .ruby .hljs-symbol .hljs-string, 87 | .lisp .hljs-keyword, 88 | .clojure .hljs-keyword, 89 | .scheme .hljs-keyword, 90 | .tex .hljs-special, 91 | .hljs-prompt { 92 | color: #990073; 93 | } 94 | 95 | .hljs-built_in { 96 | color: #0086b3; 97 | } 98 | 99 | .hljs-preprocessor, 100 | .hljs-pragma, 101 | .hljs-pi, 102 | .hljs-doctype, 103 | .hljs-shebang, 104 | .hljs-cdata { 105 | color: #999; 106 | font-weight: bold; 107 | } 108 | 109 | .hljs-deletion { 110 | background: #fdd; 111 | } 112 | 113 | .hljs-addition { 114 | background: #dfd; 115 | } 116 | 117 | .diff .hljs-change { 118 | background: #0086b3; 119 | } 120 | 121 | .hljs-chunk { 122 | color: #aaa; 123 | } 124 | -------------------------------------------------------------------------------- /resources/public/devcards/css/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #f0f0f0; 12 | -webkit-text-size-adjust: none; 13 | } 14 | 15 | .hljs, 16 | .hljs-subst, 17 | .hljs-tag .hljs-title, 18 | .nginx .hljs-title { 19 | color: black; 20 | } 21 | 22 | .hljs-string, 23 | .hljs-title, 24 | .hljs-constant, 25 | .hljs-parent, 26 | .hljs-tag .hljs-value, 27 | .hljs-rule .hljs-value, 28 | .hljs-preprocessor, 29 | .hljs-pragma, 30 | .hljs-name, 31 | .haml .hljs-symbol, 32 | .ruby .hljs-symbol, 33 | .ruby .hljs-symbol .hljs-string, 34 | .hljs-template_tag, 35 | .django .hljs-variable, 36 | .smalltalk .hljs-class, 37 | .hljs-addition, 38 | .hljs-flow, 39 | .hljs-stream, 40 | .bash .hljs-variable, 41 | .pf .hljs-variable, 42 | .apache .hljs-tag, 43 | .apache .hljs-cbracket, 44 | .tex .hljs-command, 45 | .tex .hljs-special, 46 | .erlang_repl .hljs-function_or_atom, 47 | .asciidoc .hljs-header, 48 | .markdown .hljs-header, 49 | .coffeescript .hljs-attribute, 50 | .tp .hljs-variable { 51 | color: #800; 52 | } 53 | 54 | .smartquote, 55 | .hljs-comment, 56 | .hljs-annotation, 57 | .diff .hljs-header, 58 | .hljs-chunk, 59 | .asciidoc .hljs-blockquote, 60 | .markdown .hljs-blockquote { 61 | color: #888; 62 | } 63 | 64 | .hljs-number, 65 | .hljs-date, 66 | .hljs-regexp, 67 | .hljs-literal, 68 | .hljs-hexcolor, 69 | .smalltalk .hljs-symbol, 70 | .smalltalk .hljs-char, 71 | .go .hljs-constant, 72 | .hljs-change, 73 | .lasso .hljs-variable, 74 | .makefile .hljs-variable, 75 | .asciidoc .hljs-bullet, 76 | .markdown .hljs-bullet, 77 | .asciidoc .hljs-link_url, 78 | .markdown .hljs-link_url { 79 | color: #080; 80 | } 81 | 82 | .hljs-label, 83 | .ruby .hljs-string, 84 | .hljs-decorator, 85 | .hljs-filter .hljs-argument, 86 | .hljs-localvars, 87 | .hljs-array, 88 | .hljs-attr_selector, 89 | .hljs-important, 90 | .hljs-pseudo, 91 | .hljs-pi, 92 | .haml .hljs-bullet, 93 | .hljs-doctype, 94 | .hljs-deletion, 95 | .hljs-envvar, 96 | .hljs-shebang, 97 | .apache .hljs-sqbracket, 98 | .nginx .hljs-built_in, 99 | .tex .hljs-formula, 100 | .erlang_repl .hljs-reserved, 101 | .hljs-prompt, 102 | .asciidoc .hljs-link_label, 103 | .markdown .hljs-link_label, 104 | .vhdl .hljs-attribute, 105 | .clojure .hljs-attribute, 106 | .asciidoc .hljs-attribute, 107 | .lasso .hljs-attribute, 108 | .coffeescript .hljs-property, 109 | .hljs-phony { 110 | color: #88f; 111 | } 112 | 113 | .hljs-keyword, 114 | .hljs-id, 115 | .hljs-title, 116 | .hljs-built_in, 117 | .css .hljs-tag, 118 | .hljs-doctag, 119 | .smalltalk .hljs-class, 120 | .hljs-winutils, 121 | .bash .hljs-variable, 122 | .pf .hljs-variable, 123 | .apache .hljs-tag, 124 | .hljs-type, 125 | .hljs-typename, 126 | .tex .hljs-command, 127 | .asciidoc .hljs-strong, 128 | .markdown .hljs-strong, 129 | .hljs-request, 130 | .hljs-status, 131 | .tp .hljs-data, 132 | .tp .hljs-io { 133 | font-weight: bold; 134 | } 135 | 136 | .asciidoc .hljs-emphasis, 137 | .markdown .hljs-emphasis, 138 | .tp .hljs-units { 139 | font-style: italic; 140 | } 141 | 142 | .nginx .hljs-built_in { 143 | font-weight: normal; 144 | } 145 | 146 | .coffeescript .javascript, 147 | .javascript .xml, 148 | .lasso .markup, 149 | .tex .hljs-formula, 150 | .xml .javascript, 151 | .xml .vbscript, 152 | .xml .css, 153 | .xml .hljs-cdata { 154 | opacity: 0.5; 155 | } 156 | -------------------------------------------------------------------------------- /resources/public/devcards/css/zenburn.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov 4 | based on dark.css by Ivan Sagalaev 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | /* background: #3f3f3f; */ 13 | color: #dcdcdc; 14 | -webkit-text-size-adjust: none; 15 | } 16 | 17 | .hljs-keyword, 18 | .hljs-tag, 19 | .css .hljs-class, 20 | .css .hljs-id, 21 | .lisp .hljs-title, 22 | .nginx .hljs-title, 23 | .hljs-request, 24 | .hljs-status, 25 | .clojure .hljs-attribute { 26 | color: #e3ceab; 27 | } 28 | 29 | .django .hljs-template_tag, 30 | .django .hljs-variable, 31 | .django .hljs-filter .hljs-argument { 32 | color: #dcdcdc; 33 | } 34 | 35 | .hljs-number, 36 | .hljs-date { 37 | color: #8cd0d3; 38 | } 39 | 40 | .dos .hljs-envvar, 41 | .dos .hljs-stream, 42 | .hljs-variable, 43 | .apache .hljs-sqbracket { 44 | color: #efdcbc; 45 | } 46 | 47 | .dos .hljs-flow, 48 | .diff .hljs-change, 49 | .python .exception, 50 | .python .hljs-built_in, 51 | .hljs-literal, 52 | .tex .hljs-special { 53 | color: #efefaf; 54 | } 55 | 56 | .diff .hljs-chunk, 57 | .hljs-subst { 58 | color: #8f8f8f; 59 | } 60 | 61 | .dos .hljs-keyword, 62 | .hljs-decorator, 63 | .hljs-title, 64 | .hljs-type, 65 | .diff .hljs-header, 66 | .ruby .hljs-class .hljs-parent, 67 | .apache .hljs-tag, 68 | .nginx .hljs-built_in, 69 | .tex .hljs-command, 70 | .hljs-prompt { 71 | color: #efef8f; 72 | } 73 | 74 | .dos .hljs-winutils, 75 | .ruby .hljs-symbol, 76 | .ruby .hljs-symbol .hljs-string, 77 | .ruby .hljs-string { 78 | color: #dca3a3; 79 | } 80 | 81 | .diff .hljs-deletion, 82 | .hljs-string, 83 | .hljs-tag .hljs-value, 84 | .hljs-preprocessor, 85 | .hljs-pragma, 86 | .hljs-built_in, 87 | .hljs-javadoc, 88 | .smalltalk .hljs-class, 89 | .smalltalk .hljs-localvars, 90 | .smalltalk .hljs-array, 91 | .css .hljs-rules .hljs-value, 92 | .hljs-attr_selector, 93 | .hljs-pseudo, 94 | .apache .hljs-cbracket, 95 | .tex .hljs-formula, 96 | .coffeescript .hljs-attribute { 97 | color: #cc9393; 98 | } 99 | 100 | .hljs-shebang, 101 | .diff .hljs-addition, 102 | .hljs-comment, 103 | .hljs-annotation, 104 | .hljs-pi, 105 | .hljs-doctype { 106 | color: #7f9f7f; 107 | } 108 | 109 | .coffeescript .javascript, 110 | .javascript .xml, 111 | .tex .hljs-formula, 112 | .xml .javascript, 113 | .xml .vbscript, 114 | .xml .css, 115 | .xml .hljs-cdata { 116 | opacity: 0.5; 117 | } 118 | 119 | -------------------------------------------------------------------------------- /site/two-zero.css: -------------------------------------------------------------------------------- 1 | .board-area { 2 | background-color: rgb(187,173,160); 3 | width: 499px; 4 | height: 499px; 5 | border-radius: 6px; 6 | position: relative; 7 | 8 | /* initialize as 3d */ 9 | -webkit-transform: translate3d(0,0,0); 10 | -moz-transform: translate3d(0,0,0); 11 | transform: translate3d(0,0,0); 12 | } 13 | 14 | .board-area-one-row { 15 | height: 136px; 16 | } 17 | 18 | .one-row-board .board-area { 19 | height: 136px; 20 | overflow: hidden; 21 | } 22 | 23 | .cell-pos { 24 | height: 106px; 25 | width: 106px; 26 | position:absolute; 27 | border-radius: 4px; 28 | 29 | -webkit-transition: all 0.12s; 30 | transition: all 0.12s; 31 | 32 | /* initialize as 3d */ 33 | -webkit-transform: translate3d(0,0,0); 34 | -moz-transform: translate3d(0,0,0); 35 | transform: translate3d(0,0,0); 36 | } 37 | 38 | .cell-empty { 39 | background-color: rgb(205,193,180); 40 | } 41 | 42 | .cell { 43 | background-color: rgb(205,193,180); 44 | height: 106px; 45 | width: 106px; 46 | /* position: relative; */ 47 | border-radius: 4px; 48 | font-family: "Helvetica Neue", Arial, sans-serif; 49 | font-size: 55px; 50 | line-height: 102px; 51 | font-weight: bold; 52 | text-align: center; 53 | vertical-align: middle; 54 | 55 | /* initialize as 3d */ 56 | -webkit-transform: translate3d(0,0,0); 57 | -moz-transform: translate3d(0,0,0); 58 | transform: translate3d(0,0,0); 59 | } 60 | 61 | .cell.highlight { 62 | -webkit-animation: highlight 0.1s; 63 | } 64 | 65 | @-webkit-keyframes highlight { 66 | 0% { -webkit-transform: scale3d(1.2,1.2,1.0); 67 | opacity: 0.7;} 68 | 100% { -webkit-transform: scale3d(1.0,1.0,1.0); 69 | opacity: 1.0;} 70 | } 71 | 72 | .cell.reveal { 73 | -webkit-animation: reveal 0.1s; 74 | } 75 | 76 | @-webkit-keyframes reveal { 77 | 0% { -webkit-transform: scale3d(0.1,0.1,1.0); 78 | opacity: 0.1; 79 | } 80 | 100% { -webkit-transform: scale3d(1.0,1.0,1.0); 81 | opacity: 1.0;} 82 | } 83 | 84 | .pos-top-0 { top: 15px; } 85 | .pos-top-1 { top: 136px; } 86 | .pos-top-2 { top: 257px; } 87 | .pos-top-3 { top: 378px; } 88 | 89 | .pos-left-0 { left: 15px; } 90 | .pos-left-1 { left: 136px; } 91 | .pos-left-2 { left: 257px; } 92 | .pos-left-3 { left: 378px; } 93 | 94 | 95 | 96 | .cell-num-2 { 97 | background-color: rgb(238, 228,218); 98 | color: rgb(110,102,93); 99 | } 100 | 101 | .cell-num-4 { 102 | background-color: rgb(237, 224,200); 103 | color: rgb(119,110,101); 104 | } 105 | 106 | .cell-num-8 { 107 | background-color: rgb(242, 177, 121); 108 | color: rgb(249,246,242); 109 | } 110 | 111 | .cell-num-16 { 112 | background-color: rgb(245, 149, 99); 113 | color: rgb(249,246,242); 114 | } 115 | 116 | .cell-num-32 { 117 | background-color: rgb(245, 124, 95); 118 | color: rgb(249,246,242); 119 | } 120 | 121 | .cell-num-64 { 122 | background-color: rgb(246, 94, 59); 123 | color: rgb(249,246,242); 124 | } 125 | 126 | .cell-num-128 { 127 | background-color: rgb(237, 207,114); 128 | color: rgb(249,246,242); 129 | font-size: 48px; 130 | } 131 | 132 | .cell-num-256 { 133 | background-color: rgb(237, 204, 97); 134 | color: rgb(249,246,242); 135 | font-size: 48px; 136 | border: 1px solid rgba(238, 228, 218, 0.5); 137 | box-shadow: 0 0 25px 5px rgb(237, 204, 97); 138 | } 139 | 140 | .cell-num-512 { 141 | background-color: rgb(237, 204, 97); 142 | color: rgb(249,246,242); 143 | font-size: 48px; 144 | border: 1px solid rgba(238, 228, 218, 0.5); 145 | box-shadow: 0 0 25px 5px rgb(237, 204, 97); 146 | } 147 | 148 | .cell-num-1024 { 149 | background-color: rgb(237, 204, 97); 150 | color: rgb(249,246,242); 151 | font-size: 40px; 152 | border: 1px solid rgba(238, 228, 218, 0.5); 153 | box-shadow: 0 0 25px 5px rgb(237, 204, 97); 154 | } 155 | 156 | .cell-num-2048 { 157 | background-color: rgb(237, 204, 97); 158 | color: rgb(249,246,242); 159 | font-size: 40px; 160 | border: 1px solid rgba(238, 228, 218, 0.5); 161 | box-shadow: 0 0 25px 5px rgb(237, 204, 97); 162 | } 163 | 164 | 165 | @media (max-width: 480px){ 166 | .board-area { 167 | width: 280px; 168 | height: 280px; 169 | } 170 | .cell-pos, .cell { 171 | width: 60px; 172 | height: 60px; 173 | } 174 | 175 | .cell { 176 | font-size: 25px; 177 | line-height: 60px; 178 | } 179 | 180 | .cell-num-128, .cell-num-256, .cell-num-512 { 181 | font-size: 26px; 182 | } 183 | 184 | .cell-num-1024, .cell-num-2048 { 185 | font-size: 21px; 186 | } 187 | 188 | .pos-top-0 { top: 8px; } 189 | .pos-top-1 { top: 76px; } 190 | .pos-top-2 { top: 144px; } 191 | .pos-top-3 { top: 212px; } 192 | 193 | .pos-left-0 { left: 8px; } 194 | .pos-left-1 { left: 76px; } 195 | .pos-left-2 { left: 144px; } 196 | .pos-left-3 { left: 212px; } 197 | 198 | } 199 | 200 | 201 | -------------------------------------------------------------------------------- /src/deps.cljs: -------------------------------------------------------------------------------- 1 | {:foreign-libs 2 | [{:file "devcards/js_libs/highlight.pack.js" 3 | :provides ["devcards-syntax-highlighter"] 4 | :global-exports {devcards-syntax-highlighter DevcardsSyntaxHighlighter}} 5 | {:file "devcards/js_libs/marked.min.js" 6 | :provides ["devcards-marked"] 7 | :global-exports {devcards-marked DevcardsMarked}}] 8 | :externs ["devcards/js_libs/highlight.ext.js" 9 | "devcards/js_libs/marked.ext.js"] 10 | :npm-deps {"create-react-class" "15.6.3" 11 | "react" "16.13.1" 12 | "react-dom" "16.13.1"}} 13 | -------------------------------------------------------------------------------- /src/devcards/core.clj: -------------------------------------------------------------------------------- 1 | (ns devcards.core 2 | (:require 3 | [devcards.util.utils :as utils] 4 | [cljs.compiler :refer (munge)] 5 | [cljs.analyzer :as ana] 6 | [cljs.analyzer.api :as ana-api] 7 | [cljs.repl] 8 | [cljs.test] 9 | [cljs.env] 10 | [clojure.pprint :refer [with-pprint-dispatch code-dispatch pprint]] 11 | [clojure.java.io :as io]) 12 | (:refer-clojure :exclude (munge defonce))) 13 | 14 | (defmacro start-devcard-ui! 15 | ([] 16 | `(devcards.core/start-devcard-ui!*)) 17 | ([options] 18 | `(devcards.core/start-devcard-ui!* ~options))) 19 | 20 | #_(defmacro start-single-card-ui! [] 21 | (enable-devcards!) 22 | `(devcards.core/start-single-card-ui!*)) 23 | 24 | (defmacro do [& exprs] 25 | (when (utils/devcards-active?) 26 | `(do ~@exprs))) 27 | 28 | (defn get-ns [env] 29 | (-> env :ns :name name munge)) 30 | 31 | (defn name->path [env vname] 32 | [(keyword (get-ns env)) (keyword vname)]) 33 | 34 | ;; it's nice to have this low level card i think 35 | (defmacro defcard* 36 | ([vname expr] 37 | (when (utils/devcards-active?) 38 | `(devcards.core/register-card ~{:path (name->path &env vname) 39 | :func `(fn [] ~expr)})))) 40 | 41 | (defn card 42 | ([vname docu main-obj initial-data options] 43 | `(devcards.core/defcard* ~(symbol (name vname)) 44 | (devcards.core/card-base 45 | { :name ~(name vname) 46 | :documentation ~docu 47 | :main-obj ~main-obj 48 | :initial-data ~initial-data 49 | :options ~options}))) 50 | ([vname docu main-obj initial-data] 51 | (card vname docu main-obj initial-data {})) 52 | ([vname docu main-obj] 53 | (card vname docu main-obj {} {})) 54 | ([vname docu] 55 | (card vname docu nil {} {}))) 56 | 57 | (defn optional-name [exprs default-name] 58 | (if (instance? clojure.lang.Named (first exprs)) [(first exprs) (rest exprs)] 59 | [default-name exprs])) 60 | 61 | (defn optional-doc [xs] 62 | (if (string? (first xs)) [(first xs) (rest xs)] [nil xs])) 63 | 64 | (defn parse-args [xs default-name] 65 | (let [[vname xs] (optional-name xs default-name) 66 | [docu xs] (optional-doc xs)] 67 | (concat [vname docu] xs))) 68 | 69 | (defn merge-options [lit-opt-map options] 70 | `(merge ~lit-opt-map (devcards.core/assert-options-map ~options))) 71 | 72 | (defn parse-card-args [xs default-name] 73 | (let [[vname docu main-obj initial-data options :as res] 74 | (parse-args xs default-name)] 75 | (if (= vname default-name) 76 | [vname docu main-obj initial-data (merge-options {:heading false} options)] 77 | res))) 78 | 79 | (defmacro defcard [& expr] 80 | (when (utils/devcards-active?) 81 | (apply devcards.core/card (parse-card-args expr 'card)))) 82 | 83 | (defmacro dom-node [body] 84 | (when (utils/devcards-active?) 85 | `(devcards.core/dom-node* ~body))) 86 | 87 | (defmacro hist-recorder [body] 88 | (when (utils/devcards-active?) 89 | `(devcards.core/hist-recorder* ~body))) 90 | 91 | ;; should probably get rid of this 92 | ;; there is a prize for a leaner api 93 | (defmacro doc [& body] 94 | (when (utils/devcards-active?) 95 | `(devcards.core/markdown->react ~@body))) 96 | 97 | ;; should probably get rid of this as well 98 | (defmacro edn [body] 99 | (when (utils/devcards-active?) 100 | `(devcards.util.edn-renderer/html-edn ~body))) 101 | 102 | ;; is this really needed now? 103 | (defmacro defcard-doc [& exprs] 104 | (when (utils/devcards-active?) 105 | `(devcards.core/defcard (doc ~@exprs) {} {:hide-border true}))) 106 | 107 | ;; this really needs to go now 108 | (defmacro noframe-doc [& exprs] 109 | (when (utils/devcards-active?) 110 | `(devcards.core/defcard (doc ~@exprs) {} {:frame false}))) 111 | 112 | ;; currently reflects the most common pattern for creating idevcards 113 | ;; currently to meant to only be consumed internally 114 | (defmacro create-idevcard [main-obj-body default-options-literal] 115 | (when (utils/devcards-active?) 116 | `(reify devcards.core/IDevcardOptions 117 | (~'-devcard-options [this# devcard-opts#] 118 | (assoc devcard-opts# 119 | :main-obj ~main-obj-body 120 | 121 | :options (merge ~default-options-literal 122 | (devcards.core/assert-options-map (:options devcard-opts#)))))))) 123 | 124 | ;; testing 125 | 126 | (defmacro tests [& parts] 127 | (when (utils/devcards-active?) 128 | `(devcards.core/test-card 129 | ~@(map (fn [p] (if (string? p) 130 | `(fn [] (devcards.core/test-doc ~p)) 131 | `(fn [] ~p))) parts)))) 132 | 133 | (defmacro deftest [vname & parts] 134 | `(do 135 | ~(when (utils/devcards-active?) 136 | `(devcards.core/defcard ~vname 137 | (devcards.core/tests ~@parts))) 138 | (cljs.test/deftest ~vname 139 | ~@parts))) 140 | 141 | ;; reagent helpers 142 | 143 | (defmacro reagent [body] 144 | `(create-idevcard 145 | (let [v# ~body] 146 | (if (fn? v#) 147 | (fn [data-atom# owner#] (reagent.core/as-element [v# data-atom# owner#])) 148 | (reagent.core/as-element v#))) 149 | {})) 150 | 151 | (defmacro defcard-rg [& exprs] 152 | (when (utils/devcards-active?) 153 | (let [[vname docu main initial-data options] (parse-card-args exprs 'reagent-card)] 154 | (card vname docu `(devcards.core/reagent ~main) initial-data (assoc 155 | options 156 | :watch-atom false))))) 157 | 158 | ;; om helpers 159 | 160 | (defmacro om-root 161 | ([om-comp-fn om-options] 162 | (when (utils/devcards-active?) 163 | `(create-idevcard 164 | (devcards.core/dom-node* 165 | (fn [data-atom# node#] 166 | (om.core/root ~om-comp-fn data-atom# 167 | (merge ~om-options 168 | {:target node#})))) 169 | {:watch-atom true}))) 170 | ([om-comp-fn] 171 | (when (utils/devcards-active?) 172 | `(om-root ~om-comp-fn {})))) 173 | 174 | (defmacro defcard-om [& exprs] 175 | (when (utils/devcards-active?) 176 | (let [[vname docu om-comp-fn initial-data om-options options] (parse-card-args exprs 'om-root-card)] 177 | (card vname docu `(om-root ~om-comp-fn ~om-options) initial-data options)))) 178 | 179 | ;; om next helpers 180 | 181 | (defmacro om-next-root 182 | ([om-next-comp om-next-reconciler] 183 | (when (utils/devcards-active?) 184 | `(create-idevcard 185 | (devcards.core/dom-node* 186 | (fn [data-atom# node#] 187 | (let [state# (if (map? ~om-next-reconciler) (atom ~om-next-reconciler) data-atom#) 188 | reconciler# (if (om.next/reconciler? ~om-next-reconciler) 189 | ~om-next-reconciler 190 | (om.next/reconciler {:state state# 191 | :parser (om.next/parser {:read (fn [] {:value data-atom#})})}))] 192 | (om.next/add-root! reconciler# ~om-next-comp node#)))) 193 | {:watch-atom false}))) 194 | ([om-next-comp] 195 | (when (utils/devcards-active?) 196 | `(om-next-root ~om-next-comp nil)))) 197 | 198 | (defmacro defcard-om-next [& exprs] 199 | (when (utils/devcards-active?) 200 | (let [[vname docu om-next-comp om-next-reconciler initial-data options] (parse-card-args exprs 'om-next-root-card)] 201 | (card vname docu `(om-next-root ~om-next-comp ~om-next-reconciler) initial-data options)))) 202 | 203 | ;; formatting for markdown cards 204 | 205 | (defmacro pprint-str [obj] 206 | (when (utils/devcards-active?) 207 | `(devcards.util.utils/pprint-str ~obj))) 208 | 209 | (defmacro pprint-code [obj] 210 | (when (utils/devcards-active?) 211 | `(devcards.util.utils/pprint-code ~obj))) 212 | 213 | (defmacro mkdn-code [body] `(str "\n```clojure\n" ~body "\n```\n")) 214 | 215 | (defmacro mkdn-pprint-code [obj] 216 | (when (utils/devcards-active?) 217 | `(mkdn-code 218 | (devcards.util.utils/pprint-code ~obj)))) 219 | 220 | (defmacro mkdn-pprint-source [obj] 221 | (when (utils/devcards-active?) 222 | `(mkdn-code 223 | ~(or (cljs.repl/source-fn &env obj) (str "Source not found"))))) 224 | 225 | (defmacro mkdn-pprint-str [obj] 226 | (when (utils/devcards-active?) 227 | `(mkdn-code 228 | (devcards.util.utils/pprint-str ~obj)))) 229 | 230 | (defmacro all-front-matter-meta [filter-keyword] 231 | (vec 232 | (filter 233 | (or filter-keyword :front-matter) 234 | (map 235 | (fn [x] (assoc (meta x) 236 | :namespace `(quote ~x) 237 | :munged-namespace `(quote ~(munge x)))) 238 | (ana-api/all-ns))))) 239 | -------------------------------------------------------------------------------- /src/devcards/js_libs/highlight.ext.js: -------------------------------------------------------------------------------- 1 | // copied from https://github.com/isagalaev/highlight.js/blob/6662b/src/highlight.js#L699-L711 2 | 3 | var DevcardsSyntaxHighlighter = {}; 4 | DevcardsSyntaxHighlighter.highlight = function (name, value, ignore_illegals, continuation) {}; 5 | DevcardsSyntaxHighlighter.highlightAuto = function (text, languageSubset) {}; 6 | DevcardsSyntaxHighlighter.fixMarkup = function (value) {}; 7 | DevcardsSyntaxHighlighter.highlightBlock = function (block) {}; 8 | DevcardsSyntaxHighlighter.configure = function (user_options) {}; 9 | DevcardsSyntaxHighlighter.initHighlighting = function () {}; 10 | DevcardsSyntaxHighlighter.initHighlightingOnLoad = function () {}; 11 | DevcardsSyntaxHighlighter.registerLanguage = function (name, language) {}; 12 | DevcardsSyntaxHighlighter.listLanguages = function () {}; 13 | DevcardsSyntaxHighlighter.getLanguage = function (name) {}; 14 | DevcardsSyntaxHighlighter.inherit = function (parent, obj) {}; 15 | -------------------------------------------------------------------------------- /src/devcards/js_libs/highlight.pack.js: -------------------------------------------------------------------------------- 1 | !function(e){"undefined"!=typeof exports?e(exports):(window.DevcardsSyntaxHighlighter=e({}),"function"==typeof define&&define.amd&&define([],function(){return window.DevcardsSyntaxHighlighter}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){var n=(e.className+" "+(e.parentNode?e.parentNode.className:"")).split(/\s+/);return n=n.map(function(e){return e.replace(/^lang(uage)?-/,"")}),n.filter(function(e){return N(e)||/no(-?)highlight/.test(e)})[0]}function o(e,n){var t={};for(var r in e)t[r]=e[r];if(n)for(var r in n)t[r]=n[r];return t}function i(e){var n=[];return function r(e,a){for(var o=e.firstChild;o;o=o.nextSibling)3==o.nodeType?a+=o.nodeValue.length:1==o.nodeType&&(n.push({event:"start",offset:a,node:o}),a=r(o,a),t(o).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:o}));return a}(e,0),n}function c(e,r,a){function o(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function c(e){l+=""}function u(e){("start"==e.event?i:c)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=o();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(c);do u(g.splice(0,1)[0]),g=o();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(i)}else"start"==g[0].event?f.push(g[0].node):f.pop(),u(g.splice(0,1)[0])}return l+n(a.substr(s))}function u(e){function n(e){return e&&e.source||e}function t(t,r){return RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var c={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");c[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):Object.keys(a.k).forEach(function(e){u(e,a.k[e])}),a.k=c}a.lR=t(a.l||/\b[A-Za-z0-9_]+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function s(e,t,a,o){function i(e,n){for(var t=0;t";return o+=e+'">',o+n+i}function d(){if(!w.k)return n(y);var e="",t=0;w.lR.lastIndex=0;for(var r=w.lR.exec(y);r;){e+=n(y.substr(t,r.index-t));var a=g(w,r);a?(B+=a[1],e+=p(a[0],n(r[0]))):e+=n(r[0]),t=w.lR.lastIndex,r=w.lR.exec(y)}return e+n(y.substr(t))}function h(){if(w.sL&&!R[w.sL])return n(y);var e=w.sL?s(w.sL,y,!0,L[w.sL]):l(y);return w.r>0&&(B+=e.r),"continuous"==w.subLanguageMode&&(L[w.sL]=e.top),p(e.language,e.value,!1,!0)}function v(){return void 0!==w.sL?h():d()}function b(e,t){var r=e.cN?p(e.cN,"",!0):"";e.rB?(M+=r,y=""):e.eB?(M+=n(t)+r,y=""):(M+=r,y=t),w=Object.create(e,{parent:{value:w}})}function m(e,t){if(y+=e,void 0===t)return M+=v(),0;var r=i(t,w);if(r)return M+=v(),b(r,t),r.rB?0:t.length;var a=c(w,t);if(a){var o=w;o.rE||o.eE||(y+=t),M+=v();do w.cN&&(M+=""),B+=w.r,w=w.parent;while(w!=a.parent);return o.eE&&(M+=n(t)),y="",a.starts&&b(a.starts,""),o.rE?0:t.length}if(f(t,w))throw new Error('Illegal lexeme "'+t+'" for mode "'+(w.cN||"")+'"');return y+=t,t.length||1}var x=N(e);if(!x)throw new Error('Unknown language: "'+e+'"');u(x);for(var w=o||x,L={},M="",k=w;k!=x;k=k.parent)k.cN&&(M=p(k.cN,"",!0)+M);var y="",B=0;try{for(var C,j,I=0;;){if(w.t.lastIndex=I,C=w.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}m(t.substr(I));for(var k=w;k.parent;k=k.parent)k.cN&&(M+="");return{r:B,value:M,language:e,top:w}}catch(A){if(-1!=A.message.indexOf("Illegal"))return{r:0,value:n(t)};throw A}}function l(e,t){t=t||E.languages||Object.keys(R);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(N(n)){var t=s(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function f(e){return E.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,E.tabReplace)})),E.useBR&&(e=e.replace(/\n/g,"
")),e}function g(e,n,t){var r=n?x[n]:t,a=[e.trim()];return e.match(/(\s|^)hljs(\s|$)/)||a.push("hljs"),r&&a.push(r),a.join(" ").trim()}function p(e){var n=a(e);if(!/no(-?)highlight/.test(n)){var t;E.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?s(n,r,!0):l(r),u=i(t);if(u.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(u,i(p),r)}o.value=f(o.value),e.innerHTML=o.value,e.className=g(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){E=o(E,e)}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",h,!1),addEventListener("load",h,!1)}function b(n,t){var r=R[n]=t(e);r.aliases&&r.aliases.forEach(function(e){x[e]=n})}function m(){return Object.keys(R)}function N(e){return R[e]||R[x[e]]}var E={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},R={},x={};return e.highlight=s,e.highlightAuto=l,e.fixMarkup=f,e.highlightBlock=p,e.configure=d,e.initHighlighting=h,e.initHighlightingOnLoad=v,e.registerLanguage=b,e.listLanguages=m,e.getLanguage=N,e.inherit=o,e.IR="[a-zA-Z][a-zA-Z0-9_]*",e.UIR="[a-zA-Z_][a-zA-Z0-9_]*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.CLCM={cN:"comment",b:"//",e:"$",c:[e.PWM]},e.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[e.PWM]},e.HCM={cN:"comment",b:"#",e:"$",c:[e.PWM]},e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});DevcardsSyntaxHighlighter.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",c={cN:"yardoctag",b:"@[A-Za-z]+"},a={cN:"value",b:"#<",e:">"},s={cN:"comment",v:[{b:"#",e:"$",c:[c]},{b:"^\\=begin",e:"^\\=end",c:[c],r:10},{b:"^__END__",e:"\\n$"}]},n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},i={cN:"params",b:"\\(",e:"\\)",k:r},d=[t,a,s,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]},s]},{cN:"function",bK:"def",e:" |$|;",r:0,c:[e.inherit(e.TM,{b:b}),i,s]},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[a,s,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];n.c=d,i.c=d;var l="[>?]>",u="[\\w#]+\\(\\w+\\):\\d+:\\d+>",N="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",o=[{b:/^\s*=>/,cN:"status",starts:{e:"$",c:d}},{cN:"prompt",b:"^("+l+"|"+u+"|"+N+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,c:[s].concat(o).concat(d)}});DevcardsSyntaxHighlighter.registerLanguage("javascript",function(r){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document"},c:[{cN:"pi",r:10,v:[{b:/^\s*('|")use strict('|")/},{b:/^\s*('|")use asm('|")/}]},r.ASM,r.QSM,r.CLCM,r.CBCM,r.CNM,{b:"("+r.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[r.CLCM,r.CBCM,r.RM,{b:/;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[r.inherit(r.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[r.CLCM,r.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+r.IR,r:0}]}});DevcardsSyntaxHighlighter.registerLanguage("clojure",function(e){var t={built_in:"def cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},c=e.inherit(e.QSM,{i:null}),i={cN:"comment",b:";",e:"$",r:0},d={cN:"literal",b:/\b(true|false|nil)\b/},l={cN:"collection",b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p={cN:"comment",b:"\\^\\{",e:"\\}"},u={cN:"attribute",b:"[:]"+n},f={cN:"list",b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"keyword",b:n,starts:h},b=[f,c,m,p,i,u,l,s,d,o];return f.c=[{cN:"comment",b:"comment"},y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,c,m,p,i,u,l,s,d]}});DevcardsSyntaxHighlighter.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)\}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,e.NM,s,a,t]}});DevcardsSyntaxHighlighter.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",a={cN:"function",b:c+"\\(",rB:!0,eE:!0,e:"\\("};return{cI:!0,i:"[=/|']",c:[e.CBCM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[a,e.ASM,e.QSM,e.CSSNM]}]},{cN:"tag",b:c,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[e.CBCM,{cN:"rule",b:"[^\\s]",rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:!0,i:"[^\\s]",starts:{cN:"value",eW:!0,eE:!0,c:[a,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]}]}]}});DevcardsSyntaxHighlighter.registerLanguage("clojure-repl",function(){return{c:[{cN:"prompt",b:/^([\w.-]+|\s*#_)=>/,starts:{e:/$/,sL:"clojure",subLanguageMode:"continuous"}}]}});DevcardsSyntaxHighlighter.registerLanguage("markdown",function(){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:"^\\[.+\\]:",rB:!0,c:[{cN:"link_reference",b:"\\[",e:"\\]:",eB:!0,eE:!0,starts:{cN:"link_url",e:"$"}}]}]}});DevcardsSyntaxHighlighter.registerLanguage("xml",function(){var t="[A-Za-z0-9\\._:-]+",e={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"},c={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[c],starts:{e:"",rE:!0,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[c],starts:{e:"",rE:!0,sL:"javascript"}},e,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:/[^ \/><\n\t]+/,r:0},c]}]}});DevcardsSyntaxHighlighter.registerLanguage("java",function(e){var a=e.UIR+"(<"+e.UIR+">)?",t="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",c="(\\b(0b[01_]+)|\\b0[xX][a-fA-F0-9_]+|(\\b[\\d_]+(\\.[\\d_]*)?|\\.[\\d_]+)([eE][-+]?\\d+)?)[lLfF]?",r={cN:"number",b:c,r:0};return{aliases:["jsp"],k:t,i:/<\//,c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",r:0,c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}]},e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return",r:0},{cN:"function",b:"("+a+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:t,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},r,{cN:"annotation",b:"@[A-Za-z]+"}]}});DevcardsSyntaxHighlighter.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],l={cN:"value",e:",",eW:!0,eE:!0,c:i,k:t},c={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:l}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(l,{cN:null})],i:"\\S"};return i.splice(i.length,0,c,n),{c:i,k:t,i:"\\S"}}); 2 | -------------------------------------------------------------------------------- /src/devcards/js_libs/marked.ext.js: -------------------------------------------------------------------------------- 1 | 2 | function DevcardsMarked() {}; 3 | 4 | DevcardsMarked.options = {}; 5 | DevcardsMarked.defaults = {}; 6 | 7 | DevcardsMarked.parser = function() {}; 8 | DevcardsMarked.parse = function() {}; 9 | DevcardsMarked.Parser = function() {}; 10 | 11 | DevcardsMarked.lexer = function() {}; 12 | DevcardsMarked.Lexer = function() {}; 13 | 14 | DevcardsMarked.inlineLexer = function() {}; 15 | DevcardsMarked.InlineLexer = function() {}; 16 | 17 | DevcardsMarked.Renderer = function() {}; 18 | DevcardsMarked.setOptions = function() {}; 19 | -------------------------------------------------------------------------------- /src/devcards/js_libs/marked.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/markedjs/marked 5 | */ 6 | !function(e){"use strict";var k={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:g,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,nptable:g,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?\\?>\\n*|\\n*|\\n*|)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)|(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,table:g,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/,text:/^[^\n]+/};function a(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||d.defaults,this.rules=k.normal,this.options.pedantic?this.rules=k.pedantic:this.options.gfm&&(this.options.tables?this.rules=k.tables:this.rules=k.gfm)}k._label=/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,k._title=/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/,k.def=t(k.def).replace("label",k._label).replace("title",k._title).getRegex(),k.bullet=/(?:[*+-]|\d+\.)/,k.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,k.item=t(k.item,"gm").replace(/bull/g,k.bullet).getRegex(),k.list=t(k.list).replace(/bull/g,k.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+k.def.source+")").getRegex(),k._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",k._comment=//,k.html=t(k.html,"i").replace("comment",k._comment).replace("tag",k._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),k.paragraph=t(k.paragraph).replace("hr",k.hr).replace("heading",k.heading).replace("lheading",k.lheading).replace("tag",k._tag).getRegex(),k.blockquote=t(k.blockquote).replace("paragraph",k.paragraph).getRegex(),k.normal=f({},k),k.gfm=f({},k.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),k.gfm.paragraph=t(k.paragraph).replace("(?!","(?!"+k.gfm.fences.source.replace("\\1","\\2")+"|"+k.list.source.replace("\\1","\\3")+"|").getRegex(),k.tables=f({},k.gfm,{nptable:/^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/,table:/^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/}),k.pedantic=f({},k.normal,{html:t("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",k._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/}),a.rules=k,a.lex=function(e,t){return new a(t).lex(e)},a.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},a.prototype.token=function(e,t){var n,r,s,i,l,o,a,h,p,u,c,g,f,d,b,m;for(e=e.replace(/^ +$/gm,"");e;)if((s=this.rules.newline.exec(e))&&(e=e.substring(s[0].length),1 ?/gm,""),this.token(s,t),this.tokens.push({type:"blockquote_end"});else if(s=this.rules.list.exec(e)){for(e=e.substring(s[0].length),a={type:"list_start",ordered:d=1<(i=s[2]).length,start:d?+i:"",loose:!1},this.tokens.push(a),n=!(h=[]),f=(s=s[0].match(this.rules.item)).length,c=0;c?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:g,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(href(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,strong:/^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/,em:/^_([^\s_])_(?!_)|^\*([^\s*"<\[])\*(?!\*)|^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s"<\[][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:g,text:/^[\s\S]+?(?=[\\/g,">").replace(/"/g,""").replace(/'/g,"'")}function c(e){return e.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}function t(n,e){return n=n.source||n,e=e||"",{replace:function(e,t){return t=(t=t.source||t).replace(/(^|[^\[])\^/g,"$1"),n=n.replace(e,t),this},getRegex:function(){return new RegExp(n,e)}}}function i(e,t){return l[" "+e]||(/^[^:]+:\/*[^/]*$/.test(e)?l[" "+e]=e+"/":l[" "+e]=y(e,"/",!0)),e=l[" "+e],"//"===t.slice(0,2)?e.replace(/:[\s\S]*/,":")+t:"/"===t.charAt(0)?e.replace(/(:\/*[^/]*)[\s\S]*/,"$1")+t:e+t}n._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,n._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,n._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,n.autolink=t(n.autolink).replace("scheme",n._scheme).replace("email",n._email).getRegex(),n._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,n.tag=t(n.tag).replace("comment",k._comment).replace("attribute",n._attribute).getRegex(),n._label=/(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/,n._href=/\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f\\]*\)|[^\s\x00-\x1f()\\])*?)/,n._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,n.link=t(n.link).replace("label",n._label).replace("href",n._href).replace("title",n._title).getRegex(),n.reflink=t(n.reflink).replace("label",n._label).getRegex(),n.normal=f({},n),n.pedantic=f({},n.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,link:t(/^!?\[(label)\]\((.*?)\)/).replace("label",n._label).getRegex(),reflink:t(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",n._label).getRegex()}),n.gfm=f({},n.normal,{escape:t(n.escape).replace("])","~|])").getRegex(),url:t(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("email",n._email).getRegex(),_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^~+(?=\S)([\s\S]*?\S)~+/,text:t(n.text).replace("]|","~]|").replace("|","|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&'*+/=?^_`{\\|}~-]+@|").getRegex()}),n.breaks=f({},n.gfm,{br:t(n.br).replace("{2,}","*").getRegex(),text:t(n.gfm.text).replace("{2,}","*").getRegex()}),h.rules=n,h.output=function(e,t,n){return new h(t,n).output(e)},h.prototype.output=function(e){for(var t,n,r,s,i,l,o="";e;)if(i=this.rules.escape.exec(e))e=e.substring(i[0].length),o+=i[1];else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),r="@"===i[2]?"mailto:"+(n=u(this.mangle(i[1]))):n=u(i[1]),o+=this.renderer.link(r,null,n);else if(this.inLink||!(i=this.rules.url.exec(e))){if(i=this.rules.tag.exec(e))!this.inLink&&/^/i.test(i[0])&&(this.inLink=!1),e=e.substring(i[0].length),o+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):u(i[0]):i[0];else if(i=this.rules.link.exec(e))e=e.substring(i[0].length),this.inLink=!0,r=i[2],this.options.pedantic?(t=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(r))?(r=t[1],s=t[3]):s="":s=i[3]?i[3].slice(1,-1):"",r=r.trim().replace(/^<([\s\S]*)>$/,"$1"),o+=this.outputLink(i,{href:h.escapes(r),title:h.escapes(s)}),this.inLink=!1;else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),!(t=this.links[t.toLowerCase()])||!t.href){o+=i[0].charAt(0),e=i[0].substring(1)+e;continue}this.inLink=!0,o+=this.outputLink(i,t),this.inLink=!1}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),o+=this.renderer.strong(this.output(i[4]||i[3]||i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),o+=this.renderer.em(this.output(i[6]||i[5]||i[4]||i[3]||i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),o+=this.renderer.codespan(u(i[2].trim(),!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),o+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),o+=this.renderer.del(this.output(i[1]));else if(i=this.rules.text.exec(e))e=e.substring(i[0].length),o+=this.renderer.text(u(this.smartypants(i[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else{for(;l=i[0],i[0]=this.rules._backpedal.exec(i[0])[0],l!==i[0];);e=e.substring(i[0].length),"@"===i[2]?r="mailto:"+(n=u(i[0])):(n=u(i[0]),r="www."===i[1]?"http://"+n:n),o+=this.renderer.link(r,null,n)}return o},h.escapes=function(e){return e?e.replace(h.rules._escapes,"$1"):e},h.prototype.outputLink=function(e,t){var n=t.href,r=t.title?u(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,u(e[1]))},h.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},h.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,s=0;s'+(n?e:u(e,!0))+"\n":"
"+(n?e:u(e,!0))+"
"},r.prototype.blockquote=function(e){return"
\n"+e+"
\n"},r.prototype.html=function(e){return e},r.prototype.heading=function(e,t,n){return this.options.headerIds?"'+e+"\n":""+e+"\n"},r.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"},r.prototype.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"\n"},r.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},r.prototype.checkbox=function(e){return" "},r.prototype.paragraph=function(e){return"

    "+e+"

    \n"},r.prototype.table=function(e,t){return t&&(t=""+t+""),"\n\n"+e+"\n"+t+"
    \n"},r.prototype.tablerow=function(e){return"\n"+e+"\n"},r.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+"\n"},r.prototype.strong=function(e){return""+e+""},r.prototype.em=function(e){return""+e+""},r.prototype.codespan=function(e){return""+e+""},r.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},r.prototype.del=function(e){return""+e+""},r.prototype.link=function(e,t,n){if(this.options.sanitize){try{var r=decodeURIComponent(c(e)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return n}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:")||0===r.indexOf("data:"))return n}this.options.baseUrl&&!o.test(e)&&(e=i(this.options.baseUrl,e));try{e=encodeURI(e).replace(/%25/g,"%")}catch(e){return n}var s='
    "},r.prototype.image=function(e,t,n){this.options.baseUrl&&!o.test(e)&&(e=i(this.options.baseUrl,e));var r=''+n+'":">"},r.prototype.text=function(e){return e},s.prototype.strong=s.prototype.em=s.prototype.codespan=s.prototype.del=s.prototype.text=function(e){return e},s.prototype.link=s.prototype.image=function(e,t,n){return""+n},s.prototype.br=function(){return""},p.parse=function(e,t){return new p(t).parse(e)},p.prototype.parse=function(e){this.inline=new h(e.links,this.options),this.inlineText=new h(e.links,f({},this.options,{renderer:new s})),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},p.prototype.next=function(){return this.token=this.tokens.pop()},p.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},p.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},p.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,c(this.inlineText.output(this.token.text)));case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,s="",i="";for(n="",e=0;et)n.splice(t);else for(;n.lengthAn error occurred:

    "+u(e.message+"",!0)+"
    ";throw e}}g.exec=g,d.options=d.setOptions=function(e){return f(d.defaults,e),d},d.getDefaults=function(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:new r,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tables:!0,xhtml:!1}},d.defaults=d.getDefaults(),d.Parser=p,d.parser=p.parse,d.Renderer=r,d.TextRenderer=s,d.Lexer=a,d.lexer=a.lex,d.InlineLexer=h,d.inlineLexer=h.output,d.parse=d,"undefined"!=typeof module&&"object"==typeof exports?module.exports=d:"function"==typeof define&&define.amd?define(function(){return d}):e.DevcardsMarked=d}(this||("undefined"!=typeof window?window:global)); 7 | -------------------------------------------------------------------------------- /src/devcards/system.clj: -------------------------------------------------------------------------------- 1 | (ns devcards.system 2 | (:require 3 | [clojure.java.io :as io])) 4 | 5 | (defmacro inline-resouce-file [file-url] 6 | (when-let [f (io/resource file-url)] 7 | (slurp f))) 8 | 9 | -------------------------------------------------------------------------------- /src/devcards/system.cljs: -------------------------------------------------------------------------------- 1 | (ns devcards.system 2 | (:require 3 | [clojure.string :as string] 4 | [cljs.core.async :refer [put! [f] (fn [e] (.preventDefault e) (f e))) 28 | 29 | (defn get-element-by-id [id] (.getElementById js/document id)) 30 | 31 | (defn devcards-app-node [] (get-element-by-id devcards-app-element-id)) 32 | 33 | (defn path->unique-card-id [path] 34 | (string/join "." (map (fn [x] (str "[" x "]")) 35 | (map name (cons :cardpath path))))) 36 | 37 | #_(defn unique-card-id->path [card-id] 38 | (mapv keyword 39 | (-> (subs card-id 1 40 | (dec (count card-id))) 41 | (string/split #"\].\[") 42 | rest))) 43 | 44 | (defn create-element* [tag id style-text] 45 | (let [el (js/document.createElement tag)] 46 | (set! (.-id el) id) 47 | (.appendChild el (js/document.createTextNode style-text)) 48 | el)) 49 | 50 | (def create-style-element (partial create-element* "style")) 51 | (def create-script-element (partial create-element* "script")) 52 | 53 | (defn prepend-child [node node2] 54 | (if-let [first-child (.-firstChild node)] 55 | (.insertBefore node node2 first-child) 56 | (.appendChild node node2))) 57 | 58 | (defn add-css-if-necessary! [] 59 | (if-let [heads (.getElementsByTagName js/document "head")] 60 | (let [head (aget heads 0)] 61 | (when-not (get-element-by-id "com-rigsomelight-code-highlight-css") 62 | (.appendChild head 63 | (create-style-element "com-rigsomelight-code-highlight-css" 64 | (inline-resouce-file "public/devcards/css/com_rigsomelight_github_highlight.css")))) 65 | 66 | (when-not (get-element-by-id "com-rigsomelight-devcards-css") 67 | (.appendChild head (create-style-element "com-rigsomelight-devcards-css" 68 | (inline-resouce-file "public/devcards/css/com_rigsomelight_devcards.css")))) 69 | (when-not (get-element-by-id "com-rigsomelight-devcards-addons-css") 70 | (.appendChild head (create-style-element "com-rigsomelight-devcards-addons-css" 71 | (inline-resouce-file "public/devcards/css/com_rigsomelight_devcards_addons.css")))) 72 | (when-not (get-element-by-id "com-rigsomelight-edn-css") 73 | (.appendChild head 74 | (create-style-element "com-rigsomelight-edn-css" 75 | (inline-resouce-file "public/devcards/css/com_rigsomelight_edn_flex.css"))))))) 76 | 77 | (defn render-base-if-necessary! [] 78 | (add-css-if-necessary!) 79 | (when-not (devcards-app-node) 80 | (let [el (js/document.createElement "div")] 81 | (set! (.-id el) devcards-app-element-id) 82 | (prepend-child (.-body js/document) el)))) 83 | 84 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 85 | ;;; Hashbang routing 86 | 87 | (declare set-current-path history) 88 | 89 | (defonce history 90 | (when (utils/html-env?) 91 | (let [h (History.)] 92 | (.setEnabled h true) 93 | h))) 94 | 95 | (defn path->token [path] 96 | (str "!/" (string/join "/" (map name path)))) 97 | 98 | (defn token->path [token] 99 | (vec (map keyword 100 | (-> token 101 | (string/replace-first #"#" "") 102 | (string/replace-first #"!/" "") 103 | (string/split #"/"))))) 104 | 105 | #_(prn (token->path (.getToken history))) 106 | 107 | #_(prn (token->path (gobj/get js/location "hash"))) 108 | 109 | (defn hash-navigate [path] 110 | (.setToken history (path->token path))) 111 | 112 | (defn hash-routing-init [state-atom] 113 | (events/listen history EventType/NAVIGATE 114 | #(swap! state-atom set-current-path (token->path (.-token %)))) 115 | ;; we should probably just get the location and parse this out to 116 | ;; avoid the initial race condition where .getToken isn't populated 117 | (when-let [token (gobj/get js/location "hash")] 118 | (swap! state-atom set-current-path (token->path token)))) 119 | 120 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 121 | 122 | (defn devcard? [d] 123 | (and (map? d) 124 | #_(:data-atom d) 125 | (:func d) 126 | (:path d) 127 | (:position d) 128 | d)) 129 | 130 | (defn path-collision [state path] 131 | (if-let [c (get (:path-collision-count state) path)] 132 | (vec (concat (butlast (vec path)) 133 | [(keyword (str (name (last path)) "-" c))])) 134 | path)) 135 | 136 | (defn register-collision [state path] 137 | (update-in state [:path-collision-count path] inc)) 138 | 139 | (defmulti dev-trans first) 140 | 141 | (defmethod dev-trans :default [msg state] state) 142 | 143 | (defmethod dev-trans :register-card [[_ {:keys [path options func]}] state] 144 | (let [position (:position state) 145 | new-path (path-collision state path)] 146 | (-> state 147 | (update-in [:position] inc) 148 | (update-in (cons :cards new-path) 149 | (fn [dc] 150 | { :path new-path 151 | :func func 152 | :position position })) 153 | (register-collision path)))) 154 | 155 | (def devcard-initial-data { :current-path [] 156 | :position 0 157 | :cards {} 158 | :path-collision-count {} 159 | :base-card-options { :frame true 160 | :heading true 161 | :padding true 162 | :hidden false 163 | :inspect-data false 164 | :watch-atom true 165 | :history false } }) 166 | 167 | (defonce app-state (atom devcard-initial-data)) 168 | 169 | (defn valid-path? [state path] 170 | (or (= [] path) 171 | (get-in (:cards state) path))) 172 | 173 | (defn enforce-valid-path [state path] 174 | (vec (if (valid-path? state path) path []))) 175 | 176 | (defn add-to-current-path [{:keys [current-path] :as state} path] 177 | (assoc state 178 | :current-path 179 | (enforce-valid-path state (conj current-path (keyword path))))) 180 | 181 | (defn set-current-path [{:keys [current-path] :as state} path] 182 | (let [path (vec (map keyword path))] 183 | (if (not= current-path path) 184 | (-> state 185 | (assoc :current-path (enforce-valid-path state path)) 186 | #_add-navigate-effect) 187 | state))) 188 | 189 | (defn set-current-path! [state-atom path] 190 | (swap! state-atom set-current-path path) 191 | (hash-navigate path)) 192 | 193 | (defn current-page [data] 194 | (and (:current-path data) 195 | (:cards data) 196 | (get-in (:cards data) (:current-path data)))) 197 | 198 | (defn display-single-card? [state] 199 | (devcard? (current-page state))) 200 | 201 | (defn display-dir-paths [state] 202 | (let [cur (current-page state)] 203 | (filter (complement (comp devcard? second)) cur))) 204 | 205 | (defn display-cards [cur] 206 | (filter (comp #(and (not (:delete-card %)) 207 | (devcard? %)) second) cur)) 208 | 209 | (def ^:dynamic *devcard-data* nil) 210 | 211 | (defn card-template [state-atom {:keys [path options func] :as card}] 212 | (sab/html 213 | [:div.com-rigsomelight-devcard {:key (path->unique-card-id path)} 214 | (cljs.core/binding [*devcard-data* card] 215 | (func))])) 216 | 217 | (defn render-cards [cards state-atom] 218 | (map (comp (partial card-template state-atom) second) 219 | (sort-by (comp :position second) cards))) 220 | 221 | (defn main-cards-template [state-atom] 222 | (let [data @state-atom] 223 | (if (display-single-card? data) 224 | (card-template state-atom (current-page data)) 225 | (render-cards (display-cards (current-page data)) state-atom)))) 226 | 227 | (defn breadcrumbs [{:keys [current-path] :as state}] 228 | (let [cpath (map name (cons :devcards current-path)) 229 | crumbs 230 | (map (juxt last rest) 231 | (rest (map-indexed 232 | (fn [i v] (subvec v 0 i)) 233 | (take (inc (count cpath)) 234 | (repeat (vec cpath))))))] 235 | crumbs)) 236 | 237 | (declare cljs-logo) 238 | 239 | (defn breadcrumbs-templ [crumbs state-atom] 240 | (let [counter (atom 0) 241 | sep-fn (fn [_] (sab/html [:span.com-rigsomelight-devcards-breadcrumb-sep 242 | {:key (do (swap! counter inc) @counter)} 243 | "/"]))] 244 | (sab/html 245 | [:div.com-rigsomelight-devcards-card-base.com-rigsomelight-devcards-breadcrumbs.com-rigsomelight-devcards-typog 246 | {:key "breadcrumbs-templ"} 247 | (rest 248 | (interleave 249 | (iterate sep-fn (sep-fn nil)) 250 | (map (fn [[n path]] 251 | (sab/html 252 | [:span {:style {:display "inline-block" } 253 | :key (path->unique-card-id path)} 254 | [:a.com-rigsomelight-devcards_set-current-path 255 | {:href "#" 256 | :onClick (prevent-> #(set-current-path! state-atom path))} 257 | (str n)]])) 258 | crumbs))) 259 | (cljs-logo)]))) 260 | 261 | (defn navigate-to-path [key state-atom] 262 | (swap! state-atom 263 | (fn [s] 264 | (let [new-s (add-to-current-path s key)] 265 | (hash-navigate (:current-path new-s)) 266 | new-s)))) 267 | 268 | (defn dir-links [dirs state-atom] 269 | (when-not (empty? dirs) 270 | (sab/html 271 | [:div.com-rigsomelight-devcards-list-group.com-rigsomelight-devcards-typog 272 | (map (fn [[key child-tree]] 273 | (sab/html 274 | [:a.com-rigsomelight-devcards-list-group-item 275 | {:href "#" 276 | :key (str key) 277 | :onClick 278 | (prevent-> 279 | (fn [e] (navigate-to-path key state-atom))) 280 | #_:onTouchStart 281 | #_(prevent-> 282 | (fn [e] (navigate-to-path key state-atom)))} 283 | [:span.com-rigsomelight-devcards-badge 284 | {:style {:float "right"}} 285 | (count child-tree)] 286 | [:span " " (name key)]])) 287 | (sort-by (fn [[key _]] (name key)) dirs))]))) 288 | 289 | (defn main-template [state-atom] 290 | (let [data @state-atom] 291 | (sab/html 292 | [:div 293 | {:className 294 | (str "com-rigsomelight-devcards-base " 295 | (when-let [n (first (:current-path data))] 296 | (string/replace (name n) "." "-")))} 297 | #_[:div.com-rigsomelight-devcards-navbar 298 | [:div.com-rigsomelight-devcards-container 299 | [:span.com-rigsomelight-devcards-brand 300 | "(:devcards ClojureScript)"]]] 301 | [:div.com-rigsomelight-devcards-container 302 | (when-let [crumbs (breadcrumbs data)] 303 | (breadcrumbs-templ crumbs state-atom)) 304 | (when-not (display-single-card? data) 305 | (let [dir-paths (display-dir-paths data)] 306 | (dir-links dir-paths state-atom))) 307 | [:div 308 | (main-cards-template state-atom)]]]))) 309 | 310 | (define-react-class DevcardsRoot 311 | (componentDidMount 312 | [this] 313 | (add-watch app-state :renderer-watch (fn [_ _ _ _] (.forceUpdate this)))) 314 | (render [this] (main-template app-state))) 315 | 316 | (defn renderer [state-atom] 317 | #_(prn "Rendering") 318 | (react-dom/render 319 | (react/createElement DevcardsRoot) 320 | #_(sab/html [:div 321 | (main-template state-atom) 322 | #_(edn-rend/html-edn @state-atom)]) 323 | (devcards-app-node))) 324 | 325 | (comment 326 | 327 | 328 | a debug option :debug-card true 329 | 330 | when initial state changes we should reset the state 331 | 332 | an iterator to delinate a card in many states 333 | 334 | speed test pprint and hightlighting versus edn-renderer 335 | 336 | use a pure component for the edn renderer to memoize rerenders 337 | 338 | look at upndown.js and marked.js 339 | 340 | probably switch to marked for markdown parsing 341 | 342 | fix loading race 343 | 344 | move highlighting out and force folks to require hljs if they want it? 345 | 346 | generate blog posts from a namespace with devcards 347 | - can implement code modules 348 | - look at dev mode and prod mode for this 349 | - front matter in ns meta data 350 | 351 | fix style of history so that there is no margin under it 352 | when there is no data being inspected 353 | 354 | move documentation cards into more descriptive namespaces 355 | fill out details better 356 | 357 | look at being able to render cursors 358 | 359 | BACKBURNER 360 | make slider component 361 | consider web-components for hiding css styling!!! 362 | turn system into react component? 363 | 364 | ) 365 | 366 | (defn merge-in-new-data [state new-state] 367 | (assoc state 368 | :path-collision-count {} 369 | :position (:position new-state) 370 | :cards (merge 371 | (:cards state) 372 | (:cards new-state)))) 373 | 374 | ;; the only major potential problem here is that If we only register 375 | ;; some of the cards of a namespace then the other cards in the 376 | ;; namespace will dissapear. If one is doing calculations at the top 377 | ;; level that take more than the wait time this could be a problem 378 | (defn off-the-books 379 | "Run sequential messages off the books outside of the atom and 380 | then difference the result so we can only display the new cards 381 | that have arrived. This prevents multiple renders and allows us 382 | to delete cards live." 383 | [channel start-data first-message] 384 | (let [;timer (timeout 3000) 385 | initial-data (-> start-data 386 | (assoc :path-collision-count {}) 387 | (dissoc :cards))] 388 | #_(prn "off the books") 389 | (go-loop [data (dev-trans first-message initial-data)] 390 | #_(prn "here") 391 | (let [timer (timeout 500)] ;; needs to be longer for mobile think 392 | (when-let [[[msg-name payload] ch] (alts! [channel timer])] 393 | (cond 394 | (= ch timer) (merge-in-new-data start-data data) 395 | ;; this will function without jsreload. but allows us to 396 | ;; render a tick faster 397 | (= msg-name :jsreload) (merge-in-new-data start-data data) 398 | :else 399 | (do 400 | (recur (dev-trans [msg-name payload] data))))))))) 401 | 402 | (defn load-data-from-channel! [channel] 403 | (go (let [new-state ( 465 | 466 | ") 491 | 492 | (defn cljs-logo [] 493 | (react/createElement "span" 494 | #js {:key "cljs-logo" 495 | :dangerouslySetInnerHTML 496 | #js {:__html cljs-logo-svg}})) 497 | -------------------------------------------------------------------------------- /src/devcards/util/edn_renderer.cljs: -------------------------------------------------------------------------------- 1 | (ns devcards.util.edn-renderer 2 | (:require 3 | [sablono.core :as sab] 4 | [devcards.util.utils :as utils])) 5 | 6 | (defonce ^:dynamic *key-counter* nil) 7 | 8 | (defn get-key [] 9 | (swap! *key-counter* inc) 10 | (str "k-" @*key-counter*)) 11 | 12 | (declare html) 13 | 14 | (defn literal? [x] 15 | (and 16 | (not (map-entry? x)) 17 | (not (seq? x)) 18 | (not (coll? x)))) 19 | 20 | (defn separator* [s] 21 | (sab/html [:span.seperator {:key (get-key)} s])) 22 | 23 | (defn clearfix-separator* [s] 24 | (sab/html [:span {:key (get-key)} (separator* s) [:span.clearfix]])) 25 | 26 | (defn separate-fn [coll] 27 | (try 28 | (if (not (every? literal? coll)) clearfix-separator* separator*) 29 | (catch js/Error e 30 | clearfix-separator*))) 31 | 32 | (defn interpose-separator [rct-coll s sep-fn] 33 | (->> (rest rct-coll) 34 | (interleave (repeatedly #(sep-fn s))) 35 | (cons (first rct-coll)) 36 | to-array)) 37 | 38 | (defn literal [class x] 39 | (sab/html [:span { :className class :key (get-key)} (utils/pprint-str x)])) 40 | 41 | (defn html-val [index v] 42 | (sab/html [:span {:key index} (html v)])) 43 | 44 | (defn join-html [separator coll] 45 | (interpose-separator (into [] (map-indexed html-val coll)) 46 | separator 47 | (separate-fn coll))) 48 | 49 | (defn html-keyval [[k v]] 50 | (sab/html 51 | [:span.keyval { :key (prn-str k)} (html k) (html v)])) 52 | 53 | (defn html-keyvals [coll] 54 | (interpose-separator (mapv html-keyval coll) 55 | " " 56 | (separate-fn (vals coll)))) 57 | 58 | (defn open-close [class-str opener closer rct-coll] 59 | (sab/html 60 | [:span {:className class-str :key (str (hash rct-coll))} 61 | [:span.opener {:key 1} opener] 62 | [:span.contents {:key 2} rct-coll] 63 | [:span.closer {:key 3} closer]])) 64 | 65 | (defn html-collection [class opener closer coll] 66 | (open-close (str "collection " class ) opener closer (join-html " " coll)) 67 | ;; this speeds things up but fails in om 68 | #_(rct/pure coll ...) 69 | ) 70 | 71 | (defn html-map [coll] 72 | (open-close "collection map" "{" "}" (html-keyvals coll)) 73 | ;; this speeds things up but fails in om 74 | #_(rct/pure coll ...)) 75 | 76 | (defn html-string [s] 77 | (open-close "string" "\"" "\"" s)) 78 | 79 | (defn html [x] 80 | (cond 81 | (number? x) (literal "number" x) 82 | (keyword? x) (literal "keyword" x) 83 | (symbol? x) (literal "symbol" x) 84 | (string? x) (html-string x) 85 | (map? x) (html-map x) 86 | (set? x) (html-collection "set" "#{" "}" x) 87 | (vector? x) (html-collection "vector" "[" "]" x) 88 | (seq? x) (html-collection "seq" "(" ")" x) 89 | :else (literal "literal" x))) 90 | 91 | (defn html-edn [e] 92 | (binding [*key-counter* (atom 0)] 93 | (sab/html [:div.com-rigsomelight-rendered-edn.com-rigsomelight-devcards-typog 94 | {:key "devcards-edn-block"} (html e)]))) 95 | -------------------------------------------------------------------------------- /src/devcards/util/markdown.cljs: -------------------------------------------------------------------------------- 1 | (ns devcards.util.markdown 2 | (:require 3 | [clojure.string :as string] 4 | [devcards-marked :as devcards-marked])) 5 | 6 | (defn leading-space-count [s] 7 | (when-let [ws (second (re-matches #"^([\s]*).*" s))] 8 | (.-length ws))) 9 | 10 | (defn is-bullet-item? [s] (boolean (re-matches #"^\s*([-*+]|[0-9]+\.)\s.*" s))) 11 | 12 | (defn bullets-left-edge "Find the common left edge of bullet lists in a collection of lines." 13 | [lines] 14 | (or 15 | (->> lines 16 | (filter is-bullet-item?) 17 | (map leading-space-count) 18 | (apply min)) 19 | 0)) 20 | 21 | (defn strip-left-margin "Strip the left margin's extra whitespace, but leave bullet list indents in tact." 22 | [s margin] 23 | (if (is-bullet-item? s) 24 | (subs s margin) 25 | (string/trim s))) 26 | 27 | (defn markdown-to-html [markdown-txt] 28 | (js/DevcardsMarked markdown-txt)) 29 | 30 | (defn matches-delim? [line] 31 | (re-matches #"^[\s]*```(\w*).*" line)) 32 | 33 | (defmulti block-parser 34 | (fn [{:keys [stage]} line] 35 | [(if (matches-delim? line) :delim :line) (:type stage)])) 36 | 37 | (defmethod block-parser [:line :markdown] [{:keys [stage left-margin] :as st} line] 38 | (update-in st [:stage :content] conj (strip-left-margin line left-margin))) 39 | 40 | (defmethod block-parser [:line :code-block] [{:keys [stage] :as st} line] 41 | (update-in st [:stage :content] conj (subs line (:leading-spaces stage)))) 42 | 43 | (defmethod block-parser [:delim :markdown] [{:keys [stage accum] :as st} line];; enter block 44 | (let [lang (second (matches-delim? line))] 45 | (-> st ;; the beginning 46 | (assoc :accum (conj accum stage)) 47 | (assoc :stage 48 | {:type :code-block 49 | :lang (when-not (string/blank? lang) lang) 50 | :leading-spaces (leading-space-count line) 51 | :content []})))) 52 | 53 | (defmethod block-parser [:delim :code-block] [{:keys [stage accum] :as st} line];; enter block 54 | (-> st ;; the end 55 | (assoc :accum (conj accum stage)) 56 | (assoc :stage {:type :markdown :content []}))) 57 | 58 | (defn parse-out-blocks* [m] 59 | (let [lines (string/split m "\n")] 60 | (reduce block-parser 61 | {:stage {:type :markdown :content []} :accum [] :left-margin (bullets-left-edge lines)} 62 | lines))) 63 | 64 | (defn parse-out-blocks [m] 65 | (let [{:keys [stage accum]} (parse-out-blocks* m)] 66 | (->> (conj accum stage) 67 | (filter (fn [{:keys [content]}] (not-empty content))) 68 | (map (fn [x] (update-in x [:content] #(string/join "\n" %))))))) 69 | 70 | #_(devcards.core/defcard parse-out-code-blocks3 71 | (parse-out-blocks 72 | " ```langer 73 | (defcard bmi-calculator ;; optional symbol name 74 | \"*Code taken from Reagent readme.*\" ;; optional markdown doc 75 | (fn [data-atom _] (bmi-component data-atom)) ;; object of focus 76 | {:height 180 :weight 80} ;; optional initial data 77 | {:inspect-data true :history true}) ;; optional devcard config options 78 | 79 | ``` 80 | # [Devcards](https://github.com/bhauman/devcards): the hard sell 81 | 82 | The Devcards library is intended to make ClojureScript development 83 | a pure joy. 84 | 85 | Devcards are intended to facilitate **interactive live 86 | development**. Devcards can be used in conjunction with figwheel but 87 | will also work with any form of live code reloading (repl, boot-reload, ...) 88 | 89 | Devcards revolves around a multi-purpose macro called `defcard`. 90 | You can think of `defcard` as a powerful form of **pprint** that helps you 91 | interactively lift code examples out of your source files into the 92 | Devcards interface (you are currently looking at the Devcards 93 | interface). 94 | 95 | The Devcards that you create are intended to have no impact on the 96 | size of your production code. You can use Devcards just as you 97 | would use exectuable comments inline with your source code. You 98 | can also keep them separate like a test suite. 99 | 100 | With [figwheel](https://github.com/bhauman/lein-figwheel), Devcards 101 | configuration couldn't be simpler. Just add `[devcards 102 | \"0.2.0-SNAPSHOT\"]` and create a new build config with `:figwheel 103 | {:devcards true}`. See the Quick Start instructions at the end of 104 | this document. 105 | 106 | Let's look at an advanced Devcard: 107 | 108 | ``` 109 | (defcard bmi-calculator ;; optional symbol name 110 | \"*Code taken from Reagent readme.*\" ;; optional markdown doc 111 | (fn [data-atom _] (bmi-component data-atom)) ;; object of focus 112 | {:height 180 :weight 80} ;; optional initial data 113 | {:inspect-data true :history true}) ;; optional devcard config options 114 | 115 | ``` 116 | 117 | The [defcard api](#!/devdemos.defcard_api) 118 | is intended to be small and intuitive. 119 | 120 | And you can see this devcard rendered below:")) 121 | 122 | 123 | #_(devcards.core/defcard parse-out-code-blocks3 124 | (parse-out-blocks 125 | "# [Devcards](https://github.com/bhauman/devcards): the hard sell 126 | 127 | The Devcards library is intended to make ClojureScript development 128 | a pure joy. 129 | 130 | Devcards are intended to facilitate **interactive live 131 | development**. Devcards can be used in conjunction with figwheel but 132 | will also work with any form of live code reloading (repl, boot-reload, ...) 133 | 134 | Devcards revolves around a multi-purpose macro called `defcard`. 135 | You can think of `defcard` as a powerful form of **pprint** that helps you 136 | interactively lift code examples out of your source files into the 137 | Devcards interface (you are currently looking at the Devcards 138 | interface). 139 | 140 | The Devcards that you create are intended to have no impact on the 141 | size of your production code. You can use Devcards just as you 142 | would use exectuable comments inline with your source code. You 143 | can also keep them separate like a test suite. 144 | 145 | With [figwheel](https://github.com/bhauman/lein-figwheel), Devcards 146 | configuration couldn't be simpler. Just add `[devcards 147 | \"0.2.0-SNAPSHOT\"]` and create a new build config with `:figwheel 148 | {:devcards true}`. See the Quick Start instructions at the end of 149 | this document. 150 | 151 | Let's look at an advanced Devcard: 152 | 153 | ``` 154 | (defcard bmi-calculator ;; optional symbol name 155 | \"*Code taken from Reagent readme.*\" ;; optional markdown doc 156 | (fn [data-atom _] (bmi-component data-atom)) ;; object of focus 157 | {:height 180 :weight 80} ;; optional initial data 158 | {:inspect-data true :history true}) ;; optional devcard config options 159 | 160 | ``` 161 | 162 | The [defcard api](#!/devdemos.defcard_api) 163 | is intended to be small and intuitive. 164 | 165 | And you can see this devcard rendered below:")) 166 | -------------------------------------------------------------------------------- /src/devcards/util/utils.clj: -------------------------------------------------------------------------------- 1 | (ns devcards.util.utils 2 | (:require [cljs.env])) 3 | 4 | (defn devcards-active? [] 5 | (and cljs.env/*compiler* 6 | (when-let [{:keys [options]} @cljs.env/*compiler*] 7 | (or (:devcards options) 8 | (when-let [closure-defines (get options :closure-defines)] 9 | (or (get closure-defines "devcards.core.active") 10 | (get closure-defines "devcards.core/active"))))))) 11 | 12 | (defn specify-react-class! [body] 13 | (let [constructor-fn (when-let [cf 14 | (->> body 15 | (filter #(= 'constructor (first %))) 16 | first)] 17 | (->> cf rest (cons 'fn))) 18 | constructor-fn (if constructor-fn 19 | `(fn [props#] 20 | (cljs.core/this-as this# 21 | (.call devcards.util.utils/react-holder.Component this# props#) 22 | (.call ~constructor-fn this# props#) 23 | this#)) 24 | `(fn [props#] 25 | (cljs.core/this-as this# 26 | (.call devcards.util.utils/react-holder.Component this# props#) 27 | this#))) 28 | body (->> body 29 | (remove #(= 'constructor (first %))))] 30 | `(let [ctor# ~constructor-fn] 31 | (goog.inherits ctor# devcards.util.utils/react-holder.Component) 32 | (cljs.core/specify! (.-prototype ctor#) 33 | ~'Object 34 | ~@body) 35 | ctor#))) 36 | 37 | (defmacro define-react-class-once [vname & body] 38 | `(do 39 | (defonce ~vname ~(specify-react-class! body)) 40 | (set! (.-displayName ~vname) (name '~vname)) 41 | (cljs.core/specify! (.-prototype ~vname) 42 | ~'Object 43 | ~@(filter #('#{shouldComponentUpdate 44 | componentWillReceiveProps 45 | componentWillMount 46 | componentDidMount 47 | componentWillUpdate 48 | componentDidUpdate 49 | componentWillUnmount 50 | render} (first %)) body)))) 51 | 52 | (defmacro define-react-class [vname & body] 53 | `(do 54 | (def ~vname ~(specify-react-class! body)) 55 | (set! (.-displayName ~vname) (name '~vname)))) 56 | -------------------------------------------------------------------------------- /src/devcards/util/utils.cljs: -------------------------------------------------------------------------------- 1 | (ns devcards.util.utils 2 | (:require 3 | [goog.object :as gobj] 4 | [cljs.pprint :as pprint] 5 | [react]) 6 | (:require-macros 7 | [devcards.util.utils])) 8 | 9 | ;; this is a reference 10 | (def react-holder react) 11 | 12 | (defn html-env? [] 13 | (if-let [doc js/goog.global.document] 14 | (gobj/get doc "write"))) 15 | 16 | (defn node-env? [] (not (nil? goog/nodeGlobalRequire))) 17 | 18 | (defn pprint-str [obj] 19 | ;; this is currently only to handle the 20 | ;; problem of printing JavaScript Symbols 21 | (try 22 | (with-out-str (pprint/pprint obj)) 23 | (catch js/Error e1 24 | (try 25 | (.toString obj) 26 | (catch js/Error e2 27 | (str "<>")))))) 28 | 29 | (defn pprint-code [code] 30 | (pprint/with-pprint-dispatch pprint/code-dispatch (pprint-str code))) 31 | --------------------------------------------------------------------------------