├── .gitignore ├── .npmignore ├── docs ├── screenshot-example.jpg ├── screenshot-sample.png └── screenshot-rendered-glyphs.png ├── test ├── config-file │ ├── about.markdown │ ├── about.md │ ├── test2.scss │ ├── partial.hbs │ ├── test.js │ ├── .styleguide │ ├── alt-menu.hbs │ ├── toc.js │ ├── theme-tocmenu.css │ ├── theme.css │ ├── template.hbs │ ├── test.css │ └── test.scss ├── nodemon-watch.js └── imported │ ├── test.js │ └── test │ └── test.scss ├── test.js ├── lib ├── globals.js ├── tags.js ├── sorts.js ├── log.js ├── templating.js ├── md-formats.js ├── sanitize.js └── swag.js ├── package.json ├── template ├── sticky.js ├── theme.css ├── template.hbs └── hash.js ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | bk.package.json 3 | 4 | npm-debug.log 5 | 6 | node_modules/ 7 | 8 | .git -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | bk.package.json 3 | 4 | npm-debug.log 5 | 6 | .styleguide 7 | 8 | test/* 9 | 10 | styleguide/* 11 | -------------------------------------------------------------------------------- /docs/screenshot-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UWHealth/markdown-documentation-generator/HEAD/docs/screenshot-example.jpg -------------------------------------------------------------------------------- /docs/screenshot-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UWHealth/markdown-documentation-generator/HEAD/docs/screenshot-sample.png -------------------------------------------------------------------------------- /test/config-file/about.markdown: -------------------------------------------------------------------------------- 1 | 2 | # Introduction [[about]] 3 | 4 | Some stuff about this style guide can go here. 5 | 6 | -------------------------------------------------------------------------------- /docs/screenshot-rendered-glyphs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UWHealth/markdown-documentation-generator/HEAD/docs/screenshot-rendered-glyphs.png -------------------------------------------------------------------------------- /test/config-file/about.md: -------------------------------------------------------------------------------- 1 | 2 | # Category/Article 3 | 4 | Some comments about this thing. 5 | 6 | 7 | 8 | # Category TEST/Another Article 9 | 10 | Some more comments about this other thing. 11 | 12 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var nodemon = require('nodemon'); 2 | 3 | console.log( 4 | 'Starting tests', 5 | 'Watching for changes.' 6 | ); 7 | 8 | nodemon( 9 | '--ignore */styleguide/ -e js,hbs,css,styleguide index.js ls' 10 | ) 11 | .on('quit', function() { 12 | process.exit(0); 13 | }); 14 | -------------------------------------------------------------------------------- /test/nodemon-watch.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | "restartable": "rs", 5 | "ignore": [ 6 | "./styleguide/**/*.*", 7 | "test.js" 8 | ], 9 | "verbose": false, 10 | "script": path.resolve(__dirname, "../index.js"), 11 | "env": { 12 | "NODE_ENV": "development" 13 | }, 14 | "ext": "js hbs css scss styleguide", 15 | "args": [] 16 | }; 17 | -------------------------------------------------------------------------------- /test/imported/test.js: -------------------------------------------------------------------------------- 1 | var styleguide = require('../../index.js'); 2 | var path = require('path'); 3 | 4 | var createStyleguide = styleguide.create('lf', { 5 | rootFolder: './', 6 | htmlOutput:'./styleguide/output.html' 7 | }); 8 | 9 | createStyleguide.then(function(data){ 10 | console.log('yay'); 11 | }).catch(function(data){ 12 | console.log('boo'); 13 | console.log(data); 14 | }); 15 | -------------------------------------------------------------------------------- /lib/globals.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* jshint esnext: true*/ 3 | "use strict"; 4 | 5 | var chalk = require('chalk'); 6 | var path = require('path'); 7 | 8 | module.exports = { 9 | uniqueIdentifier: "md-sg", 10 | error: chalk.red, 11 | good: chalk.green, 12 | info: chalk.white, 13 | warn: chalk.yellow, 14 | brand: chalk.cyan, 15 | logPre: chalk.cyan("[Style Guide] "), 16 | logSpace: ' ', 17 | fileList: false, 18 | configFile: '.styleguide', 19 | moduleDir: path.dirname(process.mainModule.filename), 20 | relDir: path.relative(__dirname, process.cwd()), 21 | hljsDir: path.dirname(require.resolve('highlight.js')) //Required for npm3+ compatibility 22 | }; 23 | -------------------------------------------------------------------------------- /test/config-file/test2.scss: -------------------------------------------------------------------------------- 1 | /* SG 2 | # Glyphs/Style 3 | 4 | You may resize and change the colors of the icons with the `glyph-`-classes. Available sizes and colors listed: 5 | 6 | ```html_example 7 |

8 | 9 | 10 | 11 | 12 |

13 | ``` 14 | */ 15 | 16 | a [class^="icon-"], 17 | a [class*=" icon-"] { 18 | text-decoration: none; 19 | } 20 | 21 | [class^="icon-"], 22 | [class*=" icon-"] { 23 | &.glyph-1x { font-size: 1em; } 24 | &.glyph-1_5x { font-size: 1.5em; } 25 | &.glyph-2x { font-size: 2em; } 26 | &.glyph-3x { font-size: 3em; } 27 | } 28 | 29 | /* SG 30 | ```html_example 31 |

32 | 33 |

34 | ``` 35 | **/ 36 | 37 | .glyph-red { 38 | color: $moh-red; 39 | } 40 | -------------------------------------------------------------------------------- /test/config-file/partial.hbs: -------------------------------------------------------------------------------- 1 |
  • 2 | {{> ./alt-menu.hbs }} 3 | {{#if headings.1.name}} 4 | 5 | {{~name~}} 6 | 7 | 9 | 18 | {{else}} 19 | 20 | {{~name~}} 21 | 22 | {{/if}} 23 |
  • 24 | -------------------------------------------------------------------------------- /lib/tags.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* jshint esnext: true*/ 3 | "use strict"; 4 | 5 | /** 6 | * Tag abstractions library 7 | * 8 | * Used by article parser (saveHTMLtoJSON) 9 | * Special tags created by markedown renderer for easier parsing 10 | */ 11 | 12 | module.exports = { 13 | "tags": { 14 | "section": "meta-section", 15 | "category": "meta-category", 16 | "article": "meta-article", 17 | "file": "meta-file", 18 | "priority": "meta-priority", 19 | "example": "code-example", 20 | }, 21 | "patterns": { 22 | "section": "section", 23 | "category": "category", 24 | "article": "title|article", 25 | "file": "file|files|location", 26 | "priority": "priority|order", 27 | "requires": "requires|require|req", 28 | "returns": "returns|return|ret", 29 | "alias": "alias|aliases", 30 | "param": "parameter|param|arg|argument", 31 | "links": "source|reference|ref|link" 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /test/config-file/test.js: -------------------------------------------------------------------------------- 1 | // "use strict"; 2 | // 3 | // var sys = require('util') 4 | // var exec = require('child_process').exec; 5 | // var path = require('path'); 6 | // 7 | // var pathToBin = path.join(process.cwd(), '../../node_modules', '.bin') 8 | // var pathToBin = path.join(process.cwd(), 'node_modules', '.bin') 9 | // var pathName = /^win/.test(process.platform) ? 'Path' : 'PATH' 10 | // var newPath = pathToBin + path.delimiter + process.env[pathName] 11 | // 12 | // var args = process.argv.slice(2); 13 | // 14 | // exec('md_documentation lf ' + args, { 15 | // env:{PATH:newPath} 16 | // }, 17 | // function(error, stdout, stderr){ 18 | // console.log(`stdout: ${stdout}`); 19 | // console.log(`stderr: ${stderr}`); 20 | // if (error !== null) { 21 | // console.log(`exec error: ${error}`); 22 | // } 23 | // }); 24 | 25 | var nodemon = require('nodemon'); 26 | var config = require('../nodemon-watch.js'); 27 | 28 | nodemon(config, "ls").on('log', function(event) { 29 | console.log(event.colour); 30 | }); 31 | -------------------------------------------------------------------------------- /lib/sorts.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* jshint esnext: true*/ 3 | "use strict"; 4 | 5 | /** 6 | * Sort a section based on priority, category, or heading 7 | * 8 | * @param {object} section 9 | * @returns {object} sorted section 10 | */ 11 | module.exports = function sort(section){ 12 | return section.sort(function(a,b) { 13 | 14 | //Items with the same category should be distinguished by their heading 15 | if (a.category === b.category) { 16 | 17 | if (a.priority !== b.priority) { 18 | if (a.priority === "first" || b.priority === "last") { 19 | return 1; 20 | } 21 | if (b.priority === "first" || a.priority === "last") { 22 | return -1; 23 | } 24 | return a.priority - b.priority; 25 | } 26 | 27 | return compare(a.heading, b.heading); 28 | } 29 | else { 30 | return compare(a.category, b.category); 31 | } 32 | }); 33 | }; 34 | 35 | function compare(a, b) { 36 | switch (true) { 37 | case a > b: return 1; 38 | case a < b: return -1; 39 | default: return 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/config-file/.styleguide: -------------------------------------------------------------------------------- 1 | { 2 | "sgComment": "SG", 3 | "exampleIdentifier": "html_example", 4 | "sortCategories": true, 5 | "categoryRename": { 6 | "find": "/(^\\d+\\.\\s?)/g", 7 | "replace": "" 8 | }, 9 | "sections": { 10 | "styles": "", 11 | "development": "Dev:", 12 | "Sass stuff": "Sass:" 13 | }, 14 | "rootFolder": "./", 15 | "excludeDirs": [ 16 | "target", 17 | "node_modules", 18 | ".git" 19 | ], 20 | "fileExtensions": { 21 | "scss": false, 22 | "sass": true, 23 | "less": true, 24 | "md": true, 25 | "css": true 26 | }, 27 | "templateFile": "./template.hbs", 28 | "themeFile": "./theme-tocmenu.css", 29 | "htmlOutput": "./styleguide/styleguide.html", 30 | "jsonOutput": "styleguide/styleguide.json", 31 | "handlebarsPartials": { 32 | "sticky": "../../template/sticky.js" 33 | }, 34 | "highlightStyle": "arduino-light", 35 | "highlightFolder": "../../node_modules/highlight.js/styles", 36 | "customVariables": { 37 | "pageTitle": "Style Guide", 38 | "tocMenu": true 39 | }, 40 | "markedOptions": { 41 | "gfm": true, 42 | "breaks": true, 43 | "smartypants": true 44 | }, 45 | "logging": { 46 | "prefix": "[SG]", 47 | "level": "verbose" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/config-file/alt-menu.hbs: -------------------------------------------------------------------------------- 1 | 2 |
    3 |

    4 | Pliny 5 |

    6 | {{!--Section Start --}} 7 | {{~#each menus~}} 8 | 16 | {{!-- Category list --}} 17 | 26 | {{/each}} 27 |
    28 | Back to top 29 |
    30 |
    31 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* jshint esnext: true*/ 3 | "use strict"; 4 | 5 | var _sg = require('./globals'); 6 | 7 | var chalk = require('chalk'); 8 | 9 | /** 10 | * Generic file list console output 11 | * 12 | * @param {String} Arguments 13 | */ 14 | module.exports = function (fileName, create, level) { 15 | 16 | if (create && canShowLog(level || 2)) { 17 | var time = getTime(); 18 | return console.info(_sg.logPre, 'Created ' + _sg.good(fileName) + chalk.grey(' ['+time+']')); 19 | } 20 | 21 | if (_sg.fileList || canShowLog(3)) { 22 | return console.info(_sg.logPre, 'Reading ' + _sg.info(fileName)); 23 | } 24 | 25 | }; 26 | 27 | module.exports.generic = function(message, level, includeTime) { 28 | const time = includeTime ? chalk.grey(' [' + getTime() + ']') : ''; 29 | if (canShowLog(level)) { 30 | return console.info(_sg.logPre, message, time); 31 | } 32 | } 33 | 34 | function canShowLog(level) { 35 | const levels = { 36 | "silent": 0, 37 | "minimal": 1, 38 | "default": 2, 39 | "verbose": 3 40 | } 41 | 42 | const userLevel = levels[_sg.logLevel] || 2; 43 | 44 | return level <= userLevel; 45 | } 46 | 47 | function getTime() { 48 | var time = new Date(); 49 | return time.getHours() + 50 | ':' + time.getMinutes() + 51 | ':' + time.getSeconds() + 52 | ':' + time.getMilliseconds(); 53 | } 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-documentation-generator", 3 | "version": "3.2.1", 4 | "description": "Searches files for markdown and generates a static style/documentation guide. A fork of markdown-styleguide-generator.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/UWHealth/markdown-documentation-generator.git" 12 | }, 13 | "keywords": [ 14 | "markdown", 15 | "style guide", 16 | "pattern library", 17 | "css", 18 | "sass", 19 | "documentation" 20 | ], 21 | "author": { 22 | "name": "Chris Lee", 23 | "email": "clee5@uwhealth.org" 24 | }, 25 | "license": "GPL-3.0", 26 | "bugs": { 27 | "url": "https://github.com/UWHealth/markdown-documentation-generator/issues" 28 | }, 29 | "homepage": "https://github.com/UWHealth/markdown-documentation-generator#readme", 30 | "engines": { 31 | "node": ">= 4" 32 | }, 33 | "dependencies": { 34 | "chalk": "^2.4.2", 35 | "cheerio": "^0.22.0", 36 | "fs-extra": "^8.0.1", 37 | "handlebars": "^4.1.2", 38 | "highlight.js": "^10.4.1", 39 | "lodash": "^4.17.11", 40 | "marked": "^0.7.0", 41 | "walk": "^2.3.9", 42 | "yargs": "^13.2.4" 43 | }, 44 | "devDependencies": { 45 | "nodemon": "^1.19.1" 46 | }, 47 | "bin": { 48 | "md_documentation": "index.js" 49 | }, 50 | "readmeFilename": "README.md" 51 | } 52 | -------------------------------------------------------------------------------- /template/sticky.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net 3 | */ 4 | (function(){var b,f;b=this.jQuery||window.jQuery;f=b(window);b.fn.stick_in_parent=function(d){var A,w,J,n,B,K,p,q,k,E,t;null==d&&(d={});t=d.sticky_class;B=d.inner_scrolling;E=d.recalc_every;k=d.parent;q=d.offset_top;p=d.spacer;w=d.bottoming;null==q&&(q=0);null==k&&(k=void 0);null==B&&(B=!0);null==t&&(t="is_stuck");A=b(document);null==w&&(w=!0);J=function(a,d,n,C,F,u,r,G){var v,H,m,D,I,c,g,x,y,z,h,l;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);I=A.height();g=a.parent();null!=k&&(g=g.closest(k)); 5 | if(!g.length)throw"failed to find stick parent";v=m=!1;(h=null!=p?p&&a.closest(p):b("
    "))&&h.css("position",a.css("position"));x=function(){var c,f,e;if(!G&&(I=A.height(),c=parseInt(g.css("border-top-width"),10),f=parseInt(g.css("padding-top"),10),d=parseInt(g.css("padding-bottom"),10),n=g.offset().top+c+f,C=g.height(),m&&(v=m=!1,null==p&&(a.insertAfter(h),h.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(t),e=!0),F=a.offset().top-(parseInt(a.css("margin-top"),10)||0)-q, 6 | u=a.outerHeight(!0),r=a.css("float"),h&&h.css({width:a.outerWidth(!0),height:u,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),e))return l()};x();if(u!==C)return D=void 0,c=q,z=E,l=function(){var b,l,e,k;if(!G&&(e=!1,null!=z&&(--z,0>=z&&(z=E,x(),e=!0)),e||A.height()===I||x(),e=f.scrollTop(),null!=D&&(l=e-D),D=e,m?(w&&(k=e+u+c>C+n,v&&!k&&(v=!1,a.css({position:"fixed",bottom:"",top:c}).trigger("sticky_kit:unbottom"))),eb&&!v&&(c-=l,c=Math.max(b-u,c),c=Math.min(q,c),m&&a.css({top:c+"px"})))):e>F&&(m=!0,b={position:"fixed",top:c},b.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(b).addClass(t),null==p&&(a.after(h),"left"!==r&&"right"!==r||h.append(a)),a.trigger("sticky_kit:stick")),m&&w&&(null==k&&(k=e+u+c>C+n),!v&&k)))return v=!0,"static"===g.css("position")&&g.css({position:"relative"}), 8 | a.css({position:"absolute",bottom:d,top:"auto"}).trigger("sticky_kit:bottom")},y=function(){x();return l()},H=function(){G=!0;f.off("touchmove",l);f.off("scroll",l);f.off("resize",y);b(document.body).off("sticky_kit:recalc",y);a.off("sticky_kit:detach",H);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});g.position("position","");if(m)return null==p&&("left"!==r&&"right"!==r||a.insertAfter(h),h.remove()),a.removeClass(t)},f.on("touchmove",l),f.on("scroll",l),f.on("resize", 9 | y),b(document.body).on("sticky_kit:recalc",y),a.on("sticky_kit:detach",H),setTimeout(l,0)}};n=0;for(K=this.length;n\s*['"]?((\.|\/)[a-zA-Z\/\.\-_]*)['"]? *}}/g; 73 | 74 | let matches; 75 | let partialsFound = {}; 76 | while((matches = regex.exec(template)) !== null) { 77 | checkAndRegisterPartial( 78 | matches[1], 79 | path.resolve(path.dirname(templatePath), matches[1]) 80 | ) 81 | } 82 | } 83 | 84 | 85 | function checkAndRegisterPartial(partial, partialPath) { 86 | const currentPartial = checkPartial(partialPath, partial); 87 | // Check for nested partials 88 | findPathPartials(currentPartial, partialPath); 89 | handlebars.registerPartial(partial, currentPartial); 90 | } 91 | -------------------------------------------------------------------------------- /template/theme.css: -------------------------------------------------------------------------------- 1 | /******Base Layout******/ 2 | body { 3 | background-color: #F0F2F3; 4 | } 5 | 6 | .sg-body { 7 | margin: 0; 8 | } 9 | 10 | .sg-wrap { 11 | display: block; 12 | display: flex; 13 | flex-direction: row; 14 | width: 100%; 15 | min-height: 100vh; 16 | margin: 0; 17 | overflow: auto; 18 | } 19 | 20 | .sg-menu { 21 | display: inline-block; 22 | vertical-align: top; 23 | width: 20%; 24 | flex-shrink: .75; 25 | flex-direction: column; 26 | min-width: 10em; 27 | max-width: 20em; 28 | min-height: 100%; 29 | } 30 | 31 | .sg-main { 32 | display: inline-block; 33 | padding-top: 3em; 34 | padding-top: 3vh; 35 | flex-basis: 75%; 36 | flex-grow: 1.5; 37 | flex-shrink: .75; 38 | } 39 | 40 | /******Menu******/ 41 | 42 | .sg-menu { 43 | box-sizing: border-box; 44 | transition: all .2s ease-out; 45 | overflow: auto; 46 | background-color: #DBE0E8; 47 | color: rgba(29,64,66,.2); 48 | } 49 | 50 | .sg-menu *, 51 | .sg-menu *:before, 52 | .sg-menu *:after { 53 | box-sizing: inherit; 54 | } 55 | 56 | .sg-menu_wrap { 57 | max-height: 100%; 58 | width: 100%; 59 | } 60 | 61 | .sg_menu_list, 62 | .sg-menu_item { 63 | padding: 0; 64 | margin: 0; 65 | position: relative; 66 | } 67 | 68 | .sg-menu_list { 69 | list-style: none; 70 | padding: 0; 71 | margin: 0; 72 | border-style: solid; 73 | border-width: 1px 0 0; 74 | } 75 | 76 | .sg-menu_section { 77 | margin-top: .5em; 78 | border-top: 0; 79 | } 80 | 81 | .sg-menu_category { 82 | margin-top: -1px; 83 | } 84 | 85 | .sg-menu_list-sub { 86 | background: rgba(25,53,64,.1); 87 | margin-bottom: -1px; 88 | } 89 | 90 | /* Menu links */ 91 | .sg-menu_link { 92 | display: block; 93 | font-size: .9em; 94 | padding: .75em 1.5em .5em 6%; 95 | text-decoration: none; 96 | /*color: rgb(149, 195, 206);*/ 97 | } 98 | 99 | .sg-menu_link:hover { 100 | background-color: rgba(51,218,236,.09); 101 | } 102 | 103 | 104 | .sg-menu_list-sub .sg-menu_link { 105 | padding-left: 8.5%; 106 | } 107 | 108 | .sg-section_link { 109 | /*color: #0ED2C0;*/ 110 | font-weight: bold; 111 | } 112 | 113 | .sg-menu_toggle { 114 | /*color: #8ECCD2;*/ 115 | /*opacity: .35;*/ 116 | } 117 | .sg-menu_toggle:hover { 118 | /*opacity: 1;*/ 119 | } 120 | 121 | .sg-menu_toggle { 122 | position: absolute; 123 | right: 0; 124 | top: 0; 125 | cursor: pointer; 126 | padding: .75em; 127 | transform: rotate(0deg); 128 | transform-origin: center; 129 | transition: transform 200ms ease-out; 130 | -moz-user-select: none; 131 | -webkit-user-select: none; 132 | -ms-user-select: none; 133 | user-select: none; 134 | line-height: 1; 135 | width: 2.25rem; 136 | text-align: center; 137 | } 138 | 139 | .sg-menu_toggle.toggle-active { 140 | transform: rotate(180deg); 141 | } 142 | 143 | /******Project Logo******/ 144 | 145 | .sg-logo { 146 | font-weight: normal; 147 | background-color: #575B5D; 148 | margin: 0 0 .25em; 149 | } 150 | 151 | .sg-logo .sg-menu_link { 152 | color: #fff; 153 | padding-bottom: .75em; 154 | } 155 | 156 | /******Headings******/ 157 | 158 | .sg-heading-section { 159 | color: #8e9da2; 160 | border-bottom: 1px solid #e0e8ea; 161 | padding-bottom: .5em; 162 | } 163 | 164 | .sg-heading-category { 165 | /*color: #18d2c0;*/ 166 | } 167 | 168 | .sg-heading-article { 169 | /*color: #40474A;*/ 170 | } 171 | 172 | 173 | /******Section & Article Layout******/ 174 | 175 | .sg-section { 176 | padding: 5px 2.5%; 177 | max-width: 65em; 178 | margin: auto; 179 | } 180 | 181 | .sg-article { 182 | margin-bottom: 5em; 183 | } 184 | 185 | .sg-article_comment { 186 | margin-bottom: 2em; 187 | } 188 | 189 | .sg-example { 190 | background-color: rgba(255,255,255,.8); 191 | border-top: 1px solid #fff; 192 | } 193 | 194 | .sg-example:before { 195 | content: 'Example'; 196 | background-color: #EDF0F2; 197 | color: #647582; 198 | display: inline-block; 199 | font-size: .9em; 200 | padding: .45em .5em; 201 | margin-bottom: .5em; 202 | margin-left: 1px; 203 | clear: both; 204 | } 205 | 206 | .sg-example_wrap { 207 | padding: 1em 1.25em; 208 | position: relative; 209 | } 210 | 211 | /* Clearfix */ 212 | .sg-example_wrap:before, 213 | .sg-example_wrap:after { 214 | content: " "; 215 | display: table; 216 | height: 0; 217 | width: 100%; 218 | } 219 | 220 | .sg-example_wrap:after { 221 | clear: both; 222 | } 223 | 224 | .sg-markup_wrap { 225 | margin: 0; 226 | border-top: 1px solid #EFF2F6; 227 | } 228 | 229 | .sg-codespan { 230 | color: #17C3AE; 231 | } 232 | 233 | .sg-codeblock { 234 | margin-bottom: 2em; 235 | } 236 | 237 | 238 | /* Highlight.js Code Blocks */ 239 | #styleguide .sg-markup_wrap, 240 | #styleguide .sg-markup code { 241 | overflow: auto; 242 | } 243 | #styleguide .sg-markup_wrap { 244 | max-height: 30em; 245 | } 246 | 247 | #styleguide .sg-markup code { 248 | display: block; 249 | word-wrap: normal; 250 | white-space: pre; 251 | padding: 1em; 252 | } 253 | 254 | /* Custom background color to match nav. Overwrites highlight.js default */ 255 | #styleguide .hljs { 256 | background-color: #f7f7f8; 257 | } 258 | #styleguide .hljs-comment { 259 | color: #8aaeb7; 260 | } 261 | -------------------------------------------------------------------------------- /test/config-file/toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * toc - jQuery Table of Contents Plugin 3 | * v0.3.2 4 | * http://projects.jga.me/toc/ 5 | * copyright Greg Allen 2014 6 | * MIT License 7 | */ 8 | /*! 9 | * smooth-scroller - Javascript lib to handle smooth scrolling 10 | * v0.1.2 11 | * https://github.com/firstandthird/smooth-scroller 12 | * copyright First+Third 2014 13 | * MIT License 14 | */ 15 | //smooth-scroller.js 16 | 17 | (function($) { 18 | $.fn.smoothScroller = function(options) { 19 | options = $.extend({}, $.fn.smoothScroller.defaults, options); 20 | var el = $(this); 21 | 22 | $(options.scrollEl).animate({ 23 | scrollTop: el.offset().top - $(options.scrollEl).offset().top - options.offset 24 | }, options.speed, options.ease, function() { 25 | var hash = el.attr('id'); 26 | 27 | if(hash.length) { 28 | if(history.pushState) { 29 | history.pushState(null, null, '#' + hash); 30 | } else { 31 | document.location.hash = hash; 32 | } 33 | } 34 | 35 | el.trigger('smoothScrollerComplete'); 36 | }); 37 | 38 | return this; 39 | }; 40 | 41 | $.fn.smoothScroller.defaults = { 42 | speed: 400, 43 | ease: 'swing', 44 | scrollEl: 'body,html', 45 | offset: 0 46 | }; 47 | 48 | $('body').on('click', '[data-smoothscroller]', function(e) { 49 | e.preventDefault(); 50 | var href = $(this).attr('href'); 51 | 52 | if(href.indexOf('#') === 0) { 53 | $(href).smoothScroller(); 54 | } 55 | }); 56 | }(jQuery)); 57 | 58 | (function($) { 59 | var verboseIdCache = {}; 60 | $.fn.toc = function(options) { 61 | var self = this; 62 | var opts = $.extend({}, jQuery.fn.toc.defaults, options); 63 | 64 | var container = $(opts.container); 65 | var headings = $(opts.selectors, container); 66 | var headingOffsets = []; 67 | var activeClassName = opts.activeClass; 68 | 69 | var scrollTo = function(e, callback) { 70 | if (opts.smoothScrolling && typeof opts.smoothScrolling === 'function') { 71 | e.preventDefault(); 72 | var elScrollTo = $(e.target).attr('href'); 73 | 74 | opts.smoothScrolling(elScrollTo, opts, callback); 75 | } 76 | $('li', self).removeClass(activeClassName); 77 | $(e.target).parent().addClass(activeClassName); 78 | }; 79 | 80 | //highlight on scroll 81 | var timeout; 82 | var highlightOnScroll = function(e) { 83 | if (timeout) { 84 | clearTimeout(timeout); 85 | } 86 | timeout = setTimeout(function() { 87 | var top = $(window).scrollTop(), 88 | highlighted, closest = Number.MAX_VALUE, index = 0; 89 | 90 | for (var i = 0, c = headingOffsets.length; i < c; i++) { 91 | var currentClosest = Math.abs(headingOffsets[i] - top); 92 | if (currentClosest < closest) { 93 | index = i; 94 | closest = currentClosest; 95 | } 96 | } 97 | 98 | $('li', self).removeClass(activeClassName); 99 | highlighted = $('li:eq('+ index +')', self).addClass(activeClassName); 100 | opts.onHighlight(highlighted); 101 | }, 50); 102 | }; 103 | if (opts.highlightOnScroll) { 104 | $(window).bind('scroll', highlightOnScroll); 105 | highlightOnScroll(); 106 | } 107 | 108 | return this.each(function() { 109 | //build TOC 110 | var el = $(this); 111 | var ul = $(opts.listType); 112 | 113 | headings.each(function(i, heading) { 114 | var $h = $(heading); 115 | headingOffsets.push($h.offset().top - opts.highlightOffset); 116 | 117 | var anchorName = opts.anchorName(i, heading, opts.prefix); 118 | 119 | //add anchor 120 | if(heading.id !== anchorName) { 121 | var anchor = $('').attr('id', anchorName).insertBefore($h); 122 | } 123 | 124 | //build TOC item 125 | var a = $('') 126 | .text(opts.headerText(i, heading, $h)) 127 | .attr('href', '#' + anchorName) 128 | .bind('click', function(e) { 129 | $(window).unbind('scroll', highlightOnScroll); 130 | scrollTo(e, function() { 131 | $(window).bind('scroll', highlightOnScroll); 132 | }); 133 | el.trigger('selected', $(this).attr('href')); 134 | }); 135 | 136 | var li = $('
  • ') 137 | .addClass(opts.itemClass(i, heading, $h, opts.prefix)) 138 | .append(a); 139 | 140 | ul.append(li); 141 | }); 142 | el.html(ul); 143 | }); 144 | }; 145 | 146 | 147 | jQuery.fn.toc.defaults = { 148 | container: 'body', 149 | listType: '
      ', 150 | selectors: 'h1,h2,h3', 151 | smoothScrolling: function(target, options, callback) { 152 | $(target).smoothScroller({ 153 | offset: options.scrollToOffset 154 | }).on('smoothScrollerComplete', function() { 155 | callback(); 156 | }); 157 | }, 158 | scrollToOffset: 0, 159 | prefix: 'toc', 160 | activeClass: 'toc-active', 161 | onHighlight: function() {}, 162 | highlightOnScroll: true, 163 | highlightOffset: 100, 164 | anchorName: function(i, heading, prefix) { 165 | if(heading.id.length) { 166 | return heading.id; 167 | } 168 | 169 | var candidateId = $(heading).text().replace(/[^a-z0-9]/ig, ' ').replace(/\s+/g, '-').toLowerCase(); 170 | if (verboseIdCache[candidateId]) { 171 | var j = 2; 172 | 173 | while(verboseIdCache[candidateId + j]) { 174 | j++; 175 | } 176 | candidateId = candidateId + '-' + j; 177 | 178 | } 179 | verboseIdCache[candidateId] = true; 180 | 181 | return prefix + '-' + candidateId; 182 | }, 183 | headerText: function(i, heading, $heading) { 184 | return $heading.text(); 185 | }, 186 | itemClass: function(i, heading, $heading, prefix) { 187 | return prefix + '-' + $heading[0].tagName.toLowerCase(); 188 | } 189 | 190 | }; 191 | 192 | })(jQuery); 193 | -------------------------------------------------------------------------------- /test/config-file/theme-tocmenu.css: -------------------------------------------------------------------------------- 1 | /******Base Layout******/ 2 | body { 3 | background-color: #fafbfc; 4 | } 5 | 6 | .sg-body { 7 | margin: 0; 8 | } 9 | 10 | .sg-wrap { 11 | display: block; 12 | width: 100%; 13 | min-height: 100vh; 14 | margin: 0; 15 | overflow: auto; 16 | } 17 | 18 | .sg-menu { 19 | position: fixed; 20 | vertical-align: top; 21 | background-color: #2e3e5a; 22 | width: 15em; 23 | top: 0; 24 | left: 0; 25 | bottom: 0; 26 | } 27 | 28 | .sg-main { 29 | padding-left: 15em; 30 | transition: padding .15s ease-out; 31 | } 32 | 33 | .sg-section > * { 34 | max-width: 55em; /* Should be wider for universal box-sizing: border-box */ 35 | margin: auto; 36 | padding-left: 8%; 37 | padding-right: 8%; 38 | } 39 | 40 | .sg-example { 41 | width: 116%; 42 | margin-left: -8%; 43 | } 44 | 45 | @media only screen and (max-width: 1100px) { 46 | .sg-example { 47 | width: 115%; 48 | margin-left: -7.5%; 49 | } 50 | 51 | .sg-menu { 52 | width: 10em; 53 | } 54 | 55 | .sg-main { 56 | padding-left: 10em; 57 | } 58 | } 59 | 60 | @media only screen and (max-width: 700px) { 61 | .sg-menu { 62 | width: 0; 63 | } 64 | 65 | .sg-main { 66 | padding-left: 0; 67 | } 68 | } 69 | 70 | 71 | /******Menu******/ 72 | .sg-menu { 73 | transition: all .2s ease-out; 74 | overflow: auto; 75 | } 76 | 77 | .sg_menu_list, 78 | .sg-menu_item { 79 | padding: 0; 80 | margin: 0; 81 | position: relative; 82 | } 83 | 84 | .sg-menu_list { 85 | list-style: none; 86 | padding: 0; 87 | color: rgba(29, 234, 216, 0.15); /* Used for border colors */ 88 | } 89 | 90 | .sg-menu_section { 91 | border-top: 1px solid; 92 | } 93 | 94 | .sg-menu_article > .sg-menu_link { 95 | color: rgba(172, 209, 218, .65); 96 | } 97 | 98 | .sg-category-active { 99 | background: rgba(142,204,210,.04); 100 | } 101 | 102 | /* Menu links */ 103 | .sg-menu_link { 104 | display: block; 105 | padding: 1rem 1.65rem .75rem; 106 | text-decoration: none; 107 | color: rgb(149, 195, 206); 108 | } 109 | 110 | .sg-menu_link:hover { 111 | background-color: rgba(134,230,240,.05); 112 | color: rgb(172, 209, 218); 113 | } 114 | 115 | .sg-toc-active > .sg-menu_link { 116 | background-color: rgba(134,230,240,.15); 117 | } 118 | 119 | .sg-menu_section > .sg-menu_link { 120 | color: #0ED2C0; 121 | } 122 | 123 | .sg-menu_article .sg-menu_link { 124 | padding-left: 2.5rem; 125 | padding-right: 2rem; 126 | } 127 | 128 | .sg-menu_article { 129 | display: none; 130 | } 131 | 132 | .sg-category-active .sg-menu_article, 133 | .sg-menu_article.sg-toc-active { 134 | display: block; 135 | } 136 | 137 | .sg-category-active > .sg-menu_link { 138 | border-left: 2px solid #18d2c0; 139 | text-indent: -2px; 140 | } 141 | 142 | /******Project Logo******/ 143 | 144 | .sg-logo { 145 | font-weight: normal; 146 | background-color: #18d2c0; 147 | color: #fff; 148 | margin: 0; 149 | font-size: 1.5em; 150 | } 151 | 152 | .sg-logo:hover { 153 | background-color: #14E9D5; 154 | color: #ECFCFB; 155 | } 156 | 157 | .sg-logo .sg-menu_link { 158 | color: inherit; 159 | padding-bottom: .75em; 160 | } 161 | 162 | /******Headings******/ 163 | 164 | .sg-heading-section { 165 | color: #8e9da2; 166 | border-bottom: 1px solid rgba(208,217,219,.65); 167 | padding-bottom: .5em; 168 | } 169 | 170 | .sg-heading-category { 171 | color: #18d2c0; 172 | font-weight: normal; 173 | font-size: 1.6em; 174 | } 175 | 176 | .sg-heading-article { 177 | font-size: 1.4em; 178 | color: #4F5455; 179 | font-weight: normal; 180 | } 181 | 182 | 183 | /******Section, Category & Article Layout******/ 184 | 185 | .sg-section:nth-child(even) { 186 | padding-bottom: 1.5em; 187 | background-color: rgba(0,0,0,.025); 188 | } 189 | 190 | /* Article spacing */ 191 | .sg-section { 192 | padding-top: 3em; 193 | padding-bottom: 3em; 194 | } 195 | 196 | .sg-section, 197 | .sg-category, 198 | .sg-article_comment, 199 | .sg-example { 200 | margin-bottom: 1.5em; 201 | } 202 | 203 | .sg-article { 204 | margin-bottom: 3em; 205 | } 206 | 207 | .sg-category + .sg-category { 208 | padding-top: 1.5em; 209 | } 210 | 211 | .sg-article_comment { 212 | margin-top: -1.5rem; 213 | } 214 | 215 | .sg-category_head, 216 | .sg-section_head, 217 | .sg-article_head { 218 | padding-bottom: 1.5em; 219 | } 220 | 221 | .sg-example { 222 | background-color: rgba(255,255,255,.8); 223 | border-top: 1px solid #fff; 224 | } 225 | 226 | .sg-example:before { 227 | content: 'Example'; 228 | background-color: #EDF0F2; 229 | color: #647582; 230 | display: inline-block; 231 | font-size: .9em; 232 | padding: .45em .5em; 233 | margin-bottom: .5em; 234 | margin-left: 1px; 235 | clear: both; 236 | } 237 | 238 | .sg-example_wrap { 239 | padding: .75em 1.25em; 240 | position: relative; 241 | } 242 | 243 | /* Clearfix */ 244 | .sg-example_wrap:after { 245 | content: " "; 246 | display: table; 247 | height: 0; 248 | width: 100%; 249 | } 250 | 251 | .sg-example_wrap:after { 252 | clear: both; 253 | } 254 | 255 | .sg-markup_wrap { 256 | margin: 0; 257 | } 258 | 259 | .sg-markup-block { 260 | border-top: 1px solid #EFF2F6; 261 | } 262 | 263 | .sg-codespan { 264 | color: #00979D; 265 | } 266 | 267 | .sg-codeblock { 268 | margin-bottom: 2em; 269 | } 270 | 271 | 272 | /* Highlight.js Code Blocks */ 273 | #styleguide .sg-markup_wrap, 274 | #styleguide .sg-markup code { 275 | overflow: auto; 276 | } 277 | #styleguide .sg-markup_wrap { 278 | max-height: 30em; 279 | } 280 | 281 | #styleguide .sg-markup code { 282 | display: block; 283 | word-wrap: normal; 284 | white-space: pre; 285 | padding: 1em; 286 | } 287 | 288 | /* Custom background color to match nav. Overwrites highlight.js default */ 289 | #styleguide .hljs { 290 | background-color: #fff; 291 | } 292 | #styleguide .hljs-comment { 293 | color: #8aaeb7; 294 | } 295 | -------------------------------------------------------------------------------- /test/config-file/theme.css: -------------------------------------------------------------------------------- 1 | /******Base Layout******/ 2 | body { 3 | background-color: #F0F2F3; 4 | } 5 | 6 | .sg-body { 7 | margin: 0; 8 | } 9 | 10 | .sg-wrap { 11 | display: block; 12 | display: flex; 13 | flex-direction: row; 14 | width: 100%; 15 | min-height: 100vh; 16 | margin: 0; 17 | overflow: auto; 18 | } 19 | 20 | .sg-menu { 21 | display: inline-block; 22 | vertical-align: top; 23 | width: 20%; 24 | flex-shrink: .75; 25 | flex-direction: column; 26 | background-color: #2e3e5a; 27 | min-width: 10em; 28 | max-width: 20em; 29 | min-height: 100%; 30 | } 31 | 32 | .sg-main { 33 | display: inline-block; 34 | padding-top: 3em; 35 | padding-top: 3vh; 36 | flex-basis: 75%; 37 | flex-grow: 1.5; 38 | flex-shrink: .75; 39 | } 40 | 41 | /******Menu******/ 42 | 43 | .sg-toggle-menu { 44 | position: fixed; 45 | left: 1rem; 46 | top: 1rem; 47 | border-radius: 50%; 48 | width: 2.1rem; 49 | height: 2.1rem; 50 | border: 0; 51 | outline: none; 52 | color: #fff; 53 | background-color: #18d2c0; 54 | z-index: 9999; 55 | } 56 | 57 | .sg-toggle-menu:before { 58 | font-size: 2em; 59 | content: ""; 60 | position: absolute; 61 | left: .25em; 62 | top: 0.4em; 63 | width: .95em; 64 | height: 0.15em; 65 | background: #fff; 66 | box-shadow: 67 | 0 0.25em 0 0 #fff, 68 | 0 0.5em 0 0 #fff; 69 | } 70 | 71 | .sg-toggle-menu span { 72 | width: 0; 73 | display: block; 74 | overflow: hidden; 75 | height: 0; 76 | } 77 | 78 | .sg-menu { 79 | box-sizing: border-box; 80 | transition: all .2s ease-out; 81 | overflow: auto; 82 | } 83 | 84 | .sg-menu.toggle-active { 85 | min-width: 0; 86 | width: 0; 87 | } 88 | 89 | .sg-menu *, 90 | .sg-menu *:before, 91 | .sg-menu *:after { 92 | box-sizing: inherit; 93 | } 94 | 95 | .sg-menu.toggle-active .sg-menu_wrap { 96 | width: 100% !important; 97 | position: relative !important; 98 | } 99 | 100 | .sg-menu_wrap { 101 | max-height: 100%; 102 | width: 100%; 103 | } 104 | 105 | .sg_menu_list, 106 | .sg-menu_item { 107 | padding: 0; 108 | margin: 0; 109 | position: relative; 110 | } 111 | 112 | .sg-menu_list { 113 | list-style: none; 114 | padding: 0; 115 | color: rgba(210,227,228,.08); 116 | border-style: solid; 117 | border-width: 1px 0 0; 118 | } 119 | 120 | .sg-menu_section { 121 | margin-top: .5em; 122 | border-top: 0; 123 | } 124 | 125 | .sg-menu_category { 126 | margin-top: -1px; 127 | } 128 | 129 | .sg-menu_list-sub { 130 | background: rgba(142,204,210,.04); 131 | margin-bottom: -1px; 132 | } 133 | 134 | /* Menu links */ 135 | .sg-menu_link { 136 | display: block; 137 | font-size: .9em; 138 | padding: .75em 1.5em .5em 6%; 139 | text-decoration: none; 140 | color: rgb(149, 195, 206); 141 | } 142 | 143 | .sg-menu_link:hover { 144 | background-color: rgba(134,230,240,.09); 145 | } 146 | 147 | 148 | .sg-menu_list-sub .sg-menu_link { 149 | padding-left: 7.5%; 150 | color: rgba(149, 195, 206, .8); 151 | } 152 | 153 | .sg-section_link { 154 | color: #0ED2C0; 155 | } 156 | 157 | .sg-menu_toggle { 158 | color: #8ECCD2; 159 | opacity: .35; 160 | } 161 | .sg-menu_toggle:hover { 162 | opacity: 1; 163 | } 164 | 165 | .sg-menu_toggle { 166 | position: absolute; 167 | right: 0; 168 | top: 0; 169 | cursor: pointer; 170 | padding: .75em; 171 | transform: rotate(0deg); 172 | transform-origin: center; 173 | transition: transform 200ms ease-out; 174 | -moz-user-select: none; 175 | -webkit-user-select: none; 176 | -ms-user-select: none; 177 | user-select: none; 178 | line-height: 1; 179 | width: 2.25rem; 180 | text-align: center; 181 | } 182 | 183 | .sg-menu_toggle.toggle-active { 184 | transform: rotate(180deg); 185 | } 186 | 187 | /******Project Logo******/ 188 | 189 | .sg-logo { 190 | font-weight: normal; 191 | background-color: #18d2c0; 192 | margin: 0 0 .25em; 193 | } 194 | 195 | .sg-logo .sg-menu_link { 196 | color: #fff; 197 | padding-bottom: .75em; 198 | padding-left: 3.25rem; 199 | } 200 | 201 | /******Headings******/ 202 | 203 | .sg-heading-section { 204 | color: #8e9da2; 205 | border-bottom: 1px solid #e0e8ea; 206 | padding-bottom: .5em; 207 | } 208 | 209 | .sg-heading-category { 210 | color: #18d2c0; 211 | } 212 | 213 | /******Section & Article Layout******/ 214 | 215 | .sg-section { 216 | padding: 5px 2.5%; 217 | max-width: 65em; 218 | margin: auto; 219 | } 220 | 221 | .sg-article { 222 | margin-bottom: 5em; 223 | } 224 | 225 | .sg-article_comment { 226 | margin-bottom: 2em; 227 | } 228 | 229 | .sg-example { 230 | background-color: rgba(255,255,255,.8); 231 | border-top: 1px solid #fff; 232 | } 233 | 234 | .sg-example:before { 235 | content: 'Example'; 236 | background-color: #EDF0F2; 237 | color: #647582; 238 | display: inline-block; 239 | font-size: .9em; 240 | padding: .45em .5em; 241 | margin-bottom: .5em; 242 | margin-left: 1px; 243 | clear: both; 244 | } 245 | 246 | .sg-example_wrap { 247 | padding: 1em 1.25em; 248 | position: relative; 249 | } 250 | 251 | /* Clearfix */ 252 | .sg-example_wrap:before, 253 | .sg-example_wrap:after { 254 | content: " "; 255 | display: table; 256 | height: 0; 257 | width: 100%; 258 | } 259 | 260 | .sg-example_wrap:after { 261 | clear: both; 262 | } 263 | 264 | .sg-markup_wrap { 265 | margin: 0; 266 | border-top: 1px solid #EFF2F6; 267 | } 268 | 269 | .sg-codespan { 270 | color: #de3c8c; 271 | } 272 | 273 | .sg-codeblock { 274 | margin-bottom: 2em; 275 | } 276 | 277 | 278 | /* Highlight.js Code Blocks */ 279 | #styleguide .sg-markup_wrap, 280 | #styleguide .sg-markup code { 281 | overflow: auto; 282 | } 283 | #styleguide .sg-markup_wrap { 284 | max-height: 30em; 285 | } 286 | 287 | #styleguide .sg-markup code { 288 | display: block; 289 | word-wrap: normal; 290 | white-space: pre; 291 | padding: 1em; 292 | } 293 | 294 | /* Custom background color to match nav. Overwrites highlight.js default */ 295 | #styleguide .hljs { 296 | background-color: #f7f7f8; 297 | } 298 | #styleguide .hljs-comment { 299 | color: #8aaeb7; 300 | } 301 | -------------------------------------------------------------------------------- /test/config-file/template.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{customVariables.pageTitle}} 12 | 16 | 17 | 18 | 19 | {{!-- Required ID --}} 20 | 21 | {{> ./partial.hbs}} 22 | 23 | {{!--Menu--}} 24 | 34 | 35 |
      36 | {{~#each sections}} 37 |
      38 |
      39 |

      {{titleize @key}}

      40 |
      41 | 42 | {{#each this}} {{!--Looping through Categories --}} 43 |
      44 | {{#if category}} 45 |
      46 |

      47 | {{~category~}} 48 |

      49 |
      50 | {{~/if}} 51 | 52 | {{~#each articles}} 53 |
      54 | {{#if heading}} 55 |
      56 |

      57 | {{~heading~}} 58 |

      59 | {{~#if fileLocation}} 60 | 61 | {{~fileLocation~}} 62 | 63 | {{~/if~}} 64 |
      65 | {{/if}} 66 |
      67 | {{{comment}}} 68 |
      69 | {{#if code}} 70 |
      71 |
      72 | {{#each code}}{{{this}}}{{/each}} 73 |
      74 |
      75 | {{#each markup}} 76 |
      77 |
      
       78 |                                     {{~{this}~}}
       79 |                                 
      80 |
      81 | {{/each}} 82 |
      83 |
      84 | {{/if}} 85 |
      86 | {{~/each}} 87 |
      88 | {{~/each}} 89 |
      90 | {{~/each}} 91 |
      92 | 93 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /template/template.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{customVariables.pageTitle}} 12 | 16 | 17 | 18 |
      19 | {{!--Menu--}} 20 | 68 |
      69 | {{#each sections}} 70 |
      71 |
      72 |

      {{titleize @key}}

      73 |
      74 | 75 | {{#each this}} 76 |
      77 | {{!--Check for more than one article --}} 78 |
      79 |

      80 | {{category}} 81 |

      82 |
      83 | 84 | {{#each articles}} 85 |
      86 | {{#if heading}} 87 |
      88 |

      89 | {{heading}} 90 |

      91 | {{~#if file}} 92 | {{file}} 93 | {{~/if~}} 94 |
      95 | {{/if}} 96 |
      97 | {{{comment}}} 98 |
      99 | {{#if code}} 100 |
      101 |
      102 | {{#each code}}{{{this}}}{{/each}} 103 |
      104 | {{#each markup}} 105 |
      106 |
      {{{this}}}
      107 |
      108 | {{/each}} 109 |
      110 | {{/if}} 111 |
      112 | {{~/each}} 113 |
      114 | {{/each}} 115 |
      116 | {{~/each}} 117 |
      118 |
      119 | 120 | 124 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /lib/md-formats.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* jshint esversion:6 */ 3 | "use strict"; 4 | 5 | var tags = require('./tags').tags; 6 | var patterns = require('./tags').patterns; 7 | 8 | var hl = require('highlight.js'); 9 | 10 | 11 | /** 12 | * Registers new and modified renderers to Marked's renderer 13 | * @param {object} renderer 14 | * @param {object} options 15 | * @returns {object} renderer with added functionality 16 | */ 17 | 18 | function customRenderers(renderer, options) { 19 | /** 20 | * Converts headings to special main-section/sub-section elements for later placement 21 | * Processed via convertHTMLtoJSON function 22 | */ 23 | renderer.heading = function (string, number) { 24 | if (number === 1) { 25 | 26 | var splitHeader = string.split('/', 2); 27 | 28 | var heading = '<'+tags.category+'>' + splitHeader[0] + '\n'; 29 | 30 | if (splitHeader.length > 1) { 31 | for (var i = 1; i < splitHeader.length; i++){ 32 | //Use fake html tag to more easily parse later 33 | heading += '<'+tags.article+'>' + splitHeader[i] + '\n'; 34 | } 35 | } 36 | 37 | return heading; 38 | } 39 | else { 40 | //Allow for non-mainsection h1's 41 | number = Math.floor(number); 42 | var escapedString = string.replace(/<\/?[^>]+(>|$)/g, "").trim().toLowerCase().replace(/[^\w]+/g, '-'); 43 | return ` 44 | ${string}\n`; 47 | } 48 | }; 49 | 50 | /** 51 | * Checks for example codeblocks 52 | * and runs all codeblocks through highlight.js 53 | */ 54 | renderer.code = function (text, lang) { 55 | //check for html example 56 | if (lang === options.exampleIdentifier){ 57 | return '\n<'+tags.example+'>' + text + '\n'; 58 | } 59 | else { 60 | var processedText = ''; 61 | const langPrefix = options.markedOptions.langPrefix || " "; 62 | 63 | if (lang) { 64 | processedText = hl.highlight(lang, text).value; 65 | } 66 | else{ 67 | processedText = hl.highlightAuto(text).value; 68 | } 69 | 70 | return '\n
      ' + 71 | '\n
      '+
       72 |                 '' +
       74 |                 processedText +
       75 |                 '\n
      '+ 76 | '\n
      \n'; 77 | } 78 | }; 79 | 80 | /** 81 | * Checks for mixins, variables, and functions 82 | * Adds classes for better styling control 83 | */ 84 | renderer.codespan = function(text){ 85 | 86 | var codeChecks = { 87 | "variable": /\s*(\${2}[A-z\S]+)[,\s\n]?/gi, 88 | "function": /(^\s*(?!@)\S+\(\)$)/gmi, 89 | "mixin": /\s*(@\S+\(\)$)/gi 90 | }; 91 | 92 | var classString = " sg-code sg-codespan sg-global", 93 | test; 94 | 95 | //Run through tests 96 | for (var check in codeChecks) { 97 | if ({}.hasOwnProperty.call(codeChecks, check)){ 98 | if((test = codeChecks[check].exec(text))){ 99 | //Replace double $$ (global variable) with singles 100 | var newString = test[0].replace('$$', '$').trim(), 101 | //Remove @ (mixin identifier) and parens, replace non-word characters with a dash 102 | safeString = newString.replace('@', '') 103 | .replace(/[\(\)]/gmi, '') 104 | .replace(/[\W\s]/gmi, '-'); 105 | 106 | return ''+ 110 | newString + 111 | ''; 112 | } 113 | } 114 | } 115 | 116 | //Just return a standard codespan if none of the previous regExs pass 117 | return''+text+''; 118 | }; 119 | 120 | renderer.br = function(){ 121 | //Line breaks required for tag parsing within paragraphs 122 | return "\n
      \n"; 123 | }; 124 | 125 | renderer.paragraph = function(text) { 126 | 127 | /** 128 | * Checks paragraphs that contain @returns/@requires/@params/etc. tags. 129 | * Essentially allowing for meta-data/jsdoc-style documentation. 130 | * Converts them to definition lists or styleguide-specific html. 131 | * 132 | * @param {String} text - paragraph text 133 | * @returns {String} text - newly formmatted data either surrounded by a custom tag or a dt/dd 134 | */ 135 | function testForTags(text) { 136 | //Block "tags" 137 | let tagTypes = patterns, 138 | //"Tags" that are specifically used for styleguide formatting 139 | metaTypes = Object.keys(tags), 140 | matches; 141 | 142 | function formatTags(type, matches) { 143 | 144 | if(metaTypes.indexOf(type) > -1) { 145 | return '<'+tags[type]+'>'+matches[4].trim()+''; 146 | } 147 | 148 | //Convert aliases and requires to codespans so we can use their references 149 | if (type === "alias" || type === "requires") { 150 | matches[4] = renderer.codespan.call(renderer, matches[4]); 151 | } 152 | 153 | return [ 154 | '

      ', 155 | matches[4], 156 | '

      ' 157 | ].join(' '); 158 | } 159 | 160 | //Run through all types, replacing wherever necessary 161 | for (var type in tagTypes) { 162 | if ({}.hasOwnProperty.call(tagTypes, type)) { 163 | var tagRegEx = new RegExp("(^)(@)(" + tagTypes[type] + "):?([A-z\\S\\ ]+)$", "mi"); 164 | 165 | if ((matches = text.match(tagRegEx))) { 166 | //Send text through formatting and remove unnecessary breaks 167 | text = text 168 | .replace(matches[0], formatTags(type, matches)) 169 | .replace('
      ', ''); 170 | 171 | } 172 | } 173 | } 174 | 175 | //Wrap un-tagged portions with p tags 176 | return text.replace(/^(?!(?:<\/?[^>]+>[^>]+<\/?[^>]+>|$))(.*)$/gm, '

      $&

      '); 177 | } 178 | 179 | //If a paragraph starts with an "@", test it for tags 180 | return (text.match(/(^@.+$)/gmi)) ? testForTags(text).replace('

      ', '') : '

      '+text+'

      '; 181 | }; 182 | 183 | return renderer; 184 | } 185 | 186 | module.exports.register = function(renderer, options){ 187 | return customRenderers(renderer, options); 188 | }; 189 | -------------------------------------------------------------------------------- /lib/sanitize.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | "use strict"; 3 | 4 | var _ = require('lodash'); 5 | var path = require('path'); 6 | 7 | var sanitize = {}; 8 | 9 | /** 10 | * Remove Diacrits 11 | */ 12 | var defaultDiacriticsRemovalap = [ 13 | {'base':'A', 'letters':'\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F'}, 14 | {'base':'AA','letters':'\uA732'}, 15 | {'base':'AE','letters':'\u00C6\u01FC\u01E2'}, 16 | {'base':'AO','letters':'\uA734'}, 17 | {'base':'AU','letters':'\uA736'}, 18 | {'base':'AV','letters':'\uA738\uA73A'}, 19 | {'base':'AY','letters':'\uA73C'}, 20 | {'base':'B', 'letters':'\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181'}, 21 | {'base':'C', 'letters':'\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E'}, 22 | {'base':'D', 'letters':'\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779'}, 23 | {'base':'DZ','letters':'\u01F1\u01C4'}, 24 | {'base':'Dz','letters':'\u01F2\u01C5'}, 25 | {'base':'E', 'letters':'\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E'}, 26 | {'base':'F', 'letters':'\u0046\u24BB\uFF26\u1E1E\u0191\uA77B'}, 27 | {'base':'G', 'letters':'\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E'}, 28 | {'base':'H', 'letters':'\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D'}, 29 | {'base':'I', 'letters':'\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197'}, 30 | {'base':'J', 'letters':'\u004A\u24BF\uFF2A\u0134\u0248'}, 31 | {'base':'K', 'letters':'\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2'}, 32 | {'base':'L', 'letters':'\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780'}, 33 | {'base':'LJ','letters':'\u01C7'}, 34 | {'base':'Lj','letters':'\u01C8'}, 35 | {'base':'M', 'letters':'\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C'}, 36 | {'base':'N', 'letters':'\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4'}, 37 | {'base':'NJ','letters':'\u01CA'}, 38 | {'base':'Nj','letters':'\u01CB'}, 39 | {'base':'O', 'letters':'\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C'}, 40 | {'base':'OI','letters':'\u01A2'}, 41 | {'base':'OO','letters':'\uA74E'}, 42 | {'base':'OU','letters':'\u0222'}, 43 | {'base':'OE','letters':'\u008C\u0152'}, 44 | {'base':'oe','letters':'\u009C\u0153'}, 45 | {'base':'P', 'letters':'\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754'}, 46 | {'base':'Q', 'letters':'\u0051\u24C6\uFF31\uA756\uA758\u024A'}, 47 | {'base':'R', 'letters':'\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782'}, 48 | {'base':'S', 'letters':'\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784'}, 49 | {'base':'T', 'letters':'\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786'}, 50 | {'base':'TZ','letters':'\uA728'}, 51 | {'base':'U', 'letters':'\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244'}, 52 | {'base':'V', 'letters':'\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245'}, 53 | {'base':'VY','letters':'\uA760'}, 54 | {'base':'W', 'letters':'\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72'}, 55 | {'base':'X', 'letters':'\u0058\u24CD\uFF38\u1E8A\u1E8C'}, 56 | {'base':'Y', 'letters':'\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE'}, 57 | {'base':'Z', 'letters':'\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762'}, 58 | {'base':'a', 'letters':'\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250'}, 59 | {'base':'aa','letters':'\uA733'}, 60 | {'base':'ae','letters':'\u00E6\u01FD\u01E3'}, 61 | {'base':'ao','letters':'\uA735'}, 62 | {'base':'au','letters':'\uA737'}, 63 | {'base':'av','letters':'\uA739\uA73B'}, 64 | {'base':'ay','letters':'\uA73D'}, 65 | {'base':'b', 'letters':'\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253'}, 66 | {'base':'c', 'letters':'\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184'}, 67 | {'base':'d', 'letters':'\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A'}, 68 | {'base':'dz','letters':'\u01F3\u01C6'}, 69 | {'base':'e', 'letters':'\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD'}, 70 | {'base':'f', 'letters':'\u0066\u24D5\uFF46\u1E1F\u0192\uA77C'}, 71 | {'base':'g', 'letters':'\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F'}, 72 | {'base':'h', 'letters':'\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265'}, 73 | {'base':'hv','letters':'\u0195'}, 74 | {'base':'i', 'letters':'\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131'}, 75 | {'base':'j', 'letters':'\u006A\u24D9\uFF4A\u0135\u01F0\u0249'}, 76 | {'base':'k', 'letters':'\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3'}, 77 | {'base':'l', 'letters':'\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747'}, 78 | {'base':'lj','letters':'\u01C9'}, 79 | {'base':'m', 'letters':'\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F'}, 80 | {'base':'n', 'letters':'\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5'}, 81 | {'base':'nj','letters':'\u01CC'}, 82 | {'base':'o', 'letters':'\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275'}, 83 | {'base':'oi','letters':'\u01A3'}, 84 | {'base':'ou','letters':'\u0223'}, 85 | {'base':'oo','letters':'\uA74F'}, 86 | {'base':'p','letters':'\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755'}, 87 | {'base':'q','letters':'\u0071\u24E0\uFF51\u024B\uA757\uA759'}, 88 | {'base':'r','letters':'\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783'}, 89 | {'base':'s','letters':'\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B'}, 90 | {'base':'t','letters':'\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787'}, 91 | {'base':'tz','letters':'\uA729'}, 92 | {'base':'u','letters': '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289'}, 93 | {'base':'v','letters':'\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C'}, 94 | {'base':'vy','letters':'\uA761'}, 95 | {'base':'w','letters':'\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73'}, 96 | {'base':'x','letters':'\u0078\u24E7\uFF58\u1E8B\u1E8D'}, 97 | {'base':'y','letters':'\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF'}, 98 | {'base':'z','letters':'\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763'} 99 | ]; 100 | 101 | var diacriticsMap = {}; 102 | 103 | for (var i=0; i < defaultDiacriticsRemovalap.length; i++){ 104 | var letters = defaultDiacriticsRemovalap[i].letters.split(""); 105 | for (var j=0; j < letters.length ; j++){ 106 | diacriticsMap[letters[j]] = defaultDiacriticsRemovalap[i].base; 107 | } 108 | } 109 | 110 | sanitize.removeDiacritics = function(str) { 111 | return str.replace(/[^\u0000-\u007E]/g, function(a){ 112 | return diacriticsMap[a] || a; 113 | }); 114 | }; 115 | 116 | sanitize.makeUrlSafe = function(str) { 117 | var ret = str || ''; 118 | ret = ret.toLowerCase() 119 | .replace(/ /g, '_') 120 | .replace(/(\")/g,'') 121 | .replace(/(\')/g,'') 122 | .replace(/(\()(.*?)(\))/g, '$2') 123 | .replace(/(<)(.*?)(>)/g, '_$2_') 124 | .replace('/', '_') 125 | .replace('#', '_') 126 | .replace('@', '_') 127 | .replace('(', '') 128 | .replace(')', '') 129 | .replace('=', ''); 130 | ret = sanitize.removeDiacritics(ret); 131 | return ret; 132 | }; 133 | 134 | sanitize.makeSafeForCSS = function(name) { 135 | name = name.replace(/[\@\(\)]/gi, ''); 136 | 137 | return name.replace(/<\/?[a-z][a-z0-9]*[^<>]*>|/gi, function(s) { 138 | return '_'; 139 | }); 140 | }; 141 | 142 | //Unnest Arrays 143 | sanitize.unnest = function(array, levels) { 144 | levels = levels || 1; 145 | var empty = []; 146 | 147 | array = empty.concat.apply(empty, array); 148 | 149 | if (levels && levels - 1) { 150 | return sanitize.unnest(array, levels - 1); 151 | } 152 | 153 | return array; 154 | }; 155 | 156 | /** 157 | * Allow Objects to to use array-like push 158 | */ 159 | sanitize.objectPush = function (obj, elem) { 160 | [].push.call(obj, elem); 161 | }; 162 | 163 | /** 164 | * Add wrapAll functionality to cheerio 165 | */ 166 | sanitize.cheerioWrapAll = function($){ 167 | _.extend($.prototype, { 168 | wrapAll: function(wrapper) { 169 | if (this.length < 1) { 170 | return this; 171 | } 172 | 173 | if (this.length < 2 && this.wrap) { // wrap not defined in npm version, 174 | return this.wrap(wrapper); // and git version fails testing. 175 | } 176 | 177 | var elems = this; 178 | var section = $(wrapper); 179 | var marker = $('
      '); 180 | marker = marker.insertBefore(elems.first()); // in jQuery marker would remain current 181 | elems.each(function(k, v) { // in Cheerio, we update with the output. 182 | section.append($(v)); 183 | }); 184 | section.insertBefore(marker); 185 | marker.remove(); 186 | return section; // This is what jQuery would return, IIRC. 187 | }, 188 | }); 189 | }; 190 | 191 | sanitize.path = function(from, to){ 192 | return path.relative(from, path.join(__dirname, '../', to)); 193 | }; 194 | 195 | module.exports = sanitize; 196 | -------------------------------------------------------------------------------- /test/config-file/test.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | font-family: 'Roboto'; 4 | } 5 | 6 | code, pre { 7 | font-family: 'Roboto Mono'; 8 | font-size: .95em; 9 | } 10 | 11 | h1 { 12 | font-size: 1.75em; 13 | font-weight: normal; 14 | } 15 | 16 | h2 { 17 | font-size: 1.5em; 18 | } 19 | 20 | h1, 21 | h2, 22 | h3, 23 | h4, 24 | h5, 25 | h6, 26 | p, 27 | ul, 28 | ol { 29 | margin: 0 0 1.5rem; 30 | } 31 | 32 | p { 33 | line-height: 1.5rem; 34 | } 35 | 36 | /* SG 37 | # Sass:Config/Typography Settings 38 | 39 | 40 | ## `$$base-font-size` - Pixel 41 | Font size all other values will use for calculation. Will be converted to `rems`. 42 | 43 | ## `$$base-line-height` - Pixel 44 | Line-height for base font-size. Will be used to determine the vertical spacing values when `type-space()` is used. 45 | 46 | ## `$$font-looseness` - Percentage 47 | What to base the tightness/looseness of automatically-generated `line-heights`. 48 | 49 | ## `$$auto-scale-type` - Boolean 50 | Uses `$$scale-ratio` to create font sizes for headings starting from the `$$base-font-size`. 51 | 52 | ## `$$scale-ratio` - Number (float) 53 | Used for `$$auto-scale-type` and for `modular-scale()`. 54 | 55 | ## `$$rem-px-fallback` - Boolean 56 | Determines whether `rems()` conversion includes a pixel-value fallback for older browsers like IE8. 57 | 58 | */ 59 | 60 | /* SG 61 | # Sass:Config/Grid Settings 62 | 63 | ## `$$max-site-width` - Pixel 64 | Maximum desktop width for the site wrapper. 65 | 66 | ## `$$grid-gutter` - Pixel 67 | Space between grid items. Also used in `type-space()` for horizontal spacing units. 68 | */ 69 | 70 | /* SG 71 | 72 | ## `$$max-site-width` - Pixel 73 | Maximum desktop width for the site wrapper. 74 | 75 | ## `$$grid-columns` - Number 76 | Base number of grid columns to be generated. Note that any column number will include all fractions of the fewer columns. For instance, a 12-column grid would also include all fractions of 1-12 (including things like three-sevenths). 77 | 78 | ## `$$grid-gutter` - Pixel 79 | Space between grid items. Also used in `type-space()` for horizontal spacing units. 80 | */ 81 | 82 | 83 | /* SG 84 | # Lists 85 | 86 | A list without a class is rendered normally. All lists can be given a class that will modify how its children will be styled. 87 | 88 | ```html_example 89 |
        90 |
      • List Item 91 |
          92 |
        • Nested List Item
        • 93 |
        94 |
      • 95 |
      • List Item
      • 96 |
      • List Item
      • 97 |
      98 | ``` 99 | 100 | */ 101 | 102 | 103 | /* SG 104 | # Lists/Bordered Lists 105 | 106 | @priority last 107 | 108 | Creates a list with borders above and below each list item. 109 | 110 | ```html_example 111 |
        112 |
      • List Item
      • 113 |
      • List Item
      • 114 |
      • List Item
      • 115 |
      116 | ``` 117 | */ 118 | 119 | ul { 120 | margin: 0; 121 | } 122 | 123 | [class*="list_bordered"] { 124 | list-style: none; 125 | margin-left: 0; 126 | padding-left: 0; 127 | margin-bottom: .75rem; 128 | } 129 | 130 | .list_bordered { 131 | padding-top: 24px; 132 | padding-top: 1.5rem; 133 | padding-bottom: 24px; 134 | padding-bottom: 1.5rem; 135 | } 136 | 137 | [class*="list_bordered"] > li { 138 | list-style-image: none; 139 | list-style-type: none; 140 | margin-left: 0; 141 | } 142 | 143 | [class*="list_bordered"] > li { 144 | border-top: 1px solid #e4eaf3; 145 | padding-top: 24px; 146 | padding-top: 1.5rem; 147 | padding-bottom: 24px; 148 | padding-bottom: 1.5rem; 149 | margin-bottom: -1px; 150 | } 151 | 152 | [class*="list_bordered"] > li:first-child { 153 | border-top-color: transparent; 154 | } 155 | 156 | /* SG 157 | 158 | Appending `list_bordered` with `--short` will reduce the padding between each item. 159 | 160 | ```html_example 161 |
        162 |
      • List Item
      • 163 |
      • List Item
      • 164 |
      • List Item
      • 165 |
      166 | ``` 167 | */ 168 | 169 | .list_bordered--short > li { 170 | padding-top: 12px; 171 | padding-top: 0.75rem; 172 | padding-bottom: 12px; 173 | padding-bottom: 0.75rem; 174 | } 175 | 176 | /* SG 177 | # Lists/Inline Lists 178 | 179 | @priority 1 180 | 181 | A list where each item is in a row, with spacing to the right of each item. 182 | 183 | ```html_example 184 |
        185 |
      • List Item
      • 186 |
      • List Item
      • 187 |
      • List Item
      • 188 |
      189 | ``` 190 | */ 191 | .list_inline { 192 | list-style: none; 193 | margin-left: 0; 194 | padding-left: 0; 195 | } 196 | 197 | .list_inline > li { 198 | list-style-image: none; 199 | list-style-type: none; 200 | margin-left: 0; 201 | } 202 | 203 | .list_inline > li { 204 | display: inline-block; 205 | padding-left: 0; 206 | width: auto; 207 | vertical-align: middle; 208 | padding-right: 16px; 209 | padding-right: 1rem; 210 | } 211 | 212 | .list_inline > li:last-child { 213 | padding-right: 0; 214 | } 215 | 216 | /* SG 217 | # Lists/Breadcrumbs 218 | 219 | @priority 3 220 | 221 | A list where each item is in a row, with a ▸ between each item. 222 | 223 | ```html_example 224 | 229 | ``` 230 | */ 231 | .breadcrumbs { 232 | list-style: none; 233 | margin-left: 0; 234 | padding-left: 0; 235 | padding-right: 32px; 236 | padding-right: 2rem; 237 | } 238 | 239 | .breadcrumbs > li { 240 | list-style-image: none; 241 | list-style-type: none; 242 | margin-left: 0; 243 | } 244 | 245 | .breadcrumbs > li, 246 | .breadcrumb { 247 | display: inline-block; 248 | white-space: nowrap; 249 | margin-left: 0; 250 | } 251 | 252 | .breadcrumbs > li a, 253 | .breadcrumb a { 254 | display: block; 255 | } 256 | 257 | .breadcrumbs > li:after, 258 | .breadcrumb:after { 259 | content: "\25B8"; 260 | display: inline-block; 261 | } 262 | 263 | /* SG 264 | # Lists/Navigation List 265 | 266 | @priority last 267 | 268 | List where anchor tags fill the space of their containers. Useful as a modifier class. Can be used on any item with multiple child anchors (doesn't have to be an `ol` or `ul`). 269 | 270 | ```html_example 271 | 282 | ``` 283 | */ 284 | .list_nav { 285 | list-style: none; 286 | margin-left: 0; 287 | padding-left: 0; 288 | } 289 | 290 | .list_nav > li { 291 | list-style-image: none; 292 | list-style-type: none; 293 | margin-left: 0; 294 | } 295 | 296 | .list_nav a { 297 | padding-top: 12px; 298 | padding-top: 0.75rem; 299 | padding-bottom: 12px; 300 | padding-bottom: 0.75rem; 301 | } 302 | 303 | /* SG 304 | @category Layout 305 | @title Media Object 306 | 307 | Isolates an image from text wrapping underneath. Useful for creating an association between an image and text. Often used with an icon or avatar. Adding other classes to the `isolate_body` can create a more stylized version. 308 | 309 | ```html_example 310 |
      311 | placeholder image 312 |

      313 | Content that sits to the right of the image but will never wrap underneath the image to the left. Etiam porta sem malesuada magna mollis euismod. Cras justo odio, dapibus ac facilisis in, egestas eget quam. 314 |

      315 |
      316 | ``` 317 | */ 318 | 319 | .media { 320 | overflow: hidden; 321 | margin-bottom: 1.5rem; 322 | } 323 | 324 | .media__media, 325 | .media__body { 326 | overflow: hidden; 327 | _overflow: visible; 328 | zoom: 1; 329 | } 330 | 331 | .media__body { 332 | padding-left: 1.5rem; 333 | } 334 | 335 | .media__media { 336 | float: left; 337 | } 338 | 339 | /* SG 340 | # Layout/Arrangement object 341 | 342 | Creates an image-content block that vertically aligns images and text (centered, bottom, or top). 343 | 344 | **Children of the `arrange` wrapper can be given four classes:** 345 | * `arrange__fit` or `arrange__media` will create a block that fits the width of its content (useful for images and media). 346 | * `arrange__fill` will fill the remaining space. 347 | * `arrange__body` is similar to `arrange__fill` but defaults to middle alignment. 348 | 349 | ```html_example 350 | 351 |
      352 |
      353 | placeholder image 354 |
      355 |
      356 | Content that is vertically (middle) aligned with the image. 357 |
      358 |
      359 | ``` 360 | */ 361 | 362 | 363 | .arrange { 364 | display: table; 365 | table-layout: auto; 366 | min-width: 100%; 367 | margin-bottom: 1.5rem; 368 | } 369 | 370 | .arrange__media img, 371 | .arrange__fit img { 372 | display: block; 373 | max-width: none; 374 | margin: auto; 375 | } 376 | 377 | .arrange__body { 378 | padding-left: 1.5rem; 379 | } 380 | 381 | .arrange__body, 382 | .arrange__fill { 383 | width: 100%; 384 | } 385 | 386 | .arrange__body, 387 | .arrange__fill, 388 | .arrange__fit, 389 | .arrange__media { 390 | display: table-cell; 391 | } 392 | 393 | .arrange__media, 394 | .arrange__body, 395 | .arrange__fill, 396 | .arrange__fit { 397 | vertical-align: middle; 398 | } 399 | 400 | .arrange__top > .arrange__media, 401 | .arrange__top > .arrange__body, 402 | .arrange__top > .arrange__fill, 403 | .arrange__top > .arrange__fit { 404 | vertical-align: top; 405 | } 406 | 407 | /* SG 408 | 409 | ```html_example 410 | 411 |
      412 |
      413 | placeholder image 414 |
      415 |
      416 | Content that is bottom aligned to the image. 417 |
      418 |
      419 | ``` 420 | */ 421 | 422 | .arrange--bottom > .arrange__media, 423 | .arrange--bottom > .arrange__body, 424 | .arrange--bottom > .arrange__fill, 425 | .arrange--bottom > .arrange__fit { 426 | vertical-align: bottom; 427 | } 428 | 429 | /* SG 430 | 431 | ```html_example 432 | 433 |
      434 |
      435 | Equal width columns. 436 |
      437 |
      438 | placeholder image 439 |
      440 |
      441 | Can be as many columns as you want. 442 |
      443 |
      444 | 445 | ``` 446 | */ 447 | 448 | 449 | .arrange--equal { 450 | table-layout: fixed; 451 | } 452 | 453 | .arrange--equal > .arrange__fill, 454 | .arrange--equal > .arrange__fit { 455 | width: 1%; 456 | } 457 | 458 | 459 | 460 | /* SG 461 | # Sass:Colors 462 | 463 | Colors can be defined in the `$$base-colors` variable and referenced via the `colors()` function. 464 | 465 | */ 466 | 467 | /* SG 468 | # Sass:Colors/Definition 469 | 470 | ## `$$base-colors` 471 | ### Map (key : color value) 472 | Sets up consistent color names to be used for color-palette. Dark and light values will be automatically generated. Key values should be accessed through `colors()`. 473 | `type`, `links`, and `bg` key values are required for some starter styles. If you choose not to use them, you need to replace their references. 474 | 475 | Supports a nested map style like the following: 476 | ```scss 477 | $base-colors: ( 478 | 'type': ( 479 | 'base': #020202, 480 | 'light': #232323, 481 | 'dark': #000 482 | ), 483 | 'links': ( 484 | 'base': blue, 485 | 'light': sky, 486 | 'dark': navy 487 | ), 488 | 'bg': ( 489 | 'base': #fff, 490 | 'dark': #ddd 491 | ) 492 | ); 493 | ``` 494 | */ 495 | 496 | /* SG 497 | # Colors/Lookup 498 | 499 | @section sass 500 | @file tools/_t-color-functions.scss 501 | 502 | ## `colors()` 503 | ### function( `$color-name, $tone: 'base', $opacity: 1` ) 504 | Get a color value from the global `$$base-colors` map. Darker and lighter tones are available by passing a second string. 505 | 506 | ```scss 507 | .foo { 508 | background-color: colors(links, light)); 509 | } 510 | ``` 511 | 512 | Passing only a color name will default to the 'base' color. 513 | 514 | @alias color() 515 | @requires $$base-colors 516 | @returns color 517 | 518 | [Reference](http://blog.12spokes.com/web-design-development/simple-css-color-management-with-sass/) 519 | */ 520 | 521 | 522 | /* SG 523 | # Sass:Colors/Manipulation 524 | 525 | @file tools/_t-color-functions.scss 526 | 527 | ## `generate-color-varations()` 528 | ### function( `$map, $functions, $increments, $variations, $blending-colors` ) 529 | 530 | Takes base color values and generates a full color palette. Used by the `$$base-colors` map to create a project's palette, accessible via `colors()`. 531 | 532 | **Arguments:** 533 | * `$map`: Color map you want to create variations of. Defaults to `$$base-colors`. 534 | * `$functions`: color functions used to generate variations (e.g. lighten or darken). Can use any `blend` function, provided `$blending-colors` are provided. 535 | * `$increments`: percentage amount to apply `$function` to each `$variations`. 536 | * `$variations`: actual names for each color tone when `colors()` used. 537 | * `$blending-colors`: used when a function is a `blend`. Can be a list or a single color. 538 | 539 | @requires `combine-color-maps()` 540 | 541 | */ 542 | -------------------------------------------------------------------------------- /test/config-file/test.scss: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | font-family: 'Roboto'; 4 | } 5 | 6 | code, pre { 7 | font-family: 'Roboto Mono'; 8 | font-size: .95em; 9 | } 10 | 11 | h1 { 12 | font-size: 1.75em; 13 | font-weight: normal; 14 | } 15 | 16 | h2 { 17 | font-size: 1.5em; 18 | } 19 | 20 | h1, 21 | h2, 22 | h3, 23 | h4, 24 | h5, 25 | h6, 26 | p, 27 | ul, 28 | ol { 29 | margin: 0 0 1.5rem; 30 | } 31 | 32 | p { 33 | line-height: 1.5rem; 34 | } 35 | 36 | /* SG 37 | # Sass:Config/Typography Settings 38 | 39 | 40 | ## `$$base-font-size` - Pixel 41 | Font size all other values will use for calculation. Will be converted to `rems`. 42 | 43 | ## `$$base-line-height` - Pixel 44 | Line-height for base font-size. Will be used to determine the vertical spacing values when `type-space()` is used. 45 | 46 | ## `$$font-looseness` - Percentage 47 | What to base the tightness/looseness of automatically-generated `line-heights`. 48 | 49 | ## `$$auto-scale-type` - Boolean 50 | Uses `$$scale-ratio` to create font sizes for headings starting from the `$$base-font-size`. 51 | 52 | ## `$$scale-ratio` - Number (float) 53 | Used for `$$auto-scale-type` and for `modular-scale()`. 54 | 55 | ## `$$rem-px-fallback` - Boolean 56 | Determines whether `rems()` conversion includes a pixel-value fallback for older browsers like IE8. 57 | 58 | */ 59 | 60 | /* SG 61 | # Sass:Config/Grid Settings 62 | 63 | ## `$$max-site-width` - Pixel 64 | Maximum desktop width for the site wrapper. 65 | 66 | ## `$$grid-gutter` - Pixel 67 | Space between grid items. Also used in `type-space()` for horizontal spacing units. 68 | */ 69 | 70 | /* SG 71 | 72 | ## `$$max-site-width` - Pixel 73 | Maximum desktop width for the site wrapper. 74 | 75 | ## `$$grid-columns` - Number 76 | Base number of grid columns to be generated. Note that any column number will include all fractions of the fewer columns. For instance, a 12-column grid would also include all fractions of 1-12 (including things like three-sevenths). 77 | 78 | ## `$$grid-gutter` - Pixel 79 | Space between grid items. Also used in `type-space()` for horizontal spacing units. 80 | */ 81 | 82 | 83 | /* SG 84 | # Lists 85 | 86 | A list without a class is rendered normally. All lists can be given a class that will modify how its children will be styled. 87 | 88 | ```html_example 89 |
        90 |
      • List Item 91 |
          92 |
        • Nested List Item
        • 93 |
        94 |
      • 95 |
      • List Item
      • 96 |
      • List Item
      • 97 |
      98 | ``` 99 | 100 | */ 101 | 102 | 103 | /* SG 104 | # Lists/Bordered Lists 105 | 106 | @priority last 107 | 108 | Creates a list with borders above and below each list item. 109 | 110 | ```html_example 111 |
        112 |
      • List Item
      • 113 |
      • List Item
      • 114 |
      • List Item
      • 115 |
      116 | ``` 117 | */ 118 | 119 | ul { 120 | margin: 0; 121 | } 122 | 123 | [class*="list_bordered"] { 124 | list-style: none; 125 | margin-left: 0; 126 | padding-left: 0; 127 | margin-bottom: .75rem; 128 | } 129 | 130 | .list_bordered { 131 | padding-top: 24px; 132 | padding-top: 1.5rem; 133 | padding-bottom: 24px; 134 | padding-bottom: 1.5rem; 135 | } 136 | 137 | [class*="list_bordered"] > li { 138 | list-style-image: none; 139 | list-style-type: none; 140 | margin-left: 0; 141 | } 142 | 143 | [class*="list_bordered"] > li { 144 | border-top: 1px solid #e4eaf3; 145 | padding-top: 24px; 146 | padding-top: 1.5rem; 147 | padding-bottom: 24px; 148 | padding-bottom: 1.5rem; 149 | margin-bottom: -1px; 150 | } 151 | 152 | [class*="list_bordered"] > li:first-child { 153 | border-top-color: transparent; 154 | } 155 | 156 | /* SG 157 | 158 | Appending `list_bordered` with `--short` will reduce the padding between each item. 159 | 160 | ```html_example 161 |
        162 |
      • List Item
      • 163 |
      • List Item
      • 164 |
      • List Item
      • 165 |
      166 | ``` 167 | */ 168 | 169 | .list_bordered--short > li { 170 | padding-top: 12px; 171 | padding-top: 0.75rem; 172 | padding-bottom: 12px; 173 | padding-bottom: 0.75rem; 174 | } 175 | 176 | /* SG 177 | # Lists/Inline Lists 178 | 179 | @priority 1 180 | 181 | A list where each item is in a row, with spacing to the right of each item. 182 | 183 | ```html_example 184 |
        185 |
      • List Item
      • 186 |
      • List Item
      • 187 |
      • List Item
      • 188 |
      189 | ``` 190 | */ 191 | .list_inline { 192 | list-style: none; 193 | margin-left: 0; 194 | padding-left: 0; 195 | } 196 | 197 | .list_inline > li { 198 | list-style-image: none; 199 | list-style-type: none; 200 | margin-left: 0; 201 | } 202 | 203 | .list_inline > li { 204 | display: inline-block; 205 | padding-left: 0; 206 | width: auto; 207 | vertical-align: middle; 208 | padding-right: 16px; 209 | padding-right: 1rem; 210 | } 211 | 212 | .list_inline > li:last-child { 213 | padding-right: 0; 214 | } 215 | 216 | /* SG 217 | # Lists/Breadcrumbs 218 | 219 | @priority 3 220 | 221 | A list where each item is in a row, with a ▸ between each item. 222 | 223 | ```html_example 224 | 229 | ``` 230 | */ 231 | .breadcrumbs { 232 | list-style: none; 233 | margin-left: 0; 234 | padding-left: 0; 235 | padding-right: 32px; 236 | padding-right: 2rem; 237 | } 238 | 239 | .breadcrumbs > li { 240 | list-style-image: none; 241 | list-style-type: none; 242 | margin-left: 0; 243 | } 244 | 245 | .breadcrumbs > li, 246 | .breadcrumb { 247 | display: inline-block; 248 | white-space: nowrap; 249 | margin-left: 0; 250 | } 251 | 252 | .breadcrumbs > li a, 253 | .breadcrumb a { 254 | display: block; 255 | } 256 | 257 | .breadcrumbs > li:after, 258 | .breadcrumb:after { 259 | content: "\25B8"; 260 | display: inline-block; 261 | } 262 | 263 | /* SG 264 | # Lists/Navigation List 265 | 266 | @priority last 267 | 268 | List where anchor tags fill the space of their containers. Useful as a modifier class. Can be used on any item with multiple child anchors (doesn't have to be an `ol` or `ul`). 269 | 270 | ```html_example 271 | 282 | ``` 283 | */ 284 | .list_nav { 285 | list-style: none; 286 | margin-left: 0; 287 | padding-left: 0; 288 | } 289 | 290 | .list_nav > li { 291 | list-style-image: none; 292 | list-style-type: none; 293 | margin-left: 0; 294 | } 295 | 296 | .list_nav a { 297 | padding-top: 12px; 298 | padding-top: 0.75rem; 299 | padding-bottom: 12px; 300 | padding-bottom: 0.75rem; 301 | } 302 | 303 | /* SG 304 | @category Layout 305 | @title Media Object 306 | 307 | Isolates an image from text wrapping underneath. Useful for creating an association between an image and text. Often used with an icon or avatar. Adding other classes to the `isolate_body` can create a more stylized version. 308 | 309 | ```html_example 310 |
      311 | placeholder image 312 |

      313 | Content that sits to the right of the image but will never wrap underneath the image to the left. Etiam porta sem malesuada magna mollis euismod. Cras justo odio, dapibus ac facilisis in, egestas eget quam. 314 |

      315 |
      316 | ``` 317 | */ 318 | 319 | .media { 320 | overflow: hidden; 321 | margin-bottom: 1.5rem; 322 | } 323 | 324 | .media__media, 325 | .media__body { 326 | overflow: hidden; 327 | _overflow: visible; 328 | zoom: 1; 329 | } 330 | 331 | .media__body { 332 | padding-left: 1.5rem; 333 | } 334 | 335 | .media__media { 336 | float: left; 337 | } 338 | 339 | /* SG 340 | # Layout/Arrangement object 341 | 342 | Creates an image-content block that vertically aligns images and text (centered, bottom, or top). 343 | 344 | **Children of the `arrange` wrapper can be given four classes:** 345 | * `arrange__fit` or `arrange__media` will create a block that fits the width of its content (useful for images and media). 346 | * `arrange__fill` will fill the remaining space. 347 | * `arrange__body` is similar to `arrange__fill` but defaults to middle alignment. 348 | 349 | ```html_example 350 | 351 |
      352 |
      353 | placeholder image 354 |
      355 |
      356 | Content that is vertically (middle) aligned with the image. 357 |
      358 |
      359 | ``` 360 | */ 361 | 362 | 363 | .arrange { 364 | display: table; 365 | table-layout: auto; 366 | min-width: 100%; 367 | margin-bottom: 1.5rem; 368 | } 369 | 370 | .arrange__media img, 371 | .arrange__fit img { 372 | display: block; 373 | max-width: none; 374 | margin: auto; 375 | } 376 | 377 | .arrange__body { 378 | padding-left: 1.5rem; 379 | } 380 | 381 | .arrange__body, 382 | .arrange__fill { 383 | width: 100%; 384 | } 385 | 386 | .arrange__body, 387 | .arrange__fill, 388 | .arrange__fit, 389 | .arrange__media { 390 | display: table-cell; 391 | } 392 | 393 | .arrange__media, 394 | .arrange__body, 395 | .arrange__fill, 396 | .arrange__fit { 397 | vertical-align: middle; 398 | } 399 | 400 | .arrange__top > .arrange__media, 401 | .arrange__top > .arrange__body, 402 | .arrange__top > .arrange__fill, 403 | .arrange__top > .arrange__fit { 404 | vertical-align: top; 405 | } 406 | 407 | /* SG 408 | 409 | ```html_example 410 | 411 |
      412 |
      413 | placeholder image 414 |
      415 |
      416 | Content that is bottom aligned to the image. 417 |
      418 |
      419 | ``` 420 | */ 421 | 422 | .arrange--bottom > .arrange__media, 423 | .arrange--bottom > .arrange__body, 424 | .arrange--bottom > .arrange__fill, 425 | .arrange--bottom > .arrange__fit { 426 | vertical-align: bottom; 427 | } 428 | 429 | /* SG 430 | 431 | ```html_example 432 | 433 |
      434 |
      435 | Equal width columns. 436 |
      437 |
      438 | placeholder image 439 |
      440 |
      441 | Can be as many columns as you want. 442 |
      443 |
      444 | 445 | ``` 446 | */ 447 | 448 | 449 | .arrange--equal { 450 | table-layout: fixed; 451 | } 452 | 453 | .arrange--equal > .arrange__fill, 454 | .arrange--equal > .arrange__fit { 455 | width: 1%; 456 | } 457 | 458 | 459 | 460 | /* SG 461 | # Sass:Colors 462 | 463 | Colors can be defined in the `$$base-colors` variable and referenced via the `colors()` function. 464 | 465 | */ 466 | 467 | /* SG 468 | # Sass:Colors/Definition 469 | 470 | ## `$$base-colors` 471 | ### Map (key : color value) 472 | Sets up consistent color names to be used for color-palette. Dark and light values will be automatically generated. Key values should be accessed through `colors()`. 473 | `type`, `links`, and `bg` key values are required for some starter styles. If you choose not to use them, you need to replace their references. 474 | 475 | Supports a nested map style like the following: 476 | ```scss 477 | $base-colors: ( 478 | 'type': ( 479 | 'base': #020202, 480 | 'light': #232323, 481 | 'dark': #000 482 | ), 483 | 'links': ( 484 | 'base': blue, 485 | 'light': sky, 486 | 'dark': navy 487 | ), 488 | 'bg': ( 489 | 'base': #fff, 490 | 'dark': #ddd 491 | ) 492 | ); 493 | ``` 494 | */ 495 | 496 | /* SG 497 | # Colors/Lookup 498 | 499 | @section sass 500 | @file tools/_t-color-functions.scss 501 | 502 | ## `colors()` 503 | ### function( `$color-name, $tone: 'base', $opacity: 1` ) 504 | Get a color value from the global `$$base-colors` map. Darker and lighter tones are available by passing a second string. 505 | 506 | ```scss 507 | .foo { 508 | background-color: colors(links, light)); 509 | } 510 | ``` 511 | 512 | Passing only a color name will default to the 'base' color. 513 | 514 | @alias color() 515 | @requires $$base-colors 516 | @returns color 517 | 518 | [Reference](http://blog.12spokes.com/web-design-development/simple-css-color-management-with-sass/) 519 | */ 520 | 521 | 522 | /* SG 523 | # Sass:Colors/Manipulation 524 | 525 | @file tools/_t-color-functions.scss 526 | 527 | ## `generate-color-varations()` 528 | ### function( `$map, $functions, $increments, $variations, $blending-colors` ) 529 | 530 | Takes base color values and generates a full color palette. Used by the `$$base-colors` map to create a project's palette, accessible via `colors()`. 531 | 532 | **Arguments:** 533 | * `$map`: Color map you want to create variations of. Defaults to `$$base-colors`. 534 | * `$functions`: color functions used to generate variations (e.g. lighten or darken). Can use any `blend` function, provided `$blending-colors` are provided. 535 | * `$increments`: percentage amount to apply `$function` to each `$variations`. 536 | * `$variations`: actual names for each color tone when `colors()` used. 537 | * `$blending-colors`: used when a function is a `blend`. Can be a list or a single color. 538 | 539 | @requires `combine-color-maps()` 540 | 541 | */ 542 | -------------------------------------------------------------------------------- /test/imported/test/test.scss: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | font-family: 'Roboto'; 4 | } 5 | 6 | code, pre { 7 | font-family: 'Roboto Mono'; 8 | font-size: .95em; 9 | } 10 | 11 | h1 { 12 | font-size: 1.75em; 13 | font-weight: normal; 14 | } 15 | 16 | h2 { 17 | font-size: 1.5em; 18 | } 19 | 20 | h1, 21 | h2, 22 | h3, 23 | h4, 24 | h5, 25 | h6, 26 | p, 27 | ul, 28 | ol { 29 | margin: 0 0 1.5rem; 30 | } 31 | 32 | p { 33 | line-height: 1.5rem; 34 | } 35 | 36 | /* SG 37 | # Sass:Config/Typography Settings 38 | 39 | 40 | ## `$$base-font-size` - Pixel 41 | Font size all other values will use for calculation. Will be converted to `rems`. 42 | 43 | ## `$$base-line-height` - Pixel 44 | Line-height for base font-size. Will be used to determine the vertical spacing values when `type-space()` is used. 45 | 46 | ## `$$font-looseness` - Percentage 47 | What to base the tightness/looseness of automatically-generated `line-heights`. 48 | 49 | ## `$$auto-scale-type` - Boolean 50 | Uses `$$scale-ratio` to create font sizes for headings starting from the `$$base-font-size`. 51 | 52 | ## `$$scale-ratio` - Number (float) 53 | Used for `$$auto-scale-type` and for `modular-scale()`. 54 | 55 | ## `$$rem-px-fallback` - Boolean 56 | Determines whether `rems()` conversion includes a pixel-value fallback for older browsers like IE8. 57 | 58 | */ 59 | 60 | /* SG 61 | # Sass:Config/Grid Settings 62 | 63 | ## `$$max-site-width` - Pixel 64 | Maximum desktop width for the site wrapper. 65 | 66 | ## `$$grid-gutter` - Pixel 67 | Space between grid items. Also used in `type-space()` for horizontal spacing units. 68 | */ 69 | 70 | /* SG 71 | 72 | ## `$$max-site-width` - Pixel 73 | Maximum desktop width for the site wrapper. 74 | 75 | ## `$$grid-columns` - Number 76 | Base number of grid columns to be generated. Note that any column number will include all fractions of the fewer columns. For instance, a 12-column grid would also include all fractions of 1-12 (including things like three-sevenths). 77 | 78 | ## `$$grid-gutter` - Pixel 79 | Space between grid items. Also used in `type-space()` for horizontal spacing units. 80 | */ 81 | 82 | 83 | /* SG 84 | # Lists 85 | 86 | A list without a class is rendered normally. All lists can be given a class that will modify how its children will be styled. 87 | 88 | ```html_example 89 |
        90 |
      • List Item 91 |
          92 |
        • Nested List Item
        • 93 |
        94 |
      • 95 |
      • List Item
      • 96 |
      • List Item
      • 97 |
      98 | ``` 99 | 100 | */ 101 | 102 | 103 | /* SG 104 | # Lists/Bordered Lists 105 | 106 | @priority last 107 | 108 | Creates a list with borders above and below each list item. 109 | 110 | ```html_example 111 |
        112 |
      • List Item
      • 113 |
      • List Item
      • 114 |
      • List Item
      • 115 |
      116 | ``` 117 | */ 118 | 119 | ul { 120 | margin: 0; 121 | } 122 | 123 | [class*="list_bordered"] { 124 | list-style: none; 125 | margin-left: 0; 126 | padding-left: 0; 127 | margin-bottom: .75rem; 128 | } 129 | 130 | .list_bordered { 131 | padding-top: 24px; 132 | padding-top: 1.5rem; 133 | padding-bottom: 24px; 134 | padding-bottom: 1.5rem; 135 | } 136 | 137 | [class*="list_bordered"] > li { 138 | list-style-image: none; 139 | list-style-type: none; 140 | margin-left: 0; 141 | } 142 | 143 | [class*="list_bordered"] > li { 144 | border-top: 1px solid #e4eaf3; 145 | padding-top: 24px; 146 | padding-top: 1.5rem; 147 | padding-bottom: 24px; 148 | padding-bottom: 1.5rem; 149 | margin-bottom: -1px; 150 | } 151 | 152 | [class*="list_bordered"] > li:first-child { 153 | border-top-color: transparent; 154 | } 155 | 156 | /* SG 157 | 158 | Appending `list_bordered` with `--short` will reduce the padding between each item. 159 | 160 | ```html_example 161 |
        162 |
      • List Item
      • 163 |
      • List Item
      • 164 |
      • List Item
      • 165 |
      166 | ``` 167 | */ 168 | 169 | .list_bordered--short > li { 170 | padding-top: 12px; 171 | padding-top: 0.75rem; 172 | padding-bottom: 12px; 173 | padding-bottom: 0.75rem; 174 | } 175 | 176 | /* SG 177 | # Lists/Inline Lists 178 | 179 | @priority 1 180 | 181 | A list where each item is in a row, with spacing to the right of each item. 182 | 183 | ```html_example 184 |
        185 |
      • List Item
      • 186 |
      • List Item
      • 187 |
      • List Item
      • 188 |
      189 | ``` 190 | */ 191 | .list_inline { 192 | list-style: none; 193 | margin-left: 0; 194 | padding-left: 0; 195 | } 196 | 197 | .list_inline > li { 198 | list-style-image: none; 199 | list-style-type: none; 200 | margin-left: 0; 201 | } 202 | 203 | .list_inline > li { 204 | display: inline-block; 205 | padding-left: 0; 206 | width: auto; 207 | vertical-align: middle; 208 | padding-right: 16px; 209 | padding-right: 1rem; 210 | } 211 | 212 | .list_inline > li:last-child { 213 | padding-right: 0; 214 | } 215 | 216 | /* SG 217 | # Lists/Breadcrumbs 218 | 219 | @priority 3 220 | 221 | A list where each item is in a row, with a ▸ between each item. 222 | 223 | ```html_example 224 | 229 | ``` 230 | */ 231 | .breadcrumbs { 232 | list-style: none; 233 | margin-left: 0; 234 | padding-left: 0; 235 | padding-right: 32px; 236 | padding-right: 2rem; 237 | } 238 | 239 | .breadcrumbs > li { 240 | list-style-image: none; 241 | list-style-type: none; 242 | margin-left: 0; 243 | } 244 | 245 | .breadcrumbs > li, 246 | .breadcrumb { 247 | display: inline-block; 248 | white-space: nowrap; 249 | margin-left: 0; 250 | } 251 | 252 | .breadcrumbs > li a, 253 | .breadcrumb a { 254 | display: block; 255 | } 256 | 257 | .breadcrumbs > li:after, 258 | .breadcrumb:after { 259 | content: "\25B8"; 260 | display: inline-block; 261 | } 262 | 263 | /* SG 264 | # Lists/Navigation List 265 | 266 | @priority last 267 | 268 | List where anchor tags fill the space of their containers. Useful as a modifier class. Can be used on any item with multiple child anchors (doesn't have to be an `ol` or `ul`). 269 | 270 | ```html_example 271 | 282 | ``` 283 | */ 284 | .list_nav { 285 | list-style: none; 286 | margin-left: 0; 287 | padding-left: 0; 288 | } 289 | 290 | .list_nav > li { 291 | list-style-image: none; 292 | list-style-type: none; 293 | margin-left: 0; 294 | } 295 | 296 | .list_nav a { 297 | padding-top: 12px; 298 | padding-top: 0.75rem; 299 | padding-bottom: 12px; 300 | padding-bottom: 0.75rem; 301 | } 302 | 303 | /* SG 304 | @category Layout 305 | @title Media Object 306 | 307 | Isolates an image from text wrapping underneath. Useful for creating an association between an image and text. Often used with an icon or avatar. Adding other classes to the `isolate_body` can create a more stylized version. 308 | 309 | ```html_example 310 |
      311 | placeholder image 312 |

      313 | Content that sits to the right of the image but will never wrap underneath the image to the left. Etiam porta sem malesuada magna mollis euismod. Cras justo odio, dapibus ac facilisis in, egestas eget quam. 314 |

      315 |
      316 | ``` 317 | */ 318 | 319 | .media { 320 | overflow: hidden; 321 | margin-bottom: 1.5rem; 322 | } 323 | 324 | .media__media, 325 | .media__body { 326 | overflow: hidden; 327 | _overflow: visible; 328 | zoom: 1; 329 | } 330 | 331 | .media__body { 332 | padding-left: 1.5rem; 333 | } 334 | 335 | .media__media { 336 | float: left; 337 | } 338 | 339 | /* SG 340 | # Layout/Arrangement object 341 | 342 | Creates an image-content block that vertically aligns images and text (centered, bottom, or top). 343 | 344 | **Children of the `arrange` wrapper can be given four classes:** 345 | * `arrange__fit` or `arrange__media` will create a block that fits the width of its content (useful for images and media). 346 | * `arrange__fill` will fill the remaining space. 347 | * `arrange__body` is similar to `arrange__fill` but defaults to middle alignment. 348 | 349 | ```html_example 350 | 351 |
      352 |
      353 | placeholder image 354 |
      355 |
      356 | Content that is vertically (middle) aligned with the image. 357 |
      358 |
      359 | ``` 360 | */ 361 | 362 | 363 | .arrange { 364 | display: table; 365 | table-layout: auto; 366 | min-width: 100%; 367 | margin-bottom: 1.5rem; 368 | } 369 | 370 | .arrange__media img, 371 | .arrange__fit img { 372 | display: block; 373 | max-width: none; 374 | margin: auto; 375 | } 376 | 377 | .arrange__body { 378 | padding-left: 1.5rem; 379 | } 380 | 381 | .arrange__body, 382 | .arrange__fill { 383 | width: 100%; 384 | } 385 | 386 | .arrange__body, 387 | .arrange__fill, 388 | .arrange__fit, 389 | .arrange__media { 390 | display: table-cell; 391 | } 392 | 393 | .arrange__media, 394 | .arrange__body, 395 | .arrange__fill, 396 | .arrange__fit { 397 | vertical-align: middle; 398 | } 399 | 400 | .arrange__top > .arrange__media, 401 | .arrange__top > .arrange__body, 402 | .arrange__top > .arrange__fill, 403 | .arrange__top > .arrange__fit { 404 | vertical-align: top; 405 | } 406 | 407 | /* SG 408 | 409 | ```html_example 410 | 411 |
      412 |
      413 | placeholder image 414 |
      415 |
      416 | Content that is bottom aligned to the image. 417 |
      418 |
      419 | ``` 420 | */ 421 | 422 | .arrange--bottom > .arrange__media, 423 | .arrange--bottom > .arrange__body, 424 | .arrange--bottom > .arrange__fill, 425 | .arrange--bottom > .arrange__fit { 426 | vertical-align: bottom; 427 | } 428 | 429 | /* SG 430 | 431 | ```html_example 432 | 433 |
      434 |
      435 | Equal width columns. 436 |
      437 |
      438 | placeholder image 439 |
      440 |
      441 | Can be as many columns as you want. 442 |
      443 |
      444 | 445 | ``` 446 | */ 447 | 448 | 449 | .arrange--equal { 450 | table-layout: fixed; 451 | } 452 | 453 | .arrange--equal > .arrange__fill, 454 | .arrange--equal > .arrange__fit { 455 | width: 1%; 456 | } 457 | 458 | 459 | 460 | /* SG 461 | # Sass:Colors 462 | 463 | Colors can be defined in the `$$base-colors` variable and referenced via the `colors()` function. 464 | 465 | */ 466 | 467 | /* SG 468 | # Sass:Colors/Definition 469 | 470 | ## `$$base-colors` 471 | ### Map (key : color value) 472 | Sets up consistent color names to be used for color-palette. Dark and light values will be automatically generated. Key values should be accessed through `colors()`. 473 | `type`, `links`, and `bg` key values are required for some starter styles. If you choose not to use them, you need to replace their references. 474 | 475 | Supports a nested map style like the following: 476 | ```scss 477 | $base-colors: ( 478 | 'type': ( 479 | 'base': #020202, 480 | 'light': #232323, 481 | 'dark': #000 482 | ), 483 | 'links': ( 484 | 'base': blue, 485 | 'light': sky, 486 | 'dark': navy 487 | ), 488 | 'bg': ( 489 | 'base': #fff, 490 | 'dark': #ddd 491 | ) 492 | ); 493 | ``` 494 | */ 495 | 496 | /* SG 497 | # Colors/Lookup 498 | 499 | @section sass 500 | @file tools/_t-color-functions.scss 501 | 502 | ## `colors()` 503 | ### function( `$color-name, $tone: 'base', $opacity: 1` ) 504 | Get a color value from the global `$$base-colors` map. Darker and lighter tones are available by passing a second string. 505 | 506 | ```scss 507 | .foo { 508 | background-color: colors(links, light)); 509 | } 510 | ``` 511 | 512 | Passing only a color name will default to the 'base' color. 513 | 514 | @alias color() 515 | @requires $$base-colors 516 | @returns color 517 | 518 | [Reference](http://blog.12spokes.com/web-design-development/simple-css-color-management-with-sass/) 519 | */ 520 | 521 | 522 | /* SG 523 | # Sass:Colors/Manipulation 524 | 525 | @file tools/_t-color-functions.scss 526 | 527 | ## `generate-color-varations()` 528 | ### function( `$map, $functions, $increments, $variations, $blending-colors` ) 529 | 530 | Takes base color values and generates a full color palette. Used by the `$$base-colors` map to create a project's palette, accessible via `colors()`. 531 | 532 | **Arguments:** 533 | * `$map`: Color map you want to create variations of. Defaults to `$$base-colors`. 534 | * `$functions`: color functions used to generate variations (e.g. lighten or darken). Can use any `blend` function, provided `$blending-colors` are provided. 535 | * `$increments`: percentage amount to apply `$function` to each `$variations`. 536 | * `$variations`: actual names for each color tone when `colors()` used. 537 | * `$blending-colors`: used when a function is a `blend`. Can be a list or a single color. 538 | 539 | @requires `combine-color-maps()` 540 | 541 | */ 542 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-documentation-generator 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/bb0cc50ea95f47cfba2ccece0ef73cbb)](https://www.codacy.com/app/cleecanth/markdown-documentation-generator?utm_source=github.com&utm_medium=referral&utm_content=UWHealth/markdown-documentation-generator&utm_campaign=Badge_Grade) 4 | 5 | ### Screenshot 6 | ![Screenshot](https://raw.githubusercontent.com/UWHealth/markdown-documentation-generator/master/docs/screenshot-example.jpg) 7 | 8 | ### What is a living style guide? 9 | > To me, a style guide is a living document of [style] code, which details all the various elements and coded modules of your site or application. Beyond its use in consolidating the front-end code, it also documents the visual language, such as header styles and color palettes, used to create the site. This way, it’s a one-stop place for the entire team—from product owners and producers to designers and developers—to reference when discussing site changes and iterations. [...] - [Susan Robertson/A list apart](http://alistapart.com/article/creating-style-guides) 10 | 11 | The _living_ part means that it uses your **live** css. If you change your css files, the style guide will change as well! 12 | 13 | ### What this tool does, short version: 14 | This tool will search all your style files (your `.css`, `.scss` `_partial.scss`, `.less`, `.whatever`) for comments and create an html file living style guide for your developers to use. It also has some additional hooks for creating sass/less documentation. 15 | 16 | ### What this tool does, longer version: 17 | 18 | By parsing your comments with **markdown**, the generator will convert that data to **json**, which is then run through a (customizable) **handlebars** template to create a **static html** file. 19 | 20 | What you end up with is an html file that contains comments, rendered examples, and highlighted code, ready to be copied and distributed to your team. 21 | 22 | Ultimately, the html file you end up with should inherit your site's css so your style guide is **always in sync with your codebase**. And since you are using markdown, your comment style is largely up to you and your team — they will be readable in and outside of your css. 23 | 24 | All of this can be done via CLI or as part of a larger Node project, since the json, templates, and html are all publicly exposed. It's your data, so you can do with it whatever you please. 25 | 26 | ## Install 27 | 28 | Requires [Node.js](http://nodejs.org/) (if you're unsure if you have node install, run `node -v` in a console.) 29 | 30 | Install with npm: 31 | 32 | ``` 33 | npm install -g markdown-documentation-generator 34 | ``` 35 | 36 | 37 | ## Usage 38 | 39 | ### CLI usage 40 | 41 | If you want to use the default settings, run: 42 | ``` 43 | cd /your/web/project 44 | md_documentation 45 | ``` 46 | 47 | Your html file will be created under /your/web/project/styleguide/styleguide.html 48 | 49 | To override default configuration and create a `.styleguide` config file in the current working directory, you may run: 50 | ``` 51 | md_documentation --init 52 | ``` 53 | 54 | ### Node module usage 55 | 56 | Using the default settings: 57 | ```js 58 | var styleguide = require('markdown-documentation-generator'); 59 | 60 | styleguide.create(); 61 | ``` 62 | 63 | Custom options can be passed via an options object (or read from a `.styleguide` config file) 64 | ```js 65 | var styleguide = require('markdown-documentation-generator'); 66 | 67 | var options = {...}; 68 | 69 | styleguide.create(options); 70 | ``` 71 | 72 | See the [configuration & customization](#configuration--customization") section below for more information about customization. 73 | 74 | ### Comment Style 75 | 76 | Comment your css/scss/less/whatever files. Use Markdown in the comments: 77 | 78 | Example: 79 | 80 | 81 | /* SG 82 | # Glyphs/Style 83 | 84 | You may resize and change the colors of the icons with the `glyph-`-classes. Available sizes and colors listed: 85 | 86 | ```html_example 87 |

      88 | 89 | 90 | 91 | 92 |

      93 | ``` 94 | */ 95 | 96 | a [class^="icon-"], 97 | a [class*=" icon-"] { 98 | text-decoration: none; 99 | } 100 | 101 | [class^="icon-"], 102 | [class*=" icon-"] { 103 | &.glyph-1x { font-size: 1em; } 104 | &.glyph-1_5x { font-size: 1.5em; } 105 | &.glyph-2x { font-size: 2em; } 106 | &.glyph-3x { font-size: 3em; } 107 | } 108 | 109 | /* SG 110 | ```html_example 111 |

      112 | 113 |

      114 | ``` 115 | **/ 116 | 117 | .glyph-red { 118 | color: $moh-red; 119 | } 120 | 121 | 122 | **This will be rendered as:** 123 | 124 | ![Screenshot](https://raw.githubusercontent.com/UWHealth/markdown-documentation-generator/master/docs/screenshot-rendered-glyphs.png) 125 | 126 | * `cd` to the web project (any folder containing css/scss/less files. The tool will search nested folders). 127 | * run `md_documentation` to generate style guide. 128 | * wait a few seconds and a `styleguide.html` is generated in `styleguide/` (this is configurable, see _Configuration_). 129 | 130 | ### Syntax 131 | Best described by going through the example above line by line. 132 | 133 | ##### Line 1 (Demarcation) 134 | 135 | ```css 136 | /* SG 137 | ``` 138 | 139 | `/*` is just an ordinary css comment. The `SG` means that this is a _style guide_ comment. Only comments beginning with `/* SG` will be included in the style guide, all other comments are ignored. This demarcation is configurable. 140 | 141 | 142 | ##### Line 2 (Heading) 143 | 144 | ``` 145 | # Glyphs/Style 146 | ``` 147 | 148 | Every style guide comment must have a heading. `# ` is the Markdown syntax for a heading (equivalent to h1). The name of this category will be _Glyphs_ (which will be shown in the menu). The article will be _Style_ (which will be shown in the menu when it's expanded). The heading (the part before the slash) is required, the slash and the article name are optional. 149 | 150 | 151 | ##### Line 4 (Comment) 152 | 153 | ``` 154 | You may resize and change the colors of the icons with the `glyph-`-classes. Available sizes and colors listed: 155 | ``` 156 | 157 | The comment will be shown in the style guide. Describe your css rules! Feel free to use [Markdown syntax](https://help.github.com/articles/markdown-basics/). The comment is optional but be nice to your developers! 158 | 159 | 160 | ##### Line 6-11 (Code example) 161 | 162 | ```html_example 163 |

      164 | 165 | 166 | 167 | 168 |

      169 |

      170 | 171 |

      172 | ``` 173 | 174 | This is where you write some HTML code to describe how to use your css rules. The HTML will be a) Rendered and used as an example, and b) Output with syntax highlighting. The HTML part is optional but most of the time you'll use it. Notice that the code is fenced in triple back ticks (and given a language marker of `html_example`) as per Markdown syntax. 175 | 176 | 177 | ##### Line 12 (Comment close) 178 | 179 | ``` 180 | */ 181 | ``` 182 | 183 | Closing the css comment. 184 | 185 | 186 | ##### Line 13-24 187 | 188 | ``` 189 | a [class^="icon-"], 190 | a [class*=" icon-"] { 191 | text-decoration: none; 192 | } 193 | ... 194 | ``` 195 | 196 | Ordinary css! You could stop here and understand all you need to, but let's continue. 197 | 198 | ##### Line 24+ 199 | 200 | /* SG 201 | ```html_example 202 |

      203 | 204 |

      205 | ``` 206 | */ 207 | 208 | ... 209 | 210 | Additional comments about the previous article. This allows you to break your comments up whenever and they will always become a part of the previous comment (and added to the same article). 211 | 212 | ### Markdown files 213 | 214 | Sometimes it makes more sense to have some of your documentation in a markdown file. In order to get around the limitations of using `/* */` inside markdown, it is necessary to instead use ` ` tags. Be sure to include `"md":true` in your `fileExtensions` setting if you intend on doing this. 215 | 216 | ## Sections, Categories and Articles 217 | 218 | All style guides are nested into three levels, with **sections** being the highest and **articles** being the lowest. Categories and articles are automatically generated based on the headings you create, but sections must be defined within your configuration. 219 | 220 | With this in mind, it's worth thinking of sections as deep divisions in content (almost like their own pages). For instance, the two sections defined in the default configuration are "styles" and "development". A third possible section might be "editorial". For many, sections will be completely unnecessary. 221 | 222 | ## Configuration & Customization 223 | 224 | If you want to override the default configuration you may create a `.styleguide` file in your project folder (the same folder you run `md_documentation` in). Alternatively, you can pass custom options if you're invoking the module from within another Node application. 225 | 226 | The easiest way to create a `.styleguide` file is to run `md_documentation --init` which will give you a boilerplate configuration file in the current working directory. 227 | 228 | ### Options 229 | 230 | **sgComment** `'SG'` 231 | 232 | The string you use to start your style guide comments. Can be anything, but it should be meaningful. 233 | 234 | 235 | **exampleIdentifier** `html_example` 236 | 237 | The language attribute you use to identify code examples that will be rendered in your style guide. 238 | 239 | 240 | **sortCategories** `true` 241 | 242 | Whether to automatically sort categories and articles alphabetically. 243 | 244 | 245 | **sections** `{'styles':'', 'development':'Dev:'}` 246 | 247 | The names of sections(keys) and their identifiers(values). The names will be output as-is, while the identifiers are searched for within the headings of articles and filtered out. For instance, using the settings listed above, a heading of `# Dev:Buttons/Small` would put this block into the "development" section(with the Article title being "Small", in the category of "Buttons"). 248 | 249 | Using `''` as an identifier will make this the "default" section -- meaning all articles without an identifier will be put into that section (in this case, the "styles" section). 250 | 251 | _Section identifiers cannot contain a "/" character_. 252 | 253 | 254 | **rootFolder** `'./'` 255 | 256 | Directory to start looking for style guide commented files. All other paths will be relative to this (This path itself is relative to whatever directory the script is called from). Defaults to current working directory. 257 | 258 | 259 | **excludeDirs** `['target', 'node_modules', '.git']` 260 | 261 | Directory names you want excluded from being scanned. Passed directly to [Walk](https://www.npmjs.com/package/walk) as a filter. 262 | 263 | 264 | **fileExtensions** `{scss:true, sass:true, less:true, md:true, css:false}` 265 | 266 | File extensions to include in the scan for style guide comments. 267 | 268 | 269 | **templateFile** `'./node_modules/markdown-documentation-generator/template/template.hbs'` 270 | 271 | Path to a handlebars template to run your style guide data through. 272 | 273 | 274 | **themeFile** `'./node_modules/markdown-documentation-generator/template/theme.css'` 275 | 276 | Path to a CSS file to give your style guide some extra style. This is registered as a partial and can be referenced via `{{> theme}}`. 277 | 278 | 279 | **htmlOutput** `'./styleguide/styleguide.html'` 280 | 281 | Path to where you want to save your rendered style guide. Setting this to `true` will return the html as a String. 282 | 283 | 284 | **jsonOutput** `false` 285 | 286 | Path to where you want to save your style guide's json data. Setting this to any `true` will return the json as an Object. 287 | 288 | 289 | **handlebarsPartials** `{'jquery':'./node_modules/markdown-documentation-generator/template/jquery.js'}` 290 | 291 | Partial names(keys) and paths(values) to register to Handlebars before templating. jQuery is included as a default. 292 | 293 | 294 | **highlightStyle** `'arduino-light'` 295 | 296 | Syntax highlighting style. Syntax highlighting relies on highlight.js. See available [styles](https://highlightjs.org/static/demo/) and their [internal names](https://github.com/isagalaev/highlight.js/tree/master/src/styles). 297 | 298 | 299 | **highlightFolder** `'./node_modules/highlight.js/styles/'` 300 | 301 | Folder to look for highlight styles in. Default is highlight.js folder which is installed as a dependency to this package. 302 | 303 | 304 | **customVariables** `{'pageTitle': 'Style Guide'}` 305 | 306 | Additional variables to make available to your templates (appended to your json). For instance, the default value can be accessed with `{{customVariables/pageTitle}}`. 307 | 308 | 309 | **markedOptions** `{'gfm': true}` 310 | 311 | [Marked](https://github.com/chjj/marked) options to be passed when rendering your comments. _Some options, like "breaks" and "renderer" will be overridden since they are essential to the way this application works._ 312 | 313 | 314 | ### Custom Themes 315 | 316 | The final look and feel of the style guide is based on three different files: 317 | * [template file](https://github.com/UWHealth/markdown-documentation-generator/blob/master/template/template.html) - Handlebars template which will produce the final html. 318 | * [theme file](https://github.com/UWHealth/markdown-documentation-generator/blob/master/template/theme.css) - css file which will be included in the template file. 319 | * highlight file - Syntax highlighting relies on [highlight.js](https://highlightjs.org/). To change the highlight style - set the `highlightStyle` to the name of the style (filename minus `.css`, [see the list of styles](https://github.com/isagalaev/highlight.js/tree/master/src/styles) ) in your `.styleguide`. See the [demos of available styles](https://highlightjs.org/static/demo/). 320 | 321 | To create your own template/theme, copy the [template.html and theme.css](https://github.com/UWHealth/markdown-documentation-generator/tree/master/template) to a folder of your choice. Then set the `templateFile` and `themeFile` in your `.styleguide` to the corresponding paths. 322 | 323 | The Javascript object which you may use in your template file looks like this: 324 | 325 | ```javascript 326 | { 327 | "sections": { 328 | "section Name": { 329 | "category": "Category Name", 330 | "id": "category-name (HTML safe)" 331 | "articles": [ 332 | { 333 | "id": 'Article ID (HTML safe unique identifier)', 334 | "category": 'Parent Category (from the "# Category/Heading" Markdown)', 335 | "section": { 336 | "name": "Parent Section", 337 | "parentSection": true //Useful for template checks (always camel-cased) 338 | }, 339 | "file": "File path where this article originated", 340 | "heading": 'Article Heading (from the "# Category/Heading" Markdown)', 341 | "code": ['HTML Code', ...], 342 | "markup": ['Highlighted HTML Code', ...], 343 | "comment": 'Markdown comment converted to HTML', 344 | "priority": 'Article sorting value' // Number 345 | }, 346 | {...} 347 | ], 348 | }, 349 | ... 350 | }, 351 | "menus": [ 352 | "section Name": [ 353 | { 354 | "category": 'Category Name (one per unique "# Category")', 355 | "id": 'Category ID (HTML-safe unique identifier)', 356 | "headings": [ 357 | { 358 | "id": 'Article ID (HTML-safe unique identifier)', 359 | "name": 'Heading Name' 360 | }, 361 | {...} 362 | ] 363 | }, 364 | {...} 365 | ] 366 | ], 367 | "customVariables":{...} 368 | } 369 | ``` 370 | 371 | If you'd like to see your own JSON, set a path in the `"jsonOutput"` option in your `.styleguide` file. 372 | 373 | 374 | ## Run with gulp/grunt 375 | 376 | If you want to re-create the style guide automatically every time a stylesheet file is changed, you can run it with your favorite task runner. One way of running it with gulp would be using gulp-shell to execute the shell command `md_documentation` when a file is changed. 377 | 378 | Sample gulp script: 379 | 380 | ```javascript 381 | var gulp = require('gulp'); 382 | var shell = require('gulp-shell'); 383 | var watch = require('gulp-watch'); 384 | 385 | gulp.task('watch', function() { 386 | gulp.watch('path/to/watch/for/changes/**/*.scss', ['makeStyleguide']); 387 | }); 388 | 389 | gulp.task('makeStyleguide', 390 | shell.task( 391 | ['md_documentation'] 392 | ) 393 | ); 394 | 395 | gulp.task('default', ['watch']); 396 | ``` 397 | -------------------------------------------------------------------------------- /template/hash.js: -------------------------------------------------------------------------------- 1 | window.JSON||(window.JSON={}),function(){function f(a){return a<10?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c")&&c[0]);return a>4?a:!1}();return a},m.isInternetExplorer=function(){var a=m.isInternetExplorer.cached=typeof m.isInternetExplorer.cached!="undefined"?m.isInternetExplorer.cached:Boolean(m.getInternetExplorerMajorVersion());return a},m.emulated={pushState:!Boolean(a.history&&a.history.pushState&&a.history.replaceState&&!/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i.test(e.userAgent)&&!/AppleWebKit\/5([0-2]|3[0-2])/i.test(e.userAgent)),hashChange:Boolean(!("onhashchange"in a||"onhashchange"in d)||m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8)},m.enabled=!m.emulated.pushState,m.bugs={setHash:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),safariPoll:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),ieDoubleCheck:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8),hashEscape:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<7)},m.isEmptyObject=function(a){for(var b in a)return!1;return!0},m.cloneObject=function(a){var b,c;return a?(b=k.stringify(a),c=k.parse(b)):c={},c},m.getRootUrl=function(){var a=d.location.protocol+"//"+(d.location.hostname||d.location.host);if(d.location.port||!1)a+=":"+d.location.port;return a+="/",a},m.getBaseHref=function(){var a=d.getElementsByTagName("base"),b=null,c="";return a.length===1&&(b=a[0],c=b.href.replace(/[^\/]+$/,"")),c=c.replace(/\/+$/,""),c&&(c+="/"),c},m.getBaseUrl=function(){var a=m.getBaseHref()||m.getBasePageUrl()||m.getRootUrl();return a},m.getPageUrl=function(){var a=m.getState(!1,!1),b=(a||{}).url||d.location.href,c;return c=b.replace(/\/+$/,"").replace(/[^\/]+$/,function(a,b,c){return/\./.test(a)?a:a+"/"}),c},m.getBasePageUrl=function(){var a=d.location.href.replace(/[#\?].*/,"").replace(/[^\/]+$/,function(a,b,c){return/[^\/]$/.test(a)?"":a}).replace(/\/+$/,"")+"/";return a},m.getFullUrl=function(a,b){var c=a,d=a.substring(0,1);return b=typeof b=="undefined"?!0:b,/[a-z]+\:\/\//.test(a)||(d==="/"?c=m.getRootUrl()+a.replace(/^\/+/,""):d==="#"?c=m.getPageUrl().replace(/#.*/,"")+a:d==="?"?c=m.getPageUrl().replace(/[\?#].*/,"")+a:b?c=m.getBaseUrl()+a.replace(/^(\.\/)+/,""):c=m.getBasePageUrl()+a.replace(/^(\.\/)+/,"")),c.replace(/\#$/,"")},m.getShortUrl=function(a){var b=a,c=m.getBaseUrl(),d=m.getRootUrl();return m.emulated.pushState&&(b=b.replace(c,"")),b=b.replace(d,"/"),m.isTraditionalAnchor(b)&&(b="./"+b),b=b.replace(/^(\.\/)+/g,"./").replace(/\#$/,""),b},m.store={},m.idToState=m.idToState||{},m.stateToId=m.stateToId||{},m.urlToId=m.urlToId||{},m.storedStates=m.storedStates||[],m.savedStates=m.savedStates||[],m.normalizeStore=function(){m.store.idToState=m.store.idToState||{},m.store.urlToId=m.store.urlToId||{},m.store.stateToId=m.store.stateToId||{}},m.getState=function(a,b){typeof a=="undefined"&&(a=!0),typeof b=="undefined"&&(b=!0);var c=m.getLastSavedState();return!c&&b&&(c=m.createStateObject()),a&&(c=m.cloneObject(c),c.url=c.cleanUrl||c.url),c},m.getIdByState=function(a){var b=m.extractId(a.url),c;if(!b){c=m.getStateString(a);if(typeof m.stateToId[c]!="undefined")b=m.stateToId[c];else if(typeof m.store.stateToId[c]!="undefined")b=m.store.stateToId[c];else{for(;;){b=(new Date).getTime()+String(Math.random()).replace(/\D/g,"");if(typeof m.idToState[b]=="undefined"&&typeof m.store.idToState[b]=="undefined")break}m.stateToId[c]=b,m.idToState[b]=a}}return b},m.normalizeState=function(a){var b,c;if(!a||typeof a!="object")a={};if(typeof a.normalized!="undefined")return a;if(!a.data||typeof a.data!="object")a.data={};b={},b.normalized=!0,b.title=a.title||"",b.url=m.getFullUrl(m.unescapeString(a.url||d.location.href)),b.hash=m.getShortUrl(b.url),b.data=m.cloneObject(a.data),b.id=m.getIdByState(b),b.cleanUrl=b.url.replace(/\??\&_suid.*/,""),b.url=b.cleanUrl,c=!m.isEmptyObject(b.data);if(b.title||c)b.hash=m.getShortUrl(b.url).replace(/\??\&_suid.*/,""),/\?/.test(b.hash)||(b.hash+="?"),b.hash+="&_suid="+b.id;return b.hashedUrl=m.getFullUrl(b.hash),(m.emulated.pushState||m.bugs.safariPoll)&&m.hasUrlDuplicate(b)&&(b.url=b.hashedUrl),b},m.createStateObject=function(a,b,c){var d={data:a,title:b,url:c};return d=m.normalizeState(d),d},m.getStateById=function(a){a=String(a);var c=m.idToState[a]||m.store.idToState[a]||b;return c},m.getStateString=function(a){var b,c,d;return b=m.normalizeState(a),c={data:b.data,title:a.title,url:a.url},d=k.stringify(c),d},m.getStateId=function(a){var b,c;return b=m.normalizeState(a),c=b.id,c},m.getHashByState=function(a){var b,c;return b=m.normalizeState(a),c=b.hash,c},m.extractId=function(a){var b,c,d;return c=/(.*)\&_suid=([0-9]+)$/.exec(a),d=c?c[1]||a:a,b=c?String(c[2]||""):"",b||!1},m.isTraditionalAnchor=function(a){var b=!/[\/\?\.]/.test(a);return b},m.extractState=function(a,b){var c=null,d,e;return b=b||!1,d=m.extractId(a),d&&(c=m.getStateById(d)),c||(e=m.getFullUrl(a),d=m.getIdByUrl(e)||!1,d&&(c=m.getStateById(d)),!c&&b&&!m.isTraditionalAnchor(a)&&(c=m.createStateObject(null,null,e))),c},m.getIdByUrl=function(a){var c=m.urlToId[a]||m.store.urlToId[a]||b;return c},m.getLastSavedState=function(){return m.savedStates[m.savedStates.length-1]||b},m.getLastStoredState=function(){return m.storedStates[m.storedStates.length-1]||b},m.hasUrlDuplicate=function(a){var b=!1,c;return c=m.extractState(a.url),b=c&&c.id!==a.id,b},m.storeState=function(a){return m.urlToId[a.url]=a.id,m.storedStates.push(m.cloneObject(a)),a},m.isLastSavedState=function(a){var b=!1,c,d,e;return m.savedStates.length&&(c=a.id,d=m.getLastSavedState(),e=d.id,b=c===e),b},m.saveState=function(a){return m.isLastSavedState(a)?!1:(m.savedStates.push(m.cloneObject(a)),!0)},m.getStateByIndex=function(a){var b=null;return typeof a=="undefined"?b=m.savedStates[m.savedStates.length-1]:a<0?b=m.savedStates[m.savedStates.length+a]:b=m.savedStates[a],b},m.getHash=function(){var a=m.unescapeHash(d.location.hash);return a},m.unescapeString=function(b){var c=b,d;for(;;){d=a.unescape(c);if(d===c)break;c=d}return c},m.unescapeHash=function(a){var b=m.normalizeHash(a);return b=m.unescapeString(b),b},m.normalizeHash=function(a){var b=a.replace(/[^#]*#/,"").replace(/#.*/,"");return b},m.setHash=function(a,b){var c,e,f;return b!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.setHash,args:arguments,queue:b}),!1):(c=m.escapeHash(a),m.busy(!0),e=m.extractState(a,!0),e&&!m.emulated.pushState?m.pushState(e.data,e.title,e.url,!1):d.location.hash!==c&&(m.bugs.setHash?(f=m.getPageUrl(),m.pushState(null,null,f+"#"+c,!1)):d.location.hash=c),m)},m.escapeHash=function(b){var c=m.normalizeHash(b);return c=a.escape(c),m.bugs.hashEscape||(c=c.replace(/\%21/g,"!").replace(/\%26/g,"&").replace(/\%3D/g,"=").replace(/\%3F/g,"?")),c},m.getHashByUrl=function(a){var b=String(a).replace(/([^#]*)#?([^#]*)#?(.*)/,"$2");return b=m.unescapeHash(b),b},m.setTitle=function(a){var b=a.title,c;b||(c=m.getStateByIndex(0),c&&c.url===a.url&&(b=c.title||m.options.initialTitle));try{d.getElementsByTagName("title")[0].innerHTML=b.replace("<","<").replace(">",">").replace(" & "," & ")}catch(e){}return d.title=b,m},m.queues=[],m.busy=function(a){typeof a!="undefined"?m.busy.flag=a:typeof m.busy.flag=="undefined"&&(m.busy.flag=!1);if(!m.busy.flag){h(m.busy.timeout);var b=function(){var a,c,d;if(m.busy.flag)return;for(a=m.queues.length-1;a>=0;--a){c=m.queues[a];if(c.length===0)continue;d=c.shift(),m.fireQueueItem(d),m.busy.timeout=g(b,m.options.busyDelay)}};m.busy.timeout=g(b,m.options.busyDelay)}return m.busy.flag},m.busy.flag=!1,m.fireQueueItem=function(a){return a.callback.apply(a.scope||m,a.args||[])},m.pushQueue=function(a){return m.queues[a.queue||0]=m.queues[a.queue||0]||[],m.queues[a.queue||0].push(a),m},m.queue=function(a,b){return typeof a=="function"&&(a={callback:a}),typeof b!="undefined"&&(a.queue=b),m.busy()?m.pushQueue(a):m.fireQueueItem(a),m},m.clearQueue=function(){return m.busy.flag=!1,m.queues=[],m},m.stateChanged=!1,m.doubleChecker=!1,m.doubleCheckComplete=function(){return m.stateChanged=!0,m.doubleCheckClear(),m},m.doubleCheckClear=function(){return m.doubleChecker&&(h(m.doubleChecker),m.doubleChecker=!1),m},m.doubleCheck=function(a){return m.stateChanged=!1,m.doubleCheckClear(),m.bugs.ieDoubleCheck&&(m.doubleChecker=g(function(){return m.doubleCheckClear(),m.stateChanged||a(),!0},m.options.doubleCheckInterval)),m},m.safariStatePoll=function(){var b=m.extractState(d.location.href),c;if(!m.isLastSavedState(b))c=b;else return;return c||(c=m.createStateObject()),m.Adapter.trigger(a,"popstate"),m},m.back=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.back,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.back(!1)}),n.go(-1),!0)},m.forward=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.forward,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.forward(!1)}),n.go(1),!0)},m.go=function(a,b){var c;if(a>0)for(c=1;c<=a;++c)m.forward(b);else{if(!(a<0))throw new Error("History.go: History.go requires a positive or negative integer passed.");for(c=-1;c>=a;--c)m.back(b)}return m};if(m.emulated.pushState){var o=function(){};m.pushState=m.pushState||o,m.replaceState=m.replaceState||o}else m.onPopState=function(b,c){var e=!1,f=!1,g,h;return m.doubleCheckComplete(),g=m.getHash(),g?(h=m.extractState(g||d.location.href,!0),h?m.replaceState(h.data,h.title,h.url,!1):(m.Adapter.trigger(a,"anchorchange"),m.busy(!1)),m.expectedStateId=!1,!1):(e=m.Adapter.extractEventData("state",b,c)||!1,e?f=m.getStateById(e):m.expectedStateId?f=m.getStateById(m.expectedStateId):f=m.extractState(d.location.href),f||(f=m.createStateObject(null,null,d.location.href)),m.expectedStateId=!1,m.isLastSavedState(f)?(m.busy(!1),!1):(m.storeState(f),m.saveState(f),m.setTitle(f),m.Adapter.trigger(a,"statechange"),m.busy(!1),!0))},m.Adapter.bind(a,"popstate",m.onPopState),m.pushState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.pushState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.pushState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0},m.replaceState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.replaceState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.replaceState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0};if(f){try{m.store=k.parse(f.getItem("History.store"))||{}}catch(p){m.store={}}m.normalizeStore()}else m.store={},m.normalizeStore();m.Adapter.bind(a,"beforeunload",m.clearAllIntervals),m.Adapter.bind(a,"unload",m.clearAllIntervals),m.saveState(m.storeState(m.extractState(d.location.href,!0))),f&&(m.onUnload=function(){var a,b;try{a=k.parse(f.getItem("History.store"))||{}}catch(c){a={}}a.idToState=a.idToState||{},a.urlToId=a.urlToId||{},a.stateToId=a.stateToId||{};for(b in m.idToState){if(!m.idToState.hasOwnProperty(b))continue;a.idToState[b]=m.idToState[b]}for(b in m.urlToId){if(!m.urlToId.hasOwnProperty(b))continue;a.urlToId[b]=m.urlToId[b]}for(b in m.stateToId){if(!m.stateToId.hasOwnProperty(b))continue;a.stateToId[b]=m.stateToId[b]}m.store=a,m.normalizeStore(),f.setItem("History.store",k.stringify(a))},m.intervalList.push(i(m.onUnload,m.options.storeInterval)),m.Adapter.bind(a,"beforeunload",m.onUnload),m.Adapter.bind(a,"unload",m.onUnload));if(!m.emulated.pushState){m.bugs.safariPoll&&m.intervalList.push(i(m.safariStatePoll,m.options.safariPollInterval));if(e.vendor==="Apple Computer, Inc."||(e.appCodeName||"")==="Mozilla")m.Adapter.bind(a,"hashchange",function(){m.Adapter.trigger(a,"popstate")}),m.getHash()&&m.Adapter.onDomLoad(function(){m.Adapter.trigger(a,"hashchange")})}},m.init()}(window) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* jshint node: true */ 3 | /* jshint esnext: true*/ 4 | "use strict"; 5 | 6 | var _sg = require('./lib/globals'); //"Global" variables 7 | var formatters = require('./lib/md-formats'); 8 | var logFiles = require('./lib/log'); 9 | var log = require('./lib/log').generic; 10 | var sanitize = require('./lib/sanitize'); 11 | var sorting = require('./lib/sorts'); 12 | var tags = require('./lib/tags').tags; 13 | var template = require('./lib/templating'); 14 | 15 | var chalk = require('chalk'); 16 | var cheerio = require('cheerio'); 17 | var fs = require('fs-extra'); 18 | var hl = require('highlight.js'); 19 | var _ = require('lodash'); 20 | var markdown = require('marked'); 21 | var path = require('path'); 22 | var walk = require('walk'); 23 | var argv = require('yargs').argv; 24 | 25 | //Default options 26 | let options = { 27 | sgComment: 'SG', 28 | exampleIdentifier: 'html_example', 29 | sortCategories: true, 30 | sections: { 31 | "styles": '', 32 | "development": 'Dev:' 33 | }, 34 | rootFolder: './', 35 | excludeDirs: ['target', 'node_modules', '.git'], 36 | fileExtensions: { 37 | scss: true, 38 | sass: true, 39 | less: true, 40 | md: true, 41 | css: false 42 | }, 43 | templateFile: sanitize.path('./', 'template/template.hbs'), 44 | themeFile: sanitize.path('./', 'template/theme.css'), 45 | htmlOutput: './styleguide/styleguide.html', 46 | jsonOutput: './styleguide/styleguide.json', 47 | handlebarsPartials: { 48 | "jquery": path.resolve(__dirname, 'template/jquery.js'), 49 | "sticky": sanitize.path('./', 'template/sticky.js') 50 | }, 51 | highlightStyle: 'arduino-light', 52 | highlightFolder: path.relative('./', path.join(_sg.hljsDir, '../styles/')), 53 | customVariables: { 54 | "pageTitle": "Style Guide" 55 | }, 56 | markedOptions: { 57 | gfm: true, 58 | breaks: true, 59 | smartypants: true 60 | }, 61 | logging: { 62 | prefix: "[Style Guide]", 63 | level: "verbose" 64 | } 65 | }; 66 | 67 | //Argument methods 68 | const arg = { 69 | init: function() { 70 | 71 | let configFilePath = path.join(process.cwd(), '.styleguide'), 72 | existingConfig; 73 | 74 | try { 75 | existingConfig = fs.readJSONSync(configFilePath, 'utf8'); 76 | } 77 | catch(err) { 78 | fs.writeFileSync(configFilePath, JSON.stringify(options,null,'\t')); 79 | logFiles(configFilePath, 'create'); 80 | } 81 | 82 | if (existingConfig !== undefined) { 83 | const configError = _sg.error(_sg.logPre + 84 | 'Configuration file ' + 85 | _sg.info('\'.styleguide\'') + ' already exists in this directory. \n' + 86 | _sg.logPre + 'Edit that file, or delete it and run ' + 87 | _sg.info('\'init\'') + ' again if you want to create a new configuration file.' 88 | ); 89 | throw new Error(configError); 90 | } 91 | process.exit(0); 92 | }, 93 | config: function() { 94 | _sg.configFile = argv.config || _sg.configFile; 95 | }, 96 | lf: function() { 97 | _sg.fileList = true; 98 | }, 99 | ls: function() { 100 | _sg.fileList = true; 101 | }, 102 | help: function() { 103 | console.info(''); 104 | console.info(_sg.brand(' _____ ')); 105 | console.info(_sg.brand(' / ___/ _ / | ( ) / | ')); 106 | console.info(_sg.brand(' | (___ | |_ _ _| | ___ __ _ _ _ _ __| | ___ ')); 107 | console.info(_sg.brand(' \\___ \\| __| | | | |/ _ \\/ _` | | | | |/ _` |/ _ \\')); 108 | console.info(_sg.brand(' ____) | |_| |_| | | __/ (_| | |_| | | (_| | __/')); 109 | console.info(_sg.brand(' \\____/ \\___\\__, |_|\\___/\\__, |\\__,_|_|\\__,_|\\___/')); 110 | console.info(_sg.brand(' __/ | __/ | ')); 111 | console.info(_sg.brand(' |___/ |___/ ')); 112 | console.info(''); 113 | console.info(' ' + _sg.brand('md_documentation') + ' Generate styleguide'); 114 | console.info(' ' + _sg.brand('md_documentation --config=[filename]') + ' Generate styleguide using a custom config file'); 115 | console.info(' ' + _sg.brand('md_documentation --init') + ' Create a new configuration file in the current directory'); 116 | console.info(' ' + _sg.brand('md_documentation --lf') + ' Show "Reading [filename]" during file processing' ); 117 | console.info(' ' + _sg.brand('md_documentation --help') + ' Show this'); 118 | console.info(''); 119 | console.info(' More help at'); 120 | console.info(' https://github.com/UWHealth/markdown-documentation-generator'); 121 | console.info(''); 122 | process.exit(0); 123 | } 124 | }; 125 | 126 | /** 127 | * Check arguments against arg object and run that method 128 | * 129 | * @param {Array} args - arguments 130 | */ 131 | function readArgs(args) { 132 | 133 | if (args.length > 0) { 134 | let curArg = args[0].toLowerCase().replace(/-/g, '').split(/=/g)[0]; 135 | if (_.isUndefined(arg[curArg])) { 136 | console.info( _sg.logPre + curArg + ' not recognized. Showing help instead.'); 137 | arg.help(); 138 | } 139 | else { 140 | arg[curArg](); 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * Merge custom options with default options. 147 | * Resolves paths and makes sure they're output in a relative format. 148 | * 149 | * @param {Object} customOptions - user-provided options 150 | * @return {Object} - Merged options 151 | */ 152 | 153 | function mergeOptions(defaults, customOptions) { 154 | 155 | //Resolve relative paths from what is here to what is passed in 156 | //Return a relative path for simpler display purposes 157 | function getPath(folder){ 158 | const root = path.resolve(customOptions.rootFolder); 159 | 160 | return path.isAbsolute(folder) 161 | ? folder 162 | : path.relative(root, path.resolve(__dirname, folder)); 163 | } 164 | 165 | //Resolve paths for only a custom rootFolder 166 | if (customOptions.rootFolder) { 167 | defaults.highlightFolder = getPath(defaults.highlightFolder); 168 | defaults.templateFile = getPath(defaults.templateFile); 169 | defaults.themeFile = getPath(defaults.themeFile); 170 | defaults.handlebarsPartials.jquery = getPath(defaults.handlebarsPartials.jquery); 171 | defaults.handlebarsPartials.sticky = getPath(defaults.handlebarsPartials.sticky); 172 | } 173 | 174 | let logging = Object.assign({}, defaults.logging, customOptions.logging || {}); 175 | 176 | _sg.logLevel = logging.level; 177 | _sg.logPre = _sg.brand(logging.prefix); 178 | 179 | //Overwrite default sections with custom ones if they exist 180 | defaults.sections = customOptions.sections || defaults.sections; 181 | //Merge custom and defaults 182 | let newOptions = _.merge(defaults, customOptions); 183 | 184 | newOptions.rootFolder = path.resolve(process.cwd(), newOptions.rootFolder); 185 | 186 | // Add excluded directories to walker options (if set) 187 | if (Object.prototype.toString.call(newOptions.excludeDirs) === '[object Array]') { 188 | newOptions.walkerOptions = { 189 | "filters": newOptions.excludeDirs, 190 | "followLinks": false 191 | }; 192 | } 193 | 194 | return newOptions; 195 | } 196 | 197 | /** 198 | * Read configuration 199 | * 200 | * @param {Object} customOptions - user-provided options 201 | * @return {Object} - Merged user and default options 202 | */ 203 | function registerConfig(customOptions) { 204 | 205 | try { 206 | logFiles(_sg.brand('Configuration')); 207 | _sg.configFile = path.isAbsolute(_sg.configFile) 208 | ? _sg.configFile 209 | : path.join(process.cwd(), _sg.configFile); 210 | customOptions = customOptions 211 | || require(_sg.configFile); 212 | } 213 | catch(err) { 214 | if (err.code !== "ENOENT") { 215 | err.message = err.message + '.\n Check your configuration and try again.'; 216 | throw new Error(_sg.error(err)); 217 | } 218 | } 219 | 220 | if (_.isUndefined(customOptions)) { 221 | log(_sg.warn( 222 | `No configuration file (\'${_sg.configFile}\') or options found, using defaults.` 223 | ), 1); 224 | 225 | customOptions = { 226 | walkerOptions: { 227 | "filters": options.excludeDirs, 228 | "followLinks": false 229 | } 230 | }; 231 | } 232 | 233 | return mergeOptions(options, customOptions); 234 | 235 | } 236 | 237 | /** 238 | * Read template/theme/highlight files 239 | * 240 | */ 241 | function readTheme() { 242 | 243 | try { 244 | logFiles(_sg.brand('Template: ') + options.templateFile); 245 | _sg.templateSource = fs.readFileSync(path.resolve(_sg.root, options.templateFile), 'utf8'); 246 | 247 | logFiles(_sg.brand('Theme: ') + options.themeFile); 248 | _sg.themeSource = fs.readFileSync(path.resolve(_sg.root, options.themeFile), 'utf8'); 249 | 250 | logFiles(_sg.brand('Highlight Style: ') + 251 | path.relative(_sg.root, path.join(options.highlightFolder, options.highlightStyle + '.css'))); 252 | _sg.highlightSource = fs.readFileSync(path.join( 253 | path.resolve(_sg.root, options.highlightFolder), options.highlightStyle + '.css'), 'utf8'); 254 | } 255 | catch(err) { 256 | const pathError = _sg.logPre + ' ' + _sg.error('Could not read file: ' + path.resolve(err.path)); 257 | throw new Error(pathError); 258 | } 259 | } 260 | 261 | /** 262 | * Search through tags for current section identifiers 263 | * 264 | * @param {Object} $article - article html loaded by cheerio 265 | * @return {Array} Section Name, Section identifier 266 | */ 267 | function findSection($article) { 268 | let currentSection, sectionIdentifier; 269 | let headerText = $article(tags.category).slice(0, 1).text() + $article(tags.article).text(); 270 | 271 | //Check headings for identifiers declared in "sections" option 272 | for (let sectionName in options.sections){ 273 | if ({}.hasOwnProperty.call(options.sections, sectionName)) { 274 | sectionIdentifier = options.sections[sectionName]; 275 | 276 | if (headerText.indexOf(sectionIdentifier) > -1 && sectionIdentifier !== ''){ 277 | currentSection = sectionName; 278 | break; 279 | } 280 | } 281 | } 282 | 283 | if (_.isUndefined(currentSection)){ 284 | //Use the "default" section (the section without a pattern match) 285 | currentSection = _.invert(options.sections)['']; 286 | sectionIdentifier = ''; 287 | } 288 | 289 | return [currentSection, sectionIdentifier]; 290 | } 291 | 292 | /** 293 | * Constructs a section object 294 | * 295 | * @param {Object} $article - article html loaded by cheerio 296 | * @return {Object} Section Name, Section identifier 297 | */ 298 | function SectionStructure() { 299 | let structure = {}; 300 | //Create section object for data structure, based on user's "sections" option 301 | for(let name in options.sections) { 302 | if ({}.hasOwnProperty.call(options.sections, name)) { 303 | structure[name] = []; 304 | } 305 | } 306 | 307 | return structure; 308 | } 309 | 310 | /** 311 | * Search through tags for current section identifiers 312 | * 313 | * @param {Object} $article - article html loaded by cheerio 314 | * @param {Object} articleData - structured data about the loaded article 315 | * @param {String} sectionIdentifier - string pattern that tells us the current section 316 | * @return {Object} articleData along with meta tags 317 | * 318 | */ 319 | function getMetaData($article, articleData, sectionIdentifier) { 320 | 321 | //Grab the filelocation and store it 322 | $article(tags.file).each(function () { 323 | articleData.filelocation = $article(this).text().trim(); 324 | 325 | }).remove(); 326 | 327 | $article(tags.section).each(function () { 328 | articleData.currentSection = $article(this).text().trim(); 329 | sectionIdentifier = options.sections[articleData.currentSection]; 330 | 331 | //A @section tag is pointing to a non-existant section 332 | if (_.isUndefined(sectionIdentifier)) { 333 | log(_sg.warn("Warning: '" + chalk.bold(articleData.currentSection) + 334 | "' is not a registered section in your configuration. (" + articleData.filelocation + ")"), 1); 335 | sectionIdentifier = ''; 336 | } 337 | 338 | }).remove(); 339 | 340 | $article(tags.category).each(function() { 341 | 342 | if (articleData.category === '') { 343 | articleData.category = $article(this) 344 | .text() 345 | .replace(/^\s+|\s+$/g, '') 346 | .replace(sectionIdentifier, '') 347 | .trim(); 348 | 349 | $article(this).remove(); 350 | } 351 | else { 352 | $article(this).replaceWith( 353 | _sg.renderer.heading($article(this).text(), 1.1) 354 | ); 355 | } 356 | }); 357 | 358 | $article(tags.article).each(function () { 359 | //Remove dev identifier and extra spaces 360 | articleData.heading += $article(this).text().replace(/^\s+|\s+$/g, '').replace(sectionIdentifier, '').trim(); 361 | }).remove(); 362 | 363 | //Store code examples and markup 364 | $article(tags.example).each(function () { 365 | let categoryCode = $article(this).html().replace(/^\s+|\s+$/g, ''); 366 | articleData.code.push(categoryCode); 367 | 368 | //Run example markup through highlight.js 369 | articleData.markup.push(hl.highlight("html", categoryCode).value); 370 | 371 | }).remove(); 372 | 373 | //Grab priority tag data and convert them to meaningful values 374 | $article(tags.priority).each(function() { 375 | let priority = $article(this).text().trim(); 376 | articleData.priority = (_.isNaN(Number(priority))) ? priority : Number(priority); 377 | 378 | }).remove(); 379 | 380 | if (articleData.heading === '') { 381 | articleData.priority = -1000; 382 | } 383 | 384 | return articleData; 385 | } 386 | 387 | 388 | /** 389 | * Take HTML and create JSON object to be parsed by Handlebars 390 | * 391 | * @param {String} html 392 | * @returns {Array} json 393 | */ 394 | function convertHTMLtoJSON(html) { 395 | let idCache = {}; 396 | let sectionIdentifier = ''; 397 | let previousArticle; 398 | 399 | let $ = cheerio.load(html); 400 | sanitize.cheerioWrapAll($); //Add wrapAll method to cheerio 401 | 402 | let masterData = { 403 | sections: new SectionStructure(), 404 | menus: {}, 405 | colors: {}, 406 | customVariables: options.customVariables 407 | }; 408 | 409 | // Loop each section and turn into javascript object 410 | $('.sg-article-' + _sg.uniqueIdentifier).each(function() { 411 | let $article = cheerio.load($(this).html()); 412 | 413 | let articleData = { 414 | id: '', 415 | currentSection: null, 416 | section: { 417 | name: '' 418 | }, 419 | category: '', 420 | heading: '', 421 | code: [], 422 | markup: [], 423 | comment: '', 424 | priority: 50 425 | }; 426 | 427 | //Check for category headings 428 | if ($article(tags.category)[0]) { 429 | const sectionInfo = findSection($article); 430 | articleData.currentSection = sectionInfo[0]; 431 | sectionIdentifier = sectionInfo[1]; 432 | } 433 | else if (previousArticle !== undefined) { 434 | //Without a heading, assume it should be concatenated with the previous category 435 | articleData.id = previousArticle.id; 436 | articleData.category = previousArticle.category; 437 | articleData.heading = previousArticle.heading; 438 | articleData.currentSection = previousArticle.section.name; 439 | } 440 | 441 | //Search through specific DOM elements for article meta data 442 | articleData = getMetaData($article, articleData, sectionIdentifier); 443 | 444 | //Give category an ID 445 | articleData.id = sanitize.makeUrlSafe(articleData.currentSection + '-' + articleData.category + '-' + articleData.heading); 446 | 447 | //Save sanitized comment html 448 | articleData.comment = $article.html().replace('

      ', '').replace(/^\s+|\s+$/g, ''); 449 | 450 | //Move and place article data into master 451 | articleData = checkData(articleData); 452 | 453 | return articleData; 454 | }); 455 | 456 | /** 457 | * Combine repeat categories by checking with the ID cache 458 | * ID Cache format: 459 | * {1: ["development", 5]} 460 | * {ID:[section, category-index]} 461 | * 462 | * @param {object} articleData - data parsed by DOM objects 463 | * 464 | **/ 465 | 466 | function checkData(articleData) { 467 | let currentSection = articleData.currentSection; 468 | 469 | //Bail out for un-categorized comments 470 | if (currentSection === null) { 471 | return; 472 | } 473 | 474 | //If the section's ID has already been cached, 475 | //append its data to the previous object 476 | if (idCache.hasOwnProperty(articleData.id)) { 477 | 478 | //Grab the index 479 | let currentIndex = idCache[articleData.id][1]; 480 | 481 | //Select the matched section from the masterData 482 | let selectedSection = masterData.sections[currentSection][currentIndex]; 483 | 484 | //Append the new data to the matched section 485 | selectedSection.comment += articleData.comment; 486 | 487 | if (articleData.markup.length > 0) { 488 | selectedSection.markup = _.union(selectedSection.markup, articleData.markup); 489 | } 490 | if (articleData.code.length > 0) { 491 | selectedSection.code = _.union(selectedSection.code, articleData.code); 492 | } 493 | 494 | //Set previous article so we can refer back if necessary 495 | previousArticle = selectedSection; 496 | 497 | return; 498 | } 499 | 500 | if (masterData.sections[currentSection]) { 501 | 502 | let catIndex = masterData.sections[currentSection].length; 503 | 504 | //Cache the ID and its index within its section 505 | idCache[articleData.id] = [currentSection, catIndex]; 506 | articleData.section[_.camelCase(currentSection)] = true; 507 | 508 | articleData.section.name = articleData.currentSection; 509 | 510 | //Remove unnecessary data from final JSON 511 | delete articleData.currentSection; 512 | 513 | //Set previous article so we can refer back if necessary 514 | previousArticle = articleData; 515 | 516 | //Append new section to master data 517 | sanitize.objectPush(masterData.sections[currentSection], articleData); 518 | 519 | } 520 | } 521 | 522 | return formatData(masterData); 523 | } 524 | 525 | /** 526 | * Arranges and sorts data into a handlebars-friendly object. 527 | * Uses the structure: 528 | * {sections:[ {sectionName:{id:... , category:..., articles:[{...},...]} },...]} 529 | * 530 | * @param {Object} data - unformatted data 531 | * @returns {Object} formatted data 532 | */ 533 | function formatData(data) { 534 | //Sort section data 535 | if (options.sortCategories){ 536 | //Sort Sections 537 | Object.keys(data.sections).forEach(function(category) { 538 | data.sections[category] = sorting(data.sections[category]); 539 | }); 540 | } 541 | 542 | function formatSections(sectionName, isMenu) { 543 | 544 | let menuObj = {}; 545 | let sectionObj = {}; 546 | let menuArr = []; 547 | let sectionArr = []; 548 | 549 | 550 | data.sections[sectionName].forEach(function(section) { 551 | 552 | //New categories: Create a new array to push objects into 553 | if (_.has(menuObj, section.category) === false) { 554 | menuObj[section.category] = []; 555 | sectionObj[section.category] = []; 556 | } 557 | 558 | menuObj[section.category].push({ 559 | id: section.id, 560 | name: (section.heading) ? section.heading : section.category 561 | }); 562 | 563 | sectionObj[section.category].push(section); 564 | }); 565 | 566 | Object.keys(menuObj).forEach(function(key) { 567 | menuArr.push({ 568 | category: key, 569 | id: menuObj[key][0].id, 570 | headings: menuObj[key] 571 | }); 572 | 573 | sectionArr.push({ 574 | category: key, 575 | id: menuObj[key][0].id, 576 | articles: sectionObj[key] 577 | }); 578 | }); 579 | 580 | //Wasteful but simple 581 | return isMenu ? menuArr : sectionArr; 582 | } 583 | 584 | //Create menu and section JSON 585 | Object.keys(options.sections).forEach(function(section) { 586 | data.menus[section] = formatSections(section, true); 587 | data.sections[section] = formatSections(section, false); 588 | }); 589 | 590 | return data; 591 | } 592 | 593 | /** 594 | * Based on the fileExtension, return a regular expression based on the user-defined sgComment 595 | * 596 | * @param {String} fileExtension 597 | * @returns {RegExp} pattern for either /* or 598 | * 599 | */ 600 | function regexType(fileExtension) { 601 | 602 | let sgComment = _.escapeRegExp(options.sgComment); 603 | 604 | if (["md", "markdown", "mdown"].indexOf(fileExtension) !== -1) { 605 | // Use ... for markdown files 606 | return new RegExp('\\<' + sgComment + '>([\\s\\S]*?)\\<\\/' + sgComment + '\\>', 'gi'); 607 | } 608 | // Use /*SG ... */ for everything else 609 | return new RegExp('/\\* ?' + sgComment + '([\\s\\S]*?)\\*/', 'gi'); 610 | } 611 | 612 | 613 | /** 614 | * Read valid files (default: scss/css), get the Styleguide comments and put into an array 615 | * 616 | * @param {string} root 617 | * @param {String} fileExtension 618 | * @param {Object} fileStats 619 | * @param {Array} fileContents 620 | * 621 | */ 622 | function readSGFile(fileExtension, root, name, fileContents, callback) { 623 | 624 | fs.readFile(path.join(root, name), 'utf8', function (err, content) { 625 | let match, 626 | filePath = path.join(root, name), 627 | regex = regexType(fileExtension); 628 | 629 | logFiles(path.relative(_sg.root, filePath)); 630 | 631 | if (err) { 632 | const fileError = _sg.logPre + _sg.error('File Error: ' + filePath) + err; 633 | throw new Error(fileError); 634 | } 635 | 636 | while ((match = regex.exec(content)) !== null) { 637 | //If reading anything other than css, create a file-location reference we'll use later 638 | let fileLocation = (fileExtension !== "css") ? '<'+tags.file+'>'+path.relative(_sg.root, filePath)+'': ''; 639 | //Convert markdown to html 640 | fileContents.push(markdown(match[1]) + fileLocation); 641 | } 642 | 643 | callback(); 644 | }); 645 | } 646 | 647 | 648 | /** 649 | * Take JSON, template and write out files or return as templates as a string. 650 | * 651 | * @param {Object} json - styleguide json data 652 | * 653 | */ 654 | function saveFiles(json){ 655 | 656 | let output = { 657 | 'json': JSON.stringify(json, null, ' '), 658 | 'html': template(json, options) 659 | }; 660 | 661 | let filePromises = []; 662 | 663 | Object.keys(output).forEach((fileType) => { 664 | let filePath = options[fileType+'Output']; 665 | 666 | if (filePath && _.isString(filePath)) { 667 | let fullFilePath = path.resolve(_sg.root, filePath); 668 | filePromises.push( 669 | fs.outputFile(fullFilePath, output[fileType]) 670 | .then(() => { 671 | logFiles(filePath, 'create'); 672 | }) 673 | .catch((err) => { 674 | console.error( 675 | _sg.logPre + 676 | _sg.error(` Error saving ${fileType} file`) 677 | ); 678 | console.error(err); 679 | }) 680 | ) 681 | } 682 | }) 683 | 684 | return filePromises; 685 | } 686 | 687 | 688 | /** 689 | * Walk the file tree, and return templated html 690 | * 691 | * @param {Object} walker 692 | * @returns {Promise} the file contents wrapped in divs 693 | * 694 | */ 695 | function walkFiles(walker, callback) { 696 | 697 | const extensions = _.reduce(options.fileExtensions, function(result, value, key){ 698 | if(value){result.push(key);} 699 | return result; 700 | }, []).join(', '); 701 | 702 | log(_sg.info('Reading ' + extensions + ' files...'), 2); 703 | 704 | //Send back file contents once walker has reached its end 705 | var fileContents = []; 706 | 707 | walker.on("file", function (root, fileStats, next) { 708 | const fileExtension = fileStats.name.substr((~-fileStats.name.lastIndexOf(".") >>> 0) + 2).toLowerCase(); 709 | 710 | if (options.fileExtensions[fileExtension]) { 711 | readSGFile(fileExtension, root, fileStats.name, fileContents, next); 712 | } 713 | else { 714 | next(); 715 | } 716 | }); 717 | 718 | walker.on("errors", function (root, nodeStatsArray) { 719 | const fileError = _sg.logPre + _sg.error('File reading Error ') + nodeStatsArray; 720 | throw new Error(fileError); 721 | }); 722 | 723 | walker.on("end", function () { 724 | //If nothing is found after all files are read, give some info 725 | if (fileContents.length <= 0) { 726 | log('\n'+ 727 | _sg.warn( 728 | 'Could not find anything to document.')+ 729 | '\n Please check the following:'+ 730 | '\n * You\'ve used /*'+options.sgComment+'*/ style comments.'+ 731 | '\n * Your "rootFolder" setting is pointing to the root of your style guide files.'+ 732 | '\n * If you\'re using the default settings, try using the "init" argument.'+ 733 | '\n If you\'re still receiving this error, please check the documentation or file an issue at: \n'+ 734 | chalk.blue.bold('github.com/UWHealth/markdown-documentation-generator/') 735 | , 1); 736 | } 737 | 738 | //Wrap all comments starting with SG in a section, send it back to the promise 739 | return callback(fileContents.join('
      '+ 740 | '\n
      \n')); 741 | }); 742 | } 743 | 744 | 745 | function init(args, customOptions, callback) { 746 | 747 | //Set up stuff based on arguments 748 | readArgs(args); 749 | 750 | //Read and merge default options with custom ones 751 | options = registerConfig(customOptions); 752 | 753 | //Create global root reference 754 | _sg.root = path.resolve(options.rootFolder); 755 | 756 | //Make sure theme files exist and save their contents globally 757 | readTheme(); 758 | 759 | _sg.renderer = new markdown.Renderer(); 760 | //Add custom markdown rendering formatters 761 | _sg.renderer = formatters.register(_sg.renderer, options); 762 | //Overriding any custom ones since these are crucial to this application 763 | options.markedOptions.renderer = _sg.renderer; 764 | options.markedOptions.breaks = true; 765 | //Set markdown options and set renderer to the custom one defined here 766 | markdown.setOptions(options.markedOptions); 767 | 768 | //Walk the file tree 769 | const walker = walk.walk(_sg.root, options.walkerOptions); 770 | 771 | try { 772 | return walkFiles(walker, function(fileContents) { 773 | const json = convertHTMLtoJSON('
      \n' + fileContents + '
      '); 774 | // Resolve files 775 | Promise.all(saveFiles(json, options)) 776 | .then(() => callback(json)); 777 | }); 778 | } 779 | catch(err) { 780 | throw new Error(err); 781 | } 782 | } 783 | 784 | module.exports.create = function(argv, customOptions) { 785 | //Assume args is actually customOptions if its an object 786 | if (_.isObject(argv)) { 787 | customOptions = argv; 788 | } 789 | else if (! _.isArray(argv)) { 790 | argv = [argv]; 791 | } 792 | return new Promise(function(resolve, reject) { 793 | var data; 794 | try { 795 | init(argv, customOptions, (data) => { 796 | return resolve(data); 797 | }); 798 | } 799 | catch(err){ 800 | return reject(err); 801 | } 802 | } 803 | ); 804 | }; 805 | 806 | /** 807 | * Initialize automatically if not being imported 808 | */ 809 | (function(){ 810 | if (!module.parent) { 811 | init(process.argv.slice(2)); 812 | } 813 | }()); 814 | -------------------------------------------------------------------------------- /lib/swag.js: -------------------------------------------------------------------------------- 1 | /* 2 | Swag v0.6.1 3 | Copyright 2012 Elving Rodriguez 4 | Available under MIT license 5 | */ 6 | 7 | 8 | (function() { 9 | var Dates, HTML, Swag, Utils, 10 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 11 | 12 | if (typeof window !== "undefined" && window !== null) { 13 | window.Swag = Swag = {}; 14 | } else if (typeof module !== "undefined" && module !== null) { 15 | module.exports = Swag = {}; 16 | } 17 | 18 | Swag.helpers = {}; 19 | 20 | Swag.addHelper = function(name, helper, argTypes) { 21 | if (argTypes == null) { 22 | argTypes = []; 23 | } 24 | if (!(argTypes instanceof Array)) { 25 | argTypes = [argTypes]; 26 | } 27 | return Swag.helpers[name] = function() { 28 | var arg, args, resultArgs, _i, _len; 29 | Utils.verify(name, arguments, argTypes); 30 | args = Array.prototype.slice.apply(arguments); 31 | resultArgs = []; 32 | for (_i = 0, _len = args.length; _i < _len; _i++) { 33 | arg = args[_i]; 34 | if (!Utils.isHandlebarsSpecific(arg)) { 35 | arg = Utils.result(arg); 36 | } 37 | resultArgs.push(arg); 38 | } 39 | return helper.apply(this, resultArgs); 40 | }; 41 | }; 42 | 43 | Swag.registerHelpers = function(localHandlebars) { 44 | var helper, name, _ref, _results; 45 | if (localHandlebars) { 46 | Swag.Handlebars = localHandlebars; 47 | } else { 48 | if (typeof window !== "undefined" && window !== null) { 49 | if (window.Ember != null) { 50 | Swag.Handlebars = Ember.Handlebars; 51 | } else { 52 | Swag.Handlebars = window.Handlebars; 53 | } 54 | } else if (typeof module !== "undefined" && module !== null) { 55 | Swag.Handlebars = require('handlebars'); 56 | } 57 | } 58 | Swag.registerHelper = function(name, helper) { 59 | if ((typeof window !== "undefined" && window !== null) && window.Ember) { 60 | return Swag.Handlebars.helper(name, helper); 61 | } else { 62 | return Swag.Handlebars.registerHelper(name, helper); 63 | } 64 | }; 65 | _ref = Swag.helpers; 66 | _results = []; 67 | for (name in _ref) { 68 | helper = _ref[name]; 69 | _results.push(Swag.registerHelper(name, helper)); 70 | } 71 | return _results; 72 | }; 73 | 74 | Swag.Config = { 75 | partialsPath: '', 76 | precompiledTemplates: true 77 | }; 78 | 79 | Utils = {}; 80 | 81 | Utils.isHandlebarsSpecific = function(value) { 82 | return (value && (value.fn != null)) || (value && (value.hash != null)); 83 | }; 84 | 85 | Utils.isUndefined = function(value) { 86 | return (value === void 0 || value === null) || Utils.isHandlebarsSpecific(value); 87 | }; 88 | 89 | Utils.safeString = function(str) { 90 | return new Swag.Handlebars.SafeString(str); 91 | }; 92 | 93 | Utils.trim = function(str) { 94 | var trim; 95 | trim = /\S/.test("\xA0") ? /^[\s\xA0]+|[\s\xA0]+$/g : /^\s+|\s+$/g; 96 | return str.toString().replace(trim, ''); 97 | }; 98 | 99 | Utils.isFunc = function(value) { 100 | return typeof value === 'function'; 101 | }; 102 | 103 | Utils.isString = function(value) { 104 | return typeof value === 'string'; 105 | }; 106 | 107 | Utils.result = function(value) { 108 | if (Utils.isFunc(value)) { 109 | return value(); 110 | } else { 111 | return value; 112 | } 113 | }; 114 | 115 | Utils.err = function(msg) { 116 | throw new Error(msg); 117 | }; 118 | 119 | Utils.verify = function(name, fnArg, argTypes) { 120 | var arg, i, msg, _i, _len, _results; 121 | if (argTypes == null) { 122 | argTypes = []; 123 | } 124 | fnArg = Array.prototype.slice.apply(fnArg).slice(0, argTypes.length); 125 | _results = []; 126 | for (i = _i = 0, _len = fnArg.length; _i < _len; i = ++_i) { 127 | arg = fnArg[i]; 128 | msg = '{{' + name + '}} requires ' + argTypes.length + ' arguments ' + argTypes.join(', ') + '.'; 129 | if (argTypes[i].indexOf('safe:') > -1) { 130 | if (Utils.isHandlebarsSpecific(arg)) { 131 | _results.push(Utils.err(msg)); 132 | } else { 133 | _results.push(void 0); 134 | } 135 | } else { 136 | if (Utils.isUndefined(arg)) { 137 | _results.push(Utils.err(msg)); 138 | } else { 139 | _results.push(void 0); 140 | } 141 | } 142 | } 143 | return _results; 144 | }; 145 | 146 | Swag.addHelper('lowercase', function(str) { 147 | return str.toLowerCase(); 148 | }, 'string'); 149 | 150 | Swag.addHelper('uppercase', function(str) { 151 | return str.toUpperCase(); 152 | }, 'string'); 153 | 154 | Swag.addHelper('capitalizeFirst', function(str) { 155 | return str.charAt(0).toUpperCase() + str.slice(1); 156 | }, 'string'); 157 | 158 | Swag.addHelper('capitalizeEach', function(str) { 159 | return str.replace(/\w\S*/g, function(txt) { 160 | return txt.charAt(0).toUpperCase() + txt.substr(1); 161 | }); 162 | }, 'string'); 163 | 164 | Swag.addHelper('titleize', function(str) { 165 | var capitalize, title, word, words; 166 | title = str.replace(/[ \-_]+/g, ' '); 167 | words = title.match(/\w+/g) || []; 168 | capitalize = function(word) { 169 | return word.charAt(0).toUpperCase() + word.slice(1); 170 | }; 171 | return ((function() { 172 | var _i, _len, _results; 173 | _results = []; 174 | for (_i = 0, _len = words.length; _i < _len; _i++) { 175 | word = words[_i]; 176 | _results.push(capitalize(word)); 177 | } 178 | return _results; 179 | })()).join(' '); 180 | }, 'string'); 181 | 182 | Swag.addHelper('sentence', function(str) { 183 | return str.replace(/((?:\S[^\.\?\!]*)[\.\?\!]*)/g, function(txt) { 184 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 185 | }); 186 | }, 'string'); 187 | 188 | Swag.addHelper('reverse', function(str) { 189 | return str.split('').reverse().join(''); 190 | }, 'string'); 191 | 192 | Swag.addHelper('truncate', function(str, length, omission) { 193 | if (Utils.isUndefined(omission)) { 194 | omission = ''; 195 | } 196 | if (str.length > length) { 197 | return str.substring(0, length - omission.length) + omission; 198 | } else { 199 | return str; 200 | } 201 | }, ['string', 'number']); 202 | 203 | Swag.addHelper('center', function(str, spaces) { 204 | var i, space; 205 | spaces = Utils.result(spaces); 206 | space = ''; 207 | i = 0; 208 | while (i < spaces) { 209 | space += ' '; 210 | i++; 211 | } 212 | return "" + space + str + space; 213 | }, 'string'); 214 | 215 | Swag.addHelper('newLineToBr', function(str) { 216 | return str.replace(/\r?\n|\r/g, '
      '); 217 | }, 'string'); 218 | 219 | Swag.addHelper('sanitize', function(str, replaceWith) { 220 | if (Utils.isUndefined(replaceWith)) { 221 | replaceWith = '-'; 222 | } 223 | return str.replace(/[^a-z0-9]/gi, replaceWith); 224 | }, 'string'); 225 | 226 | Swag.addHelper('first', function(array, count) { 227 | if (!Utils.isUndefined(count)) { 228 | count = parseFloat(count); 229 | } 230 | if (Utils.isUndefined(count)) { 231 | return array[0]; 232 | } else { 233 | return array.slice(0, count); 234 | } 235 | }, 'array'); 236 | 237 | Swag.addHelper('withFirst', function(array, count, options) { 238 | var item, result; 239 | if (!Utils.isUndefined(count)) { 240 | count = parseFloat(count); 241 | } 242 | if (Utils.isUndefined(count)) { 243 | options = count; 244 | return options.fn(array[0]); 245 | } else { 246 | array = array.slice(0, count); 247 | result = ''; 248 | for (item in array) { 249 | result += options.fn(array[item]); 250 | } 251 | return result; 252 | } 253 | }, 'array'); 254 | 255 | Swag.addHelper('last', function(array, count) { 256 | if (!Utils.isUndefined(count)) { 257 | count = parseFloat(count); 258 | } 259 | if (Utils.isUndefined(count)) { 260 | return array[array.length - 1]; 261 | } else { 262 | return array.slice(-count); 263 | } 264 | }, 'array'); 265 | 266 | Swag.addHelper('withLast', function(array, count, options) { 267 | var item, result; 268 | if (!Utils.isUndefined(count)) { 269 | count = parseFloat(count); 270 | } 271 | if (Utils.isUndefined(count)) { 272 | options = count; 273 | return options.fn(array[array.length - 1]); 274 | } else { 275 | array = array.slice(-count); 276 | result = ''; 277 | for (item in array) { 278 | result += options.fn(array[item]); 279 | } 280 | return result; 281 | } 282 | }, 'array'); 283 | 284 | Swag.addHelper('after', function(array, count) { 285 | if (!Utils.isUndefined(count)) { 286 | count = parseFloat(count); 287 | } 288 | return array.slice(count); 289 | }, ['array', 'number']); 290 | 291 | Swag.addHelper('withAfter', function(array, count, options) { 292 | var item, result; 293 | if (!Utils.isUndefined(count)) { 294 | count = parseFloat(count); 295 | } 296 | array = array.slice(count); 297 | result = ''; 298 | for (item in array) { 299 | result += options.fn(array[item]); 300 | } 301 | return result; 302 | }, ['array', 'number']); 303 | 304 | Swag.addHelper('before', function(array, count) { 305 | if (!Utils.isUndefined(count)) { 306 | count = parseFloat(count); 307 | } 308 | return array.slice(0, -count); 309 | }, ['array', 'number']); 310 | 311 | Swag.addHelper('withBefore', function(array, count, options) { 312 | var item, result; 313 | if (!Utils.isUndefined(count)) { 314 | count = parseFloat(count); 315 | } 316 | array = array.slice(0, -count); 317 | result = ''; 318 | for (item in array) { 319 | result += options.fn(array[item]); 320 | } 321 | return result; 322 | }, ['array', 'number']); 323 | 324 | Swag.addHelper('join', function(array, separator) { 325 | return array.join(Utils.isUndefined(separator) ? ' ' : separator); 326 | }, 'array'); 327 | 328 | Swag.addHelper('sort', function(array, field) { 329 | if (Utils.isUndefined(field)) { 330 | return array.sort(); 331 | } else { 332 | return array.sort(function(a, b) { 333 | return a[field] > b[field]; 334 | }); 335 | } 336 | }, 'array'); 337 | 338 | Swag.addHelper('withSort', function(array, field, options) { 339 | var item, result, _i, _len; 340 | result = ''; 341 | if (Utils.isUndefined(field)) { 342 | options = field; 343 | array = array.sort(); 344 | for (_i = 0, _len = array.length; _i < _len; _i++) { 345 | item = array[_i]; 346 | result += options.fn(item); 347 | } 348 | } else { 349 | array = array.sort(function(a, b) { 350 | return a[field] > b[field]; 351 | }); 352 | for (item in array) { 353 | result += options.fn(array[item]); 354 | } 355 | } 356 | return result; 357 | }, 'array'); 358 | 359 | Swag.addHelper('length', function(array) { 360 | return array.length; 361 | }, 'array'); 362 | 363 | Swag.addHelper('lengthEqual', function(array, length, options) { 364 | if (!Utils.isUndefined(length)) { 365 | length = parseFloat(length); 366 | } 367 | if (array.length === length) { 368 | return options.fn(this); 369 | } else { 370 | return options.inverse(this); 371 | } 372 | }, ['array', 'number']); 373 | 374 | Swag.addHelper('empty', function(array, options) { 375 | if (!array || array.length <= 0) { 376 | return options.fn(this); 377 | } else { 378 | return options.inverse(this); 379 | } 380 | }, 'safe:array'); 381 | 382 | Swag.addHelper('any', function(array, options) { 383 | if (array && array.length > 0) { 384 | return options.fn(this); 385 | } else { 386 | return options.inverse(this); 387 | } 388 | }, 'safe:array'); 389 | 390 | Swag.addHelper('inArray', function(array, value, options) { 391 | if (__indexOf.call(array, value) >= 0) { 392 | return options.fn(this); 393 | } else { 394 | return options.inverse(this); 395 | } 396 | }, ['array', 'string|number']); 397 | 398 | Swag.addHelper('eachIndex', function(array, options) { 399 | var index, result, value, _i, _len; 400 | result = ''; 401 | for (index = _i = 0, _len = array.length; _i < _len; index = ++_i) { 402 | value = array[index]; 403 | result += options.fn({ 404 | item: value, 405 | index: index 406 | }); 407 | } 408 | return result; 409 | }, 'array'); 410 | 411 | Swag.addHelper('eachProperty', function(obj, options) { 412 | var key, result, value; 413 | result = ''; 414 | for (key in obj) { 415 | value = obj[key]; 416 | result += options.fn({ 417 | key: key, 418 | value: value 419 | }); 420 | } 421 | return result; 422 | }, 'object'); 423 | 424 | Swag.addHelper('add', function(value, addition) { 425 | value = parseFloat(value); 426 | addition = parseFloat(addition); 427 | return value + addition; 428 | }, ['number', 'number']); 429 | 430 | Swag.addHelper('subtract', function(value, substraction) { 431 | value = parseFloat(value); 432 | substraction = parseFloat(substraction); 433 | return value - substraction; 434 | }, ['number', 'number']); 435 | 436 | Swag.addHelper('divide', function(value, divisor) { 437 | value = parseFloat(value); 438 | divisor = parseFloat(divisor); 439 | return value / divisor; 440 | }, ['number', 'number']); 441 | 442 | Swag.addHelper('multiply', function(value, multiplier) { 443 | value = parseFloat(value); 444 | multiplier = parseFloat(multiplier); 445 | return value * multiplier; 446 | }, ['number', 'number']); 447 | 448 | Swag.addHelper('floor', function(value) { 449 | value = parseFloat(value); 450 | return Math.floor(value); 451 | }, 'number'); 452 | 453 | Swag.addHelper('ceil', function(value) { 454 | value = parseFloat(value); 455 | return Math.ceil(value); 456 | }, 'number'); 457 | 458 | Swag.addHelper('round', function(value) { 459 | value = parseFloat(value); 460 | return Math.round(value); 461 | }, 'number'); 462 | 463 | Swag.addHelper('toFixed', function(number, digits) { 464 | number = parseFloat(number); 465 | digits = Utils.isUndefined(digits) ? 0 : digits; 466 | return number.toFixed(digits); 467 | }, 'number'); 468 | 469 | Swag.addHelper('toPrecision', function(number, precision) { 470 | number = parseFloat(number); 471 | precision = Utils.isUndefined(precision) ? 1 : precision; 472 | return number.toPrecision(precision); 473 | }, 'number'); 474 | 475 | Swag.addHelper('toExponential', function(number, fractions) { 476 | number = parseFloat(number); 477 | fractions = Utils.isUndefined(fractions) ? 0 : fractions; 478 | return number.toExponential(fractions); 479 | }, 'number'); 480 | 481 | Swag.addHelper('toInt', function(number) { 482 | return parseInt(number, 10); 483 | }, 'number'); 484 | 485 | Swag.addHelper('toFloat', function(number) { 486 | return parseFloat(number); 487 | }, 'number'); 488 | 489 | Swag.addHelper('digitGrouping', function(number, separator) { 490 | number = parseFloat(number); 491 | separator = Utils.isUndefined(separator) ? ',' : separator; 492 | return number.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + separator); 493 | }, 'number'); 494 | 495 | Swag.addHelper('is', function(value, test, options) { 496 | if (value && value === test) { 497 | return options.fn(this); 498 | } else { 499 | return options.inverse(this); 500 | } 501 | }, ['safe:string|number', 'safe:string|number']); 502 | 503 | Swag.addHelper('isnt', function(value, test, options) { 504 | if (!value || value !== test) { 505 | return options.fn(this); 506 | } else { 507 | return options.inverse(this); 508 | } 509 | }, ['safe:string|number', 'safe:string|number']); 510 | 511 | Swag.addHelper('gt', function(value, test, options) { 512 | if (value > test) { 513 | return options.fn(this); 514 | } else { 515 | return options.inverse(this); 516 | } 517 | }, ['safe:string|number', 'safe:string|number']); 518 | 519 | Swag.addHelper('gte', function(value, test, options) { 520 | if (value >= test) { 521 | return options.fn(this); 522 | } else { 523 | return options.inverse(this); 524 | } 525 | }, ['safe:string|number', 'safe:string|number']); 526 | 527 | Swag.addHelper('lt', function(value, test, options) { 528 | if (value < test) { 529 | return options.fn(this); 530 | } else { 531 | return options.inverse(this); 532 | } 533 | }, ['safe:string|number', 'safe:string|number']); 534 | 535 | Swag.addHelper('lte', function(value, test, options) { 536 | if (value <= test) { 537 | return options.fn(this); 538 | } else { 539 | return options.inverse(this); 540 | } 541 | }, ['safe:string|number', 'safe:string|number']); 542 | 543 | Swag.addHelper('or', function(testA, testB, options) { 544 | if (testA || testB) { 545 | return options.fn(this); 546 | } else { 547 | return options.inverse(this); 548 | } 549 | }, ['safe:string|number', 'safe:string|number']); 550 | 551 | Swag.addHelper('and', function(testA, testB, options) { 552 | if (testA && testB) { 553 | return options.fn(this); 554 | } else { 555 | return options.inverse(this); 556 | } 557 | }, ['safe:string|number', 'safe:string|number']); 558 | 559 | Dates = {}; 560 | 561 | Dates.padNumber = function(num, count, padCharacter) { 562 | var lenDiff, padding; 563 | if (typeof padCharacter === 'undefined') { 564 | padCharacter = '0'; 565 | } 566 | lenDiff = count - String(num).length; 567 | padding = ''; 568 | if (lenDiff > 0) { 569 | while (lenDiff--) { 570 | padding += padCharacter; 571 | } 572 | } 573 | return padding + num; 574 | }; 575 | 576 | Dates.dayOfYear = function(date) { 577 | var oneJan; 578 | oneJan = new Date(date.getFullYear(), 0, 1); 579 | return Math.ceil((date - oneJan) / 86400000); 580 | }; 581 | 582 | Dates.weekOfYear = function(date) { 583 | var oneJan; 584 | oneJan = new Date(date.getFullYear(), 0, 1); 585 | return Math.ceil((((date - oneJan) / 86400000) + oneJan.getDay() + 1) / 7); 586 | }; 587 | 588 | Dates.isoWeekOfYear = function(date) { 589 | var dayDiff, dayNr, jan4, target; 590 | target = new Date(date.valueOf()); 591 | dayNr = (date.getDay() + 6) % 7; 592 | target.setDate(target.getDate() - dayNr + 3); 593 | jan4 = new Date(target.getFullYear(), 0, 4); 594 | dayDiff = (target - jan4) / 86400000; 595 | return 1 + Math.ceil(dayDiff / 7); 596 | }; 597 | 598 | Dates.tweleveHour = function(date) { 599 | if (date.getHours() > 12) { 600 | return date.getHours() - 12; 601 | } else { 602 | return date.getHours(); 603 | } 604 | }; 605 | 606 | Dates.timeZoneOffset = function(date) { 607 | var hoursDiff, result; 608 | hoursDiff = -date.getTimezoneOffset() / 60; 609 | result = Dates.padNumber(Math.abs(hoursDiff), 4); 610 | return (hoursDiff > 0 ? '+' : '-') + result; 611 | }; 612 | 613 | Dates.format = function(date, format) { 614 | return format.replace(Dates.formats, function(m, p) { 615 | switch (p) { 616 | case 'a': 617 | return Dates.abbreviatedWeekdays[date.getDay()]; 618 | case 'A': 619 | return Dates.fullWeekdays[date.getDay()]; 620 | case 'b': 621 | return Dates.abbreviatedMonths[date.getMonth()]; 622 | case 'B': 623 | return Dates.fullMonths[date.getMonth()]; 624 | case 'c': 625 | return date.toLocaleString(); 626 | case 'C': 627 | return Math.round(date.getFullYear() / 100); 628 | case 'd': 629 | return Dates.padNumber(date.getDate(), 2); 630 | case 'D': 631 | return Dates.format(date, '%m/%d/%y'); 632 | case 'e': 633 | return Dates.padNumber(date.getDate(), 2, ' '); 634 | case 'F': 635 | return Dates.format(date, '%Y-%m-%d'); 636 | case 'h': 637 | return Dates.format(date, '%b'); 638 | case 'H': 639 | return Dates.padNumber(date.getHours(), 2); 640 | case 'I': 641 | return Dates.padNumber(Dates.tweleveHour(date), 2); 642 | case 'j': 643 | return Dates.padNumber(Dates.dayOfYear(date), 3); 644 | case 'k': 645 | return Dates.padNumber(date.getHours(), 2, ' '); 646 | case 'l': 647 | return Dates.padNumber(Dates.tweleveHour(date), 2, ' '); 648 | case 'L': 649 | return Dates.padNumber(date.getMilliseconds(), 3); 650 | case 'm': 651 | return Dates.padNumber(date.getMonth() + 1, 2); 652 | case 'M': 653 | return Dates.padNumber(date.getMinutes(), 2); 654 | case 'n': 655 | return '\n'; 656 | case 'p': 657 | if (date.getHours() > 11) { 658 | return 'PM'; 659 | } else { 660 | return 'AM'; 661 | } 662 | case 'P': 663 | return Dates.format(date, '%p').toLowerCase(); 664 | case 'r': 665 | return Dates.format(date, '%I:%M:%S %p'); 666 | case 'R': 667 | return Dates.format(date, '%H:%M'); 668 | case 's': 669 | return date.getTime() / 1000; 670 | case 'S': 671 | return Dates.padNumber(date.getSeconds(), 2); 672 | case 't': 673 | return '\t'; 674 | case 'T': 675 | return Dates.format(date, '%H:%M:%S'); 676 | case 'u': 677 | if (date.getDay() === 0) { 678 | return 7; 679 | } else { 680 | return date.getDay(); 681 | } 682 | case 'U': 683 | return Dates.padNumber(Dates.weekOfYear(date), 2); 684 | case 'v': 685 | return Dates.format(date, '%e-%b-%Y'); 686 | case 'V': 687 | return Dates.padNumber(Dates.isoWeekOfYear(date), 2); 688 | case 'W': 689 | return Dates.padNumber(Dates.weekOfYear(date), 2); 690 | case 'w': 691 | return Dates.padNumber(date.getDay(), 2); 692 | case 'x': 693 | return date.toLocaleDateString(); 694 | case 'X': 695 | return date.toLocaleTimeString(); 696 | case 'y': 697 | return String(date.getFullYear()).substring(2); 698 | case 'Y': 699 | return date.getFullYear(); 700 | case 'z': 701 | return Dates.timeZoneOffset(date); 702 | default: 703 | return match; 704 | } 705 | }); 706 | }; 707 | 708 | Dates.formats = /%(a|A|b|B|c|C|d|D|e|F|h|H|I|j|k|l|L|m|M|n|p|P|r|R|s|S|t|T|u|U|v|V|W|w|x|X|y|Y|z)/g; 709 | 710 | Dates.abbreviatedWeekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat']; 711 | 712 | Dates.fullWeekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 713 | 714 | Dates.abbreviatedMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 715 | 716 | Dates.fullMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 717 | 718 | Swag.addHelper('formatDate', function(date, format) { 719 | date = new Date(date); 720 | return Dates.format(date, format); 721 | }, ['string|number|date', 'string']); 722 | 723 | Swag.addHelper('now', function(format) { 724 | var date; 725 | date = new Date(); 726 | if (Utils.isUndefined(format)) { 727 | return date; 728 | } else { 729 | return Dates.format(date, format); 730 | } 731 | }); 732 | 733 | Swag.addHelper('timeago', function(date) { 734 | var interval, seconds; 735 | date = new Date(date); 736 | seconds = Math.floor((new Date() - date) / 1000); 737 | interval = Math.floor(seconds / 31536000); 738 | if (interval > 1) { 739 | return "" + interval + " years ago"; 740 | } 741 | interval = Math.floor(seconds / 2592000); 742 | if (interval > 1) { 743 | return "" + interval + " months ago"; 744 | } 745 | interval = Math.floor(seconds / 86400); 746 | if (interval > 1) { 747 | return "" + interval + " days ago"; 748 | } 749 | interval = Math.floor(seconds / 3600); 750 | if (interval > 1) { 751 | return "" + interval + " hours ago"; 752 | } 753 | interval = Math.floor(seconds / 60); 754 | if (interval > 1) { 755 | return "" + interval + " minutes ago"; 756 | } 757 | if (Math.floor(seconds) === 0) { 758 | return 'Just now'; 759 | } else { 760 | return Math.floor(seconds) + ' seconds ago'; 761 | } 762 | }, 'string|number|date'); 763 | 764 | Swag.addHelper('inflect', function(count, singular, plural, include) { 765 | var word; 766 | count = parseFloat(count); 767 | word = count > 1 || count === 0 ? plural : singular; 768 | if (Utils.isUndefined(include) || include === false) { 769 | return word; 770 | } else { 771 | return "" + count + " " + word; 772 | } 773 | }, ['number', 'string', 'string']); 774 | 775 | Swag.addHelper('ordinalize', function(value) { 776 | var normal, _ref; 777 | value = parseFloat(value); 778 | normal = Math.abs(Math.round(value)); 779 | if (_ref = normal % 100, __indexOf.call([11, 12, 13], _ref) >= 0) { 780 | return "" + value + "th"; 781 | } else { 782 | switch (normal % 10) { 783 | case 1: 784 | return "" + value + "st"; 785 | case 2: 786 | return "" + value + "nd"; 787 | case 3: 788 | return "" + value + "rd"; 789 | default: 790 | return "" + value + "th"; 791 | } 792 | } 793 | }, 'number'); 794 | 795 | HTML = {}; 796 | 797 | HTML.parseAttributes = function(hash) { 798 | return Object.keys(hash).map(function(key) { 799 | return "" + key + "=\"" + hash[key] + "\""; 800 | }).join(' '); 801 | }; 802 | 803 | Swag.addHelper('ul', function(context, options) { 804 | return ("
        ") + context.map(function(item) { 805 | return "
      • " + (options.fn(Utils.result(item))) + "
      • "; 806 | }).join('\n') + "
      "; 807 | }); 808 | 809 | Swag.addHelper('ol', function(context, options) { 810 | return ("
        ") + context.map(function(item) { 811 | return "
      1. " + (options.fn(Utils.result(item))) + "
      2. "; 812 | }).join('\n') + "
      "; 813 | }); 814 | 815 | Swag.addHelper('br', function(count, options) { 816 | var br, i; 817 | br = '
      '; 818 | if (!Utils.isUndefined(count)) { 819 | i = 0; 820 | while (i < (parseFloat(count)) - 1) { 821 | br += '
      '; 822 | i++; 823 | } 824 | } 825 | return Utils.safeString(br); 826 | }); 827 | 828 | Swag.addHelper('log', function(value) { 829 | return console.log(value); 830 | }, 'string|number|boolean|array|object'); 831 | 832 | Swag.addHelper('debug', function(value) { 833 | console.log('Context: ', this); 834 | if (!Utils.isUndefined(value)) { 835 | console.log('Value: ', value); 836 | } 837 | return console.log('-----------------------------------------------'); 838 | }); 839 | 840 | Swag.addHelper('default', function(value, defaultValue) { 841 | return value || defaultValue; 842 | }, 'safe:string|number', 'string|number'); 843 | 844 | if (typeof Ember === "undefined" || Ember === null) { 845 | Swag.addHelper('partial', function(name, data, template) { 846 | var path; 847 | path = Swag.Config.partialsPath + name; 848 | if (Swag.Handlebars.partials[name] == null) { 849 | if (!Utils.isUndefined(template)) { 850 | if (Utils.isString(template)) { 851 | template = Swag.Handlebars.compile(template); 852 | } 853 | Swag.Handlebars.registerPartial(name, template); 854 | } else if ((typeof define !== "undefined" && define !== null) && (Utils.isFunc(define)) && define.amd) { 855 | if (!Swag.Config.precompiledTemplates) { 856 | path = "!text" + path; 857 | } 858 | require([path], function(template) { 859 | if (Utils.isString(template)) { 860 | template = Swag.Handlebars.compile(template); 861 | } 862 | return Swag.Handlebars.registerPartial(name, template); 863 | }); 864 | } else if (typeof require !== "undefined" && require !== null) { 865 | template = require(path); 866 | if (Utils.isString(template)) { 867 | template = Swag.Handlebars.compile(template); 868 | } 869 | Swag.Handlebars.registerPartial(name, template); 870 | } else { 871 | Utils.err('{{partial}} no amd or commonjs module support found.'); 872 | } 873 | } 874 | return Utils.safeString(Swag.Handlebars.partials[name](data)); 875 | }, 'string'); 876 | } 877 | 878 | }).call(this); 879 | --------------------------------------------------------------------------------