├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── JasperHelpers.html ├── JasperHelpers │ ├── Forms.html │ ├── Kit.html │ ├── Links.html │ ├── OptionHash.html │ ├── Tags.html │ └── Text.html ├── css │ └── style.css ├── index.html ├── index.json ├── js │ └── doc.js └── search-index.js ├── shard.lock ├── shard.yml ├── spec ├── jasper_helpers │ ├── forms_spec.cr │ ├── kit_spec.cr │ ├── links_spec.cr │ ├── tags_spec.cr │ └── text_spec.cr └── spec_helper.cr └── src ├── jasper_helpers.cr └── jasper_helpers ├── forms.cr ├── kit.cr ├── links.cr ├── tags.cr ├── text.cr └── version.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /lib/ 3 | /bin/ 4 | /.shards/ 5 | .shard.lock 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Amber Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/amberframework/jasper-helpers.svg?branch=master)](https://travis-ci.org/https://travis-ci.org/amberframework/jasper-helpers) 2 | 3 | # Jasper::Helpers 4 | 5 | A library of helpers for [Amber Framework](https://amberframework.org). 6 | 7 | ## Installation 8 | 9 | Add this to your application's shard.yml: 10 | 11 | ```yaml 12 | dependencies: 13 | jasper_helpers: 14 | github: amberframework/jasper-helpers 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```crystal 20 | require "jasper_helpers" 21 | 22 | class ApplicationController < Amber::Controller::Base 23 | include JasperHelpers 24 | LAYOUT = "application.slang" 25 | end 26 | ``` 27 | 28 | ## Contributing 29 | 30 | 1. Fork it ( https://github.com/amberframework/Jasper-Helpers/fork ) 31 | 2. Create your feature branch (git checkout -b my-new-feature) 32 | 3. Commit your changes (git commit -am 'Add some feature') 33 | 4. Push to the branch (git push origin my-new-feature) 34 | 5. Create a new Pull Request 35 | 36 | ## Contributors 37 | 38 | - [elorest](https://github.com/elorest) Isaac Sloan - Maintainer 39 | - [skunkworker](https://github.com/skunkworker) John Bolliger Maintainer 40 | -------------------------------------------------------------------------------- /docs/JasperHelpers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | JasperHelpers - jasper_helpers rs/docs 17 | 20 | 21 | 22 | 23 | 28 | 98 | 99 | 100 |
101 |

102 | 103 | module JasperHelpers 104 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |

116 | 117 | 120 | 121 | Included Modules 122 |

123 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |

145 | 146 | 149 | 150 | Defined in: 151 |

152 | 153 | 154 | 155 | jasper_helpers.cr 156 | 157 | 158 |
159 | 160 | 161 | 162 | jasper_helpers/forms.cr 163 | 164 | 165 |
166 | 167 | 168 | 169 | jasper_helpers/version.cr 170 | 171 | 172 |
173 | 174 | 175 | 176 | 177 | 178 |

179 | 180 | 183 | 184 | Constant Summary 185 |

186 | 187 |
188 | 189 |
190 | VERSION = "0.2.5" 191 |
192 | 193 | 194 |
195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 |
207 | 208 | 209 | 210 |

Instance methods inherited from module JasperHelpers::Links

211 | 212 | 213 | 214 | button_to(body : String, url : String, method : Symbol = :post)
button_to(body : String, url : String, method : Symbol = :post, &)
button_to(body : String, url : String, method : Symbol = :post, **options : Object)
button_to(body : String, url : String, method : Symbol = :post, **options : Object, &)
215 | button_to
, 216 | 217 | 218 | 219 | link_to(body : String, url : String)
link_to(body : String, url : String, **options : Object)
link_to(url : String, &)
link_to(url : String, **options : Object, &)
220 | link_to
221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 |

Instance methods inherited from module JasperHelpers::Forms

235 | 236 | 237 | 238 | check_box(name : String | Symbol, checked_value = "1", unchecked_value = "0", **options : Object)
check_box(name : String | Symbol, checked_value = "1", unchecked_value = "0")
239 | check_box
, 240 | 241 | 242 | 243 | file_field(name : String | Symbol)
file_field(name : String | Symbol, **options : Object)
244 | file_field
, 245 | 246 | 247 | 248 | form(method = :post, &block)
form(method = :post, **options : Object, &)
249 | form
, 250 | 251 | 252 | 253 | hidden_field(name : String | Symbol)
hidden_field(name : String | Symbol, **options : Object)
254 | hidden_field
, 255 | 256 | 257 | 258 | label(name : String | Symbol, content : String? = nil)
label(name : String | Symbol, content : String? = nil, **options : Object)
label(name : String | Symbol, &)
259 | label
, 260 | 261 | 262 | 263 | select_field(name : String | Symbol, collection : Array | Range, **options : Object)
select_field(name : String | Symbol, collection : NamedTuple, **options : Object)
select_field(name : String | Symbol, collection : Hash, **options : Object)
select_field(name : String | Symbol, collection : Array(Hash), **options : Object)
select_field(name : String | Symbol, collection : Array(Array), **options : Object)
select_field(name : String | Symbol, collection : Array | Range)
select_field(name : String | Symbol, collection : NamedTuple)
select_field(name : String | Symbol, collection : Hash)
select_field(name : String | Symbol, collection : Array(Hash))
select_field(name : String | Symbol, collection : Array(Array))
264 | select_field
, 265 | 266 | 267 | 268 | submit(value : String | Symbol = "Save Changes")
submit(value : String | Symbol = "Save Changes", **options : Object)
269 | submit
, 270 | 271 | 272 | 273 | text_area(name : String | Symbol, content : String?)
text_area(name : String | Symbol, content : String?, **options : Object)
274 | text_area
, 275 | 276 | 277 | 278 | text_field(name : String | Symbol, **options : Object)
text_field(name : String | Symbol)
279 | text_field
, 280 | 281 | 282 | 283 | wrapper_field(*args) 284 | wrapper_field 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 |

Instance methods inherited from module JasperHelpers::Text

299 | 300 | 301 | 302 | render_markdown(markdown) 303 | render_markdown 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 |

Instance methods inherited from module JasperHelpers::Tags

318 | 319 | 320 | 321 | content(element_name : Symbol, content : String, options : OptionHash)
content(element_name : Symbol, options : OptionHash, &)
322 | content
, 323 | 324 | 325 | 326 | input_field(type : Symbol, options : OptionHash)
input_field(type : Symbol, **options)
327 | input_field
, 328 | 329 | 330 | 331 | input_field_string(type : Symbol, options : OptionHash) 332 | input_field_string, 333 | 334 | 335 | 336 | prepare_input_field_options(options : OptionHash) 337 | prepare_input_field_options 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 |
350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 |
360 | 361 | 362 | 363 | -------------------------------------------------------------------------------- /docs/JasperHelpers/Forms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | JasperHelpers::Forms - jasper_helpers rs/docs 17 | 20 | 21 | 22 | 23 | 28 | 98 | 99 | 100 |
101 |

102 | 103 | module JasperHelpers::Forms 104 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |

122 | 123 | 126 | 127 | Direct including types 128 |

129 | 134 | 135 | 136 | 137 | 138 |

139 | 140 | 143 | 144 | Defined in: 145 |

146 | 147 | 148 | 149 | jasper_helpers/forms.cr 150 | 151 | 152 |
153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |

164 | 165 | 168 | 169 | Instance Method Summary 170 |

171 | 338 | 339 | 340 | 341 | 342 | 343 |
344 | 345 |
346 | 347 | 348 | 349 | 350 | 351 | 352 |

353 | 354 | 357 | 358 | Instance Method Detail 359 |

360 | 361 |
362 |
363 | 364 | def check_box(name : String | Symbol, checked_value = "1", unchecked_value = "0", **options : Object) 365 | 366 | # 367 |
368 | 369 |
370 | 371 |

check_box

372 |
373 | 374 |
375 |
376 | 377 | [View source] 378 | 379 |
380 |
381 | 382 |
383 |
384 | 385 | def check_box(name : String | Symbol, checked_value = "1", unchecked_value = "0") 386 | 387 | # 388 |
389 | 390 |
391 |
392 | 393 | [View source] 394 | 395 |
396 |
397 | 398 |
399 |
400 | 401 | def file_field(name : String | Symbol) 402 | 403 | # 404 |
405 | 406 |
407 |
408 | 409 | [View source] 410 | 411 |
412 |
413 | 414 |
415 |
416 | 417 | def file_field(name : String | Symbol, **options : Object) 418 | 419 | # 420 |
421 | 422 |
423 | 424 |

file_field

425 |
426 | 427 |
428 |
429 | 430 | [View source] 431 | 432 |
433 |
434 | 435 |
436 |
437 | 438 | def form(method = :post, &block) 439 | 440 | # 441 |
442 | 443 |
444 |
445 | 446 | [View source] 447 | 448 |
449 |
450 | 451 |
452 |
453 | 454 | def form(method = :post, **options : Object, &) 455 | 456 | # 457 |
458 | 459 |
460 | 461 |

form

462 |
463 | 464 |
465 |
466 | 467 | [View source] 468 | 469 |
470 |
471 | 472 |
473 |
474 | 475 | def hidden_field(name : String | Symbol) 476 | 477 | # 478 |
479 | 480 |
481 |
482 | 483 | [View source] 484 | 485 |
486 |
487 | 488 |
489 |
490 | 491 | def hidden_field(name : String | Symbol, **options : Object) 492 | 493 | # 494 |
495 | 496 |
497 | 498 |

hidden_field

499 |
500 | 501 |
502 |
503 | 504 | [View source] 505 | 506 |
507 |
508 | 509 |
510 |
511 | 512 | def label(name : String | Symbol, content : String? = nil) 513 | 514 | # 515 |
516 | 517 |
518 |
519 | 520 | [View source] 521 | 522 |
523 |
524 | 525 |
526 |
527 | 528 | def label(name : String | Symbol, content : String? = nil, **options : Object) 529 | 530 | # 531 |
532 | 533 |
534 |
535 | 536 | [View source] 537 | 538 |
539 |
540 | 541 |
542 |
543 | 544 | def label(name : String | Symbol, &) 545 | 546 | # 547 |
548 | 549 |
550 |
551 | 552 | [View source] 553 | 554 |
555 |
556 | 557 |
558 |
559 | 560 | def select_field(name : String | Symbol, collection : Array | Range, **options : Object) 561 | 562 | # 563 |
564 | 565 |
566 | 567 |

with collection Array

568 |
569 | 570 |
571 |
572 | 573 | [View source] 574 | 575 |
576 |
577 | 578 |
579 |
580 | 581 | def select_field(name : String | Symbol, collection : NamedTuple, **options : Object) 582 | 583 | # 584 |
585 | 586 |
587 |
588 | 589 | [View source] 590 | 591 |
592 |
593 | 594 |
595 |
596 | 597 | def select_field(name : String | Symbol, collection : Hash, **options : Object) 598 | 599 | # 600 |
601 | 602 |
603 | 604 |

with collection Hash

605 |
606 | 607 |
608 |
609 | 610 | [View source] 611 | 612 |
613 |
614 | 615 |
616 |
617 | 618 | def select_field(name : String | Symbol, collection : Array(Hash), **options : Object) 619 | 620 | # 621 |
622 | 623 |
624 | 625 |

with collection Array(Hash)

626 |
627 | 628 |
629 |
630 | 631 | [View source] 632 | 633 |
634 |
635 | 636 |
637 |
638 | 639 | def select_field(name : String | Symbol, collection : Array(Array), **options : Object) 640 | 641 | # 642 |
643 | 644 |
645 | 646 |

select_field 647 | with collection Array(Array)

648 |
649 | 650 |
651 |
652 | 653 | [View source] 654 | 655 |
656 |
657 | 658 |
659 |
660 | 661 | def select_field(name : String | Symbol, collection : Array | Range) 662 | 663 | # 664 |
665 | 666 |
667 |
668 | 669 | [View source] 670 | 671 |
672 |
673 | 674 |
675 |
676 | 677 | def select_field(name : String | Symbol, collection : NamedTuple) 678 | 679 | # 680 |
681 | 682 |
683 |
684 | 685 | [View source] 686 | 687 |
688 |
689 | 690 |
691 |
692 | 693 | def select_field(name : String | Symbol, collection : Hash) 694 | 695 | # 696 |
697 | 698 |
699 |
700 | 701 | [View source] 702 | 703 |
704 |
705 | 706 |
707 |
708 | 709 | def select_field(name : String | Symbol, collection : Array(Hash)) 710 | 711 | # 712 |
713 | 714 |
715 |
716 | 717 | [View source] 718 | 719 |
720 |
721 | 722 |
723 |
724 | 725 | def select_field(name : String | Symbol, collection : Array(Array)) 726 | 727 | # 728 |
729 | 730 |
731 | 732 |

Utilizes method above for when options are not defined and sets class and id.

733 |
734 | 735 |
736 |
737 | 738 | [View source] 739 | 740 |
741 |
742 | 743 |
744 |
745 | 746 | def submit(value : String | Symbol = "Save Changes") 747 | 748 | # 749 |
750 | 751 |
752 |
753 | 754 | [View source] 755 | 756 |
757 |
758 | 759 |
760 |
761 | 762 | def submit(value : String | Symbol = "Save Changes", **options : Object) 763 | 764 | # 765 |
766 | 767 |
768 | 769 |

submit

770 |
771 | 772 |
773 |
774 | 775 | [View source] 776 | 777 |
778 |
779 | 780 |
781 |
782 | 783 | def text_area(name : String | Symbol, content : String?) 784 | 785 | # 786 |
787 | 788 |
789 |
790 | 791 | [View source] 792 | 793 |
794 |
795 | 796 |
797 |
798 | 799 | def text_area(name : String | Symbol, content : String?, **options : Object) 800 | 801 | # 802 |
803 | 804 |
805 | 806 |

text_area

807 |
808 | 809 |
810 |
811 | 812 | [View source] 813 | 814 |
815 |
816 | 817 |
818 |
819 | 820 | def text_field(name : String | Symbol, **options : Object) 821 | 822 | # 823 |
824 | 825 |
826 | 827 |

text_field

828 |
829 | 830 |
831 |
832 | 833 | [View source] 834 | 835 |
836 |
837 | 838 |
839 |
840 | 841 | def text_field(name : String | Symbol) 842 | 843 | # 844 |
845 | 846 |
847 |
848 | 849 | [View source] 850 | 851 |
852 |
853 | 854 |
855 |
856 | 857 | def wrapper_field(*args) 858 | 859 | # 860 |
861 | 862 |
863 |
864 | 865 | [View source] 866 | 867 |
868 |
869 | 870 | 871 | 872 | 873 | 874 |
875 | 876 | 877 | 878 | -------------------------------------------------------------------------------- /docs/JasperHelpers/Kit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | JasperHelpers::Kit - jasper_helpers rs/docs 17 | 20 | 21 | 22 | 23 | 28 | 98 | 99 | 100 |
101 |

102 | 103 | module JasperHelpers::Kit 104 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 |

118 | 119 | 122 | 123 | Extended Modules 124 |

125 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 |

139 | 140 | 143 | 144 | Defined in: 145 |

146 | 147 | 148 | 149 | jasper_helpers/kit.cr 150 | 151 | 152 |
153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |

164 | 165 | 168 | 169 | Instance Method Summary 170 |

171 | 194 | 195 | 196 | 197 | 198 | 199 |
200 | 201 |
202 | 203 | 204 | 205 | 206 | 207 | 208 |

209 | 210 | 213 | 214 | Instance Method Detail 215 |

216 | 217 |
218 |
219 | 220 | def css_safe(value) 221 | 222 | # 223 |
224 | 225 |
226 |
227 | 228 | [View source] 229 | 230 |
231 |
232 | 233 |
234 |
235 | 236 | def merge(*options) 237 | 238 | # 239 |
240 | 241 |
242 |
243 | 244 | [View source] 245 | 246 |
247 |
248 | 249 |
250 |
251 | 252 | def safe_hash(*options) 253 | 254 | # 255 |
256 | 257 |
258 |
259 | 260 | [View source] 261 | 262 |
263 |
264 | 265 |
266 |
267 | 268 | def sanitize(options : OptionHash) 269 | 270 | # 271 |
272 | 273 |
274 |
275 | 276 | [View source] 277 | 278 |
279 |
280 | 281 | 282 | 283 | 284 | 285 |
286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /docs/JasperHelpers/Links.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | JasperHelpers::Links - jasper_helpers rs/docs 17 | 20 | 21 | 22 | 23 | 28 | 98 | 99 | 100 |
101 |

102 | 103 | module JasperHelpers::Links 104 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |

122 | 123 | 126 | 127 | Direct including types 128 |

129 | 134 | 135 | 136 | 137 | 138 |

139 | 140 | 143 | 144 | Defined in: 145 |

146 | 147 | 148 | 149 | jasper_helpers/links.cr 150 | 151 | 152 |
153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |

164 | 165 | 168 | 169 | Instance Method Summary 170 |

171 | 222 | 223 | 224 | 225 | 226 | 227 |
228 | 229 |
230 | 231 | 232 | 233 | 234 | 235 | 236 |

237 | 238 | 241 | 242 | Instance Method Detail 243 |

244 | 245 |
246 |
247 | 248 | def button_to(body : String, url : String, method : Symbol = :post) 249 | 250 | # 251 |
252 | 253 |
254 |
255 | 256 | [View source] 257 | 258 |
259 |
260 | 261 |
262 |
263 | 264 | def button_to(body : String, url : String, method : Symbol = :post, &) 265 | 266 | # 267 |
268 | 269 |
270 |
271 | 272 | [View source] 273 | 274 |
275 |
276 | 277 |
278 |
279 | 280 | def button_to(body : String, url : String, method : Symbol = :post, **options : Object) 281 | 282 | # 283 |
284 | 285 |
286 |
287 | 288 | [View source] 289 | 290 |
291 |
292 | 293 |
294 |
295 | 296 | def button_to(body : String, url : String, method : Symbol = :post, **options : Object, &) 297 | 298 | # 299 |
300 | 301 |
302 |
303 | 304 | [View source] 305 | 306 |
307 |
308 | 309 |
310 |
311 | 312 | def link_to(body : String, url : String) 313 | 314 | # 315 |
316 | 317 |
318 | 319 |

:ditto:

320 | 321 |

With the exception of not accepting tag options

322 |
323 | 324 |
325 |
326 | 327 | [View source] 328 | 329 |
330 |
331 | 332 |
333 |
334 | 335 | def link_to(body : String, url : String, **options : Object) 336 | 337 | # 338 |
339 | 340 | 352 | 353 |
354 |
355 | 356 | [View source] 357 | 358 |
359 |
360 | 361 |
362 |
363 | 364 | def link_to(url : String, &) 365 | 366 | # 367 |
368 | 369 |
370 | 371 |

:ditto:

372 | 373 |

With the exception of not accepting tag options

374 |
375 | 376 |
377 |
378 | 379 | [View source] 380 | 381 |
382 |
383 | 384 |
385 |
386 | 387 | def link_to(url : String, **options : Object, &) 388 | 389 | # 390 |
391 | 392 | 405 | 406 |
407 |
408 | 409 | [View source] 410 | 411 |
412 |
413 | 414 | 415 | 416 | 417 | 418 |
419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /docs/JasperHelpers/OptionHash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | JasperHelpers::OptionHash - jasper_helpers rs/docs 17 | 20 | 21 | 22 | 23 | 28 | 98 | 99 | 100 |
101 |

102 | 103 | alias JasperHelpers::OptionHash 104 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |

114 | 115 | 118 | 119 | Alias Definition 120 |

121 | Hash(Symbol, Array(Int32 | String) | Array(Int32) | Array(String) | Bool | Float32 | Float64 | Int16 | Int32 | Int64 | Int8 | Slice(UInt8) | String | Symbol | Time | Nil) 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 |

135 | 136 | 139 | 140 | Defined in: 141 |

142 | 143 | 144 | 145 | jasper_helpers.cr 146 | 147 | 148 |
149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
164 | 165 |
166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 |
176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /docs/JasperHelpers/Tags.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | JasperHelpers::Tags - jasper_helpers rs/docs 17 | 20 | 21 | 22 | 23 | 28 | 98 | 99 | 100 |
101 |

102 | 103 | module JasperHelpers::Tags 104 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |

122 | 123 | 126 | 127 | Direct including types 128 |

129 | 134 | 135 | 136 | 137 | 138 |

139 | 140 | 143 | 144 | Defined in: 145 |

146 | 147 | 148 | 149 | jasper_helpers/tags.cr 150 | 151 | 152 |
153 | 154 | 155 | 156 | 157 | 158 |

159 | 160 | 163 | 164 | Constant Summary 165 |

166 | 167 |
168 | 169 |
170 | INPUT_BOOLEAN_ATTRIBUTES = [:disabled] 171 |
172 | 173 | 174 |
175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 |

183 | 184 | 187 | 188 | Instance Method Summary 189 |

190 | 227 | 228 | 229 | 230 | 231 | 232 |
233 | 234 |
235 | 236 | 237 | 238 | 239 | 240 | 241 |

242 | 243 | 246 | 247 | Instance Method Detail 248 |

249 | 250 |
251 |
252 | 253 | def content(element_name : Symbol, content : String, options : OptionHash) 254 | 255 | # 256 |
257 | 258 |
259 |
260 | 261 | [View source] 262 | 263 |
264 |
265 | 266 |
267 |
268 | 269 | def content(element_name : Symbol, options : OptionHash, &) 270 | 271 | # 272 |
273 | 274 |
275 | 276 |

Builds an arbitrary HTML tag for the provided element_name using options to configure additional parameters for accordingly.

277 | 278 |

Example

279 | 280 |
  content(:a, { aria_data_toggle: true, href: "http://example.com" })
281 | 282 |

Produces:

283 | 284 |
 <a href="http://example.com", aria_data_toggle="true" />
285 |
286 | 287 |
288 |
289 | 290 | [View source] 291 | 292 |
293 |
294 | 295 |
296 |
297 | 298 | def input_field(type : Symbol, options : OptionHash) 299 | 300 | # 301 |
302 | 303 |
304 |
305 | 306 | [View source] 307 | 308 |
309 |
310 | 311 |
312 |
313 | 314 | def input_field(type : Symbol, **options) 315 | 316 | # 317 |
318 | 319 |
320 |
321 | 322 | [View source] 323 | 324 |
325 |
326 | 327 |
328 |
329 | 330 | def input_field_string(type : Symbol, options : OptionHash) 331 | 332 | # 333 |
334 | 335 |
336 |
337 | 338 | [View source] 339 | 340 |
341 |
342 | 343 |
344 |
345 | 346 | def prepare_input_field_options(options : OptionHash) 347 | 348 | # 349 |
350 | 351 |
352 | 353 |

helper for input field options

354 |
355 | 356 |
357 |
358 | 359 | [View source] 360 | 361 |
362 |
363 | 364 | 365 | 366 | 367 | 368 |
369 | 370 | 371 | 372 | -------------------------------------------------------------------------------- /docs/JasperHelpers/Text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | JasperHelpers::Text - jasper_helpers rs/docs 17 | 20 | 21 | 22 | 23 | 28 | 98 | 99 | 100 |
101 |

102 | 103 | module JasperHelpers::Text 104 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 |

122 | 123 | 126 | 127 | Direct including types 128 |

129 | 134 | 135 | 136 | 137 | 138 |

139 | 140 | 143 | 144 | Defined in: 145 |

146 | 147 | 148 | 149 | jasper_helpers/text.cr 150 | 151 | 152 |
153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |

164 | 165 | 168 | 169 | Instance Method Summary 170 |

171 | 179 | 180 | 181 | 182 | 183 | 184 |
185 | 186 |
187 | 188 | 189 | 190 | 191 | 192 | 193 |

194 | 195 | 198 | 199 | Instance Method Detail 200 |

201 | 202 |
203 |
204 | 205 | def render_markdown(markdown) 206 | 207 | # 208 |
209 | 210 |
211 |
212 | 213 | [View source] 214 | 215 |
216 |
217 | 218 | 219 | 220 | 221 | 222 |
223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | background: #FFFFFF; 3 | position: relative; 4 | margin: 0; 5 | padding: 0; 6 | width: 100%; 7 | height: 100%; 8 | overflow: hidden; 9 | } 10 | 11 | body { 12 | font-family: "Avenir", "Tahoma", "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; 13 | color: #333; 14 | line-height: 1.5; 15 | } 16 | 17 | a { 18 | color: #263F6C; 19 | } 20 | 21 | a:visited { 22 | color: #112750; 23 | } 24 | 25 | h1, h2, h3, h4, h5, h6 { 26 | margin: 35px 0 25px; 27 | color: #444444; 28 | } 29 | 30 | h1.type-name { 31 | color: #47266E; 32 | margin: 20px 0 30px; 33 | background-color: #F8F8F8; 34 | padding: 10px 12px; 35 | border: 1px solid #EBEBEB; 36 | border-radius: 2px; 37 | } 38 | 39 | h2 { 40 | border-bottom: 1px solid #E6E6E6; 41 | padding-bottom: 5px; 42 | } 43 | 44 | body { 45 | display: flex; 46 | } 47 | 48 | .sidebar, .main-content { 49 | overflow: auto; 50 | } 51 | 52 | .sidebar { 53 | width: 30em; 54 | color: #F8F4FD; 55 | background-color: #2E1052; 56 | padding: 0 0 30px; 57 | box-shadow: inset -3px 0 4px rgba(0,0,0,.35); 58 | line-height: 1.2; 59 | z-index: 0; 60 | } 61 | 62 | .sidebar .search-box { 63 | padding: 13px 9px; 64 | } 65 | 66 | .sidebar input { 67 | display: block; 68 | box-sizing: border-box; 69 | margin: 0; 70 | padding: 5px; 71 | font: inherit; 72 | font-family: inherit; 73 | line-height: 1.2; 74 | width: 100%; 75 | border: 0; 76 | outline: 0; 77 | border-radius: 2px; 78 | box-shadow: 0px 3px 5px rgba(0,0,0,.25); 79 | transition: box-shadow .12s; 80 | } 81 | 82 | .sidebar input:focus { 83 | box-shadow: 0px 5px 6px rgba(0,0,0,.5); 84 | } 85 | 86 | .sidebar input::-webkit-input-placeholder { /* Chrome/Opera/Safari */ 87 | color: #C8C8C8; 88 | font-size: 14px; 89 | text-indent: 2px; 90 | } 91 | 92 | .sidebar input::-moz-placeholder { /* Firefox 19+ */ 93 | color: #C8C8C8; 94 | font-size: 14px; 95 | text-indent: 2px; 96 | } 97 | 98 | .sidebar input:-ms-input-placeholder { /* IE 10+ */ 99 | color: #C8C8C8; 100 | font-size: 14px; 101 | text-indent: 2px; 102 | } 103 | 104 | .sidebar input:-moz-placeholder { /* Firefox 18- */ 105 | color: #C8C8C8; 106 | font-size: 14px; 107 | text-indent: 2px; 108 | } 109 | 110 | .project-summary { 111 | padding: 9px 15px 30px 30px; 112 | } 113 | 114 | .project-name { 115 | font-size: 1.4rem; 116 | margin: 0; 117 | color: #f4f4f4; 118 | font-weight: 600; 119 | } 120 | 121 | .project-version { 122 | margin-top: 5px; 123 | display: inline-block; 124 | position: relative; 125 | } 126 | 127 | .project-version > form::after { 128 | position: absolute; 129 | right: 0; 130 | top: 0; 131 | content: "\25BC"; 132 | font-size: .6em; 133 | line-height: 1.2rem; 134 | z-index: -1; 135 | } 136 | 137 | .project-versions-nav { 138 | cursor: pointer; 139 | margin: 0; 140 | padding: 0 .9em 0 0; 141 | border: none; 142 | -moz-appearance: none; 143 | -webkit-appearance: none; 144 | appearance: none; 145 | background-color: transparent; 146 | color: inherit; 147 | font-family: inherit; 148 | font-size: inherit; 149 | line-height: inherit; 150 | } 151 | .project-versions-nav:focus { 152 | outline: none; 153 | } 154 | 155 | .project-versions-nav > option { 156 | color: initial; 157 | } 158 | 159 | .sidebar ul { 160 | margin: 0; 161 | padding: 0; 162 | list-style: none outside; 163 | } 164 | 165 | .sidebar li { 166 | display: block; 167 | position: relative; 168 | } 169 | 170 | .types-list li.hide { 171 | display: none; 172 | } 173 | 174 | .sidebar a { 175 | text-decoration: none; 176 | color: inherit; 177 | transition: color .14s; 178 | } 179 | .types-list a { 180 | display: block; 181 | padding: 5px 15px 5px 30px; 182 | } 183 | 184 | .types-list { 185 | display: block; 186 | } 187 | 188 | .sidebar a:focus { 189 | outline: 1px solid #D1B7F1; 190 | } 191 | 192 | .types-list a { 193 | padding: 5px 15px 5px 30px; 194 | } 195 | 196 | .sidebar .current > a, 197 | .sidebar a:hover { 198 | color: #866BA6; 199 | } 200 | 201 | .types-list li ul { 202 | overflow: hidden; 203 | height: 0; 204 | max-height: 0; 205 | transition: 1s ease-in-out; 206 | } 207 | 208 | .types-list li.parent { 209 | padding-left: 30px; 210 | } 211 | 212 | .types-list li.parent::before { 213 | box-sizing: border-box; 214 | content: "▼"; 215 | display: block; 216 | width: 30px; 217 | height: 30px; 218 | position: absolute; 219 | top: 0; 220 | left: 0; 221 | text-align: center; 222 | color: white; 223 | font-size: 8px; 224 | line-height: 30px; 225 | transform: rotateZ(-90deg); 226 | cursor: pointer; 227 | transition: .2s linear; 228 | } 229 | 230 | 231 | .types-list li.parent > a { 232 | padding-left: 0; 233 | } 234 | 235 | .types-list li.parent.open::before { 236 | transform: rotateZ(0); 237 | } 238 | 239 | .types-list li.open > ul { 240 | height: auto; 241 | max-height: 1000em; 242 | } 243 | 244 | .main-content { 245 | padding: 0 30px 30px 30px; 246 | width: 100%; 247 | } 248 | 249 | .kind { 250 | font-size: 60%; 251 | color: #866BA6; 252 | } 253 | 254 | .superclass-hierarchy { 255 | margin: -15px 0 30px 0; 256 | padding: 0; 257 | list-style: none outside; 258 | font-size: 80%; 259 | } 260 | 261 | .superclass-hierarchy .superclass { 262 | display: inline-block; 263 | margin: 0 7px 0 0; 264 | padding: 0; 265 | } 266 | 267 | .superclass-hierarchy .superclass + .superclass::before { 268 | content: "<"; 269 | margin-right: 7px; 270 | } 271 | 272 | .other-types-list li { 273 | display: inline-block; 274 | } 275 | 276 | .other-types-list, 277 | .list-summary { 278 | margin: 0 0 30px 0; 279 | padding: 0; 280 | list-style: none outside; 281 | } 282 | 283 | .entry-const { 284 | font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; 285 | } 286 | 287 | .entry-const code { 288 | white-space: pre-wrap; 289 | } 290 | 291 | .entry-summary { 292 | padding-bottom: 4px; 293 | } 294 | 295 | .superclass-hierarchy .superclass a, 296 | .other-type a, 297 | .entry-summary .signature { 298 | padding: 4px 8px; 299 | margin-bottom: 4px; 300 | display: inline-block; 301 | background-color: #f8f8f8; 302 | color: #47266E; 303 | border: 1px solid #f0f0f0; 304 | text-decoration: none; 305 | border-radius: 3px; 306 | font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; 307 | transition: background .15s, border-color .15s; 308 | } 309 | 310 | .superclass-hierarchy .superclass a:hover, 311 | .other-type a:hover, 312 | .entry-summary .signature:hover { 313 | background: #D5CAE3; 314 | border-color: #624288; 315 | } 316 | 317 | .entry-summary .summary { 318 | padding-left: 32px; 319 | } 320 | 321 | .entry-summary .summary p { 322 | margin: 12px 0 16px; 323 | } 324 | 325 | .entry-summary a { 326 | text-decoration: none; 327 | } 328 | 329 | .entry-detail { 330 | padding: 30px 0; 331 | } 332 | 333 | .entry-detail .signature { 334 | position: relative; 335 | padding: 5px 15px; 336 | margin-bottom: 10px; 337 | display: block; 338 | border-radius: 5px; 339 | background-color: #f8f8f8; 340 | color: #47266E; 341 | border: 1px solid #f0f0f0; 342 | font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; 343 | transition: .2s ease-in-out; 344 | } 345 | 346 | .entry-detail:target .signature { 347 | background-color: #D5CAE3; 348 | border: 1px solid #624288; 349 | } 350 | 351 | .entry-detail .signature .method-permalink { 352 | position: absolute; 353 | top: 0; 354 | left: -35px; 355 | padding: 5px 15px; 356 | text-decoration: none; 357 | font-weight: bold; 358 | color: #624288; 359 | opacity: .4; 360 | transition: opacity .2s; 361 | } 362 | 363 | .entry-detail .signature .method-permalink:hover { 364 | opacity: 1; 365 | } 366 | 367 | .entry-detail:target .signature .method-permalink { 368 | opacity: 1; 369 | } 370 | 371 | .methods-inherited { 372 | padding-right: 10%; 373 | line-height: 1.5em; 374 | } 375 | 376 | .methods-inherited h3 { 377 | margin-bottom: 4px; 378 | } 379 | 380 | .methods-inherited a { 381 | display: inline-block; 382 | text-decoration: none; 383 | color: #47266E; 384 | } 385 | 386 | .methods-inherited a:hover { 387 | text-decoration: underline; 388 | color: #6C518B; 389 | } 390 | 391 | .methods-inherited .tooltip>span { 392 | background: #D5CAE3; 393 | padding: 4px 8px; 394 | border-radius: 3px; 395 | margin: -4px -8px; 396 | } 397 | 398 | .methods-inherited .tooltip * { 399 | color: #47266E; 400 | } 401 | 402 | pre { 403 | padding: 10px 20px; 404 | margin-top: 4px; 405 | border-radius: 3px; 406 | line-height: 1.45; 407 | overflow: auto; 408 | color: #333; 409 | background: #fdfdfd; 410 | font-size: 14px; 411 | border: 1px solid #eee; 412 | } 413 | 414 | code { 415 | font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; 416 | } 417 | 418 | :not(pre) > code { 419 | background-color: rgba(40,35,30,0.05); 420 | padding: 0.2em 0.4em; 421 | font-size: 85%; 422 | border-radius: 3px; 423 | } 424 | 425 | span.flag { 426 | padding: 2px 4px 1px; 427 | border-radius: 3px; 428 | margin-right: 3px; 429 | font-size: 11px; 430 | border: 1px solid transparent; 431 | } 432 | 433 | span.flag.orange { 434 | background-color: #EE8737; 435 | color: #FCEBDD; 436 | border-color: #EB7317; 437 | } 438 | 439 | span.flag.yellow { 440 | background-color: #E4B91C; 441 | color: #FCF8E8; 442 | border-color: #B69115; 443 | } 444 | 445 | span.flag.green { 446 | background-color: #469C14; 447 | color: #E2F9D3; 448 | border-color: #34700E; 449 | } 450 | 451 | span.flag.red { 452 | background-color: #BF1919; 453 | color: #F9ECEC; 454 | border-color: #822C2C; 455 | } 456 | 457 | span.flag.purple { 458 | background-color: #2E1052; 459 | color: #ECE1F9; 460 | border-color: #1F0B37; 461 | } 462 | 463 | span.flag.lime { 464 | background-color: #a3ff00; 465 | color: #222222; 466 | border-color: #00ff1e; 467 | } 468 | 469 | .tooltip>span { 470 | position: absolute; 471 | opacity: 0; 472 | display: none; 473 | pointer-events: none; 474 | } 475 | 476 | .tooltip:hover>span { 477 | display: inline-block; 478 | opacity: 1; 479 | } 480 | 481 | .c { 482 | color: #969896; 483 | } 484 | 485 | .n { 486 | color: #0086b3; 487 | } 488 | 489 | .t { 490 | color: #0086b3; 491 | } 492 | 493 | .s { 494 | color: #183691; 495 | } 496 | 497 | .i { 498 | color: #7f5030; 499 | } 500 | 501 | .k { 502 | color: #a71d5d; 503 | } 504 | 505 | .o { 506 | color: #a71d5d; 507 | } 508 | 509 | .m { 510 | color: #795da3; 511 | } 512 | 513 | .hidden { 514 | display: none; 515 | } 516 | .search-results { 517 | font-size: 90%; 518 | line-height: 1.3; 519 | } 520 | 521 | .search-results mark { 522 | color: inherit; 523 | background: transparent; 524 | font-weight: bold; 525 | } 526 | .search-result { 527 | padding: 5px 8px 5px 5px; 528 | cursor: pointer; 529 | border-left: 5px solid transparent; 530 | transform: translateX(-3px); 531 | transition: all .2s, background-color 0s, border .02s; 532 | min-height: 3.2em; 533 | } 534 | .search-result.current { 535 | border-left-color: #ddd; 536 | background-color: rgba(200,200,200,0.4); 537 | transform: translateX(0); 538 | transition: all .2s, background-color .5s, border 0s; 539 | } 540 | .search-result.current:hover, 541 | .search-result.current:focus { 542 | border-left-color: #866BA6; 543 | } 544 | .search-result:not(.current):nth-child(2n) { 545 | background-color: rgba(255,255,255,.06); 546 | } 547 | .search-result__title { 548 | font-size: 105%; 549 | word-break: break-all; 550 | line-height: 1.1; 551 | padding: 3px 0; 552 | } 553 | .search-result__title strong { 554 | font-weight: normal; 555 | } 556 | .search-results .search-result__title > a { 557 | padding: 0; 558 | display: block; 559 | } 560 | .search-result__title > a > .args { 561 | color: #dddddd; 562 | font-weight: 300; 563 | transition: inherit; 564 | font-size: 88%; 565 | line-height: 1.2; 566 | letter-spacing: -.02em; 567 | } 568 | .search-result__title > a > .args * { 569 | color: inherit; 570 | } 571 | 572 | .search-result a, 573 | .search-result a:hover { 574 | color: inherit; 575 | } 576 | .search-result:not(.current):hover .search-result__title > a, 577 | .search-result:not(.current):focus .search-result__title > a, 578 | .search-result__title > a:focus { 579 | color: #866BA6; 580 | } 581 | .search-result:not(.current):hover .args, 582 | .search-result:not(.current):focus .args { 583 | color: #6a5a7d; 584 | } 585 | 586 | .search-result__type { 587 | color: #e8e8e8; 588 | font-weight: 300; 589 | } 590 | .search-result__doc { 591 | color: #bbbbbb; 592 | font-size: 90%; 593 | } 594 | .search-result__doc p { 595 | margin: 0; 596 | text-overflow: ellipsis; 597 | display: -webkit-box; 598 | -webkit-box-orient: vertical; 599 | -webkit-line-clamp: 2; 600 | overflow: hidden; 601 | line-height: 1.2em; 602 | max-height: 2.4em; 603 | } 604 | 605 | .js-modal-visible .modal-background { 606 | display: flex; 607 | } 608 | .main-content { 609 | position: relative; 610 | } 611 | .modal-background { 612 | position: absolute; 613 | display: none; 614 | height: 100%; 615 | width: 100%; 616 | background: rgba(120,120,120,.4); 617 | z-index: 100; 618 | align-items: center; 619 | justify-content: center; 620 | } 621 | .usage-modal { 622 | max-width: 90%; 623 | background: #fff; 624 | border: 2px solid #ccc; 625 | border-radius: 9px; 626 | padding: 5px 15px 20px; 627 | min-width: 50%; 628 | color: #555; 629 | position: relative; 630 | transform: scale(.5); 631 | transition: transform 200ms; 632 | } 633 | .js-modal-visible .usage-modal { 634 | transform: scale(1); 635 | } 636 | .usage-modal > .close-button { 637 | position: absolute; 638 | right: 15px; 639 | top: 8px; 640 | color: #aaa; 641 | font-size: 27px; 642 | cursor: pointer; 643 | } 644 | .usage-modal > .close-button:hover { 645 | text-shadow: 2px 2px 2px #ccc; 646 | color: #999; 647 | } 648 | .modal-title { 649 | margin: 0; 650 | text-align: center; 651 | font-weight: normal; 652 | color: #666; 653 | border-bottom: 2px solid #ddd; 654 | padding: 10px; 655 | } 656 | .usage-list { 657 | padding: 0; 658 | margin: 13px; 659 | } 660 | .usage-list > li { 661 | padding: 5px 2px; 662 | overflow: auto; 663 | padding-left: 100px; 664 | min-width: 12em; 665 | } 666 | .usage-modal kbd { 667 | background: #eee; 668 | border: 1px solid #ccc; 669 | border-bottom-width: 2px; 670 | border-radius: 3px; 671 | padding: 3px 8px; 672 | font-family: monospace; 673 | margin-right: 2px; 674 | display: inline-block; 675 | } 676 | .usage-key { 677 | float: left; 678 | clear: left; 679 | margin-left: -100px; 680 | margin-right: 12px; 681 | } 682 | .doc-inherited { 683 | font-weight: bold; 684 | } 685 | 686 | .anchor { 687 | float: left; 688 | padding-right: 4px; 689 | margin-left: -20px; 690 | } 691 | 692 | .main-content .anchor .octicon-link { 693 | width: 16px; 694 | height: 16px; 695 | } 696 | 697 | .main-content .anchor:focus { 698 | outline: none 699 | } 700 | 701 | .main-content h1:hover .anchor, 702 | .main-content h2:hover .anchor, 703 | .main-content h3:hover .anchor, 704 | .main-content h4:hover .anchor, 705 | .main-content h5:hover .anchor, 706 | .main-content h6:hover .anchor { 707 | text-decoration: none 708 | } 709 | 710 | .main-content h1 .octicon-link, 711 | .main-content h2 .octicon-link, 712 | .main-content h3 .octicon-link, 713 | .main-content h4 .octicon-link, 714 | .main-content h5 .octicon-link, 715 | .main-content h6 .octicon-link { 716 | visibility: hidden 717 | } 718 | 719 | .main-content h1:hover .anchor .octicon-link, 720 | .main-content h2:hover .anchor .octicon-link, 721 | .main-content h3:hover .anchor .octicon-link, 722 | .main-content h4:hover .anchor .octicon-link, 723 | .main-content h5:hover .anchor .octicon-link, 724 | .main-content h6:hover .anchor .octicon-link { 725 | visibility: visible 726 | } 727 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | jasper_helpers rs/docs 17 | 20 | 21 | 22 | 23 | 28 | 98 | 99 | 100 |
101 |

Build Status

102 | 103 |

104 | 107 | Jasper::Helpers

108 | 109 |

A library of helpers for Amber Framework.

110 | 111 |

112 | 115 | Installation

116 | 117 |

Add this to your application's shard.yml:

118 | 119 |
dependencies:
120 |   jasper_helpers:
121 |     github: amberframework/jasper-helpers
122 | 123 |

124 | 127 | Usage

128 | 129 |
require "jasper_helpers"
130 | 
131 | class ApplicationController < Amber::Controller::Base
132 |   include JasperHelpers
133 |   LAYOUT = "application.slang"
134 | end
135 | 136 |

137 | 140 | Contributing

141 | 142 |
  1. Fork it ( https://github.com/amberframework/Jasper-Helpers/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request
143 | 144 |

145 | 148 | Contributors

149 | 150 | 151 |
152 | 153 | 154 | -------------------------------------------------------------------------------- /docs/js/doc.js: -------------------------------------------------------------------------------- 1 | window.CrystalDocs = (window.CrystalDocs || {}); 2 | 3 | CrystalDocs.base_path = (CrystalDocs.base_path || ""); 4 | 5 | CrystalDocs.searchIndex = (CrystalDocs.searchIndex || false); 6 | CrystalDocs.MAX_RESULTS_DISPLAY = 140; 7 | 8 | CrystalDocs.runQuery = function(query) { 9 | function searchType(type, query, results) { 10 | var matches = []; 11 | var matchedFields = []; 12 | var name = type.full_name; 13 | var i = name.lastIndexOf("::"); 14 | if (i > 0) { 15 | name = name.substring(i + 2); 16 | } 17 | var nameMatches = query.matches(name); 18 | if (nameMatches){ 19 | matches = matches.concat(nameMatches); 20 | matchedFields.push("name"); 21 | } 22 | 23 | var namespaceMatches = query.matchesNamespace(type.full_name); 24 | if(namespaceMatches){ 25 | matches = matches.concat(namespaceMatches); 26 | matchedFields.push("name"); 27 | } 28 | 29 | var docMatches = query.matches(type.doc); 30 | if(docMatches){ 31 | matches = matches.concat(docMatches); 32 | matchedFields.push("doc"); 33 | } 34 | if (matches.length > 0) { 35 | results.push({ 36 | id: type.id, 37 | result_type: "type", 38 | kind: type.kind, 39 | name: name, 40 | full_name: type.full_name, 41 | href: type.path, 42 | summary: type.summary, 43 | matched_fields: matchedFields, 44 | matched_terms: matches 45 | }); 46 | } 47 | 48 | type.instance_methods.forEach(function(method) { 49 | searchMethod(method, type, "instance_method", query, results); 50 | }) 51 | type.class_methods.forEach(function(method) { 52 | searchMethod(method, type, "class_method", query, results); 53 | }) 54 | type.constructors.forEach(function(constructor) { 55 | searchMethod(constructor, type, "constructor", query, results); 56 | }) 57 | type.macros.forEach(function(macro) { 58 | searchMethod(macro, type, "macro", query, results); 59 | }) 60 | type.constants.forEach(function(constant){ 61 | searchConstant(constant, type, query, results); 62 | }); 63 | 64 | type.types.forEach(function(subtype){ 65 | searchType(subtype, query, results); 66 | }); 67 | }; 68 | 69 | function searchMethod(method, type, kind, query, results) { 70 | var matches = []; 71 | var matchedFields = []; 72 | var nameMatches = query.matchesMethod(method.name, kind, type); 73 | if (nameMatches){ 74 | matches = matches.concat(nameMatches); 75 | matchedFields.push("name"); 76 | } 77 | 78 | method.args.forEach(function(arg){ 79 | var argMatches = query.matches(arg.external_name); 80 | if (argMatches) { 81 | matches = matches.concat(argMatches); 82 | matchedFields.push("args"); 83 | } 84 | }); 85 | 86 | var docMatches = query.matches(type.doc); 87 | if(docMatches){ 88 | matches = matches.concat(docMatches); 89 | matchedFields.push("doc"); 90 | } 91 | 92 | if (matches.length > 0) { 93 | var typeMatches = query.matches(type.full_name); 94 | if (typeMatches) { 95 | matchedFields.push("type"); 96 | matches = matches.concat(typeMatches); 97 | } 98 | results.push({ 99 | id: method.id, 100 | type: type.full_name, 101 | result_type: kind, 102 | name: method.name, 103 | full_name: type.full_name + "#" + method.name, 104 | args_string: method.args_string, 105 | summary: method.summary, 106 | href: type.path + "#" + method.id, 107 | matched_fields: matchedFields, 108 | matched_terms: matches 109 | }); 110 | } 111 | } 112 | 113 | function searchConstant(constant, type, query, results) { 114 | var matches = []; 115 | var matchedFields = []; 116 | var nameMatches = query.matches(constant.name); 117 | if (nameMatches){ 118 | matches = matches.concat(nameMatches); 119 | matchedFields.push("name"); 120 | } 121 | var docMatches = query.matches(constant.doc); 122 | if(docMatches){ 123 | matches = matches.concat(docMatches); 124 | matchedFields.push("doc"); 125 | } 126 | if (matches.length > 0) { 127 | var typeMatches = query.matches(type.full_name); 128 | if (typeMatches) { 129 | matchedFields.push("type"); 130 | matches = matches.concat(typeMatches); 131 | } 132 | results.push({ 133 | id: constant.id, 134 | type: type.full_name, 135 | result_type: "constant", 136 | name: constant.name, 137 | full_name: type.full_name + "#" + constant.name, 138 | value: constant.value, 139 | summary: constant.summary, 140 | href: type.path + "#" + constant.id, 141 | matched_fields: matchedFields, 142 | matched_terms: matches 143 | }); 144 | } 145 | } 146 | 147 | var results = []; 148 | searchType(CrystalDocs.searchIndex.program, query, results); 149 | return results; 150 | }; 151 | 152 | CrystalDocs.rankResults = function(results, query) { 153 | function uniqueArray(ar) { 154 | var j = {}; 155 | 156 | ar.forEach(function(v) { 157 | j[v + "::" + typeof v] = v; 158 | }); 159 | 160 | return Object.keys(j).map(function(v) { 161 | return j[v]; 162 | }); 163 | } 164 | 165 | results = results.sort(function(a, b) { 166 | var matchedTermsDiff = uniqueArray(b.matched_terms).length - uniqueArray(a.matched_terms).length; 167 | var aHasDocs = b.matched_fields.includes("doc"); 168 | var bHasDocs = b.matched_fields.includes("doc"); 169 | 170 | var aOnlyDocs = aHasDocs && a.matched_fields.length == 1; 171 | var bOnlyDocs = bHasDocs && b.matched_fields.length == 1; 172 | 173 | if (a.result_type == "type" && b.result_type != "type" && !aOnlyDocs) { 174 | if(CrystalDocs.DEBUG) { console.log("a is type b not"); } 175 | return -1; 176 | } else if (b.result_type == "type" && a.result_type != "type" && !bOnlyDocs) { 177 | if(CrystalDocs.DEBUG) { console.log("b is type, a not"); } 178 | return 1; 179 | } 180 | if (a.matched_fields.includes("name")) { 181 | if (b.matched_fields.includes("name")) { 182 | var a_name = (CrystalDocs.prefixForType(a.result_type) || "") + ((a.result_type == "type") ? a.full_name : a.name); 183 | var b_name = (CrystalDocs.prefixForType(b.result_type) || "") + ((b.result_type == "type") ? b.full_name : b.name); 184 | a_name = a_name.toLowerCase(); 185 | b_name = b_name.toLowerCase(); 186 | for(var i = 0; i < query.normalizedTerms.length; i++) { 187 | var term = query.terms[i].replace(/^::?|::?$/, ""); 188 | var a_orig_index = a_name.indexOf(term); 189 | var b_orig_index = b_name.indexOf(term); 190 | if(CrystalDocs.DEBUG) { console.log("term: " + term + " a: " + a_name + " b: " + b_name); } 191 | if(CrystalDocs.DEBUG) { console.log(a_orig_index, b_orig_index, a_orig_index - b_orig_index); } 192 | if (a_orig_index >= 0) { 193 | if (b_orig_index >= 0) { 194 | if(CrystalDocs.DEBUG) { console.log("both have exact match", a_orig_index > b_orig_index ? -1 : 1); } 195 | if(a_orig_index != b_orig_index) { 196 | if(CrystalDocs.DEBUG) { console.log("both have exact match at different positions", a_orig_index > b_orig_index ? 1 : -1); } 197 | return a_orig_index > b_orig_index ? 1 : -1; 198 | } 199 | } else { 200 | if(CrystalDocs.DEBUG) { console.log("a has exact match, b not"); } 201 | return -1; 202 | } 203 | } else if (b_orig_index >= 0) { 204 | if(CrystalDocs.DEBUG) { console.log("b has exact match, a not"); } 205 | return 1; 206 | } 207 | } 208 | } else { 209 | if(CrystalDocs.DEBUG) { console.log("a has match in name, b not"); } 210 | return -1; 211 | } 212 | } else if ( 213 | !a.matched_fields.includes("name") && 214 | b.matched_fields.includes("name") 215 | ) { 216 | return 1; 217 | } 218 | 219 | if (matchedTermsDiff != 0 || (aHasDocs != bHasDocs)) { 220 | if(CrystalDocs.DEBUG) { console.log("matchedTermsDiff: " + matchedTermsDiff, aHasDocs, bHasDocs); } 221 | return matchedTermsDiff; 222 | } 223 | 224 | var matchedFieldsDiff = b.matched_fields.length - a.matched_fields.length; 225 | if (matchedFieldsDiff != 0) { 226 | if(CrystalDocs.DEBUG) { console.log("matched to different number of fields: " + matchedFieldsDiff); } 227 | return matchedFieldsDiff > 0 ? 1 : -1; 228 | } 229 | 230 | var nameCompare = a.name.localeCompare(b.name); 231 | if(nameCompare != 0){ 232 | if(CrystalDocs.DEBUG) { console.log("nameCompare resulted in: " + a.name + "<=>" + b.name + ": " + nameCompare); } 233 | return nameCompare > 0 ? 1 : -1; 234 | } 235 | 236 | if(a.matched_fields.includes("args") && b.matched_fields.includes("args")) { 237 | for(var i = 0; i < query.terms.length; i++) { 238 | var term = query.terms[i]; 239 | var aIndex = a.args_string.indexOf(term); 240 | var bIndex = b.args_string.indexOf(term); 241 | if(CrystalDocs.DEBUG) { console.log("index of " + term + " in args_string: " + aIndex + " - " + bIndex); } 242 | if(aIndex >= 0){ 243 | if(bIndex >= 0){ 244 | if(aIndex != bIndex){ 245 | return aIndex > bIndex ? 1 : -1; 246 | } 247 | }else{ 248 | return -1; 249 | } 250 | }else if(bIndex >= 0) { 251 | return 1; 252 | } 253 | } 254 | } 255 | 256 | return 0; 257 | }); 258 | 259 | if (results.length > 1) { 260 | // if we have more than two search terms, only include results with the most matches 261 | var bestMatchedTerms = uniqueArray(results[0].matched_terms).length; 262 | 263 | results = results.filter(function(result) { 264 | return uniqueArray(result.matched_terms).length + 1 >= bestMatchedTerms; 265 | }); 266 | } 267 | return results; 268 | }; 269 | 270 | CrystalDocs.prefixForType = function(type) { 271 | switch (type) { 272 | case "instance_method": 273 | return "#"; 274 | 275 | case "class_method": 276 | case "macro": 277 | case "constructor": 278 | return "."; 279 | 280 | default: 281 | return false; 282 | } 283 | }; 284 | 285 | CrystalDocs.displaySearchResults = function(results, query) { 286 | function sanitize(html){ 287 | return html.replace(/<(?!\/?code)[^>]+>/g, ""); 288 | } 289 | 290 | // limit results 291 | if (results.length > CrystalDocs.MAX_RESULTS_DISPLAY) { 292 | results = results.slice(0, CrystalDocs.MAX_RESULTS_DISPLAY); 293 | } 294 | 295 | var $frag = document.createDocumentFragment(); 296 | var $resultsElem = document.querySelector(".search-list"); 297 | $resultsElem.innerHTML = ""; 298 | 299 | results.forEach(function(result, i) { 300 | var url = CrystalDocs.base_path + result.href; 301 | var type = false; 302 | 303 | var title = query.highlight(result.result_type == "type" ? result.full_name : result.name); 304 | 305 | var prefix = CrystalDocs.prefixForType(result.result_type); 306 | if (prefix) { 307 | title = "" + prefix + "" + title; 308 | } 309 | 310 | title = "" + title + ""; 311 | 312 | if (result.args_string) { 313 | title += 314 | "" + query.highlight(result.args_string) + ""; 315 | } 316 | 317 | $elem = document.createElement("li"); 318 | $elem.className = "search-result search-result--" + result.result_type; 319 | $elem.dataset.href = url; 320 | $elem.setAttribute("title", result.full_name + " docs page"); 321 | 322 | var $title = document.createElement("div"); 323 | $title.setAttribute("class", "search-result__title"); 324 | var $titleLink = document.createElement("a"); 325 | $titleLink.setAttribute("href", url); 326 | 327 | $titleLink.innerHTML = title; 328 | $title.appendChild($titleLink); 329 | $elem.appendChild($title); 330 | $elem.addEventListener("click", function() { 331 | $titleLink.click(); 332 | }); 333 | 334 | if (result.result_type !== "type") { 335 | var $type = document.createElement("div"); 336 | $type.setAttribute("class", "search-result__type"); 337 | $type.innerHTML = query.highlight(result.type); 338 | $elem.appendChild($type); 339 | } 340 | 341 | if(result.summary){ 342 | var $doc = document.createElement("div"); 343 | $doc.setAttribute("class", "search-result__doc"); 344 | $doc.innerHTML = query.highlight(sanitize(result.summary)); 345 | $elem.appendChild($doc); 346 | } 347 | 348 | $elem.appendChild(document.createComment(JSON.stringify(result))); 349 | $frag.appendChild($elem); 350 | }); 351 | 352 | $resultsElem.appendChild($frag); 353 | 354 | CrystalDocs.toggleResultsList(true); 355 | }; 356 | 357 | CrystalDocs.toggleResultsList = function(visible) { 358 | if (visible) { 359 | document.querySelector(".types-list").classList.add("hidden"); 360 | document.querySelector(".search-results").classList.remove("hidden"); 361 | } else { 362 | document.querySelector(".types-list").classList.remove("hidden"); 363 | document.querySelector(".search-results").classList.add("hidden"); 364 | } 365 | }; 366 | 367 | CrystalDocs.Query = function(string) { 368 | this.original = string; 369 | this.terms = string.split(/\s+/).filter(function(word) { 370 | return CrystalDocs.Query.stripModifiers(word).length > 0; 371 | }); 372 | 373 | var normalized = this.terms.map(CrystalDocs.Query.normalizeTerm); 374 | this.normalizedTerms = normalized; 375 | 376 | function runMatcher(field, matcher) { 377 | if (!field) { 378 | return false; 379 | } 380 | var normalizedValue = CrystalDocs.Query.normalizeTerm(field); 381 | 382 | var matches = []; 383 | normalized.forEach(function(term) { 384 | if (matcher(normalizedValue, term)) { 385 | matches.push(term); 386 | } 387 | }); 388 | return matches.length > 0 ? matches : false; 389 | } 390 | 391 | this.matches = function(field) { 392 | return runMatcher(field, function(normalized, term) { 393 | if (term[0] == "#" || term[0] == ".") { 394 | return false; 395 | } 396 | return normalized.indexOf(term) >= 0; 397 | }); 398 | }; 399 | 400 | function namespaceMatcher(normalized, term){ 401 | var i = term.indexOf(":"); 402 | if(i >= 0){ 403 | term = term.replace(/^::?|::?$/, ""); 404 | var index = normalized.indexOf(term); 405 | if((index == 0) || (index > 0 && normalized[index-1] == ":")){ 406 | return true; 407 | } 408 | } 409 | return false; 410 | } 411 | this.matchesMethod = function(name, kind, type) { 412 | return runMatcher(name, function(normalized, term) { 413 | var i = term.indexOf("#"); 414 | if(i >= 0){ 415 | if (kind != "instance_method") { 416 | return false; 417 | } 418 | }else{ 419 | i = term.indexOf("."); 420 | if(i >= 0){ 421 | if (kind != "class_method" && kind != "macro" && kind != "constructor") { 422 | return false; 423 | } 424 | }else{ 425 | //neither # nor . 426 | if(term.indexOf(":") && namespaceMatcher(normalized, term)){ 427 | return true; 428 | } 429 | } 430 | } 431 | 432 | var methodName = term; 433 | if(i >= 0){ 434 | var termType = term.substring(0, i); 435 | methodName = term.substring(i+1); 436 | 437 | if(termType != "") { 438 | if(CrystalDocs.Query.normalizeTerm(type.full_name).indexOf(termType) < 0){ 439 | return false; 440 | } 441 | } 442 | } 443 | return normalized.indexOf(methodName) >= 0; 444 | }); 445 | }; 446 | 447 | this.matchesNamespace = function(namespace){ 448 | return runMatcher(namespace, namespaceMatcher); 449 | }; 450 | 451 | this.highlight = function(string) { 452 | if (typeof string == "undefined") { 453 | return ""; 454 | } 455 | function escapeRegExp(s) { 456 | return s.replace(/[.*+?\^${}()|\[\]\\]/g, "\\$&").replace(/^[#\.:]+/, ""); 457 | } 458 | return string.replace( 459 | new RegExp("(" + this.normalizedTerms.map(escapeRegExp).join("|") + ")", "gi"), 460 | "$1" 461 | ); 462 | }; 463 | }; 464 | CrystalDocs.Query.normalizeTerm = function(term) { 465 | return term.toLowerCase(); 466 | }; 467 | CrystalDocs.Query.stripModifiers = function(term) { 468 | switch (term[0]) { 469 | case "#": 470 | case ".": 471 | case ":": 472 | return term.substr(1); 473 | 474 | default: 475 | return term; 476 | } 477 | } 478 | 479 | CrystalDocs.search = function(string) { 480 | if(!CrystalDocs.searchIndex) { 481 | console.log("CrystalDocs search index not initialized, delaying search"); 482 | 483 | document.addEventListener("CrystalDocs:loaded", function listener(){ 484 | document.removeEventListener("CrystalDocs:loaded", listener); 485 | CrystalDocs.search(string); 486 | }); 487 | return; 488 | } 489 | 490 | document.dispatchEvent(new Event("CrystalDocs:searchStarted")); 491 | 492 | var query = new CrystalDocs.Query(string); 493 | var results = CrystalDocs.runQuery(query); 494 | results = CrystalDocs.rankResults(results, query); 495 | CrystalDocs.displaySearchResults(results, query); 496 | 497 | document.dispatchEvent(new Event("CrystalDocs:searchPerformed")); 498 | }; 499 | 500 | CrystalDocs.initializeIndex = function(data) { 501 | CrystalDocs.searchIndex = data; 502 | 503 | document.dispatchEvent(new Event("CrystalDocs:loaded")); 504 | }; 505 | 506 | CrystalDocs.loadIndex = function() { 507 | function loadJSON(file, callback) { 508 | var xobj = new XMLHttpRequest(); 509 | xobj.overrideMimeType("application/json"); 510 | xobj.open("GET", file, true); 511 | xobj.onreadystatechange = function() { 512 | if (xobj.readyState == 4 && xobj.status == "200") { 513 | callback(xobj.responseText); 514 | } 515 | }; 516 | xobj.send(null); 517 | } 518 | 519 | function loadScript(file) { 520 | script = document.createElement("script"); 521 | script.src = file; 522 | document.body.appendChild(script); 523 | } 524 | 525 | function parseJSON(json) { 526 | CrystalDocs.initializeIndex(JSON.parse(json)); 527 | } 528 | 529 | for(var i = 0; i < document.scripts.length; i++){ 530 | var script = document.scripts[i]; 531 | if (script.src && script.src.indexOf("js/doc.js") >= 0) { 532 | if (script.src.indexOf("file://") == 0) { 533 | // We need to support JSONP files for the search to work on local file system. 534 | var jsonPath = script.src.replace("js/doc.js", "search-index.js"); 535 | loadScript(jsonPath); 536 | return; 537 | } else { 538 | var jsonPath = script.src.replace("js/doc.js", "index.json"); 539 | loadJSON(jsonPath, parseJSON); 540 | return; 541 | } 542 | } 543 | } 544 | console.error("Could not find location of js/doc.js"); 545 | }; 546 | 547 | // Callback for jsonp 548 | function crystal_doc_search_index_callback(data) { 549 | CrystalDocs.initializeIndex(data); 550 | } 551 | 552 | Navigator = function(sidebar, searchInput, list, leaveSearchScope){ 553 | this.list = list; 554 | var self = this; 555 | 556 | var performingSearch = false; 557 | 558 | document.addEventListener('CrystalDocs:searchStarted', function(){ 559 | performingSearch = true; 560 | }); 561 | document.addEventListener('CrystalDocs:searchDebounceStarted', function(){ 562 | performingSearch = true; 563 | }); 564 | document.addEventListener('CrystalDocs:searchPerformed', function(){ 565 | performingSearch = false; 566 | }); 567 | document.addEventListener('CrystalDocs:searchDebounceStopped', function(event){ 568 | performingSearch = false; 569 | }); 570 | 571 | function delayWhileSearching(callback) { 572 | if(performingSearch){ 573 | document.addEventListener('CrystalDocs:searchPerformed', function listener(){ 574 | document.removeEventListener('CrystalDocs:searchPerformed', listener); 575 | 576 | // add some delay to let search results display kick in 577 | setTimeout(callback, 100); 578 | }); 579 | }else{ 580 | callback(); 581 | } 582 | } 583 | 584 | function clearMoveTimeout() { 585 | clearTimeout(self.moveTimeout); 586 | self.moveTimeout = null; 587 | } 588 | 589 | function startMoveTimeout(upwards){ 590 | /*if(self.moveTimeout) { 591 | clearMoveTimeout(); 592 | } 593 | 594 | var go = function() { 595 | if (!self.moveTimeout) return; 596 | self.move(upwards); 597 | self.moveTimout = setTimeout(go, 600); 598 | }; 599 | self.moveTimeout = setTimeout(go, 800);*/ 600 | } 601 | 602 | function scrollCenter(element) { 603 | var rect = element.getBoundingClientRect(); 604 | var middle = sidebar.clientHeight / 2; 605 | sidebar.scrollTop += rect.top + rect.height / 2 - middle; 606 | } 607 | 608 | var move = this.move = function(upwards){ 609 | if(!this.current){ 610 | this.highlightFirst(); 611 | return true; 612 | } 613 | var next = upwards ? this.current.previousElementSibling : this.current.nextElementSibling; 614 | if(next && next.classList) { 615 | this.highlight(next); 616 | scrollCenter(next); 617 | return true; 618 | } 619 | return false; 620 | }; 621 | 622 | this.moveRight = function(){ 623 | }; 624 | this.moveLeft = function(){ 625 | }; 626 | 627 | this.highlight = function(elem) { 628 | if(!elem){ 629 | return; 630 | } 631 | this.removeHighlight(); 632 | 633 | this.current = elem; 634 | this.current.classList.add("current"); 635 | }; 636 | 637 | this.highlightFirst = function(){ 638 | this.highlight(this.list.querySelector('li:first-child')); 639 | }; 640 | 641 | this.removeHighlight = function() { 642 | if(this.current){ 643 | this.current.classList.remove("current"); 644 | } 645 | this.current = null; 646 | } 647 | 648 | this.openSelectedResult = function() { 649 | if(this.current) { 650 | this.current.click(); 651 | } 652 | } 653 | 654 | this.focus = function() { 655 | searchInput.focus(); 656 | searchInput.select(); 657 | this.highlightFirst(); 658 | } 659 | 660 | function handleKeyUp(event) { 661 | switch(event.key) { 662 | case "ArrowUp": 663 | case "ArrowDown": 664 | case "i": 665 | case "j": 666 | case "k": 667 | case "l": 668 | case "c": 669 | case "h": 670 | case "t": 671 | case "n": 672 | event.stopPropagation(); 673 | clearMoveTimeout(); 674 | } 675 | } 676 | 677 | function handleKeyDown(event) { 678 | switch(event.key) { 679 | case "Enter": 680 | event.stopPropagation(); 681 | event.preventDefault(); 682 | leaveSearchScope(); 683 | self.openSelectedResult(); 684 | break; 685 | case "Escape": 686 | event.stopPropagation(); 687 | event.preventDefault(); 688 | leaveSearchScope(); 689 | break; 690 | case "j": 691 | case "c": 692 | case "ArrowUp": 693 | if(event.ctrlKey || event.key == "ArrowUp") { 694 | event.stopPropagation(); 695 | self.move(true); 696 | startMoveTimeout(true); 697 | } 698 | break; 699 | case "k": 700 | case "h": 701 | case "ArrowDown": 702 | if(event.ctrlKey || event.key == "ArrowDown") { 703 | event.stopPropagation(); 704 | self.move(false); 705 | startMoveTimeout(false); 706 | } 707 | break; 708 | case "k": 709 | case "t": 710 | case "ArrowLeft": 711 | if(event.ctrlKey || event.key == "ArrowLeft") { 712 | event.stopPropagation(); 713 | self.moveLeft(); 714 | } 715 | break; 716 | case "l": 717 | case "n": 718 | case "ArrowRight": 719 | if(event.ctrlKey || event.key == "ArrowRight") { 720 | event.stopPropagation(); 721 | self.moveRight(); 722 | } 723 | break; 724 | } 725 | } 726 | 727 | function handleInputKeyUp(event) { 728 | switch(event.key) { 729 | case "ArrowUp": 730 | case "ArrowDown": 731 | event.stopPropagation(); 732 | event.preventDefault(); 733 | clearMoveTimeout(); 734 | } 735 | } 736 | 737 | function handleInputKeyDown(event) { 738 | switch(event.key) { 739 | case "Enter": 740 | event.stopPropagation(); 741 | event.preventDefault(); 742 | delayWhileSearching(function(){ 743 | self.openSelectedResult(); 744 | leaveSearchScope(); 745 | }); 746 | break; 747 | case "Escape": 748 | event.stopPropagation(); 749 | event.preventDefault(); 750 | // remove focus from search input 751 | leaveSearchScope(); 752 | sidebar.focus(); 753 | break; 754 | case "ArrowUp": 755 | event.stopPropagation(); 756 | event.preventDefault(); 757 | self.move(true); 758 | startMoveTimeout(true); 759 | break; 760 | 761 | case "ArrowDown": 762 | event.stopPropagation(); 763 | event.preventDefault(); 764 | self.move(false); 765 | startMoveTimeout(false); 766 | break; 767 | } 768 | } 769 | 770 | sidebar.tabIndex = 100; // set tabIndex to enable keylistener 771 | sidebar.addEventListener('keyup', function(event) { 772 | handleKeyUp(event); 773 | }); 774 | sidebar.addEventListener('keydown', function(event) { 775 | handleKeyDown(event); 776 | }); 777 | searchInput.addEventListener('keydown', function(event) { 778 | handleInputKeyDown(event); 779 | }); 780 | searchInput.addEventListener('keyup', function(event) { 781 | handleInputKeyUp(event); 782 | }); 783 | this.move(); 784 | }; 785 | 786 | CrystalDocs.initializeVersions = function () { 787 | function loadJSON(file, callback) { 788 | var xobj = new XMLHttpRequest(); 789 | xobj.overrideMimeType("application/json"); 790 | xobj.open("GET", file, true); 791 | xobj.onreadystatechange = function() { 792 | if (xobj.readyState == 4 && xobj.status == "200") { 793 | callback(xobj.responseText); 794 | } 795 | }; 796 | xobj.send(null); 797 | } 798 | 799 | function parseJSON(json) { 800 | CrystalDocs.loadConfig(JSON.parse(json)); 801 | } 802 | 803 | $elem = document.querySelector("html > head > meta[name=\"crystal_docs.json_config_url\"]") 804 | if ($elem == undefined) { 805 | return 806 | } 807 | jsonURL = $elem.getAttribute("content") 808 | if (jsonURL && jsonURL != "") { 809 | loadJSON(jsonURL, parseJSON); 810 | } 811 | } 812 | 813 | CrystalDocs.loadConfig = function (config) { 814 | var projectVersions = config["versions"] 815 | var currentVersion = document.querySelector("html > head > meta[name=\"crystal_docs.project_version\"]").getAttribute("content") 816 | 817 | var currentVersionInList = projectVersions.find(function (element) { 818 | return element.name == currentVersion 819 | }) 820 | 821 | if (!currentVersionInList) { 822 | projectVersions.unshift({ name: currentVersion, url: '#' }) 823 | } 824 | 825 | $version = document.querySelector(".project-summary > .project-version") 826 | $version.innerHTML = "" 827 | 828 | $select = document.createElement("select") 829 | $select.classList.add("project-versions-nav") 830 | $select.addEventListener("change", function () { 831 | window.location.href = this.value 832 | }) 833 | projectVersions.forEach(function (version) { 834 | $item = document.createElement("option") 835 | $item.setAttribute("value", version.url) 836 | $item.append(document.createTextNode(version.name)) 837 | 838 | if (version.name == currentVersion) { 839 | $item.setAttribute("selected", true) 840 | $item.setAttribute("disabled", true) 841 | } 842 | $select.append($item) 843 | }); 844 | $form = document.createElement("form") 845 | $form.setAttribute("autocomplete", "off") 846 | $form.append($select) 847 | $version.append($form) 848 | } 849 | 850 | document.addEventListener("DOMContentLoaded", function () { 851 | CrystalDocs.initializeVersions() 852 | }) 853 | 854 | var UsageModal = function(title, content) { 855 | var $body = document.body; 856 | var self = this; 857 | var $modalBackground = document.createElement("div"); 858 | $modalBackground.classList.add("modal-background"); 859 | var $usageModal = document.createElement("div"); 860 | $usageModal.classList.add("usage-modal"); 861 | $modalBackground.appendChild($usageModal); 862 | var $title = document.createElement("h3"); 863 | $title.classList.add("modal-title"); 864 | $title.innerHTML = title 865 | $usageModal.appendChild($title); 866 | var $closeButton = document.createElement("span"); 867 | $closeButton.classList.add("close-button"); 868 | $closeButton.setAttribute("title", "Close modal"); 869 | $closeButton.innerText = '×'; 870 | $usageModal.appendChild($closeButton); 871 | $usageModal.insertAdjacentHTML("beforeend", content); 872 | 873 | $modalBackground.addEventListener('click', function(event) { 874 | var element = event.target || event.srcElement; 875 | 876 | if(element == $modalBackground) { 877 | self.hide(); 878 | } 879 | }); 880 | $closeButton.addEventListener('click', function(event) { 881 | self.hide(); 882 | }); 883 | 884 | $body.insertAdjacentElement('beforeend', $modalBackground); 885 | 886 | this.show = function(){ 887 | $body.classList.add("js-modal-visible"); 888 | }; 889 | this.hide = function(){ 890 | $body.classList.remove("js-modal-visible"); 891 | }; 892 | this.isVisible = function(){ 893 | return $body.classList.contains("js-modal-visible"); 894 | } 895 | } 896 | 897 | 898 | document.addEventListener('DOMContentLoaded', function() { 899 | var sessionStorage; 900 | try { 901 | sessionStorage = window.sessionStorage; 902 | } catch (e) { } 903 | if(!sessionStorage) { 904 | sessionStorage = { 905 | setItem: function() {}, 906 | getItem: function() {}, 907 | removeItem: function() {} 908 | }; 909 | } 910 | 911 | var repositoryName = document.querySelector('[name=repository-name]').getAttribute('content'); 912 | var typesList = document.querySelector('.types-list'); 913 | var searchInput = document.querySelector('.search-input'); 914 | var parents = document.querySelectorAll('.types-list li.parent'); 915 | 916 | var scrollSidebarToOpenType = function(){ 917 | var openTypes = typesList.querySelectorAll('.current'); 918 | if (openTypes.length > 0) { 919 | var lastOpenType = openTypes[openTypes.length - 1]; 920 | lastOpenType.scrollIntoView(); 921 | } 922 | } 923 | 924 | scrollSidebarToOpenType(); 925 | 926 | var setPersistentSearchQuery = function(value){ 927 | sessionStorage.setItem(repositoryName + '::search-input:value', value); 928 | } 929 | 930 | for(var i = 0; i < parents.length; i++) { 931 | var _parent = parents[i]; 932 | _parent.addEventListener('click', function(e) { 933 | e.stopPropagation(); 934 | 935 | if(e.target.tagName.toLowerCase() == 'li') { 936 | if(e.target.className.match(/open/)) { 937 | sessionStorage.removeItem(e.target.getAttribute('data-id')); 938 | e.target.className = e.target.className.replace(/ +open/g, ''); 939 | } else { 940 | sessionStorage.setItem(e.target.getAttribute('data-id'), '1'); 941 | if(e.target.className.indexOf('open') == -1) { 942 | e.target.className += ' open'; 943 | } 944 | } 945 | } 946 | }); 947 | 948 | if(sessionStorage.getItem(_parent.getAttribute('data-id')) == '1') { 949 | _parent.className += ' open'; 950 | } 951 | } 952 | 953 | var leaveSearchScope = function(){ 954 | CrystalDocs.toggleResultsList(false); 955 | window.focus(); 956 | } 957 | 958 | var navigator = new Navigator(document.querySelector('.types-list'), searchInput, document.querySelector(".search-results"), leaveSearchScope); 959 | 960 | CrystalDocs.loadIndex(); 961 | var searchTimeout; 962 | var lastSearchText = false; 963 | var performSearch = function() { 964 | document.dispatchEvent(new Event("CrystalDocs:searchDebounceStarted")); 965 | 966 | clearTimeout(searchTimeout); 967 | searchTimeout = setTimeout(function() { 968 | var text = searchInput.value; 969 | 970 | if(text == "") { 971 | CrystalDocs.toggleResultsList(false); 972 | }else if(text == lastSearchText){ 973 | document.dispatchEvent(new Event("CrystalDocs:searchDebounceStopped")); 974 | }else{ 975 | CrystalDocs.search(text); 976 | navigator.highlightFirst(); 977 | searchInput.focus(); 978 | } 979 | lastSearchText = text; 980 | setPersistentSearchQuery(text); 981 | }, 200); 982 | }; 983 | 984 | if(location.hash.length > 3 && location.hash.substring(0,3) == "#q="){ 985 | // allows directly linking a search query which is then executed on the client 986 | // this comes handy for establishing a custom browser search engine with https://crystal-lang.org/api/#q=%s as a search URL 987 | // TODO: Add OpenSearch description 988 | var searchQuery = location.hash.substring(3); 989 | history.pushState({searchQuery: searchQuery}, "Search for " + searchQuery, location.href.replace(/#q=.*/, "")); 990 | searchInput.value = searchQuery; 991 | document.addEventListener('CrystalDocs:loaded', performSearch); 992 | } 993 | 994 | if (searchInput.value.length == 0) { 995 | var searchText = sessionStorage.getItem(repositoryName + '::search-input:value'); 996 | if(searchText){ 997 | searchInput.value = searchText; 998 | } 999 | } 1000 | searchInput.addEventListener('keyup', performSearch); 1001 | searchInput.addEventListener('input', performSearch); 1002 | 1003 | var usageModal = new UsageModal('Keyboard Shortcuts', '' + 1004 | '' 1042 | ); 1043 | 1044 | function handleShortkeys(event) { 1045 | var element = event.target || event.srcElement; 1046 | 1047 | if(element.tagName == "INPUT" || element.tagName == "TEXTAREA" || element.parentElement.tagName == "TEXTAREA"){ 1048 | return; 1049 | } 1050 | 1051 | switch(event.key) { 1052 | case "?": 1053 | usageModal.show(); 1054 | break; 1055 | 1056 | case "Escape": 1057 | usageModal.hide(); 1058 | break; 1059 | 1060 | case "s": 1061 | case "/": 1062 | if(usageModal.isVisible()) { 1063 | return; 1064 | } 1065 | event.stopPropagation(); 1066 | navigator.focus(); 1067 | performSearch(); 1068 | break; 1069 | } 1070 | } 1071 | 1072 | document.addEventListener('keyup', handleShortkeys); 1073 | 1074 | var scrollToEntryFromLocationHash = function() { 1075 | var hash = window.location.hash; 1076 | if (hash) { 1077 | var targetAnchor = decodeURI(hash.substr(1)); 1078 | var targetEl = document.getElementById(targetAnchor) 1079 | if (targetEl) { 1080 | targetEl.offsetParent.scrollTop = targetEl.offsetTop; 1081 | } 1082 | } 1083 | }; 1084 | window.addEventListener("hashchange", scrollToEntryFromLocationHash, false); 1085 | scrollToEntryFromLocationHash(); 1086 | }); 1087 | -------------------------------------------------------------------------------- /shard.lock: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | shards: 3 | markd: 4 | git: https://github.com/icyleaf/markd.git 5 | version: 0.4.0 6 | 7 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: jasper_helpers 2 | version: 1.2.2 3 | 4 | authors: 5 | - Dru Jensen 6 | - Isaac Sloan 7 | 8 | dependencies: 9 | markd: 10 | github: icyleaf/markd 11 | version: ~> 0.4.0 12 | 13 | crystal: ">= 0.31.1, < 2.0" 14 | 15 | license: MIT 16 | -------------------------------------------------------------------------------- /spec/jasper_helpers/forms_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | describe JasperHelpers::Forms do 4 | describe "#text_field" do 5 | it "main param works with string" do 6 | expected = "" 7 | text_field("my-great-text-input").should eq(expected) 8 | end 9 | 10 | it "main param works with symbol" do 11 | text_field(:name).should eq("") 12 | end 13 | 14 | it "input type with symbol works" do 15 | text_field(:name, type: :password).should eq("") 16 | end 17 | 18 | it "style value works" do 19 | text_field(:name, style: "color: white;").should eq("") 20 | end 21 | end 22 | 23 | describe "#file_field" do 24 | it "main param works with string" do 25 | expected = "" 26 | file_field("my-great-file-input").should eq(expected) 27 | end 28 | 29 | it "main param works with symbol" do 30 | file_field(:name).should eq("") 31 | end 32 | 33 | it "style value works" do 34 | file_field(:name, style: "color: white;").should eq("") 35 | end 36 | end 37 | 38 | describe "#wrapper_field" do 39 | it "creates with nil" do 40 | wrapper_field(nil).should eq("") 41 | end 42 | 43 | it "creates with string" do 44 | wrapper_field("
").should eq("
") 45 | end 46 | 47 | it "creates with element" do 48 | expected = "" 49 | 50 | wrapper_field(text_field("my-great-text-input")).should eq(expected) 51 | end 52 | 53 | it "creates with string and element" do 54 | expected = "
" 55 | 56 | wrapper_field( 57 | "
", 58 | text_field("my-great-text-input") 59 | ).should eq(expected) 60 | end 61 | end 62 | 63 | describe "#label" do 64 | it "creates with string" do 65 | label("name", "My Label").should eq("") 66 | end 67 | 68 | it "creates with symbol" do 69 | label(:name, "My Label").should eq("") 70 | end 71 | 72 | it "creates with string and options" do 73 | label(:name, "My Label", class: "label").should eq("") 74 | end 75 | 76 | it "creates with content" do 77 | expected = "" 78 | label(:name) do 79 | check_box(:allowed) 80 | end.should eq(expected) 81 | end 82 | 83 | it "creates with wrapper_field" do 84 | expected = "" 85 | label(:name) do 86 | wrapper_field( 87 | "name", 88 | check_box(:allowed) 89 | ) 90 | end.should eq(expected) 91 | end 92 | end 93 | 94 | describe "#form" do 95 | it "allows for nested input fields" do 96 | result = form(id: "myForm") do 97 | text_field(:name) 98 | end 99 | expected = "
" 100 | 101 | result.should eq(expected) 102 | end 103 | 104 | it "sets up form for multipart" do 105 | result = form(method: :post, action: "/test/1", id: "myForm", multipart: true) do 106 | text_field(:name) 107 | end 108 | expected = %(
) 109 | 110 | result.should eq(expected) 111 | end 112 | end 113 | 114 | describe "#hidden_field" do 115 | it "creates a hidden field" do 116 | hidden_field(:token).should eq("") 117 | end 118 | end 119 | 120 | describe "#select_field" do 121 | it "creates a select_field with two dimension arrays" do 122 | select_field(:age, [[1, "A"], [2, "B"]]).should eq("") 123 | end 124 | 125 | it "creates a select_field with array of hashes" do 126 | select_field(:age, [{:"1" => "A"}, {:"2" => "B"}]).should eq("") 127 | end 128 | 129 | it "creates a select_field with a hash" do 130 | select_field(:age, {:"1" => "A", :"2" => "B"}).should eq("") 131 | end 132 | 133 | it "creates a select_field with a hash and id" do 134 | expected = "" 135 | select_field(:age, {:"1" => "A", :"2" => "B"}, id: "age_of_thing").should eq(expected) 136 | end 137 | 138 | it "creates a select_field with a named tuple" do 139 | select_field(:age, {"1": "A", "2": "B"}).should eq("") 140 | end 141 | 142 | it "creates a select_field with a namedtuple and id" do 143 | expected = "" 144 | select_field(:age, {"1": "A", "2": "B"}, id: "age_of_thing").should eq(expected) 145 | end 146 | 147 | it "creates a select_field with B selected (String scalar)" do 148 | expected = "" 149 | select_field(:age, {"1": "A", "2": "B"}, selected: "2").should eq(expected) 150 | end 151 | 152 | it "creates a select_field with B selected (Int32 scalar)" do 153 | expected = "" 154 | select_field(:age, {"1": "A", "2": "B"}, selected: 2).should eq(expected) 155 | end 156 | 157 | it "creates a select_field with B and C selected (String array)" do 158 | expected = "" 159 | select_field(:age, {"1": "A", "2": "B", "3": "C"}, selected: ["2", "3"]).should eq(expected) 160 | end 161 | 162 | it "creates a select_field with B and C selected (Int32 array)" do 163 | expected = "" 164 | select_field(:age, {"1": "A", "2": "B", "3": "C"}, selected: [2, 3]).should eq(expected) 165 | end 166 | 167 | it "creates a select_field with B and C selected (Int32 | String array)" do 168 | expected = "" 169 | select_field(:age, {"1": "A", "2": "B", "3": "C"}, selected: [2, "3"]).should eq(expected) 170 | end 171 | 172 | it "creates a select_field with single dimension array" do 173 | select_field(:age, ["A", "B"]).should eq("") 174 | end 175 | 176 | it "creates a select_field with range" do 177 | select_field(:age, collection: 1..5).should eq("") 178 | end 179 | end 180 | 181 | describe "#text_area" do 182 | it "creates a text_area" do 183 | text_area(:description, "My Great Textarea").should eq("") 184 | end 185 | 186 | it "allows for rows and cols to be specified" do 187 | text_area(:description, "My Great Textarea", cols: 5, rows: 10).should eq("") 188 | end 189 | 190 | it "allows for size to be specified" do 191 | text_area(:description, "My Great Textarea", size: "5x10").should eq("") 192 | end 193 | end 194 | 195 | describe "#submit" do 196 | it "creates a submit with no parameters" do 197 | submit.should eq("") 198 | end 199 | 200 | it "creates a submit with no parameters except id" do 201 | submit(id: :submit_button).should eq("") 202 | end 203 | 204 | it "creates a submit with value parameter" do 205 | submit(:create).should eq("") 206 | end 207 | 208 | it "creates a submit with value and id parameters" do 209 | submit(:create, id: "my-submit-tag").should eq("") 210 | end 211 | end 212 | 213 | describe "#check_box" do 214 | it "creates a check_box with yes/no" do 215 | expected = "" 216 | check_box(:allowed, checked_value: "yes", unchecked_value: "no").should eq(expected) 217 | end 218 | 219 | it "creates a check_box with only value" do 220 | expected = "" 221 | check_box(:allowed).should eq(expected) 222 | end 223 | 224 | it "marks box as checked" do 225 | expected = "" 226 | check_box(:allowed, checked: true).should eq(expected) 227 | end 228 | 229 | it "marks box as not checked" do 230 | expected = "" 231 | check_box(:allowed, checked: false).should eq(expected) 232 | end 233 | end 234 | end 235 | -------------------------------------------------------------------------------- /spec/jasper_helpers/kit_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | describe JasperHelpers::Kit do 4 | describe "#css_safe" do 5 | it "creates safe id from bracketed name" do 6 | Kit.css_safe("test[name]").should eq("test_name") 7 | end 8 | 9 | it "shouldn't put more than underscores in a row or have ending or leading underscores" do 10 | Kit.css_safe(" david[bowie!]{rules}").should eq("david_bowie_rules") 11 | end 12 | end 13 | 14 | describe "merging and sanitizing" do 15 | it "it merges hash and namedtuple together in the right order" do 16 | expected = {:name => "kalidan[storm_blessed]", :age => 22, :id => "oath[bringer]", :selected => "syl"} 17 | Kit.merge({:name => "kalidan[storm_blessed]", :age => 22}, {id: "oath[bringer]", selected: "syl"}).should eq(expected) 18 | end 19 | 20 | it "it merges and sanitizes params" do 21 | expected = {:name => "dragon[reborn]", :age => 22, :class => "rand al thor", :selected => "sidar"} 22 | Kit.safe_hash({:name => "dragon[reborn]", :age => 22}, {class: "rand 'al thor", selected: "sidar"}).should eq(expected) 23 | end 24 | 25 | it "it sanitizes id and class" do 26 | expected = {:name => "kalidan[storm_blessed]", :age => 22, :id => "oath_bringer"} 27 | Kit.sanitize({:name => "kalidan[storm_blessed]", :age => 22, :id => "oath[bringer]"}).should eq(expected) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/jasper_helpers/links_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | describe JasperHelpers::Links do 4 | describe "#link_to" do 5 | it "works with body and url provided" do 6 | link_to("Save", "/save").should eq("Save") 7 | end 8 | 9 | it "works with a block" do 10 | result = link_to("/save") do 11 | "Badge" 12 | end 13 | 14 | result.should eq("Badge") 15 | end 16 | 17 | it "works with a class" do 18 | link_to("Save", "/save", class: "my-link").should eq("Save") 19 | end 20 | end 21 | 22 | describe "#button_to" do 23 | it "works with body and url provided" do 24 | button_to("Save", "/save").should eq expected_form 25 | end 26 | 27 | it "changes the method when provided" do 28 | button_to("Save", "/save", :put).should eq expected_form(override_method: :put) 29 | end 30 | 31 | context "with block" do 32 | it "renders form with hidden field" do 33 | form = button_to("Save", "/save", :delete) do 34 | hidden_field(:_csrf, value: "some value") 35 | end 36 | 37 | form.should eq expected_form(hidden_field(:_csrf, value: "some value"), :delete) 38 | end 39 | 40 | it "renders form with hidden field" do 41 | form = button_to("Save", "/save", :delete) do 42 | hidden_field(:_csrf, value: "some value") + %(Random text) 43 | end 44 | 45 | form.should eq expected_form( 46 | hidden_field(:_csrf, value: "some value") + %(Random text), :delete 47 | ) 48 | end 49 | 50 | it "renders form with hidden field and styled button" do 51 | form = button_to("Save", "/save", :delete, type: "button", class: "btn btn-primary") do 52 | hidden_field(:_csrf, value: "some value") + %(Random text) 53 | end 54 | 55 | form.should eq expected_form( 56 | hidden_field(:_csrf, value: "some value") + %(Random text), 57 | :delete, 58 | "btn btn-primary", 59 | "button" 60 | ) 61 | end 62 | end 63 | end 64 | end 65 | 66 | def expected_form(content = "", override_method = nil, css_class = nil, type = "submit") 67 | <<-FORM 68 |
#{%() if override_method}#{content}\ 69 | \ 70 |
71 | FORM 72 | end 73 | -------------------------------------------------------------------------------- /spec/jasper_helpers/tags_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | describe JasperHelpers::Tags do 4 | describe "#input_field" do 5 | it "id value works" do 6 | input_field(type: :text, id: "my-great-input").should eq("") 7 | end 8 | 9 | it "id value inside of options hash" do 10 | input_field(type: :text, options: {:id => "my-great-input"}).should eq("") 11 | end 12 | 13 | it "name value works" do 14 | input_field(type: :text, name: "my-great-input").should eq("") 15 | end 16 | 17 | it "disabled true only has key, not value" do 18 | input_field(type: :text, disabled: true).should eq("") 19 | end 20 | end 21 | 22 | describe "#content" do 23 | it "accepts a content string" do 24 | content(element_name: :span, options: {:id => "bar"}, content: "Hello").should eq("Hello") 25 | end 26 | 27 | it "accepts a block as input" do 28 | result = content(element_name: :div, options: {:id => "foo"}) do 29 | content(element_name: :span, options: {:id => "bar"}, content: "Hello") 30 | end 31 | 32 | result.should eq("
Hello
") 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/jasper_helpers/text_spec.cr: -------------------------------------------------------------------------------- 1 | require "../spec_helper" 2 | 3 | describe JasperHelpers::Text do 4 | describe "#render_markdown" do 5 | it "returns html from markdown" do 6 | expected = "

Hello world

\n" 7 | render_markdown("# Hello world").should eq(expected) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/jasper_helpers" 3 | 4 | include JasperHelpers 5 | -------------------------------------------------------------------------------- /src/jasper_helpers.cr: -------------------------------------------------------------------------------- 1 | require "./jasper_helpers/*" 2 | 3 | module JasperHelpers 4 | alias OptionHash = Hash(Symbol, Nil | String | Symbol | Bool | Int8 | Int16 | Int32 | Int64 | Float32 | Float64 | Time | Bytes | Array(String) | Array(Int32) | Array(String | Int32)) 5 | 6 | include Tags 7 | include Text 8 | include Forms 9 | include Links 10 | end 11 | -------------------------------------------------------------------------------- /src/jasper_helpers/forms.cr: -------------------------------------------------------------------------------- 1 | module JasperHelpers::Forms 2 | # text_field 3 | def text_field(name : String | Symbol, **options : Object) 4 | options_hash = Kit.safe_hash({:name => name, :id => name}, options) 5 | type = options[:type]? || :text 6 | input_field(type: type, options: options_hash.reject(:type)) 7 | end 8 | 9 | def text_field(name : String | Symbol) 10 | text_field(name, id: name) 11 | end 12 | 13 | # file_field 14 | def file_field(name : String | Symbol, **options : Object) 15 | options_hash = Kit.safe_hash({:name => name, :id => name}, options) 16 | type = options[:type]? || :file 17 | input_field(type: type, options: options_hash.reject(:type)) 18 | end 19 | 20 | def file_field(name : String | Symbol) 21 | file_field(name, id: name) 22 | end 23 | 24 | def label(name : String | Symbol, content : String? = nil, **options : Object) 25 | options_hash = Kit.safe_hash({for: name, id: "#{Kit.css_safe(name)}_label"}, options) 26 | content(element_name: :label, content: (content ? content : name.to_s.capitalize), options: options_hash) 27 | end 28 | 29 | def label(name : String | Symbol, content : String? = nil) 30 | label(name, content: (content ? content : name.to_s.capitalize), for: name, id: "#{Kit.css_safe(name)}_label") 31 | end 32 | 33 | def label(name : String | Symbol) 34 | content = "#{yield}" 35 | label(name, content: content, for: name, id: "#{Kit.css_safe(name)}_label") 36 | end 37 | 38 | def wrapper_field(*args) 39 | return "" unless args.first? 40 | args.join("") 41 | end 42 | 43 | # form 44 | def form(method = :post, **options : Object, &block) 45 | options_hash = Kit.safe_hash(options, {:method => (method == :get ? :get : :post)}) 46 | options_hash[:enctype] = "multipart/form-data" if options_hash[:multipart]? == true 47 | content(element_name: :form, options: options_hash) do 48 | String.build do |str| 49 | str << hidden_field(name: "_method", value: method) unless [:get, :post].includes?(method) 50 | str << yield 51 | end 52 | end 53 | end 54 | 55 | def form(method = :post, &block) 56 | form(:post, class: "amber_form", &block) 57 | end 58 | 59 | # hidden_field 60 | def hidden_field(name : String | Symbol, **options : Object) 61 | options_hash = Kit.safe_hash({:name => name, :id => name}, options) 62 | input_field(type: :hidden, options: options_hash) 63 | end 64 | 65 | def hidden_field(name : String | Symbol) 66 | hidden_field(name: name, id: name) 67 | end 68 | 69 | # select_field 70 | # with collection Array(Array) 71 | def select_field(name : String | Symbol, collection : Array(Array), **options : Object) 72 | options_hash = Kit.safe_hash(options, {:name => name}) 73 | selected = [options_hash.delete(:selected)].flatten.map(&.to_s) 74 | content(element_name: :select, options: options_hash) do 75 | String.build do |str| 76 | collection.map do |item| 77 | str << %() 78 | end 79 | end 80 | end 81 | end 82 | 83 | # Utilizes method above for when options are not defined and sets class and id. 84 | def select_field(name : String | Symbol, collection : Array(Array)) 85 | select_field(name, collection, class: name, id: name) 86 | end 87 | 88 | # with collection Array(Hash) 89 | def select_field(name : String | Symbol, collection : Array(Hash), **options : Object) 90 | select_field(name, collection.map(&.first.to_a), **options) 91 | end 92 | 93 | def select_field(name : String | Symbol, collection : Array(Hash)) 94 | select_field(name, collection.map(&.first.to_a), class: name, id: name) 95 | end 96 | 97 | # with collection Hash 98 | def select_field(name : String | Symbol, collection : Hash, **options : Object) 99 | select_field(name, collection.map { |k, v| [k, v] }, **options) 100 | end 101 | 102 | def select_field(name : String | Symbol, collection : Hash) 103 | select_field(name, collection.map { |k, v| [k, v] }, class: name, id: name) 104 | end 105 | 106 | def select_field(name : String | Symbol, collection : NamedTuple, **options : Object) 107 | select_field(name, collection.map { |k, v| [k, v] }, **options) 108 | end 109 | 110 | def select_field(name : String | Symbol, collection : NamedTuple) 111 | select_field(name, collection.map { |k, v| [k, v] }, class: name, id: name) 112 | end 113 | 114 | # with collection Array 115 | def select_field(name : String | Symbol, collection : Array | Range, **options : Object) 116 | select_field(name, collection.map { |i| [i.to_s, i.to_s.capitalize] }, **options) 117 | end 118 | 119 | def select_field(name : String | Symbol, collection : Array | Range) 120 | select_field(name, collection.map { |i| [i.to_s, i.to_s.capitalize] }, class: name, id: name) 121 | end 122 | 123 | # text_area 124 | def text_area(name : String | Symbol, content : String?, **options : Object) 125 | options_hash = Kit.safe_hash({:name => name, :id => name}, options) 126 | options_hash[:cols], options_hash[:rows] = options_hash.delete(:size).to_s.split("x") if options.has_key?(:size) 127 | content(element_name: :textarea, options: options_hash) do 128 | content 129 | end 130 | end 131 | 132 | def text_area(name : String | Symbol, content : String?) 133 | text_area(name, content, id: name) 134 | end 135 | 136 | # submit 137 | def submit(value : String | Symbol = "Save Changes", **options : Object) 138 | options_hash = Kit.safe_hash({value: value}, options) 139 | input_field(type: :submit, options: options_hash) 140 | end 141 | 142 | def submit(value : String | Symbol = "Save Changes") 143 | submit(value: value.to_s, id: value.to_s.gsub(" ", "_").downcase) 144 | end 145 | 146 | # check_box 147 | def check_box(name : String | Symbol, checked_value = "1", unchecked_value = "0", **options : Object) 148 | options_hash = Kit.safe_hash({:name => name, :id => name, :value => checked_value}, options) 149 | # Allows you to pass in checked=true/false 150 | if options_hash[:checked]? 151 | options_hash[:checked] = "checked" 152 | else 153 | options_hash.delete(:checked) 154 | end 155 | 156 | String.build do |str| 157 | str << input_field(type: :checkbox, options: options_hash) 158 | str << hidden_field(name, value: unchecked_value, id: "#{options_hash[:id]}_default") 159 | end 160 | end 161 | 162 | def check_box(name : String | Symbol, checked_value = "1", unchecked_value = "0") 163 | check_box(name, checked_value: checked_value, unchecked_value: unchecked_value, id: name) 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /src/jasper_helpers/kit.cr: -------------------------------------------------------------------------------- 1 | module JasperHelpers::Kit 2 | extend self 3 | 4 | def css_safe(value) 5 | values = value.to_s.strip.split(' ') 6 | values.map { |v| v.gsub(/[^\w-]+/, " ").strip.gsub(/\s+/, "_") }.join(' ') 7 | end 8 | 9 | def merge(*options) 10 | options.reduce(OptionHash.new) do |h, opts| 11 | h.merge!(opts.to_h) 12 | end 13 | end 14 | 15 | def sanitize(options : OptionHash) 16 | name = options[:name]?.to_s 17 | [:id, :class].each do |k| 18 | next if (v = options[k]?.to_s).empty? 19 | v = v.gsub(" ", "_").downcase if v == name 20 | options[k] = css_safe(v) 21 | end 22 | options 23 | end 24 | 25 | def safe_hash(*options) 26 | sanitize(merge(*options)) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /src/jasper_helpers/links.cr: -------------------------------------------------------------------------------- 1 | module JasperHelpers::Links 2 | 3 | # Creates an anchor,``, tag rendering the `body` parameter as content. The anchor tag's `href` will be set to the `url` parameter provided. The `options` parameter is be used to provide additional configurations or attributes for the tag. 4 | # 5 | # Example: 6 | # ``` 7 | # link_to("Go to GitHub", "http://github.com", { id: "github-url" }) 8 | # ``` 9 | # 10 | # Produces: 11 | # ``` 12 | # Go to GitHub 13 | # ``` 14 | def link_to(body : String, url : String, **options : Object) 15 | content(element_name: :a, content: body, options: {:href => url}.merge(options.to_h)) 16 | end 17 | 18 | # :ditto: 19 | # 20 | # With the exception of not accepting tag options 21 | def link_to(body : String, url : String) 22 | content(element_name: :a, content: body, options: {:href => url}) 23 | end 24 | 25 | # Creates an anchor,``, tag with the `href` attribute set to the `url` parameter provided. 26 | # The `options` parameter can be used to provide additional configurations or attributes for the tag. 27 | # 28 | # Example: 29 | # ``` 30 | # link_to("http://github.com", { id: "github-url" }) 31 | # ``` 32 | # 33 | # Produces: 34 | # ``` 35 | # 36 | # ``` 37 | def link_to(url : String, **options : Object, &block) 38 | link_to(yield, url, **options) 39 | end 40 | 41 | # :ditto: 42 | # 43 | # With the exception of not accepting tag options 44 | def link_to(url : String, &block) 45 | link_to(yield, url) 46 | end 47 | 48 | def button_to(body : String, url : String, method : Symbol = :post) 49 | form(action: url, method: method) do 50 | content(element_name: :button, content: body, options: {:type => "submit"}) 51 | end 52 | end 53 | 54 | def button_to(body : String, url : String, method : Symbol = :post) 55 | form(action: url, method: method) do 56 | String.build do |str| 57 | str << yield 58 | str << content(element_name: :button, content: body, options: {:type => "submit"}) 59 | end 60 | end 61 | end 62 | 63 | def button_to(body : String, url : String, method : Symbol = :post, **options : Object) 64 | form(action: url, method: method) do 65 | content(element_name: :button, content: body, options: options.to_h) 66 | end 67 | end 68 | 69 | def button_to(body : String, url : String, method : Symbol = :post, **options : Object, &block) 70 | form(action: url, method: method) do 71 | String.build do |str| 72 | str << yield 73 | str << content(element_name: :button, content: body, options: options.to_h) 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /src/jasper_helpers/tags.cr: -------------------------------------------------------------------------------- 1 | module JasperHelpers::Tags 2 | INPUT_BOOLEAN_ATTRIBUTES = [:disabled] 3 | 4 | def input_field(type : Symbol, **options) 5 | options = options.to_h 6 | input_field_string(type: type, options: options) 7 | end 8 | 9 | def input_field(type : Symbol, options : OptionHash) 10 | input_field_string(type: type, options: options) 11 | end 12 | 13 | def input_field_string(type : Symbol, options : OptionHash) 14 | tag_options = prepare_input_field_options(options: options) 15 | "" 16 | end 17 | 18 | # helper for input field options 19 | def prepare_input_field_options(options : OptionHash) 20 | options[:id] = options[:name] if (options[:name]?) && !(options[:id]?) 21 | boolean_options = options.select(INPUT_BOOLEAN_ATTRIBUTES) 22 | tag_options = options.reject!(INPUT_BOOLEAN_ATTRIBUTES).map { |k, v| "#{k}=\"#{v}\"" } 23 | tag_options = tag_options << boolean_options.keys.join(" ") if !boolean_options.empty? 24 | tag_options = tag_options.join(" ") 25 | tag_options 26 | end 27 | 28 | def content(element_name : Symbol, content : String, options : OptionHash) 29 | content(element_name: element_name, options: options) do 30 | content 31 | end 32 | end 33 | 34 | # Builds an arbitrary HTML tag for the provided `element_name` using `options` to configure additional parameters for accordingly. 35 | # 36 | # Example 37 | # ``` 38 | # content(:a, { aria_data_toggle: true, href: "http://example.com" }) 39 | # ``` 40 | # 41 | # Produces: 42 | # ```text 43 | # 44 | # ``` 45 | def content(element_name : Symbol, options : OptionHash, &block) 46 | String.build do |str| 47 | str << "<#{element_name}" 48 | options.each do |k, v| 49 | next if v.nil? 50 | str << %( #{k}="#{v}") 51 | end 52 | str << ">#{yield}" 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /src/jasper_helpers/text.cr: -------------------------------------------------------------------------------- 1 | require "markd" 2 | 3 | module JasperHelpers::Text 4 | def render_markdown(markdown : String) 5 | Markd.to_html(markdown) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/jasper_helpers/version.cr: -------------------------------------------------------------------------------- 1 | module JasperHelpers 2 | VERSION = "1.2.2" 3 | end 4 | --------------------------------------------------------------------------------