aladdin 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +43 -0
  3. data/assets/images/foundation/orbit/bullets.jpg +0 -0
  4. data/assets/images/foundation/orbit/left-arrow-small.png +0 -0
  5. data/assets/images/foundation/orbit/left-arrow.png +0 -0
  6. data/assets/images/foundation/orbit/loading.gif +0 -0
  7. data/assets/images/foundation/orbit/mask-black.png +0 -0
  8. data/assets/images/foundation/orbit/pause-black.png +0 -0
  9. data/assets/images/foundation/orbit/right-arrow-small.png +0 -0
  10. data/assets/images/foundation/orbit/right-arrow.png +0 -0
  11. data/assets/images/foundation/orbit/rotator-black.png +0 -0
  12. data/assets/images/foundation/orbit/timer-black.png +0 -0
  13. data/assets/javascripts/foundation/app.js +38 -0
  14. data/assets/javascripts/foundation/jquery.cookie.js +72 -0
  15. data/assets/javascripts/foundation/jquery.event.move.js +580 -0
  16. data/assets/javascripts/foundation/jquery.event.swipe.js +130 -0
  17. data/assets/javascripts/foundation/jquery.foundation.accordion.js +34 -0
  18. data/assets/javascripts/foundation/jquery.foundation.alerts.js +20 -0
  19. data/assets/javascripts/foundation/jquery.foundation.buttons.js +74 -0
  20. data/assets/javascripts/foundation/jquery.foundation.clearing.js +468 -0
  21. data/assets/javascripts/foundation/jquery.foundation.forms.js +486 -0
  22. data/assets/javascripts/foundation/jquery.foundation.joyride.js +639 -0
  23. data/assets/javascripts/foundation/jquery.foundation.magellan.js +85 -0
  24. data/assets/javascripts/foundation/jquery.foundation.mediaQueryToggle.js +27 -0
  25. data/assets/javascripts/foundation/jquery.foundation.navigation.js +55 -0
  26. data/assets/javascripts/foundation/jquery.foundation.orbit.js +897 -0
  27. data/assets/javascripts/foundation/jquery.foundation.reveal.js +794 -0
  28. data/assets/javascripts/foundation/jquery.foundation.tabs.js +43 -0
  29. data/assets/javascripts/foundation/jquery.foundation.tooltips.js +193 -0
  30. data/assets/javascripts/foundation/jquery.foundation.topbar.js +152 -0
  31. data/assets/javascripts/foundation/jquery.js +9440 -0
  32. data/assets/javascripts/foundation/jquery.offcanvas.js +50 -0
  33. data/assets/javascripts/foundation/jquery.placeholder.js +157 -0
  34. data/assets/javascripts/foundation/modernizr.foundation.js +4 -0
  35. data/bin/aladdin +20 -0
  36. data/lib/aladdin.rb +43 -0
  37. data/lib/aladdin/app.rb +89 -0
  38. data/lib/aladdin/render/markdown.rb +40 -0
  39. data/lib/aladdin/render/sanitize.rb +84 -0
  40. data/lib/aladdin/version.rb +4 -0
  41. data/views/haml/index.haml +43 -0
  42. data/views/haml/layout.haml +51 -0
  43. data/views/scss/_settings.scss +242 -0
  44. data/views/scss/app.scss +47 -0
  45. data/views/scss/github.scss +65 -0
  46. data/views/scss/pygment.scss +12 -0
  47. metadata +307 -0
@@ -0,0 +1,50 @@
1
+ ;(function (window, document, $) {
2
+ // Set the negative margin on the top menu for slide-menu pages
3
+ var $selector1 = $('#topMenu'),
4
+ events = 'click.fndtn';
5
+ if ($selector1.length > 0) $selector1.css("margin-top", $selector1.height() * -1);
6
+
7
+ // Watch for clicks to show the sidebar
8
+ var $selector2 = $('#sidebarButton');
9
+ if ($selector2.length > 0) {
10
+ $('#sidebarButton').on(events, function (e) {
11
+ e.preventDefault();
12
+ $('body').toggleClass('active');
13
+ });
14
+ }
15
+
16
+ // Watch for clicks to show the menu for slide-menu pages
17
+ var $selector3 = $('#menuButton');
18
+ if ($selector3.length > 0) {
19
+ $('#menuButton').on(events, function (e) {
20
+ e.preventDefault();
21
+ $('body').toggleClass('active-menu');
22
+ });
23
+ }
24
+
25
+ // // Adjust sidebars and sizes when resized
26
+ // $(window).resize(function() {
27
+ // // if (!navigator.userAgent.match(/Android/i)) $('body').removeClass('active');
28
+ // var $selector4 = $('#topMenu');
29
+ // if ($selector4.length > 0) $selector4.css("margin-top", $selector4.height() * -1);
30
+ // });
31
+
32
+ // Switch panels for the paneled nav on mobile
33
+ var $selector5 = $('#switchPanels');
34
+ if ($selector5.length > 0) {
35
+ $('#switchPanels dd').on(events, function (e) {
36
+ e.preventDefault();
37
+ var switchToPanel = $(this).children('a').attr('href'),
38
+ switchToIndex = $(switchToPanel).index();
39
+ $(this).toggleClass('active').siblings().removeClass('active');
40
+ $(switchToPanel).parent().css("left", (switchToIndex * (-100) + '%'));
41
+ });
42
+ }
43
+
44
+ $('#nav li a').on(events, function (e) {
45
+ e.preventDefault();
46
+ var href = $(this).attr('href'),
47
+ $target = $(href);
48
+ $('html, body').animate({scrollTop : $target.offset().top}, 300);
49
+ });
50
+ }(this, document, jQuery));
@@ -0,0 +1,157 @@
1
+ /*! http://mths.be/placeholder v2.0.7 by @mathias */
2
+ ;(function(window, document, $) {
3
+
4
+ var isInputSupported = 'placeholder' in document.createElement('input'),
5
+ isTextareaSupported = 'placeholder' in document.createElement('textarea'),
6
+ prototype = $.fn,
7
+ valHooks = $.valHooks,
8
+ hooks,
9
+ placeholder;
10
+
11
+ if (isInputSupported && isTextareaSupported) {
12
+
13
+ placeholder = prototype.placeholder = function() {
14
+ return this;
15
+ };
16
+
17
+ placeholder.input = placeholder.textarea = true;
18
+
19
+ } else {
20
+
21
+ placeholder = prototype.placeholder = function() {
22
+ var $this = this;
23
+ $this
24
+ .filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]')
25
+ .not('.placeholder')
26
+ .bind({
27
+ 'focus.placeholder': clearPlaceholder,
28
+ 'blur.placeholder': setPlaceholder
29
+ })
30
+ .data('placeholder-enabled', true)
31
+ .trigger('blur.placeholder');
32
+ return $this;
33
+ };
34
+
35
+ placeholder.input = isInputSupported;
36
+ placeholder.textarea = isTextareaSupported;
37
+
38
+ hooks = {
39
+ 'get': function(element) {
40
+ var $element = $(element);
41
+ return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value;
42
+ },
43
+ 'set': function(element, value) {
44
+ var $element = $(element);
45
+ if (!$element.data('placeholder-enabled')) {
46
+ return element.value = value;
47
+ }
48
+ if (value == '') {
49
+ element.value = value;
50
+ // Issue #56: Setting the placeholder causes problems if the element continues to have focus.
51
+ if (element != document.activeElement) {
52
+ // We can't use `triggerHandler` here because of dummy text/password inputs :(
53
+ setPlaceholder.call(element);
54
+ }
55
+ } else if ($element.hasClass('placeholder')) {
56
+ clearPlaceholder.call(element, true, value) || (element.value = value);
57
+ } else {
58
+ element.value = value;
59
+ }
60
+ // `set` can not return `undefined`; see http://jsapi.info/jquery/1.7.1/val#L2363
61
+ return $element;
62
+ }
63
+ };
64
+
65
+ isInputSupported || (valHooks.input = hooks);
66
+ isTextareaSupported || (valHooks.textarea = hooks);
67
+
68
+ $(function() {
69
+ // Look for forms
70
+ $(document).delegate('form', 'submit.placeholder', function() {
71
+ // Clear the placeholder values so they don't get submitted
72
+ var $inputs = $('.placeholder', this).each(clearPlaceholder);
73
+ setTimeout(function() {
74
+ $inputs.each(setPlaceholder);
75
+ }, 10);
76
+ });
77
+ });
78
+
79
+ // Clear placeholder values upon page reload
80
+ $(window).bind('beforeunload.placeholder', function() {
81
+ $('.placeholder').each(function() {
82
+ this.value = '';
83
+ });
84
+ });
85
+
86
+ }
87
+
88
+ function args(elem) {
89
+ // Return an object of element attributes
90
+ var newAttrs = {},
91
+ rinlinejQuery = /^jQuery\d+$/;
92
+ $.each(elem.attributes, function(i, attr) {
93
+ if (attr.specified && !rinlinejQuery.test(attr.name)) {
94
+ newAttrs[attr.name] = attr.value;
95
+ }
96
+ });
97
+ return newAttrs;
98
+ }
99
+
100
+ function clearPlaceholder(event, value) {
101
+ var input = this,
102
+ $input = $(input);
103
+ if (input.value == $input.attr('placeholder') && $input.hasClass('placeholder')) {
104
+ if ($input.data('placeholder-password')) {
105
+ $input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id'));
106
+ // If `clearPlaceholder` was called from `$.valHooks.input.set`
107
+ if (event === true) {
108
+ return $input[0].value = value;
109
+ }
110
+ $input.focus();
111
+ } else {
112
+ input.value = '';
113
+ $input.removeClass('placeholder');
114
+ input == document.activeElement && input.select();
115
+ }
116
+ }
117
+ }
118
+
119
+ function setPlaceholder() {
120
+ var $replacement,
121
+ input = this,
122
+ $input = $(input),
123
+ $origInput = $input,
124
+ id = this.id;
125
+ if (input.value == '') {
126
+ if (input.type == 'password') {
127
+ if (!$input.data('placeholder-textinput')) {
128
+ try {
129
+ $replacement = $input.clone().attr({ 'type': 'text' });
130
+ } catch(e) {
131
+ $replacement = $('<input>').attr($.extend(args(this), { 'type': 'text' }));
132
+ }
133
+ $replacement
134
+ .removeAttr('name')
135
+ .data({
136
+ 'placeholder-password': true,
137
+ 'placeholder-id': id
138
+ })
139
+ .bind('focus.placeholder', clearPlaceholder);
140
+ $input
141
+ .data({
142
+ 'placeholder-textinput': $replacement,
143
+ 'placeholder-id': id
144
+ })
145
+ .before($replacement);
146
+ }
147
+ $input = $input.removeAttr('id').hide().prev().attr('id', id).show();
148
+ // Note: `$input[0] != input` now!
149
+ }
150
+ $input.addClass('placeholder');
151
+ $input[0].value = $input.attr('placeholder');
152
+ } else {
153
+ $input.removeClass('placeholder');
154
+ }
155
+ }
156
+
157
+ }(this, document, jQuery));
@@ -0,0 +1,4 @@
1
+ /* Modernizr 2.6.2 (Custom Build) | MIT & BSD
2
+ * Build: http://modernizr.com/download/#-inlinesvg-svg-svgclippaths-touch-shiv-mq-cssclasses-teststyles-prefixes-ie8compat-load
3
+ */
4
+ ;window.Modernizr=function(a,b,c){function y(a){j.cssText=a}function z(a,b){return y(m.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n={svg:"http://www.w3.org/2000/svg"},o={},p={},q={},r=[],s=r.slice,t,u=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},v=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return u("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=s.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(s.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(s.call(arguments)))};return e}),o.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:u(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},o.svg=function(){return!!b.createElementNS&&!!b.createElementNS(n.svg,"svg").createSVGRect},o.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="<svg/>",(a.firstChild&&a.firstChild.namespaceURI)==n.svg},o.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(l.call(b.createElementNS(n.svg,"clipPath")))};for(var D in o)x(o,D)&&(t=D.toLowerCase(),e[t]=o[D](),r.push((e[t]?"":"no-")+t));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},y(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function p(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return r.shivMethods?n(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+l().join().replace(/\w+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(r,b.frag)}function q(a){a||(a=b);var c=m(a);return r.shivCSS&&!f&&!c.hasCSS&&(c.hasCSS=!!k(a,"article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}")),j||p(a,c),a}var c=a.html5||{},d=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,e=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,f,g="_html5shiv",h=0,i={},j;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e.mq=v,e.testStyles=u,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+r.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))},Modernizr.addTest("ie8compat",function(){return!window.addEventListener&&document.documentMode&&document.documentMode===7});
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'aladdin'
6
+ require 'optparse'
7
+
8
+ opt_parser = OptionParser.new do |opts|
9
+
10
+ opts.banner = 'Usage: aladdin /path/to/tutorial/directory'
11
+
12
+ opts.on_tail('--version', 'Display current gem version.') do
13
+ puts 'aladdin v' + Aladdin::VERSION
14
+ exit 0
15
+ end
16
+
17
+ end
18
+ opt_parser.parse!
19
+
20
+ Aladdin.launch from: ARGV[0]
@@ -0,0 +1,43 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+ require 'sinatra'
3
+ require 'zurb-foundation'
4
+ require 'albino'
5
+ require 'haml'
6
+ require 'redcarpet'
7
+ require 'sanitize'
8
+
9
+ require 'aladdin/render/sanitize'
10
+ require 'aladdin/render/markdown'
11
+
12
+ # Aladdin is for tutorial apps.
13
+ module Aladdin
14
+
15
+ # Launches the tutorial app using 'thin' as the default webserver.
16
+ # @option opts [String] from path to author's markdown documents;
17
+ # defaults to the current working directory
18
+ def self.launch(opts = {})
19
+ Aladdin::App.set :views, Aladdin::VIEWS.merge(markdown: opts[:from] || '.')
20
+ Aladdin::App.run!
21
+ end
22
+
23
+ # Converts a hash to struct.
24
+ def self.to_struct(hash)
25
+ Struct.new( *(k = hash.keys) ).new( *hash.values_at( *k ) )
26
+ end
27
+ private_class_method :to_struct
28
+
29
+ # Paths to different types of views.
30
+ VIEWS = {
31
+ haml: File.expand_path('../../views/haml', __FILE__),
32
+ scss: File.expand_path('../../views/scss', __FILE__),
33
+ default: File.expand_path('../../views', __FILE__)
34
+ }
35
+
36
+ # Paths to other parts of the library.
37
+ PATHS = to_struct(
38
+ assets: File.expand_path('../../assets', __FILE__),
39
+ ).freeze
40
+
41
+ end
42
+
43
+ require 'aladdin/app'
@@ -0,0 +1,89 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+ module Aladdin
3
+
4
+ # Sinatra app that serves the tutorial. Should be able to use this in a
5
+ # config.ru file or as middleware. Authors should launch the app using the
6
+ # +bin/aladdin+ executable.
7
+ # Adapted from https://github.com/jerodsanto/sinatra-foundation-skeleton/
8
+ class App < Sinatra::Base
9
+
10
+ # Default markdown options.
11
+ MARKDOWN_OPTIONS = {
12
+ renderer: Aladdin::Render::HTML,
13
+ no_intra_emphasis: true,
14
+ tables: true,
15
+ fenced_code_blocks: true,
16
+ autolink: true,
17
+ strikethrough: true,
18
+ layout_engine: :haml
19
+ }
20
+
21
+ class << self
22
+ private
23
+
24
+ # Configures path to the views, with different paths for different file
25
+ # types.
26
+ # @return [void]
27
+ def configure_views
28
+ helpers do
29
+ def find_template(views, name, engine, &block)
30
+ _, dir = views.detect { |k,v| engine == Tilt[k] }
31
+ dir ||= views[:default]
32
+ super(dir, name, engine, &block)
33
+ end
34
+ end
35
+ end
36
+
37
+ # Configures path to static assets in the public folder.
38
+ # @return [void]
39
+ def configure_assets
40
+ set :public_folder, Aladdin::PATHS.assets
41
+ end
42
+
43
+ # Configures ZURB's compass to compile laddin's scss assets.
44
+ # @return [void]
45
+ def configure_compass
46
+ Compass.configuration do |config|
47
+ config.http_path = '/'
48
+ config.http_images_path = '/images'
49
+ end
50
+ set :scss, Compass.sass_engine_options
51
+ end
52
+
53
+ # Registers redcarpet2 and laddin's markdown renderer to be as close to
54
+ # the github-flavored markdown as possible.
55
+ # @return [void]
56
+ def configure_markdown
57
+ Tilt.register Tilt::RedcarpetTemplate::Redcarpet2, 'markdown', 'mkd', 'md'
58
+ set :markdown, MARKDOWN_OPTIONS
59
+ end
60
+
61
+ end
62
+
63
+ # Calls the given +block+ and invokes +pass+ on error.
64
+ # @param block block to call within wrapper
65
+ def render_or_pass(&block)
66
+ begin return block.call
67
+ rescue pass
68
+ end
69
+ end
70
+
71
+ configure_views
72
+ configure_markdown
73
+
74
+ configure :development, :test do
75
+ configure_assets
76
+ configure_compass
77
+ end
78
+
79
+ get '/stylesheets/*.css' do |path|
80
+ render_or_pass { scss path.to_sym }
81
+ end
82
+
83
+ get '/*' do |path|
84
+ render_or_pass { markdown path.to_sym }
85
+ end
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,40 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+ module Aladdin
3
+
4
+ # laddin-render module for all of Laddin's rendering needs.
5
+ module Render
6
+
7
+ # HTML Renderer for Markdown.
8
+ # It creates pygmentized code blocks, supports hard-wraps, and only
9
+ # generates links for protocols which are considered safe.
10
+ # @see http://github.github.com/github-flavored-markdown/
11
+ class HTML < ::Redcarpet::Render::HTML
12
+
13
+ @sanitize = Aladdin::Sanitize.new
14
+ class << self; attr_reader :sanitize; end
15
+
16
+ # Creates a new HTML renderer.
17
+ # @param [Hash] options described in the RedCarpet documentation.
18
+ def initialize(options = {})
19
+ super options.merge(hard_wrap: true, safe_links_only: true)
20
+ end
21
+
22
+ # Pygmentizes code blocks.
23
+ # @param [String] code code block contents
24
+ # @param [String] language name of language, for syntax highlighting
25
+ # @return [String] highlighted code
26
+ def block_code(code, language)
27
+ Albino.colorize code, language
28
+ end
29
+
30
+ # Sanitizes the final document.
31
+ # @param [String] document html document
32
+ # @return [String] sanitized document
33
+ def postprocess(document)
34
+ HTML.sanitize.clean document
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,84 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+
3
+ module Aladdin
4
+
5
+ # Encapsulate sanitization options.
6
+ # Adapted from
7
+ # https://github.com/github/gollum/blob/master/lib/gollum/sanitization.rb
8
+ class Sanitize < ::Sanitize
9
+
10
+ # white-listed elements
11
+ ELEMENTS = [
12
+ 'a', 'abbr', 'acronym', 'address', 'area', 'b', 'big',
13
+ 'blockquote', 'br', 'button', 'caption', 'center', 'cite',
14
+ 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir',
15
+ 'div', 'dl', 'dt', 'em', 'fieldset', 'font', 'form', 'h1',
16
+ 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input',
17
+ 'ins', 'kbd', 'label', 'legend', 'li', 'map', 'menu',
18
+ 'ol', 'optgroup', 'option', 'p', 'pre', 'q', 's', 'samp',
19
+ 'select', 'small', 'span', 'strike', 'strong', 'sub',
20
+ 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th',
21
+ 'thead', 'tr', 'tt', 'u', 'ul', 'var'
22
+ ].freeze
23
+
24
+ # white-listed attributes
25
+ ATTRIBUTES = {
26
+ 'a' => ['href'],
27
+ 'img' => ['src'],
28
+ :all => ['abbr', 'accept', 'accept-charset',
29
+ 'accesskey', 'action', 'align', 'alt', 'axis',
30
+ 'border', 'cellpadding', 'cellspacing', 'char',
31
+ 'charoff', 'class', 'charset', 'checked', 'cite',
32
+ 'clear', 'cols', 'colspan', 'color',
33
+ 'compact', 'coords', 'datetime', 'dir',
34
+ 'disabled', 'enctype', 'for', 'frame',
35
+ 'headers', 'height', 'hreflang',
36
+ 'hspace', 'id', 'ismap', 'label', 'lang',
37
+ 'longdesc', 'maxlength', 'media', 'method',
38
+ 'multiple', 'name', 'nohref', 'noshade',
39
+ 'nowrap', 'prompt', 'readonly', 'rel', 'rev',
40
+ 'rows', 'rowspan', 'rules', 'scope',
41
+ 'selected', 'shape', 'size', 'span',
42
+ 'start', 'summary', 'tabindex', 'target',
43
+ 'title', 'type', 'usemap', 'valign', 'value',
44
+ 'vspace', 'width']
45
+ }.freeze
46
+
47
+ # white-listed protocols
48
+ PROTOCOLS = {
49
+ 'a' => {'href' => ['http', 'https', 'mailto', 'ftp', 'irc', 'apt', :relative]},
50
+ 'img' => {'src' => ['http', 'https', :relative]}
51
+ }.freeze
52
+
53
+ # elements to remove (incl. contents)
54
+ REMOVE_CONTENTS = [
55
+ 'script',
56
+ 'style'
57
+ ].freeze
58
+
59
+ # attributes to add to elements
60
+ ADD_ATTRIBUTES = {
61
+ 'a' => {'rel' => 'nofollow'}
62
+ }
63
+
64
+ # Creates a new sanitizer with Aladdin's configuration.
65
+ def initialize
66
+ super config
67
+ end
68
+
69
+ private
70
+
71
+ # Returns a configuration hash.
72
+ def config
73
+ { elements: ELEMENTS.dup,
74
+ attributes: ATTRIBUTES.dup,
75
+ protocols: PROTOCOLS.dup,
76
+ add_attributes: ADD_ATTRIBUTES.dup,
77
+ remove_contents: REMOVE_CONTENTS.dup,
78
+ allow_comments: false
79
+ }
80
+ end
81
+
82
+ end
83
+
84
+ end