├── .npmignore ├── styles ├── abstracts │ ├── mixins.sass │ ├── functions.sass │ ├── variables-layout.sass │ └── variables-colors.sass ├── components │ ├── tag.sass │ ├── details.sass │ ├── method-parameter.sass │ ├── tags.sass │ ├── code.sass │ ├── footer.sass │ ├── side-nav.sass │ ├── mermaid.sass │ ├── params.sass │ ├── button.sass │ ├── member.sass │ ├── top-navbar.sass │ └── sidebar.sass ├── app.sass └── base │ ├── content.sass │ ├── layout.sass │ └── landing.sass ├── tmpl ├── content.tmpl ├── name-link.tmpl ├── example.tmpl ├── tutorial.tmpl ├── modifies.tmpl ├── source.tmpl ├── type.tmpl ├── augments.tmpl ├── mainpage.tmpl ├── react-component.tmpl ├── returns.tmpl ├── landing.tmpl ├── examples.tmpl ├── exceptions.tmpl ├── vue-component.tmpl ├── head.tmpl ├── property.tmpl ├── param.tmpl ├── members.tmpl ├── proptypes.tmpl ├── layout.tmpl ├── properties.tmpl ├── topnav.tmpl ├── params.tmpl ├── details.tmpl ├── method.tmpl └── container.tmpl ├── scripts ├── app.js ├── sidebar-active.js ├── hamburger.js └── side-nav.js ├── fixtures ├── tutorials │ └── my-awesome-tutorial.md ├── typescript │ ├── regular-class.ts │ ├── index.ts │ ├── functions.ts │ ├── type4.js │ ├── type5.js │ ├── protected-member.js │ ├── members.ts │ ├── static-member.js │ ├── type6.js │ ├── abstract-class.ts │ ├── type.ts │ ├── interface3.js │ ├── type1.js │ ├── index.doc.md │ ├── entity.ts │ ├── type3.js │ ├── type2.js │ ├── type7.js │ ├── interface1.js │ ├── interface.ts │ ├── interface2.js │ └── class.ts ├── examples │ └── class-with-load.js ├── component.jsx └── component.vue ├── .babelrc ├── readme ├── class.png ├── logo.png ├── component.png └── with-mermaid.png ├── commitlint.config.js ├── typedef-import.js ├── static ├── styles │ ├── iframe.css │ ├── reset.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css └── scripts │ ├── linenumber.js │ ├── search.js │ └── app.min.js ├── .releaserc ├── category.js ├── load.js ├── src ├── component-renderer.jsx ├── vue-wrapper.js └── react-wrapper.jsx ├── jsdoc.json ├── .vscode └── launch.json ├── LICENSE ├── lib ├── load │ ├── index.js │ └── fill-component-preview.js ├── vue-wrapper.js ├── component-renderer.js └── react-wrapper.js ├── typescript.js ├── .eslintrc.js ├── .gitignore ├── component.spec.js ├── gulpfile.js ├── package.json ├── CHANGELOG.md ├── .github └── workflows │ └── push.yml ├── bundler.js ├── typescript ├── type-converter.spec.js └── type-converter.js ├── component.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | ./readme -------------------------------------------------------------------------------- /styles/abstracts/mixins.sass: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmpl/content.tmpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/app.js: -------------------------------------------------------------------------------- 1 | $().ready(() => { 2 | }) -------------------------------------------------------------------------------- /fixtures/tutorials/my-awesome-tutorial.md: -------------------------------------------------------------------------------- 1 | tutorial -------------------------------------------------------------------------------- /styles/components/tag.sass: -------------------------------------------------------------------------------- 1 | .tag 2 | text-transform: uppercase -------------------------------------------------------------------------------- /tmpl/name-link.tmpl: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /tmpl/example.tmpl: -------------------------------------------------------------------------------- 1 | 2 |
3 | -------------------------------------------------------------------------------- /readme/class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareBrothers/better-docs/HEAD/readme/class.png -------------------------------------------------------------------------------- /readme/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareBrothers/better-docs/HEAD/readme/logo.png -------------------------------------------------------------------------------- /readme/component.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareBrothers/better-docs/HEAD/readme/component.png -------------------------------------------------------------------------------- /readme/with-mermaid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareBrothers/better-docs/HEAD/readme/with-mermaid.png -------------------------------------------------------------------------------- /tmpl/tutorial.tmpl: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@commitlint/config-conventional', 4 | ], 5 | } 6 | -------------------------------------------------------------------------------- /styles/components/details.sass: -------------------------------------------------------------------------------- 1 | .details 2 | dt 3 | font-size: 20px 4 | border-left: 2px solid $blue 5 | padding-left: 16px -------------------------------------------------------------------------------- /styles/components/method-parameter.sass: -------------------------------------------------------------------------------- 1 | .method-parameter 2 | font-size: 20px 3 | label 4 | color: 18px 5 | ul 6 | margin: 0 0 0 25px -------------------------------------------------------------------------------- /fixtures/typescript/regular-class.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is amazing class 3 | * 4 | * @tutorial my-awesome-tutorial 5 | */ 6 | class SomeClass { 7 | 8 | } -------------------------------------------------------------------------------- /styles/abstracts/functions.sass: -------------------------------------------------------------------------------- 1 | @use "sass:math" 2 | 3 | @function rem($size-in-px, $context: $base-font-size) 4 | @return math.div($size-in-px, $context) * 1rem 5 | -------------------------------------------------------------------------------- /typedef-import.js: -------------------------------------------------------------------------------- 1 | exports.handlers = { 2 | beforeParse: function(e) { 3 | e.source = e.source.replace(/\/\*\*\s*?@typedef\s*?{\s*?import.*\*\//g, '') 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /styles/components/tags.sass: -------------------------------------------------------------------------------- 1 | .tag-source 2 | margin: 28px 0 3 | span 4 | display: inline-block 5 | padding: 13px 14px 6 | a 7 | color: $border 8 | &:hover 9 | color: $grey -------------------------------------------------------------------------------- /fixtures/typescript/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module Classes 3 | * @subcategory Abstract 4 | * @section Classes 5 | * @load ./index.doc.md 6 | */ 7 | 8 | export { default as AbstractClass } from './abstract-class' 9 | -------------------------------------------------------------------------------- /tmpl/modifies.tmpl: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tmpl/source.tmpl: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 |
7 |
8 |
-------------------------------------------------------------------------------- /tmpl/type.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 | | 7 | -------------------------------------------------------------------------------- /fixtures/typescript/functions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Add 3 | */ 4 | function add(x: number, y: number): number { 5 | return x + y; 6 | } 7 | 8 | /** 9 | * My Add 10 | */ 11 | let myAdd = function (x: number, y: number): number { 12 | return x + y; 13 | }; 14 | -------------------------------------------------------------------------------- /styles/components/code.sass: -------------------------------------------------------------------------------- 1 | .prettyprint 2 | border-radius: 2px 3 | background-color: $codeBg 4 | code 5 | font-family: $code-font-family 6 | pre.prettyprint 7 | li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9 8 | background: none 9 | 10 | -------------------------------------------------------------------------------- /static/styles/iframe.css: -------------------------------------------------------------------------------- 1 | .bd__button { 2 | padding: 10px 0; 3 | text-align: right; 4 | } 5 | .bd__button > a{ 6 | font-weight: 100; 7 | text-decoration: none; 8 | color: #BDC3CB; 9 | font-family: sans-serif; 10 | } 11 | .bd__button > a:hover { 12 | color: #798897; 13 | } -------------------------------------------------------------------------------- /tmpl/augments.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /scripts/sidebar-active.js: -------------------------------------------------------------------------------- 1 | $().ready(() => { 2 | $('#sidebarNav a').each((index, el) => { 3 | const href = $(el).attr('href'); 4 | if (window.location.pathname.match('/' + href)) { 5 | $(el).addClass('active') 6 | $('#sidebarNav').scrollTop($(el).offset().top - 150) 7 | } 8 | }) 9 | }) -------------------------------------------------------------------------------- /styles/components/footer.sass: -------------------------------------------------------------------------------- 1 | .footer 2 | border-top: 1px solid $border 3 | padding: 20px 4 | margin: 0 -30px -30px 5 | background: $bg 6 | .content 7 | margin-bottom: 0 8 | .fas 9 | color: $red-active 10 | a 11 | font-weight: bold 12 | &:hover 13 | color: $red-active -------------------------------------------------------------------------------- /fixtures/examples/class-with-load.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class with load feature 3 | * 4 | * @load ./docs/description.md 5 | */ 6 | class ClassWithLoad { 7 | /** 8 | * Save method 9 | * 10 | * @load ./docs/save.md 11 | */ 12 | save() { 13 | 14 | } 15 | } 16 | 17 | export default ClassWithLoad 18 | -------------------------------------------------------------------------------- /tmpl/mainpage.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 |

8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /tmpl/react-component.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /styles/abstracts/variables-layout.sass: -------------------------------------------------------------------------------- 1 | $space-xs: 2px 2 | $space-sm: 4px 3 | $space-md: 8px 4 | $space-lg: 16px 5 | $space-xl: 24px 6 | $space-xxl: 48px 7 | 8 | $main-nav-height: 120px 9 | $main-nav-height-mobile: 75px 10 | 11 | $base-font-size: 15px 12 | $header-font-family: 'TT Norms Medium', sans-serif 13 | $base-font-family: 'TT Norms Medium', sans-serif 14 | $code-font-family: 'Inconsolata', monospace -------------------------------------------------------------------------------- /styles/abstracts/variables-colors.sass: -------------------------------------------------------------------------------- 1 | 2 | $red-active: #E6282B 3 | $main-nav: #211D1A 4 | $light-grey: #F9FAFB 5 | $codeBg: #2F4858 6 | $codeFont: #c5c8c6 7 | $blue: #008DDF 8 | 9 | 10 | $black: #101010 11 | $white: #FFF 12 | $shadow: rgba(115,134,160,0.24) 13 | 14 | $bg: #F8F8F9 15 | $border: #EAEAF1 16 | $primary100: #4268F6 17 | $hoverBg: #535B8E 18 | $grey100: #192035 19 | $grey: #798897 20 | $grey80: #767676 -------------------------------------------------------------------------------- /fixtures/typescript/type4.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * Button props 5 | */ 6 | type Button = { 7 | children: ReactNode; 8 | onClick: () => void; 9 | }; 10 | ` 11 | 12 | const outputs = [ 13 | '* Button props', 14 | '* @typedef {object} Button', 15 | '* @property {ReactNode} children', 16 | '* @property {function} onClick', 17 | ] 18 | 19 | 20 | module.exports = { 21 | input, 22 | outputs, 23 | } -------------------------------------------------------------------------------- /fixtures/typescript/type5.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** Button props */ 4 | type Button = { 5 | children: ReactNode; 6 | onClick: () => void; 7 | }; 8 | ` 9 | 10 | const outputs = [ 11 | '/** Button props', 12 | '* @typedef {object} Button', 13 | '* @property {ReactNode} children', 14 | '* @property {function} onClick', 15 | '*/', 16 | ] 17 | 18 | 19 | module.exports = { 20 | input, 21 | outputs, 22 | } -------------------------------------------------------------------------------- /fixtures/typescript/protected-member.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * Class name 5 | */ 6 | class ClassName { 7 | /** 8 | * Static member 9 | */ 10 | protected protectedMember: string = 'I am protected' 11 | } 12 | ` 13 | 14 | const outputs = [ 15 | '* @type {string}', 16 | '* @protected', 17 | 'ClassName.prototype.protectedMember', 18 | ] 19 | 20 | 21 | module.exports = { 22 | input, 23 | outputs, 24 | } -------------------------------------------------------------------------------- /fixtures/typescript/members.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Class name 3 | */ 4 | class ClassName { 5 | /** 6 | * Name of a class2 7 | */ 8 | private name: string 9 | 10 | /** 11 | * Describe that 12 | */ 13 | protected soemthingIs: number 14 | 15 | /** 16 | * Staticove 17 | */ 18 | static soemthingIs: number 19 | 20 | 21 | constructor(color: string) { 22 | const a = 1 23 | } 24 | } 25 | 26 | export default ClassName -------------------------------------------------------------------------------- /fixtures/typescript/static-member.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * Class name 5 | */ 6 | class ClassName { 7 | /** 8 | * Static member 9 | */ 10 | static somethingIs: number 11 | 12 | someMethod() { 13 | const a = {g: 1} 14 | return a?.g 15 | } 16 | } 17 | ` 18 | 19 | const outputs = [ 20 | '* @type {number}', 21 | 'ClassName.somethingIs', 22 | ] 23 | 24 | 25 | module.exports = { 26 | input, 27 | outputs, 28 | } -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "+([0-9])?(.{+([0-9]),x}).x", 4 | "master", 5 | "next", 6 | "next-major", 7 | { 8 | "name": "beta", 9 | "prerelease": true 10 | } 11 | ], 12 | "plugins": [ 13 | "@semantic-release/commit-analyzer", 14 | "@semantic-release/release-notes-generator", 15 | "@semantic-release/npm", 16 | "@semantic-release/github", 17 | "@semantic-release/git" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tmpl/returns.tmpl: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | 9 | 10 |
11 | 12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /styles/components/side-nav.sass: -------------------------------------------------------------------------------- 1 | .side-nav 2 | a 3 | color: $grey 4 | overflow-wrap: break-word 5 | &:hover, &.is-active 6 | color: $red-active 7 | &.is-past 8 | opacity: 0.7 9 | h3 10 | margin: rem(24px) 0 rem(6px) 11 | color: $main-nav 12 | font-size: 12px 13 | text-transform: uppercase 14 | ul 15 | padding: 0 0 rem(4px) rem(16px) 16 | li 17 | padding: rem(3px) 0 18 | 19 | @include until($tablet) 20 | display: none -------------------------------------------------------------------------------- /category.js: -------------------------------------------------------------------------------- 1 | exports.handlers = { 2 | newDoclet: function({ doclet }) { 3 | if (doclet.tags && doclet.tags.length > 0) { 4 | const categoryTag = doclet.tags.find(tag => tag.title === 'category') 5 | if (categoryTag) { 6 | doclet.category = categoryTag.value 7 | } 8 | const subCategoryTag = doclet.tags.find(tag => tag.title === 'subcategory') 9 | if (subCategoryTag) { 10 | doclet.subCategory = subCategoryTag.value 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /fixtures/typescript/type6.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * Some comment 5 | */ 6 | export type RecordActionAPIParams = AxiosRequestConfig & { 7 | /** 8 | * Id of a record taken from {@link RecordJSON} 9 | */ 10 | recordId: string; 11 | } 12 | ` 13 | 14 | const outputs = [ 15 | '/**', 16 | '* Some comment', 17 | '* @typedef {object} RecordActionAPIParams', 18 | '* @property {string} recordId Id of a record taken from {@link RecordJSON}', 19 | '*/', 20 | ] 21 | 22 | 23 | module.exports = { 24 | input, 25 | outputs, 26 | } -------------------------------------------------------------------------------- /scripts/hamburger.js: -------------------------------------------------------------------------------- 1 | let sidebarIsVisible = false 2 | 3 | const toggleSidebar = (show = true) => { 4 | $('#sidebarNav').toggleClass('sticky', show) 5 | $('#stickyNavbarOverlay').toggleClass('active', show) 6 | $('#hamburger').toggleClass('is-active') 7 | sidebarIsVisible = show 8 | } 9 | 10 | 11 | $().ready(() => { 12 | $('#hamburger').click(() => { 13 | toggleSidebar(!sidebarIsVisible) 14 | }) 15 | 16 | $('#stickyNavbarOverlay').click(() => { 17 | if(sidebarIsVisible){ 18 | toggleSidebar(false) 19 | } 20 | }) 21 | }) -------------------------------------------------------------------------------- /fixtures/typescript/abstract-class.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition of abstract class 3 | */ 4 | abstract class AbstractClass { 5 | /** 6 | * Some property 7 | */ 8 | someProperty: number; 9 | 10 | /** 11 | * Abstract property 12 | */ 13 | abstract someAbstractProperty: string; 14 | 15 | /** 16 | * Some function 17 | */ 18 | public someFunction(): void; 19 | 20 | /** 21 | * Abstract function 22 | */ 23 | abstract someAbstractFunction(param?: string): string | void; 24 | 25 | #test: string 26 | } 27 | 28 | export default AbstractClass -------------------------------------------------------------------------------- /fixtures/typescript/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of union type definition 3 | */ 4 | type SomeCombinedType = string | number | 'Specific string' | SomeOtherType; 5 | 6 | 7 | /** 8 | * Example of simple type definition 9 | */ 10 | type SomeOtherType = boolean; 11 | 12 | /** 13 | * Example of generic type definition with T as parameter 14 | */ 15 | type SomeGenericType = T | SomeOtherGenericType | { property: T, other: string } 16 | 17 | /** 18 | * Example of generic type definition with T as parameter 19 | */ 20 | type SomeOtherGenericType = { children: T[] } -------------------------------------------------------------------------------- /fixtures/typescript/interface3.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * Represents a logger 5 | * @category Logging 6 | */ 7 | export interface ILogger { 8 | /** 9 | * Logs to the INFO channel 10 | * @param {string} module The module being logged 11 | * @param {any[]} messageOrObject The data to log 12 | */ 13 | info(module: string, ...messageOrObject: any[]); 14 | } 15 | ` 16 | 17 | const outputs = [ 18 | '* @name ILogger#info', 19 | '* @type {function}', 20 | '* @method', 21 | ] 22 | 23 | module.exports = { 24 | input, 25 | outputs, 26 | } -------------------------------------------------------------------------------- /fixtures/typescript/type1.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * Before action hook. When it is given - it is performed before the {@link ActionHandler} 5 | * method. 6 | * @alias Before 7 | * @memberof Action 8 | */ 9 | export type Before = ( 10 | /** 11 | * Request object 12 | */ 13 | request: ActionRequest 14 | ) => ActionRequest 15 | ` 16 | 17 | const outputs = [ 18 | '* @memberof Action', 19 | '* @typedef {function} Before', 20 | '* @param {ActionRequest} request Request object' 21 | ] 22 | 23 | 24 | module.exports = { 25 | input, 26 | outputs, 27 | } -------------------------------------------------------------------------------- /styles/components/mermaid.sass: -------------------------------------------------------------------------------- 1 | $fill: rgba(248,249,250,0.8) 2 | $stroke: rgba(121,136,151,0.6) 3 | $fillWrap: rgba(125,132,255,0.1) 4 | $strokeWrap: rgba(125,132,255,0.5) 5 | 6 | .mermaid 7 | .edgeLabel 8 | background: white 9 | color: $codeBg 10 | font-size: 15px 11 | font-weight: normal 12 | .node circle, .node ellipse, .node polygon, .node rect 13 | fill: $fill !important 14 | stroke: $stroke !important 15 | .cluster rect 16 | fill: $fillWrap !important 17 | stroke: $strokeWrap !important 18 | .node g.label 19 | color: $codeBg 20 | div 21 | font-weight: normal 22 | font-size: 15px -------------------------------------------------------------------------------- /styles/components/params.sass: -------------------------------------------------------------------------------- 1 | table.params, table.props 2 | border: 1px solid $border 3 | line-height: 26px 4 | thead 5 | border: none 6 | th 7 | font-weight: normal 8 | padding: 13px 26px 9 | tr 10 | border-bottom: 1px solid $border 11 | td 12 | padding: 13px 26px 13 | td.name code 14 | background: transparent 15 | padding: 0 16 | font-size: 15px 17 | color: $main-nav 18 | 19 | @for $i from 1 through 10 20 | tr.deep-level-#{$i} 21 | background: darken(#fff, $i * 2) 22 | .name code 23 | padding-left: 25px 24 | margin-left: #{($i - 1)*25}px 25 | border-left: 1px solid #DEE1E5 -------------------------------------------------------------------------------- /fixtures/typescript/index.doc.md: -------------------------------------------------------------------------------- 1 | Module header 2 | ============ 3 | 4 | Paragraphs are separated by a blank line. 5 | 6 | 2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists 7 | look like: 8 | 9 | * this one 10 | * that one 11 | * the other one 12 | 13 | Note that --- not considering the asterisk --- the actual text 14 | content starts at 4-columns in. 15 | 16 | > Block quotes are 17 | > written like so. 18 | > 19 | > They can span multiple paragraphs, 20 | > if you like. 21 | 22 | Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all 23 | in chapters 12--14"). Three dots ... will be converted to an ellipsis. 24 | Unicode is supported. ☺ 25 | -------------------------------------------------------------------------------- /load.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | const { load } = require('./lib/load') 3 | 4 | exports.defineTags = (dictionary) => { 5 | dictionary.defineTag('load', { 6 | /** 7 | * @param {import('./src/jsdoc.type').DocLet} doclet 8 | * @param {import('./src/jsdoc.type').JSDocTag} tag 9 | */ 10 | onTagged: (doclet, tag) => { 11 | const text = load(tag, doclet.meta.path) 12 | 13 | if (doclet.classdesc) { 14 | doclet.classdesc = [doclet.classdesc, text].join('\n') 15 | } else if (doclet.description || doclet.kind === 'module') { 16 | doclet.description = [doclet.description, text].join('\n') 17 | } 18 | }, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /styles/app.sass: -------------------------------------------------------------------------------- 1 | @import 'node_modules/bulma/bulma' 2 | 3 | @import 'abstracts/functions' 4 | @import 'abstracts/variables-layout' 5 | @import 'abstracts/variables-colors' 6 | @import 'abstracts/mixins' 7 | 8 | @import 'base/layout' 9 | @import 'base/content' 10 | @import 'base/landing' 11 | 12 | @import 'components/top-navbar' 13 | @import 'components/sidebar' 14 | @import 'components/side-nav' 15 | @import 'components/footer' 16 | @import 'components/member' 17 | @import 'components/params' 18 | @import 'components/code' 19 | @import 'components/button' 20 | @import 'components/tags' 21 | @import 'components/method-parameter' 22 | @import 'components/mermaid' 23 | @import 'components/tag' 24 | @import 'components/details' -------------------------------------------------------------------------------- /src/component-renderer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const DefaultWrapper = (props) => ( 4 |
{props.children}
5 | ) 6 | 7 | class ComponentRenderer extends React.Component { 8 | constructor(props) { 9 | super(props) 10 | this.Wrapper = window._CustomWrapper || DefaultWrapper 11 | this.state = { 12 | hasError: false, 13 | error: null, 14 | } 15 | } 16 | 17 | componentDidCatch(error) { 18 | console.log(error.message) 19 | } 20 | 21 | render() { 22 | const { children } = this.props 23 | return ( 24 | 25 | {children} 26 | 27 | ) 28 | } 29 | } 30 | 31 | export default ComponentRenderer 32 | -------------------------------------------------------------------------------- /tmpl/landing.tmpl: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tmpl/examples.tmpl: -------------------------------------------------------------------------------- 1 | 9 |

10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /tmpl/exceptions.tmpl: -------------------------------------------------------------------------------- 1 | 4 |
5 | 6 |
7 |
8 | 9 |
10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 | -------------------------------------------------------------------------------- /static/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | 3 | (function() { 4 | var source = document.getElementsByClassName('prettyprint source linenums'); 5 | var i = 0; 6 | var lineNumber = 0; 7 | var lineId; 8 | var lines; 9 | var totalLines; 10 | var anchorHash; 11 | 12 | if (source && source[0]) { 13 | anchorHash = document.location.hash.substring(1); 14 | lines = source[0].getElementsByTagName('li'); 15 | totalLines = lines.length; 16 | 17 | for (; i < totalLines; i++) { 18 | lineNumber++; 19 | lineId = 'line' + lineNumber; 20 | lines[i].id = lineId; 21 | if (lineId === anchorHash) { 22 | lines[i].className += ' selected'; 23 | } 24 | } 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /styles/components/button.sass: -------------------------------------------------------------------------------- 1 | .button 2 | transition: all 0.2s 3 | border-radius: 4px 4 | padding: $space-md $space-xl 5 | height: 40px 6 | border-color: $primary100 7 | color: $primary100 8 | &:hover 9 | color: $hoverBg 10 | border-color: $hoverBg 11 | transition: all 0.2s 12 | 13 | &.is-primary 14 | background-color: $primary100 15 | &:hover 16 | background-color: $hoverBg 17 | &.is-outlined 18 | border-color: $primary100 19 | color: $primary100 20 | &:hover 21 | border-color: $hoverBg 22 | color: $hoverBg 23 | background: transparent 24 | 25 | &.is-success 26 | background: #69D6D4 27 | 28 | &.is-white.is-outlined 29 | background: transparent 30 | border-color: $white 31 | 32 | & > i:first-child 33 | margin-right: $space-md 34 | margin-left: -$space-md -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "include": [ 7 | "fixtures/typescript", 8 | "fixtures/examples" 9 | ], 10 | "includePattern": "\\.(jsx|js|ts|tsx)$" 11 | }, 12 | "plugins": [ 13 | "plugins/markdown", 14 | "category", 15 | "component", 16 | "load", 17 | "typescript" 18 | ], 19 | "markdown": { 20 | "tags": ["load"] 21 | }, 22 | "opts": { 23 | "encoding": "utf8", 24 | "destination": "docs/", 25 | "recurse": true, 26 | "verbose": true, 27 | "template": "./", 28 | "tutorials": "./fixtures/tutorials" 29 | }, 30 | "templates": { 31 | "better-docs": { 32 | "name": "Sample Documentation" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Yarn test", 12 | "program": "${workspaceRoot}/node_modules/.bin/mocha", 13 | "args": [ 14 | "--recursive", "./*spec.js", "./typescript/*spec.js", 15 | ], 16 | "internalConsoleOptions": "openOnSessionStart" 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Yarn docs", 22 | "program": "${workspaceRoot}/node_modules/.bin/jsdoc", 23 | "args": [ 24 | "-c", "jsdoc.json", 25 | ], 26 | "internalConsoleOptions": "openOnSessionStart" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /fixtures/typescript/entity.ts: -------------------------------------------------------------------------------- 1 | import { RecordError } from '../adapters/base-record' 2 | 3 | /** 4 | * Error which is thrown when there are validation errors with records 5 | * @category Errors 6 | */ 7 | class ValidationError extends Error { 8 | public errors: {[key: string]: RecordError} 9 | 10 | /** 11 | * @param {String} message custom message 12 | * @param {Object} errors error messages 13 | * @param {String} errors.{...} error for particular field where ... is a 14 | * {@link BaseProperty#path} 15 | * @param {String} errors.{...}.message human readible message 16 | * @param {String} errors.{...}.type string type (i.e. required) 17 | */ 18 | constructor(message: string, errors: {[key: string]: RecordError}) { 19 | super(message) 20 | this.errors = errors 21 | this.message = message || 'Resource cannot be stored because of validation errors' 22 | this.name = 'ValidationError' 23 | } 24 | } 25 | 26 | export default ValidationError 27 | -------------------------------------------------------------------------------- /fixtures/typescript/type3.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * Type representing the Sample.Router 5 | * @memberof Sample 6 | * @alias RouterType 7 | */ 8 | export type RouterType = { 9 | assets: Array<{ 10 | path: string; 11 | src: string; 12 | }>; 13 | routes: Array<{ 14 | method: string; 15 | path: string; 16 | Controller: any; 17 | action: string; 18 | contentType?: string; 19 | }>; 20 | } 21 | ` 22 | 23 | const outputs = [ 24 | '* @memberof Sample', 25 | '* @alias RouterType', 26 | '* @typedef {object} RouterType', 27 | '* @property {Array} assets', 28 | '* @property {string} assets[].path', 29 | '* @property {string} assets[].src', 30 | '* @property {Array} routes', 31 | '* @property {string} routes[].method', 32 | '* @property {string} routes[].path', 33 | '* @property {any} routes[].Controller', 34 | '* @property {string} routes[].action', 35 | '* @property {string} [routes[].contentType]', 36 | ] 37 | 38 | 39 | module.exports = { 40 | input, 41 | outputs, 42 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 SoftwareBrothers.co 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /styles/base/content.sass: -------------------------------------------------------------------------------- 1 | .content 2 | blockquote 3 | margin: 30px 0 !important 4 | margin-bottom: 50px 5 | .signature-attributes 6 | margin-left: 8px 7 | margin-right: 3px 8 | font-style: italic 9 | header.page-title 10 | p 11 | font-size: 13px 12 | margin: 0 0 5px 13 | text-transform: uppercase 14 | header p 15 | font-size: 20px 16 | 17 | h1, header.page-title h1 18 | font-family: $header-font-family 19 | font-size: 47px 20 | font-weight: bold 21 | margin: $space-md 0 22 | h2 23 | font-size: 26px 24 | line-height: 48px 25 | font-weight: bold 26 | margin-bottom: 26px 27 | 28 | h3, h4, h5, h6 29 | font-family: $header-font-family 30 | font-weight: 900 31 | letter-spacing: 0 32 | 33 | code 34 | color: $black 35 | font-family: $code-font-family 36 | 37 | .container-overview .prettyprint:last-child 38 | margin-bottom: 50px 39 | 40 | .vertical-section 41 | padding: $space-lg 0 42 | 43 | #main-content-wrapper 44 | @include until($desktop) 45 | padding: 0 30px -------------------------------------------------------------------------------- /lib/load/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.load = void 0; 7 | /* eslint-disable @typescript-eslint/no-var-requires */ 8 | const path_1 = __importDefault(require("path")); 9 | const fs_1 = __importDefault(require("fs")); 10 | const fill_component_preview_1 = require("./fill-component-preview"); 11 | const { getParser } = require('../../node_modules/jsdoc/lib/jsdoc/util/markdown'); 12 | const markdownParser = getParser(); 13 | const mdParser = (content) => { 14 | const text = markdownParser(content); 15 | return text; 16 | }; 17 | const load = (loadTag, docletFilePath) => { 18 | const filename = loadTag.value; 19 | const filePath = path_1.default.join(docletFilePath, filename); 20 | const body = fs_1.default.readFileSync(filePath, 'utf-8'); 21 | return (0, fill_component_preview_1.fillComponentPreview)(body, mdParser); 22 | }; 23 | exports.load = load; 24 | -------------------------------------------------------------------------------- /fixtures/typescript/type2.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * ActionRequest 5 | * @memberof Action 6 | * @alias ActionRequest 7 | */ 8 | export type ActionRequest = { 9 | /** 10 | * parameters passed in an URL 11 | */ 12 | params: { 13 | /** 14 | * Id of current resource 15 | */ 16 | resourceId: string; 17 | /** 18 | * Id of current record 19 | */ 20 | recordId?: string; 21 | /** 22 | * Name of an action 23 | */ 24 | action: string; 25 | 26 | [key: string]: any; 27 | }; 28 | } 29 | ` 30 | 31 | const outputs = [ 32 | '* ActionRequest', 33 | '* @memberof Action', 34 | '* @alias ActionRequest', 35 | '* @typedef {object} ActionRequest', 36 | '* @property {object} params parameters passed in an URL', 37 | '* @property {string} params.resourceId Id of current resource', 38 | '* @property {string} [params.recordId] Id of current record', 39 | '* @property {string} params.action Name of an action', 40 | '* @property {any} params.{...}', 41 | ] 42 | 43 | 44 | module.exports = { 45 | input, 46 | outputs, 47 | } -------------------------------------------------------------------------------- /lib/load/fill-component-preview.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.fillComponentPreview = void 0; 4 | const regex = /^```reactComponent[\r\n]+(.*?)[\r\n]+```$/gms; 5 | const fillComponentPreview = (text, mdParser) => { 6 | let number = 0; 7 | const components = {}; 8 | const result = text.replace(regex, (match, group) => { 9 | const uniqId = ['BetterDocsPreviewComponent', number += 1].join(''); 10 | components[uniqId] = ` 11 |
12 | 13 | 19 | `; 20 | return uniqId; 21 | }); 22 | let markdown = mdParser(result); 23 | Object.keys(components).forEach((componentId) => { 24 | markdown = markdown.replace(componentId, components[componentId]); 25 | }); 26 | return markdown; 27 | }; 28 | exports.fillComponentPreview = fillComponentPreview; 29 | -------------------------------------------------------------------------------- /fixtures/typescript/type7.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * @alias BuildHandlerOptions 5 | * 6 | * @memberof module:admin-bro-firebase-functions 7 | */ 8 | export type BuildHandlerOptions = { 9 | /** Function region */ 10 | region: string; 11 | /** Optional before async hook which can be used to initialize database */ 12 | before?: () => Promise; 13 | /** 14 | * custom authentication options 15 | */ 16 | auth?: { 17 | /** 18 | * secret which is used to encrypt the session cookie 19 | */ 20 | secret: string; 21 | /** 22 | * authenticate function 23 | */ 24 | authenticate?: ( 25 | email: string, 26 | password: string) => Promise | CurrentAdmin | null; 27 | }; 28 | } 29 | ` 30 | 31 | const outputs = [ 32 | '/**', 33 | '* @property {object} [auth] custom authentication options', 34 | '* @property {string} auth.secret secret which is used to encrypt the session cookie', 35 | '* @property {function} [auth.authenticate] authenticate function', 36 | '*/', 37 | ] 38 | 39 | 40 | module.exports = { 41 | input, 42 | outputs, 43 | } -------------------------------------------------------------------------------- /typescript.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ts = require('typescript') 3 | 4 | const typeConverter = require('./typescript/type-converter') 5 | 6 | exports.handlers = { 7 | newDoclet: function({ doclet }) { 8 | if (doclet.tags && doclet.tags.length > 0) { 9 | const categoryTag = doclet.tags.find(tag => tag.title === 'optional') 10 | if (categoryTag) { 11 | doclet.optional = true 12 | } 13 | } 14 | }, 15 | 16 | beforeParse: function(e) { 17 | if (['.ts', '.tsx'].includes(path.extname(e.filename))) { 18 | // adding const a = 1 ensures that the comments always will be copied, 19 | // even when there is no javascript inside (just interfaces) 20 | let result = ts.transpileModule('const _____a = 1; \n' + e.source, { 21 | compilerOptions: { 22 | target: 'esnext', 23 | esModuleInterop: true, 24 | jsx: path.extname(e.filename) === '.tsx' ? 'react' : null, 25 | } 26 | }) 27 | 28 | const types = typeConverter(e.source, e.filename) 29 | let src = result.outputText 30 | e.source = src + '\n' + types 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /styles/components/member.sass: -------------------------------------------------------------------------------- 1 | .members 2 | margin-top: $space-xl 3 | 4 | .member 5 | &:not(:last-child) 6 | &:after 7 | content: "" 8 | background: $border 9 | height: 2px 10 | display: block 11 | margin: 45px -40px 40px 12 | & > .is-pulled-right 13 | position: relative 14 | z-index: 2 15 | & > .name 16 | color: $main-nav 17 | font-size: 20px 18 | line-height: 26px 19 | position: relative 20 | margin-bottom: $space-md 21 | .code-name 22 | font-family: $code-font-family 23 | display: block 24 | font-size: 25px 25 | line-height: 30px 26 | margin-top: 8px 27 | &:first-child 28 | margin-left: 0 29 | .tag 30 | position: relative 31 | top: -1px 32 | margin-right: 3px 33 | .href-link 34 | color: $main-nav 35 | position: absolute 36 | padding: 1px 37 | left: -20px 38 | top: 0 39 | bottom: 0 40 | width: 21px 41 | opacity: 0 42 | &:hover .href-link 43 | opacity: 1 44 | h5 45 | font-size: 20px 46 | & > .description 47 | p 48 | font-size: 20px 49 | margin: 25px 0 50 | margin-bottom: 25px 51 | -------------------------------------------------------------------------------- /fixtures/component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | const Bold = styled.div` 6 | background: red; 7 | color: #fff; 8 | padding: 5px; 9 | ` 10 | 11 | /** 12 | * Some documented component 13 | * 14 | * @component 15 | * @example Default example 16 | * const text = 'Meva' 17 | * return ( 18 | * 19 | * 20 | * 21 | * ) 22 | * 23 | * @example Ala ma kota 24 | * const text = 'some example text 2' 25 | * return ( 26 | * 27 | * 28 | * 29 | * ) 30 | */ 31 | const Documented = (props) => { 32 | const { text, header } = props 33 | return ( 34 |

35 | {header} 36 | 37 | {text} 38 | 39 |

40 | ) 41 | } 42 | 43 | Documented.propTypes = { 44 | /** 45 | * Text is a text 46 | */ 47 | text: PropTypes.string, 48 | header: PropTypes.string.isRequired, 49 | } 50 | 51 | Documented.defaultProps = { 52 | text: 'Hello World', 53 | } 54 | 55 | export default Documented 56 | -------------------------------------------------------------------------------- /static/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* reset css */ 2 | html, body, div, span, applet, object, iframe, 3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 4 | a, abbr, acronym, address, big, cite, code, 5 | del, dfn, em, img, ins, kbd, q, s, samp, 6 | small, strike, strong, sub, sup, tt, var, 7 | b, u, i, center, 8 | dl, dt, dd, ol, ul, li, 9 | fieldset, form, label, legend, 10 | table, caption, tbody, tfoot, thead, tr, th, td, 11 | article, aside, canvas, details, embed, 12 | figure, figcaption, footer, header, hgroup, 13 | menu, nav, output, ruby, section, summary, 14 | time, mark, audio, video { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | font-size: 100%; 19 | font: inherit; 20 | vertical-align: baseline; 21 | } 22 | /* HTML5 display-role reset for older browsers */ 23 | article, aside, details, figcaption, figure, 24 | footer, header, hgroup, menu, nav, section { 25 | display: block; 26 | } 27 | body { 28 | line-height: 1; 29 | } 30 | ol, ul { 31 | list-style: none; 32 | } 33 | blockquote, q { 34 | quotes: none; 35 | } 36 | blockquote:before, blockquote:after, 37 | q:before, q:after { 38 | content: ''; 39 | content: none; 40 | } 41 | table { 42 | border-collapse: collapse; 43 | border-spacing: 0; 44 | } 45 | -------------------------------------------------------------------------------- /styles/components/top-navbar.sass: -------------------------------------------------------------------------------- 1 | .top-nav 2 | background: $white 3 | padding: $space-md $space-xl 4 | box-shadow: 0 0 40px 0 $shadow 5 | position: fixed 6 | top: 0 7 | left: 0 8 | right: 0 9 | z-index: 5 10 | @include until($tablet) 11 | padding: $space-md 12 | 13 | h1 14 | font-size: 20px 15 | 16 | .inner 17 | display: flex 18 | align-items: center 19 | 20 | 21 | #hamburger 22 | margin-left: 0 23 | 24 | @include until($tablet) 25 | .logo 26 | display: none 27 | 28 | .menu 29 | flex-grow: 1 30 | 31 | .menu .top-buttons 32 | text-align: right 33 | margin-bottom: $space-md 34 | margin-top: $space-xs 35 | @include until($desktop) 36 | display: none 37 | .button 38 | margin-left: $space-lg 39 | 40 | .menu .navigation 41 | text-align: right 42 | margin-bottom: $space-sm 43 | .link 44 | border: none 45 | display: inline-block 46 | padding: $space-sm $space-md 47 | color: $black 48 | margin-right: $space-xs 49 | line-height: $space-xxl 50 | height: $space-xxl 51 | vertical-align: middle 52 | height: 46px 53 | &:hover:not(.no-hover) 54 | border-bottom: 2px solid $black 55 | @include until($tablet) 56 | &.user-link 57 | display: none -------------------------------------------------------------------------------- /styles/components/sidebar.sass: -------------------------------------------------------------------------------- 1 | .sidebar 2 | padding-bottom: 120px 3 | 4 | .search-wrapper 5 | // margin-bottom: rem(20px) 6 | margin: -20px -15px 21px 7 | input 8 | border-radius: 0 9 | a 10 | color: $grey 11 | overflow-wrap: break-word 12 | &:hover, &.active 13 | color: $red-active 14 | h3 15 | margin: rem(24px) 0 rem(6px) 16 | color: $main-nav 17 | font-size: 12px 18 | text-transform: uppercase 19 | ul 20 | padding: 0 0 rem(4px) rem(16px) 21 | li 22 | padding: rem(3px) 0 23 | li > ul 24 | padding: 0 0 0px 25px 25 | 26 | .category 27 | h2 28 | color: #000 29 | font-size: 20px 30 | margin-top: 40px 31 | 32 | #sidebarNav.sticky 33 | left: 0 34 | transition: left 0.5s 35 | 36 | #sidebarNav 37 | @include until($desktop) 38 | z-index: 100 39 | top: 0 40 | left: -300px 41 | position: fixed 42 | transition: left 0.5s 43 | padding: 28px 44 | width: 300px 45 | bottom: 0 46 | overflow: auto 47 | background: #fff 48 | .sidebar 49 | padding-bottom: 10px 50 | 51 | #stickyNavbarOverlay 52 | position: absolute 53 | left: 0 54 | right: 0 55 | bottom: 0 56 | top: 0 57 | z-index: 40 58 | background: rgba(0,0,0,0.2) 59 | display: none 60 | 61 | &.active 62 | display: block 63 | -------------------------------------------------------------------------------- /tmpl/vue-component.tmpl: -------------------------------------------------------------------------------- 1 | 7 |
8 |
9 | 28 | 29 | 30 | 31 | 44 |
-------------------------------------------------------------------------------- /fixtures/typescript/interface1.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * JSON representation of an {@link Action} 5 | * @see Action 6 | */ 7 | export default interface ActionJSON { 8 | /** 9 | * Unique action name 10 | */ 11 | name: string; 12 | /** 13 | * Type of an action 14 | */ 15 | actionType: 'record' | 'resource' | Array<'record' | 'resource'>; 16 | /** 17 | * Action icon 18 | */ 19 | icon?: string; 20 | /** 21 | * Action label - visible on the frontend 22 | */ 23 | label: string; 24 | /** 25 | * Guarding message 26 | */ 27 | guard?: string; 28 | /** 29 | * If action should have a filter (for resource actions) 30 | */ 31 | showFilter: boolean; 32 | /** 33 | * Action component. When set to false action will be invoked immediately after clicking it, 34 | * to put in another words: tere wont be an action view 35 | */ 36 | component?: string | false | null; 37 | } 38 | ` 39 | 40 | const outputs = [ 41 | // main interface 42 | '* JSON representation of an {@link Action}', 43 | '* @see Action', 44 | '* @interface ActionJSON', 45 | 46 | // representation of a name property 47 | '* Unique action name', 48 | '* @name ActionJSON#name', 49 | '* @type {string}', 50 | 51 | // representation for an action property 52 | '* Type of an action', 53 | '* @name ActionJSON#actionType', 54 | '* @type {\'record\' | \'resource\' | Array<\'record\' | \'resource\'>}', 55 | ] 56 | 57 | module.exports = { 58 | input, 59 | outputs, 60 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | plugins: ["@typescript-eslint", "mocha"], 4 | env: { 5 | es6: true, 6 | node: true, 7 | mocha: true, 8 | }, 9 | extends: [ 10 | "airbnb", 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:vue/essential", 13 | ], 14 | parserOptions: { 15 | ecmaVersion: 2015, 16 | sourceType: "module", 17 | }, 18 | rules: { 19 | indent: ["error", 2], 20 | "linebreak-style": ["error", "unix"], 21 | quotes: ["error", "single"], 22 | semi: ["error", "never"], 23 | "import/no-unresolved": "off", 24 | "no-underscore-dangle": "off", 25 | "guard-for-in": "off", 26 | "no-restricted-syntax": "off", 27 | "no-await-in-loop": "off", 28 | "object-curly-newline": "off", 29 | }, 30 | overrides: [ 31 | { 32 | files: [ 33 | "*-test.js", 34 | "*.spec.js", 35 | "*-test.ts", 36 | "*.spec.ts", 37 | "*.spec.tsx", 38 | "*.factory.ts", 39 | "*.factory.js", 40 | ], 41 | rules: { 42 | "no-unused-expressions": "off", 43 | "func-names": "off", 44 | "prefer-arrow-callback": "off", 45 | }, 46 | }, 47 | { 48 | files: ["*.jsx", "*.js"], 49 | rules: { "@typescript-eslint/explicit-function-return-type": "off" }, 50 | }, 51 | { 52 | files: ["*.tsx"], 53 | rules: { "react/prop-types": "off" }, 54 | }, 55 | ], 56 | globals: { 57 | expect: true, 58 | window: true, 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /styles/base/layout.sass: -------------------------------------------------------------------------------- 1 | html 2 | height: 100% 3 | width: 100% 4 | 5 | body 6 | font-family: $base-font-family 7 | font-size: $base-font-size 8 | position: relative 9 | height: 100% 10 | width: 100% 11 | overflow-x: hidden 12 | 13 | &.small-header 14 | .top-nav 15 | height: $main-nav-height-mobile 16 | #main 17 | padding-top: $main-nav-height-mobile 18 | 19 | .top-nav 20 | height: $main-nav-height 21 | @include until($desktop) 22 | height: $main-nav-height-mobile 23 | 24 | #main 25 | height: 100% 26 | display: flex 27 | flex-direction: row 28 | background: $bg 29 | padding-top: $main-nav-height 30 | overflow-x: hidden 31 | width: 100% 32 | @include until($desktop) 33 | padding-top: $main-nav-height-mobile 34 | & > .sidebar 35 | padding: 40px 30px 36 | flex-grow: 0 37 | flex-shrink: 0 38 | width: 240px 39 | border-right: 1px solid $border 40 | height: 100% 41 | overflow: auto 42 | 43 | &.tutorials 44 | width: 320px 45 | 46 | & > .core 47 | padding: 28px 48 | height: 100% 49 | overflow: auto 50 | flex-grow: 1 51 | @include until($tablet) 52 | padding: 0px 53 | & > .content 54 | background: $white 55 | padding: 40px 56 | border-radius: 4px 57 | box-shadow: 0 0 40px 0 rgba(115,134,160,0.24) 58 | & > .side-nav 59 | width: 240px 60 | padding: 40px 20px 61 | flex-grow: 0 62 | flex-shrink: 0 63 | height: 100% 64 | border-left: 1px solid $border 65 | overflow: auto 66 | -------------------------------------------------------------------------------- /tmpl/head.tmpl: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | <?js= betterDocs.title ?> <?js= title ?> 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /fixtures/typescript/interface.ts: -------------------------------------------------------------------------------- 1 | export enum PropertyPlace { 2 | show = 'show', 3 | list = 'list', 4 | edit = 'edit', 5 | filter = 'filter', 6 | } 7 | 8 | /** 9 | * JSON representation of a Property. 10 | */ 11 | interface PropertyJSON { 12 | /** 13 | * If given property should be treated as a title 14 | */ 15 | isTitle: boolean; 16 | /** 17 | * If given property should be treaten as a Id field 18 | */ 19 | isId: boolean; 20 | /** 21 | * Property position on a list 22 | */ 23 | position: number; 24 | /** 25 | * If property is sortable 26 | */ 27 | isSortable: boolean; 28 | /** 29 | * If property has restricted number of values 30 | */ 31 | availableValues: Array<{label: string; value: string}> | null; 32 | /** 33 | * Property uniq name/path 34 | */ 35 | name: string; 36 | /** 37 | * Property label 38 | */ 39 | label: string; 40 | /** 41 | * Property type 42 | */ 43 | type: string; 44 | /** 45 | * Has a name of a resource to which it is a reference. 46 | * For instance property `userId` will have here `Users` 47 | */ 48 | reference: string | null; 49 | /** 50 | * Indicates if property is an array of properties 51 | */ 52 | isArray: boolean; 53 | /** 54 | * Contain list of all sub properties 55 | */ 56 | subProperties: Array; 57 | /** 58 | * All components overriden by the user in PropertyOptions 59 | */ 60 | components?: { 61 | show?: string; 62 | edit?: string; 63 | filter?: string; 64 | list?: string; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /static/scripts/search.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const input = document.querySelector('#search') 3 | const targets = [ ...document.querySelectorAll('#sidebarNav li')] 4 | input.addEventListener('keyup', () => { 5 | // loop over each targets and hide the not corresponding ones 6 | targets.forEach(target => { 7 | if (!target.innerText.toLowerCase().includes(input.value.toLowerCase())) { 8 | target.style.display = 'none' 9 | 10 | /** 11 | * Detects an empty list 12 | * Remove the list and the list's title if the list is not displayed 13 | */ 14 | const list = [...target.parentNode.childNodes].filter( elem => elem.style.display !== 'none') 15 | 16 | if (!list.length) { 17 | target.parentNode.style.display = 'none' 18 | target.parentNode.previousSibling.style.display = 'none' 19 | } 20 | 21 | /** 22 | * Detects empty category 23 | * Remove the entire category if no item is displayed 24 | */ 25 | const category = [...target.parentNode.parentNode.childNodes] 26 | .filter( elem => elem.tagName !== 'H2' && elem.style.display !== 'none') 27 | 28 | if (!category.length) { 29 | target.parentNode.parentNode.style.display = 'none' 30 | } 31 | } else { 32 | target.parentNode.style.display = 'block' 33 | target.parentNode.previousSibling.style.display = 'block' 34 | target.parentNode.parentNode.style.display = 'block' 35 | target.style.display = 'block' 36 | } 37 | }) 38 | }) 39 | })() -------------------------------------------------------------------------------- /tmpl/property.tmpl: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <optional>
23 | 24 | 25 | 26 | <nullable>
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # IDEs and editors 33 | .idea 34 | .project 35 | .classpath 36 | .c9/ 37 | *.launch 38 | .settings/ 39 | *.sublime-workspace 40 | 41 | # IDE - VSCode 42 | .vscode/* 43 | !.vscode/settings.json 44 | !.vscode/tasks.json 45 | !.vscode/launch.json 46 | !.vscode/extensions.json 47 | 48 | # misc 49 | .sass-cache 50 | connect.lock 51 | typings 52 | 53 | # Logs 54 | logs 55 | *.log 56 | npm-debug.log* 57 | yarn-debug.log* 58 | yarn-error.log* 59 | 60 | 61 | # Dependency directories 62 | node_modules/ 63 | jspm_packages/ 64 | 65 | # Optional npm cache directory 66 | .npm 67 | 68 | # Optional eslint cache 69 | .eslintcache 70 | 71 | # Optional REPL history 72 | .node_repl_history 73 | 74 | # Output of 'npm pack' 75 | *.tgz 76 | 77 | # Yarn Integrity file 78 | .yarn-integrity 79 | 80 | # dotenv environment variables file 81 | .env 82 | 83 | # next.js build output 84 | .next 85 | 86 | # Lerna 87 | lerna-debug.log 88 | 89 | # System Files 90 | .DS_Store 91 | Thumbs.db 92 | 93 | .cache 94 | 95 | docs -------------------------------------------------------------------------------- /tmpl/param.tmpl: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <optional>
23 | 24 | 25 | 26 | <nullable>
27 | 28 | 29 | 30 | <repeatable>
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /fixtures/typescript/interface2.js: -------------------------------------------------------------------------------- 1 | const input = 2 | ` 3 | /** 4 | * Represents an application service's registration file. This is expected to be 5 | * loaded from another source, such as a YAML file. 6 | * @category Application services 7 | */ 8 | export interface IAppserviceRegistration { 9 | /** 10 | * The various namespaces the application service can support. 11 | */ 12 | namespaces: { 13 | /** 14 | * The room alias namespaces the application service is requesting. 15 | */ 16 | aliases: { 17 | /** 18 | * Whether or not the application service holds an exclusive lock on the namespace. This means 19 | * that no other user on the homeserver may register aliases that match this namespace. 20 | */ 21 | exclusive: boolean; 22 | 23 | /** 24 | * The regular expression that the homeserver uses to determine if an alias is in this namespace. 25 | */ 26 | regex: string; 27 | }[]; 28 | }; 29 | } 30 | ` 31 | 32 | const outputs = [ 33 | '* @property {Array} aliases The room alias namespaces the application service is requesting.', 34 | '* @property {boolean} aliases[].exclusive Whether or not the application service holds an exclusive lock on the namespace. This means,', 35 | '* that no other user on the homeserver may register aliases that match this namespace.', 36 | '* @property {string} aliases[].regex The regular expression that the homeserver uses to determine if an alias is in this namespace.', 37 | '* @type {object}', 38 | ] 39 | 40 | module.exports = { 41 | input, 42 | outputs, 43 | } -------------------------------------------------------------------------------- /tmpl/members.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Optional 23 | 24 |

25 | 26 | 27 |

28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 |
    43 |
  • 44 |
45 |
46 |
47 | 48 | 49 | 50 |
Example 1? 's':'' ?>
51 | 52 | 53 | -------------------------------------------------------------------------------- /static/scripts/app.min.js: -------------------------------------------------------------------------------- 1 | "use strict";$().ready(function(){});var sidebarIsVisible=!1,toggleSidebar=function(e){var a=!(0 h1").text();if(t){o.append($("

").text(t));var s=$("
    ");i.find(".members h4.name").each(function(e,a){var i=$(a),t=i.find(".code-name").clone().children().remove().end().text(),n=i.find("a").attr("href"),r=$('')).text(t);s.append($("
  • ").append(r)),c.push({link:r,offset:i.offset().top})}),o.append(s)}else i.find(".members h4.name").each(function(e,a){var i=$(a),t=i.find(".code-name").clone().children().remove().end().text(),n=i.find("a").attr("href"),r=$('
    ')).text(t);o.append(r),c.push({link:r,offset:i.offset().top})})}),!$.trim(o.text()))return o.hide();function e(){for(var e=n.scrollTop(),a=!1,i=c.length-1;0<=i;i--){var t=c[i];t.link.removeClass("is-active"),e+OFFSET>=t.offset?a?t.link.addClass("is-past"):(t.link.addClass("is-active"),a=!0):t.link.removeClass("is-past")}}var n=$("#main-content-wrapper");n.on("scroll",e),e(),c.forEach(function(e){e.link.click(function(){n.animate({scrollTop:e.offset-OFFSET+1},500)})})}),$().ready(function(){$("#sidebarNav a").each(function(e,a){var i=$(a).attr("href");window.location.pathname.match("/"+i)&&($(a).addClass("active"),$("#sidebarNav").scrollTop($(a).offset().top-150))})}); -------------------------------------------------------------------------------- /tmpl/proptypes.tmpl: -------------------------------------------------------------------------------- 1 | 5 |
    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
    NameTypeRequiredDescriptionDefault
    26 | 27 |
    40 | 41 | 42 | 43 | 44 | 45 |
    55 |
    -------------------------------------------------------------------------------- /fixtures/typescript/class.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility class for common operations performed by bridges (represented 3 | * as appservices). 4 | * 5 | * The storage utilities are not intended for bridges which allow 1:many 6 | * relationships with the remote network. 7 | * 8 | * Bridges are generally expected to create their own classes which extend 9 | * the IRemoteRoomInfo and IRemoteUserInfo interfaces and serialize to JSON 10 | * cleanly. The serialized version of these classes is persisted in various 11 | * account data locations for future lookups. 12 | * @category Application services 13 | */ 14 | export class MatrixBridge { 15 | /** 16 | * Gets information about a remote user. 17 | * @param {Intent} userIntent The Matrix user intent to get information on. 18 | * @returns {Promise} Resolves to the remote user information. 19 | */ 20 | public async getRemoteUserInfo(userIntent: Intent): Promise { 21 | await userIntent.ensureRegistered(); 22 | return >userIntent.underlyingClient.getAccountData(REMOTE_USER_INFO_ACCOUNT_DATA_EVENT_TYPE); 23 | } 24 | 25 | /** 26 | * Gets information about a remote room. 27 | * @param {string} matrixRoomId The Matrix room ID to get information on. 28 | * @returns {Promise} Resolves to the remote room information. 29 | */ 30 | public async getRemoteRoomInfo(matrixRoomId: string): Promise { 31 | const bridgeBot = this.appservice.botIntent; 32 | await bridgeBot.ensureRegistered(); 33 | // We do not need to ensure the user is joined to the room because we can associate 34 | // room account data with any arbitrary room. 35 | return >bridgeBot.underlyingClient.getRoomAccountData(REMOTE_ROOM_INFO_ACCOUNT_DATA_EVENT_TYPE, matrixRoomId); 36 | } 37 | } -------------------------------------------------------------------------------- /component.spec.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { expect } = require('chai') 3 | 4 | const { parseVue, parseReact } = require('./component') 5 | 6 | const VUE_PATH = path.join(__dirname, 'fixtures/component.vue') 7 | const REACT_PATH = path.join(__dirname, 'fixtures/component.jsx') 8 | 9 | describe('@component', function () { 10 | describe('.parseVue', function () { 11 | beforeEach(function () { 12 | this.doclet = {} 13 | this.output = parseVue(VUE_PATH, this.doclet) 14 | }) 15 | 16 | it('returns displayName', function () { 17 | expect(this.output.displayName).to.equal('grid') 18 | }) 19 | 20 | it('returns prop types', function () { 21 | expect(this.output.props).to.have.lengthOf(5) 22 | expect(this.output.props[0]).to.deep.equal({ 23 | description: 'object/array defaults should be returned from a factory function', 24 | name: 'msg', 25 | required: true, 26 | type: 'string|number', 27 | defaultValue: 'function()' 28 | }) 29 | expect(this.output.props[1]).to.have.property('defaultValue', '\'something\'') 30 | }) 31 | 32 | it('returns slots', function () { 33 | expect(this.output.slots).to.have.lengthOf(2) 34 | expect(this.output.slots[0]).to.deep.equal({ 35 | name: 'header', 36 | description: 'Use this slot header', 37 | }) 38 | }) 39 | }) 40 | 41 | describe('.parseReact', function () { 42 | beforeEach(function () { 43 | this.doclet = {} 44 | this.output = parseReact(REACT_PATH, this.doclet) 45 | }) 46 | 47 | it('returns displayName', function () { 48 | expect(this.output.displayName).to.equal('Documented') 49 | }) 50 | 51 | it('returns prop types', function () { 52 | expect(this.output.props).to.have.lengthOf(2) 53 | expect(this.output.props[0]).to.deep.equal({ 54 | description: 'Text is a text', 55 | name: 'text', 56 | required: false, 57 | type: 'string', 58 | defaultValue: '\'Hello World\'' 59 | }) 60 | }) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const DOCS_COMMAND = process.env.DOCS_COMMAND || 'yarn docs' 2 | const DOCS_OUTPUT = process.env.DOCS_OUTPUT || '../docs' 3 | 4 | const gulp = require('gulp') 5 | const sass = require('gulp-sass')(require('sass')) 6 | const autoprefixer = require('gulp-autoprefixer') 7 | const babel = require('gulp-babel') 8 | const uglify = require('gulp-uglify') 9 | const rename = require('gulp-rename') 10 | const concat = require('gulp-concat') 11 | const path = require('path') 12 | const browserSync = require('browser-sync').create() 13 | const exec = require('child_process').exec; 14 | 15 | gulp.task('sass', () => 16 | gulp.src('styles/app.sass') 17 | .pipe(sass({ 18 | outputStyle: 'compressed', 19 | })) 20 | .pipe(autoprefixer()) 21 | .pipe(rename({suffix: '.min'})) 22 | .pipe(gulp.dest('static/styles')) 23 | ) 24 | 25 | gulp.task('js', () => 26 | gulp.src(path.join('scripts/', '*.js'), {base: 'app'}) 27 | .pipe(concat('app.js')) 28 | .pipe(babel({ 29 | presets: ['@babel/preset-env'], 30 | })) 31 | .pipe(uglify()) 32 | .pipe(rename({suffix: '.min'})) 33 | .pipe(gulp.dest('static/scripts')) 34 | ) 35 | 36 | gulp.task('docs', (cb) => exec(`cd .. && ${DOCS_COMMAND}`, cb)) 37 | 38 | gulp.task('watch', function () { 39 | gulp.watch('styles/**/*.sass', gulp.series(['sass', 'docs'])) 40 | gulp.watch('scripts/**/*.js', gulp.series(['js', 'docs'])) 41 | gulp.watch('tmpl/**/*.tmpl', gulp.series(['docs'])) 42 | gulp.watch('publish.js', gulp.series(['docs'])) 43 | if (process.env.DOCS) { 44 | const array = [ 45 | ...process.env.DOCS.split(','), 46 | ...process.env.DOCS.split(',').map(src => '!' + src.replace('**/*', 'node_modules/**/*')) 47 | ] 48 | console.log(array) 49 | gulp.watch(array, gulp.series(['docs'])) 50 | } 51 | }) 52 | 53 | gulp.task('sync', () => { 54 | browserSync.init({ 55 | server: { 56 | baseDir: DOCS_OUTPUT 57 | } 58 | }); 59 | 60 | return gulp.watch(`${DOCS_OUTPUT}/*`, gulp.series([browserSync.reload])) 61 | }) 62 | 63 | gulp.task('default', gulp.series(['sass', 'js', 'docs', gulp.parallel(['watch', 'sync'])])) 64 | 65 | -------------------------------------------------------------------------------- /static/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: #006400; 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "better-docs", 3 | "version": "2.7.3", 4 | "description": "JSdoc theme", 5 | "repository": "github:SoftwareBrothers/better-docs", 6 | "main": "publish.js", 7 | "scripts": { 8 | "gulp": "./node_modules/.bin/gulp", 9 | "test": "mocha --recursive './*spec.js' './typescript/*spec.js'", 10 | "build": "babel src --out-dir lib", 11 | "docs": "jsdoc -c jsdoc.json", 12 | "release": "semantic-release" 13 | }, 14 | "author": "Wojciech Krysiak", 15 | "license": "MIT", 16 | "dependencies": { 17 | "brace": "^0.11.1", 18 | "react-ace": "^9.5.0", 19 | "react-docgen": "^5.4.0", 20 | "react-frame-component": "^5.2.1", 21 | "typescript": "^4.5.4", 22 | "underscore": "^1.13.2", 23 | "vue-docgen-api": "^3.26.0", 24 | "vue2-ace-editor": "^0.0.15" 25 | }, 26 | "peerDependencies": { 27 | "react": "^17.0.2", 28 | "react-dom": "^17.0.2" 29 | }, 30 | "devDependencies": { 31 | "@babel/cli": "^7.16.8", 32 | "@babel/core": "^7.16.7", 33 | "@babel/preset-env": "^7.16.8", 34 | "@babel/preset-react": "^7.16.7", 35 | "@commitlint/cli": "^16.0.2", 36 | "@commitlint/config-conventional": "^16.0.0", 37 | "@semantic-release/git": "^10.0.1", 38 | "@vue/component-compiler-utils": "^3.3.0", 39 | "browser-sync": "^2.27.7", 40 | "bulma": "^0.9.3", 41 | "chai": "^4.3.4", 42 | "eslint": "^8.7.0", 43 | "eslint-config-airbnb": "^19.0.4", 44 | "eslint-config-airbnb-base": "^15.0.0", 45 | "eslint-plugin-import": "^2.25.4", 46 | "eslint-plugin-jsx-a11y": "^6.5.1", 47 | "eslint-plugin-react": "^7.28.0", 48 | "eslint-plugin-vue": "^8.3.0", 49 | "gulp": "^4.0.2", 50 | "gulp-autoprefixer": "^8.0.0", 51 | "gulp-babel": "^8.0.0", 52 | "gulp-concat": "^2.6.1", 53 | "gulp-rename": "^2.0.0", 54 | "gulp-sass": "^5.1.0", 55 | "gulp-uglify": "^3.0.2", 56 | "husky": "^7.0.4", 57 | "jsdoc": "^3.6.7", 58 | "mocha": "^9.1.4", 59 | "sass": "^1.48.0", 60 | "semantic-release": "^18.0.1" 61 | }, 62 | "husky": { 63 | "hooks": { 64 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 65 | } 66 | }, 67 | "engines": { 68 | "node": ">=12" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /scripts/side-nav.js: -------------------------------------------------------------------------------- 1 | const OFFSET = 150 2 | 3 | $().ready(() => { 4 | const wrapper = $('#side-nav') 5 | 6 | /** 7 | * @type {Array<{link: El, offset: number}>} 8 | */ 9 | const links = [] 10 | 11 | if (!$('.vertical-section').length) { 12 | wrapper.hide() 13 | } 14 | 15 | $('.vertical-section').each((i, el) => { 16 | const section = $(el) 17 | const sectionName = section.find('> h1').text() 18 | if (sectionName) { 19 | wrapper.append($('

    ').text(sectionName)) 20 | const list = $('
      ') 21 | section.find('.members h4.name').each((i, el) => { 22 | const navLink = $(el) 23 | const name = navLink.find('.code-name') 24 | .clone().children().remove().end().text() 25 | const href = navLink.find('a').attr('href') 26 | const link = $(`
      `).text(name) 27 | list.append($('
    • ').append(link)) 28 | links.push({ link, offset: navLink.offset().top}) 29 | }) 30 | wrapper.append(list) 31 | } 32 | else { 33 | section.find('.members h4.name').each((i, el) => { 34 | const navLink = $(el) 35 | const name = navLink.find('.code-name') 36 | .clone().children().remove().end().text() 37 | const href = navLink.find('a').attr('href') 38 | const link = $(`
      `).text(name) 39 | wrapper.append(link) 40 | links.push({ link, offset: navLink.offset().top}) 41 | }) 42 | } 43 | }) 44 | 45 | if (!$.trim(wrapper.text())) { 46 | return wrapper.hide() 47 | } 48 | 49 | const core = $('#main-content-wrapper') 50 | 51 | const selectOnScroll = () => { 52 | const position = core.scrollTop() 53 | let activeSet = false 54 | for (let index = (links.length-1); index >= 0; index--) { 55 | const link = links[index] 56 | link.link.removeClass('is-active') 57 | if ((position + OFFSET) >= link.offset) { 58 | if (!activeSet) { 59 | link.link.addClass('is-active') 60 | activeSet = true 61 | } else { 62 | link.link.addClass('is-past') 63 | } 64 | } else { 65 | link.link.removeClass('is-past') 66 | } 67 | } 68 | } 69 | core.on('scroll', selectOnScroll) 70 | 71 | selectOnScroll() 72 | 73 | links.forEach(link => { 74 | link.link.click(() => { 75 | core.animate({ scrollTop: link.offset - OFFSET + 1 }, 500) 76 | }) 77 | }) 78 | }) -------------------------------------------------------------------------------- /src/vue-wrapper.js: -------------------------------------------------------------------------------- 1 | import editor from 'vue2-ace-editor' 2 | import _ from 'underscore' 3 | 4 | export default { 5 | template: ` 6 |
      7 | 8 |

      Modify Example Code

      9 |
      10 | 18 | 19 |
      20 |
      21 | `, 22 | props: { 23 | defaultCode: String 24 | }, 25 | data: function () { 26 | return { 27 | code: this.defaultCode, 28 | userComponent: this.renderComponent(this.defaultCode), 29 | isActive: false, 30 | } 31 | }, 32 | components: {editor}, 33 | created: function () { 34 | this.debounceRenderComponent = _.debounce(this.renderComponent, 500).bind(this) 35 | }, 36 | methods: { 37 | toggleEditor: function () { 38 | this.isActive = !this.isActive 39 | }, 40 | editorInit: function () { 41 | require('brace/ext/language_tools') //language extension prerequsite... 42 | require('brace/mode/jsx') //language 43 | require('brace/theme/monokai') 44 | }, 45 | renderComponent: function (originalCode) { 46 | const code = originalCode || this.code 47 | let json = {} 48 | try { 49 | if (code && code.length && code[0] === '{') { 50 | json = eval('(' + code + ')') 51 | } 52 | } catch(e) { 53 | // simply example is not a json object 54 | } 55 | 56 | try { 57 | json.components = vueComponents 58 | json.template = json.template || code 59 | const component = Vue.component('user-component', json) 60 | this.userComponent = component 61 | return component 62 | } catch (error) { 63 | console.log(error) 64 | } 65 | } 66 | }, 67 | updated: function () { 68 | this.$nextTick(function () { 69 | window.updateHeight(this.$refs.wrapperBox.clientHeight) 70 | }) 71 | }, 72 | mounted: function () { 73 | this.$nextTick(function () { 74 | window.updateHeight(this.$refs.wrapperBox.clientHeight) 75 | }) 76 | }, 77 | watch: { 78 | // whenever question changes, this function will run 79 | code: function (newCode, oldCode) { 80 | this.debounceRenderComponent(newCode) 81 | } 82 | }, 83 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | Changelog maintained after version 1.3.0 8 | 9 | ## Version v2.0.1 - 2020-05-03 10 | 11 | ### Fixed 12 | 13 | - fix `css` parameter in better-docs theme properties. 14 | 15 | ## Version 2.0.0 - 2020-04-29 16 | 17 | ### Changed 18 | 19 | - totally changed the UI 20 | 21 | ## Version 1.4.13 - 2020-04-28 22 | 23 | ### Added 24 | 25 | - [#27] - search ability 26 | 27 | ## Version 1.4.12 - 2020-04-28 28 | 29 | ### Fixed 30 | 31 | - update peerDependencies to more up to date version 32 | 33 | ## Version 1.4.11 - 2020-04-28 34 | 35 | ### Added 36 | 37 | - support for flow type 38 | 39 | ## Version 1.4.10 - 2020-04-28 40 | 41 | ### Fixed 42 | 43 | - [#30] fix line links in source code view 44 | 45 | ## Version 1.4.9 - 2020-04-28 46 | 47 | ### Added 48 | 49 | - add the ability to include a stylesheet via templates.default.staticFiles and then include it in the final theme via templates[better-docs].css 50 | 51 | ## Version 1.4.8 - 2020-04-28 52 | 53 | ### Fixed 54 | 55 | - [#43] - fixed windows issue 56 | 57 | ## [1.4.7] - 2019-10-18 58 | 59 | ### Fixed 60 | 61 | - fixed problem of one line comments of types and interfaces [#40] 62 | 63 | 64 | ## [1.4.6] - 2019-10-17 65 | 66 | ### Fixed 67 | 68 | - fix problem with the methods defined directly in the interface (related to [#39]) 69 | 70 | ## [1.4.5] - 2019-10-16 71 | 72 | ### Fixed 73 | 74 | - fix problem treating not react files as react (related to [#39]) 75 | 76 | ## [1.4.4] - 2019-10-16 77 | 78 | ### Fixed 79 | 80 | - fix problem with Array defined as {attribute: type}[] [#39] 81 | 82 | ## [1.4.4] - 2019-10-11 83 | 84 | ### Added 85 | - support for documenting class members in typescript 86 | - current item in sidebar is selected 87 | - hash with the link to the documentation element 88 | 89 | ## [1.4.0] - 2019-10-03 90 | 91 | ### Added 92 | - Add typescript support 93 | 94 | ## [1.3.3] - 2019-09-25 95 | ### Fixed 96 | - fixed vue components preview 97 | - attempt to fix error with paths on windows 98 | 99 | ## [1.3.2] - 2019-09-25 100 | ### Added 101 | - add the ability to document vue components with dash in a name 102 | ### Fixed 103 | - fix @classdesc tag for components 104 | 105 | ## [1.3.1] - 2019-09-25 106 | ### Added 107 | - add ability to document both react and vue components 108 | - temporarily fix height issue on react components 109 | - add typedef plugin 110 | ### Fixed 111 | - fix warning with className -------------------------------------------------------------------------------- /static/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /tmpl/layout.tmpl: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
      25 | 42 |
      43 |
      44 |
      45 |

      46 |

      47 |
      48 | 49 |
      50 | 51 | 60 | 61 |
      62 |
      63 |
      64 |
      65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /tmpl/properties.tmpl: -------------------------------------------------------------------------------- 1 | 58 |
      59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 92 | 93 | 94 | 95 |
      NameTypeAttributesDefaultDescription
      96 |
      -------------------------------------------------------------------------------- /lib/vue-wrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _vue2AceEditor = _interopRequireDefault(require("vue2-ace-editor")); 9 | 10 | var _underscore = _interopRequireDefault(require("underscore")); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 13 | 14 | var _default = { 15 | template: "\n
      \n \n

      Modify Example Code

      \n
      \n \n \n
      \n
      \n ", 16 | props: { 17 | defaultCode: String 18 | }, 19 | data: function data() { 20 | return { 21 | code: this.defaultCode, 22 | userComponent: this.renderComponent(this.defaultCode), 23 | isActive: false 24 | }; 25 | }, 26 | components: { 27 | editor: _vue2AceEditor["default"] 28 | }, 29 | created: function created() { 30 | this.debounceRenderComponent = _underscore["default"].debounce(this.renderComponent, 500).bind(this); 31 | }, 32 | methods: { 33 | toggleEditor: function toggleEditor() { 34 | this.isActive = !this.isActive; 35 | }, 36 | editorInit: function editorInit() { 37 | require('brace/ext/language_tools'); //language extension prerequsite... 38 | 39 | 40 | require('brace/mode/jsx'); //language 41 | 42 | 43 | require('brace/theme/monokai'); 44 | }, 45 | renderComponent: function renderComponent(originalCode) { 46 | var code = originalCode || this.code; 47 | var json = {}; 48 | 49 | try { 50 | if (code && code.length && code[0] === '{') { 51 | json = eval('(' + code + ')'); 52 | } 53 | } catch (e) {// simply example is not a json object 54 | } 55 | 56 | try { 57 | json.components = vueComponents; 58 | json.template = json.template || code; 59 | var component = Vue.component('user-component', json); 60 | this.userComponent = component; 61 | return component; 62 | } catch (error) { 63 | console.log(error); 64 | } 65 | } 66 | }, 67 | updated: function updated() { 68 | this.$nextTick(function () { 69 | window.updateHeight(this.$refs.wrapperBox.clientHeight); 70 | }); 71 | }, 72 | mounted: function mounted() { 73 | this.$nextTick(function () { 74 | window.updateHeight(this.$refs.wrapperBox.clientHeight); 75 | }); 76 | }, 77 | watch: { 78 | // whenever question changes, this function will run 79 | code: function code(newCode, oldCode) { 80 | this.debounceRenderComponent(newCode); 81 | } 82 | } 83 | }; 84 | exports["default"] = _default; -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | on: push 3 | jobs: 4 | setup: 5 | name: setup 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Dump GitHub context 9 | env: 10 | GITHUB_CONTEXT: ${{ toJson(github) }} 11 | run: echo "$GITHUB_CONTEXT" 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: Setup 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: '16.x' 18 | - uses: actions/cache@v1 19 | id: yarn-cache 20 | with: 21 | path: node_modules 22 | key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} 23 | restore-keys: | 24 | ${{ runner.os }}-node_modules- 25 | - name: Install 26 | if: steps.yarn-cache.outputs.cache-hit != 'true' 27 | run: yarn install 28 | 29 | test: 30 | name: Test 31 | runs-on: ubuntu-latest 32 | needs: setup 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v2 36 | - name: Setup 37 | uses: actions/setup-node@v1 38 | with: 39 | node-version: '16.x' 40 | - uses: actions/cache@v1 41 | id: yarn-cache 42 | with: 43 | path: node_modules 44 | key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} 45 | restore-keys: | 46 | ${{ runner.os }}-node_modules- 47 | - name: Install 48 | if: steps.yarn-cache.outputs.cache-hit != 'true' 49 | run: yarn install 50 | - name: test 51 | run: yarn test 52 | 53 | notify: 54 | name: Notify 55 | runs-on: ubuntu-latest 56 | if: failure() 57 | needs: 58 | - test 59 | - setup 60 | - publish 61 | steps: 62 | - uses: technote-space/workflow-conclusion-action@v1 63 | - uses: 8398a7/action-slack@v3 64 | with: 65 | status: ${{ env.WORKFLOW_CONCLUSION }} 66 | fields: repo,message,commit,author,action,eventName,ref,workflow # selectable (default: repo,message) 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 70 | if: always() 71 | 72 | publish: 73 | name: Publish 74 | needs: test 75 | runs-on: ubuntu-latest 76 | steps: 77 | - name: Checkout 78 | uses: actions/checkout@v2 79 | - name: Setup 80 | uses: actions/setup-node@v1 81 | with: 82 | node-version: '16.x' 83 | - uses: actions/cache@v1 84 | id: yarn-cache 85 | with: 86 | path: node_modules 87 | key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} 88 | restore-keys: | 89 | ${{ runner.os }}-node_modules- 90 | - name: Install 91 | if: steps.yarn-cache.outputs.cache-hit != 'true' 92 | run: yarn install 93 | - name: Release 94 | env: 95 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 96 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 97 | run: yarn release 98 | 99 | -------------------------------------------------------------------------------- /tmpl/topnav.tmpl: -------------------------------------------------------------------------------- 1 | 4 | 5 |
      6 |
      7 | 12 | 24 | 74 |
      75 |
      -------------------------------------------------------------------------------- /bundler.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const execSync = require('child_process').execSync 3 | const path = require('path')[process.platform === 'win32' ? 'win32' : 'posix'] 4 | 5 | const VUE_WRAPPER = process.env.IS_DEV ? 'src/vue-wrapper.js' : 'lib/vue-wrapper.js' 6 | const REACT_WRAPPER = process.env.IS_DEV ? 'src/react-wrapper.jsx' : 'lib/react-wrapper.js' 7 | 8 | const pathCrossEnv = (path) => 9 | process.platform !== 'win32' ? path : path.replace(/\\/g, '/') 10 | 11 | module.exports = function bundle (Components, out, config) { 12 | if (!Components.length) { 13 | return 14 | } 15 | const vueComponents = Components.filter(c => c.component.type === 'vue') 16 | const reactComponents = Components.filter(c => c.component.type === 'react') 17 | const entry = path.join(out, 'entry.js') 18 | const absoluteOut = path.resolve(out) 19 | let init = ` 20 | window.reactComponents = {};\n 21 | window.vueComponents = {};\n 22 | ` 23 | if (vueComponents.length) { 24 | init = init + ` 25 | import Vue from 'vue/dist/vue.js';\n 26 | window.Vue = Vue;\n 27 | 28 | import VueWrapper from '${pathCrossEnv(path.relative(absoluteOut, path.join(__dirname, VUE_WRAPPER)))}';\n 29 | window.VueWrapper = VueWrapper;\n 30 | ` 31 | } 32 | if (reactComponents.length) { 33 | const reactWrapperRelPath = pathCrossEnv( 34 | path.relative(absoluteOut, path.join(__dirname, REACT_WRAPPER)) 35 | ) 36 | 37 | init = init + ` 38 | import React from "react";\n 39 | import ReactDOM from "react-dom";\n 40 | 41 | import ReactWrapper from '${reactWrapperRelPath}';\n 42 | window.React = React;\n 43 | window.ReactDOM = ReactDOM;\n 44 | window.ReactWrapper = ReactWrapper;\n 45 | ` 46 | } 47 | 48 | // Import css 49 | init = init + ` 50 | import './styles/reset.css';\n 51 | import './styles/iframe.css';\n 52 | ` 53 | 54 | if (config.betterDocs.component) { 55 | if(config.betterDocs.component.wrapper) { 56 | const absolute = path.resolve(config.betterDocs.component.wrapper) 57 | const relative = pathCrossEnv(path.relative(absoluteOut, absolute)) 58 | init +=` 59 | import _CustomWrapper from '${relative}';\n 60 | window._CustomWrapper = _CustomWrapper;\n 61 | ` 62 | } 63 | if(config.betterDocs.component.entry 64 | && config.betterDocs.component.entry.length) { 65 | init = `${config.betterDocs.component.entry.join('\n')}\n${init}` 66 | } 67 | } 68 | 69 | const entryFile = init + Components.map((c, i) => { 70 | const { displayName, filePath, type } = c.component 71 | const relativePath = pathCrossEnv(path.relative(absoluteOut, filePath)) 72 | const name = `Component${i}` 73 | return [ 74 | `import ${name} from '${relativePath}';`, 75 | `${type}Components['${displayName}'] = ${name};`, 76 | ].join('\n') 77 | }).join('\n\n') 78 | 79 | console.log('Generating entry file for "components" plugin') 80 | fs.writeFileSync(entry, entryFile) 81 | console.log('Bundling components') 82 | const outDist = path.join(out, 'build') 83 | const cmd = `${process.platform === 'win32' ? 'SET ' : ''}NODE_ENV=development parcel build ${entry} --dist-dir ${outDist}` 84 | console.log(`running: ${cmd}`) 85 | try { 86 | execSync(cmd) 87 | } catch (error) { 88 | if(error.output && error.output.length){ 89 | console.log(error.output[1].toString()) 90 | } 91 | throw error 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /typescript/type-converter.spec.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const typeConverter = require('./type-converter') 3 | 4 | const interface1 = require('../fixtures/typescript/interface1') 5 | const interface2 = require('../fixtures/typescript/interface2') 6 | const interface3 = require('../fixtures/typescript/interface3') 7 | 8 | const type1 = require('../fixtures/typescript/type1') 9 | const type2 = require('../fixtures/typescript/type2') 10 | const type3 = require('../fixtures/typescript/type3') 11 | const type4 = require('../fixtures/typescript/type4') 12 | const type5 = require('../fixtures/typescript/type5') 13 | const type6 = require('../fixtures/typescript/type6') 14 | const type7 = require('../fixtures/typescript/type7') 15 | const staticMember = require('../fixtures/typescript/static-member') 16 | const protectedMember = require('../fixtures/typescript/protected-member') 17 | 18 | describe('.typeConverter', function () { 19 | describe('@typedef', function () { 20 | it('parses type 1', function () { 21 | type1.outputs.forEach(out => { 22 | expect(typeConverter(type1.input)).to.have.string(out) 23 | }) 24 | }) 25 | 26 | it('parses type 2: [key]: string', function () { 27 | type2.outputs.forEach(out => { 28 | expect(typeConverter(type2.input)).to.have.string(out) 29 | }) 30 | }) 31 | 32 | it('parses type 3: Array<{key: string}>', function () { 33 | type3.outputs.forEach(out => { 34 | expect(typeConverter(type3.input)).to.have.string(out) 35 | }) 36 | }) 37 | 38 | it('parses type 4: inline function', function () { 39 | type4.outputs.forEach(out => { 40 | expect(typeConverter(type4.input)).to.have.string(out) 41 | }) 42 | }) 43 | 44 | it('parses type 5: one line comment', function () { 45 | type5.outputs.forEach(out => { 46 | expect(typeConverter(type5.input)).to.have.string(out) 47 | }) 48 | }) 49 | 50 | it('parses type 6: joined by && types', function () { 51 | type6.outputs.forEach(out => { 52 | expect(typeConverter(type6.input)).to.have.string(out) 53 | }) 54 | }) 55 | 56 | it('parses type 7: object types', function () { 57 | type7.outputs.forEach(out => { 58 | expect(typeConverter(type7.input)).to.have.string(out) 59 | }) 60 | }) 61 | }) 62 | 63 | describe('@interface', function () { 64 | it('parses interface 1', function () { 65 | interface1.outputs.forEach(out => { 66 | expect(typeConverter(interface1.input)).to.have.string(out) 67 | }) 68 | }) 69 | 70 | it('parses interface 2 - with nested array defined as []', function () { 71 | interface2.outputs.forEach(out => { 72 | expect(typeConverter(interface2.input)).to.have.string(out) 73 | }) 74 | }) 75 | 76 | it('parses interface 3 - with methods', function () { 77 | interface3.outputs.forEach(out => { 78 | expect(typeConverter(interface3.input)).to.have.string(out) 79 | }) 80 | }) 81 | }) 82 | 83 | describe('class members', function () { 84 | it('parses static member', function () { 85 | staticMember.outputs.forEach(out => { 86 | expect(typeConverter(staticMember.input)).to.have.string(out) 87 | }) 88 | }) 89 | 90 | it('parses protected', function () { 91 | protectedMember.outputs.forEach(out => { 92 | expect(typeConverter(protectedMember.input)).to.have.string(out) 93 | }) 94 | }) 95 | }) 96 | 97 | // TODO: Provide tests for typescript 98 | }) -------------------------------------------------------------------------------- /tmpl/params.tmpl: -------------------------------------------------------------------------------- 1 | 77 |
      78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 111 | 112 | 113 | 114 | 115 | 116 |
      NameTypeAttributesDefaultDescription
      117 |
      -------------------------------------------------------------------------------- /lib/component-renderer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 11 | 12 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 13 | 14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 15 | 16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 17 | 18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 19 | 20 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 21 | 22 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 23 | 24 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 25 | 26 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 27 | 28 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 29 | 30 | var DefaultWrapper = function DefaultWrapper(props) { 31 | return _react["default"].createElement("div", null, props.children); 32 | }; 33 | 34 | var ComponentRenderer = 35 | /*#__PURE__*/ 36 | function (_React$Component) { 37 | _inherits(ComponentRenderer, _React$Component); 38 | 39 | function ComponentRenderer(props) { 40 | var _this; 41 | 42 | _classCallCheck(this, ComponentRenderer); 43 | 44 | _this = _possibleConstructorReturn(this, _getPrototypeOf(ComponentRenderer).call(this, props)); 45 | _this.Wrapper = window._CustomWrapper || DefaultWrapper; 46 | _this.state = { 47 | hasError: false, 48 | error: null 49 | }; 50 | return _this; 51 | } 52 | 53 | _createClass(ComponentRenderer, [{ 54 | key: "componentDidCatch", 55 | value: function componentDidCatch(error) { 56 | console.log(error.message); 57 | } 58 | }, { 59 | key: "render", 60 | value: function render() { 61 | var children = this.props.children; 62 | return _react["default"].createElement(this.Wrapper, this.props, children); 63 | } 64 | }]); 65 | 66 | return ComponentRenderer; 67 | }(_react["default"].Component); 68 | 69 | var _default = ComponentRenderer; 70 | exports["default"] = _default; -------------------------------------------------------------------------------- /component.js: -------------------------------------------------------------------------------- 1 | var reactDocs = require('react-docgen') 2 | var vueDocs = require('vue-docgen-api') 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | exports.handlers = { 7 | beforeParse: function(e) { 8 | if (path.extname(e.filename) === '.vue') { 9 | e.componentInfo = vueDocs.parse(e.filename) 10 | var script = e.source.match(new RegExp('', 's')) 11 | e.source = script[1] 12 | } 13 | }, 14 | 15 | newDoclet: function({ doclet }) { 16 | var filePath = path.join(doclet.meta.path, doclet.meta.filename) 17 | const componentTag = (doclet.tags || []).find(tag => tag.title === 'component') 18 | if (componentTag) { 19 | if (path.extname(filePath) === '.vue') { 20 | doclet.component = parseVue(filePath, doclet) 21 | doclet.component.type = 'vue' 22 | } else { 23 | doclet.component = parseReact(filePath, doclet) 24 | doclet.component.type = 'react' 25 | } 26 | doclet.kind = 'class' 27 | } else { 28 | if (path.extname(filePath) === '.vue') { 29 | const docGen = vueDocs.parse(filePath) 30 | const name = docGen.displayName 31 | if (doclet.kind === 'function' || doclet.kind === 'event') { 32 | doclet.memberof = name 33 | } else { 34 | doclet.undocumented = true 35 | } 36 | } 37 | 38 | if (path.extname(filePath) === '.jsx') { 39 | if (doclet.kind !== 'function' && doclet.kind !== 'event') { 40 | doclet.undocumented = true 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | var parseReact = function (filePath, doclet) { 48 | if (path.extname(filePath) === '.tsx') { 49 | return { 50 | props: [], 51 | displayName: doclet.name, 52 | filePath: filePath, 53 | } 54 | } 55 | var src = fs.readFileSync(filePath, 'UTF-8') 56 | var docGen 57 | try { 58 | docGen = reactDocs.parse(src) 59 | } catch (error) { 60 | if (error.message === 'No suitable component definition found.') { 61 | return { 62 | props: [], 63 | filePath: filePath, 64 | displayName: doclet.name, 65 | } 66 | } else { 67 | throw error 68 | } 69 | } 70 | 71 | return { 72 | props: Object.entries(docGen.props || {}).map(([key, prop]) => ({ 73 | name: key, 74 | description: prop.description, 75 | type: prop.type ? prop.type.name : prop.flowType.name, 76 | required: typeof prop.required === 'boolean' && prop.required, 77 | defaultValue: prop.defaultValue 78 | ? (prop.defaultValue.computed ? 'function()' : prop.defaultValue.value) 79 | : undefined 80 | })), 81 | displayName: docGen.displayName, 82 | filePath: filePath, 83 | } 84 | } 85 | 86 | var parseVue = function (filePath, doclet) { 87 | const docGen = vueDocs.parse(filePath) 88 | doclet.name = doclet.longname = docGen.displayName 89 | return { 90 | displayName: docGen.displayName, 91 | filePath: filePath, 92 | props: Object.values(docGen.props || {}).map(prop => ({ 93 | name: prop.name, 94 | description: prop.description, 95 | type: prop.type ? prop.type.name : prop.flowType.name, 96 | required: typeof prop.required === 'boolean' && prop.required, 97 | defaultValue: prop.defaultValue 98 | ? (prop.defaultValue.func ? 'function()' : prop.defaultValue.value) 99 | : undefined 100 | })), 101 | slots: Object.keys(docGen.slots || {}).map(key => ({ 102 | name: key, 103 | description: docGen.slots[key].description, 104 | })) 105 | } 106 | } 107 | 108 | exports.parseVue = parseVue 109 | exports.parseReact = parseReact 110 | -------------------------------------------------------------------------------- /fixtures/component.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | -------------------------------------------------------------------------------- /src/react-wrapper.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import brace from 'brace' 4 | import AceEditor from 'react-ace' 5 | import Frame, { FrameContextConsumer } from 'react-frame-component' 6 | 7 | import 'brace/mode/jsx' 8 | import 'brace/theme/monokai' 9 | import ComponentRenderer from './component-renderer' 10 | 11 | window.component = null 12 | 13 | class Wrapper extends React.Component { 14 | constructor(props) { 15 | super(props) 16 | window.component = window.component || {} 17 | this.iframeRef= React.createRef() 18 | this.handleChange = this.handleChange.bind(this) 19 | this.toggleEditor = this.toggleEditor.bind(this) 20 | let { example } = props 21 | example = example || 'return (
      Example
      )' 22 | this.state = { 23 | example, 24 | height: 200, 25 | showEditor: false, 26 | } 27 | this.executeScript(example) 28 | } 29 | 30 | executeScript(source) { 31 | const { uniqId } = this.props 32 | const script = document.createElement('script') 33 | const self = this 34 | script.onload = script.onerror = function() { 35 | this.remove() 36 | self.setState(state =>({ 37 | ...state, 38 | component: window.component[uniqId] || '', 39 | })) 40 | } 41 | const wrapper = `window.component['${uniqId}'] = (() => { 42 | ${Object.keys(reactComponents).map(k => `const ${k} = reactComponents['${k}'];`).join('\n')} 43 | try { 44 | ${source} 45 | } catch (error) { 46 | console.log(error) 47 | } 48 | })()` 49 | try { 50 | const src = Babel.transform(wrapper, { presets: ['react', 'es2015'] }).code 51 | script.src = 'data:text/plain;base64,' + btoa(src) 52 | } catch (error) { 53 | console.log(error) 54 | } 55 | 56 | document.body.appendChild(script) 57 | } 58 | 59 | handleChange(code) { 60 | this.executeScript(code) 61 | this.setState(state => ({ 62 | ...state, 63 | example: code, 64 | })) 65 | } 66 | 67 | computeHeight() { 68 | const { height } = this.state 69 | const padding = 5 // buffer for any unstyled margins 70 | if ( 71 | this.iframeRef.current 72 | && this.iframeRef.current.node.contentDocument 73 | && this.iframeRef.current.node.contentDocument.body.offsetHeight !== 0 74 | && this.iframeRef.current.node.contentDocument.body.offsetHeight !== (height - padding) 75 | ) { 76 | this.setState({ 77 | height: this.iframeRef.current.node.contentDocument.body.offsetHeight + padding, 78 | }) 79 | } 80 | } 81 | 82 | componentDidUpdate() { 83 | this.computeHeight() 84 | } 85 | 86 | componentDidMount() { 87 | this.heightInterval = setInterval(() => { 88 | this.computeHeight() 89 | }, 1000) 90 | } 91 | 92 | componentWillUnmount() { 93 | clearInterval(this.heightInterval) 94 | } 95 | 96 | toggleEditor(event) { 97 | event.preventDefault() 98 | this.setState(state => ({ 99 | ...state, 100 | showEditor: !state.showEditor, 101 | })) 102 | } 103 | 104 | render () { 105 | const { component, height, showEditor } = this.state 106 | return ( 107 |
      108 | 114 | 115 | 116 | { 117 | frameContext => ( 118 | 119 | {component} 120 | 121 | ) 122 | } 123 | 124 | 125 |
      126 | Modify Example Code 127 |
      128 | {showEditor ? ( 129 |
      130 | this.handleChange(code)} 136 | name="editor-div" 137 | editorProps={{ $useSoftTabs: true }} 138 | /> 139 |
      140 | ) : ''} 141 |
      142 | ) 143 | } 144 | } 145 | 146 | export default (props) => { 147 | return ( 148 | 149 | ) 150 | } -------------------------------------------------------------------------------- /tmpl/details.tmpl: -------------------------------------------------------------------------------- 1 | " + data.defaultvalue + ""; 9 | defaultObjectClass = ' class="object-value"'; 10 | } 11 | ?> 12 | 16 | 17 |
      Properties:
      18 | 19 | 20 | 21 | 22 | 23 |
      24 | 25 | 26 |
      Version:
      27 |
      28 | 29 | 30 | 31 |
      Since:
      32 |
      33 | 34 | 35 | 36 |
      Inherited From:
      37 |
      • 38 | 39 |
      40 | 41 | 42 | 43 |
      Overrides:
      44 |
      • 45 | 46 |
      47 | 48 | 49 | 50 |
      Implementations:
      51 |
        52 | 53 |
      • 54 | 55 |
      56 | 57 | 58 | 59 |
      Implements:
      60 |
        61 | 62 |
      • 63 | 64 |
      65 | 66 | 67 | 68 |
      Mixes In:
      69 | 70 |
        71 | 72 |
      • 73 | 74 |
      75 | 76 | 77 | 78 |
      Deprecated:
      • Yes
      82 | 83 | 84 | 85 |
      Author:
      86 |
      87 |
        88 |
      • 89 |
      90 |
      91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
      License:
      100 |
      101 | 102 | 103 | 104 |
      Default Value:
      105 |
        106 | > 107 |
      108 | 109 | 110 |
      Tutorials:
      111 |
      112 |
        113 |
      • 114 |
      115 |
      116 | 117 | 118 | 119 |
      See:
      120 |
      121 |
        122 |
      • 123 |
      124 |
      125 | 126 | 127 | 128 |
      To Do:
      129 |
      130 |
        131 |
      • 132 |
      133 |
      134 | 135 | 136 | 137 |

      138 | 139 | 140 | , 141 | 142 |

      143 | 144 |
      145 | -------------------------------------------------------------------------------- /tmpl/method.tmpl: -------------------------------------------------------------------------------- 1 | 5 | 6 |

      7 | 8 | 9 |

      10 | 11 | 12 | 13 |

      14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | '' + p.name + '').join(' ') + ' />' ?> 23 | 24 | 25 | 26 | 27 |

      28 | 29 | 30 | 31 |

      32 | 33 | 34 |
      35 | 36 |
      37 | 38 | 39 | 40 | 41 |
      42 |
      43 |
      44 | 45 |
      46 |
      47 | 48 | 49 | 50 |

      51 | 52 | 53 |

      54 | 55 | 56 | 57 | 58 |
      PropTypes:
      59 | 60 | 61 | 62 | 63 | 64 |
      Parameters:
      65 | 66 | 67 | 68 | 69 | 70 | 71 |
      72 |
      73 |
      74 |
        75 |
      • 76 |
      77 |
      78 |
      79 | 80 | 81 | 82 |
      83 |
      84 |
      85 |
        86 |
      • 87 |
      88 |
      89 |
      90 | 91 | 92 | 93 | 94 |
      95 |
      96 |
      97 |
        98 |
      • 99 |
      100 |
      101 |
      102 | 103 | 104 | 105 | 106 | 107 |
      108 |
      109 |
      110 |
        111 |
      • 112 |
      113 |
      114 |
      115 | 116 | 117 | 118 |
      119 |
      120 |
      121 | 123 | 124 | 126 |
      127 |
      128 | 129 | 130 | 131 |
      132 |
      133 |
      134 | 136 | 137 | 139 |
      140 |
      141 | 142 | 143 | 144 |
      145 |
      146 |
      147 | 149 | 150 | 152 |
      153 |
      154 | 155 | 156 |
      157 |
      158 |
      159 | 161 | 162 | 164 |
      165 |
      166 | 167 | 168 | 169 |
      Example 1? 's':'' ?>
      170 | 171 | 172 | -------------------------------------------------------------------------------- /styles/base/landing.sass: -------------------------------------------------------------------------------- 1 | body.landing 2 | & > .top-nav 3 | box-shadow: none 4 | transition: margin-top 0.3s 5 | &.hidden 6 | transition: margin-top 0.3s 7 | margin-top: (-$main-nav-height - 10px) 8 | @include until($desktop) 9 | margin-top: (-$main-nav-height-mobile - 10px) 10 | &.sticky 11 | box-shadow: 0 0 20px 0 rgba(0,0,255,0.5) 12 | & .inner 13 | margin: 0 auto 14 | max-width: 1226px 15 | color: $white 16 | background: $primary100 17 | & a.button 18 | color: $white 19 | border-color: $white 20 | background: transparent 21 | &:hover 22 | background: $white 23 | border-color: $white 24 | color: $grey100 25 | & .menu .navigation a.link 26 | color: $white 27 | &:hover 28 | border-color: $white 29 | 30 | .image img 31 | content: url("../images/logo.svg") 32 | 33 | #hamburger 34 | display: none 35 | 36 | #main 37 | display: block 38 | height: auto 39 | 40 | .main-hero 41 | background: $primary100 42 | color: $white 43 | padding: 300px 40% 160px 44 | border-bottom-left-radius: 50% 45 | border-bottom-right-radius: 50% 46 | margin: -250px -30% 0 47 | text-align: center 48 | .action-buttons 49 | margin: 60px 0 50 | vertical-align: middle 51 | span 52 | color: $white 53 | h3 54 | font-size: 24px 55 | line-height: 65px 56 | font-weight: lighter 57 | h1 58 | font-size: 52px 59 | line-height: 65px 60 | font-weight: lighter 61 | max-width: 900px 62 | margin-left: auto 63 | margin-right: auto 64 | strong 65 | color: $white 66 | 67 | .gif-box 68 | margin-top: -140px 69 | text-align: center 70 | 71 | .grey-logos 72 | text-align: center 73 | margin-bottom: 50px 74 | .column 75 | display: flex 76 | align-items: center 77 | justify-content: center 78 | 79 | .white-oval 80 | background: $white 81 | padding: 110px 500px 100px 82 | margin: 0 -500px 83 | text-align: center 84 | border-bottom-left-radius: 50% 85 | border-bottom-right-radius: 50% 86 | 87 | 88 | h2 89 | font-weight: bold 90 | font-size: 36px 91 | line-height: 48px 92 | color: $black 93 | margin-bottom: 15px 94 | &+p 95 | color: $grey80 96 | font-size: 16px 97 | 98 | h4 99 | font-weight: bold 100 | font-size: 24px 101 | line-height: 32px 102 | 103 | .header-message 104 | margin-bottom: 80px 105 | 106 | .todo-actions 107 | text-align: left 108 | padding: 100px 0 100px 100px 109 | @include until($widescreen) 110 | padding-top: 20px 111 | h4 112 | margin-bottom: 60px 113 | position: relative 114 | &:before 115 | content: '' 116 | position: absolute 117 | left: -50px 118 | top: 0 119 | height: 30px 120 | width: 30px 121 | background: url('../images/check.svg') no-repeat 50% 50% 122 | .action-buttons span 123 | line-height: 36px 124 | margin: 0 10px 125 | color: $primary100 126 | 127 | 128 | .credentials 129 | text-align: center 130 | padding: 100px 0 131 | background: url('../images/map.svg') no-repeat 50% 50% 132 | 133 | .fa-youtube 134 | color: #FF0000 135 | .fa-reddit 136 | color: #FF4500 137 | .fa-github 138 | color: $black 139 | 140 | .columns 141 | margin-bottom: 20px 142 | 143 | .column 144 | display: flex 145 | flex-direction: column 146 | 147 | .box 148 | flex-direction: column 149 | padding: 30px 150 | height: 100% 151 | box-shadow: 0px 0px 40px rgba(115, 134, 160, 0.25) 152 | display: flex 153 | justify-content: center 154 | &:hover 155 | box-shadow: 0px 0px 40px rgba(115, 134, 160, 0.5) 156 | h5 157 | align-self: center 158 | font-size: 22px 159 | line-height: 26px 160 | margin-bottom: 30px 161 | span 162 | font-size: 11px 163 | 164 | .stat-box 165 | padding: 40px 65px 166 | box-shadow: 0px 0px 40px rgba(115, 134, 160, 0.25) 167 | 168 | .column:first-child 169 | @include mobile 170 | padding-bottom: 40px 171 | 172 | .fa-github 173 | color: $black 174 | 175 | h2 176 | margin: 0 0 60px 177 | 178 | h4 179 | font-size: 32px 180 | font-weight: bolder 181 | margin-top: 15px 182 | color: $black 183 | strong 184 | color: $black 185 | 186 | .level 187 | border-bottom: #4C73F7 3px solid 188 | margin-bottom: -3px 189 | img 190 | position: relative 191 | bottom: -3px 192 | 193 | .action-buttons 194 | margin: 50px 0 0 195 | 196 | .feature-docs 197 | margin-top: -200px 198 | padding-top: 300px 199 | 200 | @include from($widescreen) 201 | .container .columns.is-multiline 202 | margin: 0 8.333% 203 | .columns.is-multiline 204 | .column 205 | display: flex 206 | 207 | 208 | .box 209 | color: $black 210 | &:hover 211 | box-shadow: 4px 8px 12px rgba(115, 134, 160, 0.25) 212 | img 213 | margin: -10px 0 214 | h4 215 | line-height: 36px 216 | font-size: 26px 217 | p 218 | font-size: 20px 219 | line-height: 26px 220 | margin: 35px 0 221 | .action-buttons 222 | margin: 100px 0 50px 223 | 224 | 225 | .feature-side-blocks 226 | .bg-crud 227 | background: url('../images/bg-crud.png') no-repeat 100% 50% 228 | @include until($widescreen) 229 | background-position-x: 150% 230 | @include until($desktop) 231 | background: none 232 | text-align: center 233 | .bg-filter 234 | background: url('../images/bg-filter.png') no-repeat 0% 50% 235 | @include until($widescreen) 236 | background-position-x: -200px 237 | @include until($desktop) 238 | background: none 239 | text-align: center 240 | .column 241 | justify-content: center 242 | display: flex 243 | flex-direction: column 244 | @include from($desktop) 245 | height: 700px 246 | .container 247 | margin-top: 50px 248 | margin-bottom: 50px 249 | 250 | .action-buttons 251 | margin: 30px 0 252 | 253 | .support-block 254 | padding: 80px 0 350px 255 | background: #fff 256 | margin-bottom: -200px 257 | .column 258 | display: flex 259 | flex-direction: column 260 | .box 261 | flex-grow: 1 262 | .img 263 | text-align: center 264 | border-bottom: 1px solid #D8D8D8 265 | margin: 0 -20px 20px 266 | h4 267 | font-weight: bolder 268 | font-size: 26px 269 | line-height: 48px 270 | .text 271 | padding: 0 10px 20px 272 | .form 273 | background: #4268F6 274 | padding: 40px 275 | border-radius: 10px 276 | 277 | .button.is-success 278 | width: 170px 279 | height: 50px 280 | 281 | .form 282 | color: #fff 283 | .success-msg 284 | display: none 285 | img 286 | width: 150px 287 | margin: 100px 0 288 | 289 | &.completed 290 | .success-msg 291 | display: block 292 | .form-fields 293 | display: none 294 | h2 295 | color: #fff 296 | margin-bottom: 30px 297 | .label 298 | font-size: 20px 299 | .field 300 | margin-bottom: 40px 301 | color: #fff 302 | label 303 | color: #fff 304 | font-weight: bold 305 | input 306 | border-radius: 4px 307 | height: 54px 308 | .checkbox 309 | display: block 310 | padding: 8px 0 311 | font-size: 16px 312 | &:hover 313 | color: #fff 314 | input 315 | margin-right: 5px 316 | .interested 317 | .label 318 | color: #fff 319 | padding: 0 0 20px 320 | textarea 321 | height: 80px 322 | .notice 323 | font-size: 14px 324 | font-weight: lighter 325 | padding: 10px 30px 326 | 327 | 328 | .curved-footer 329 | background: #4268F6 330 | padding: 110px 500px 331 | margin: 0 -500px 332 | border-top-left-radius: 50% 333 | border-top-right-radius: 50% 334 | 335 | color: #fff 336 | 337 | .the-part 338 | position: relative 339 | h2 340 | font-size: 90px 341 | line-height: 120px 342 | opacity: 0.08 343 | color: #fff 344 | @include until($desktop) 345 | font-size: 70px 346 | 347 | h4 348 | font-size: 56px 349 | line-height: 65px 350 | position: absolute 351 | left: 0 352 | top: 0 353 | right: 0 354 | padding: 80px 0 355 | 356 | .button.is-link 357 | background: transparent 358 | span 359 | border-bottom: 1px solid #fff 360 | &:hover span 361 | border-bottom: none 362 | 363 | .top 364 | border-bottom: 1px solid rgba(255,255,255, 0.2) 365 | padding-bottom: 120px 366 | text-align: center 367 | 368 | .bottom 369 | font-size: 14px 370 | padding: 65px 0 0 371 | @include until($tablet) 372 | padding: 20px 373 | strong 374 | color: #fff 375 | p 376 | padding: 6px 0 377 | a 378 | color: #fff 379 | 380 | .sb 381 | padding-top: 40px 382 | .logo 383 | padding-bottom: 30px 384 | 385 | .button.is-success 386 | margin-top: 40px 387 | height: 54px 388 | 389 | .form 390 | margin-top: 30px 391 | -------------------------------------------------------------------------------- /lib/react-wrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _brace = _interopRequireDefault(require("brace")); 11 | 12 | var _reactAce = _interopRequireDefault(require("react-ace")); 13 | 14 | var _reactFrameComponent = _interopRequireWildcard(require("react-frame-component")); 15 | 16 | require("brace/mode/jsx"); 17 | 18 | require("brace/theme/monokai"); 19 | 20 | var _componentRenderer = _interopRequireDefault(require("./component-renderer")); 21 | 22 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj["default"] = obj; return newObj; } } 23 | 24 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 25 | 26 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 27 | 28 | function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } 29 | 30 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 31 | 32 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 33 | 34 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 35 | 36 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 37 | 38 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 39 | 40 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 41 | 42 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 43 | 44 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 45 | 46 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 47 | 48 | window.component = null; 49 | 50 | var Wrapper = 51 | /*#__PURE__*/ 52 | function (_React$Component) { 53 | _inherits(Wrapper, _React$Component); 54 | 55 | function Wrapper(props) { 56 | var _this; 57 | 58 | _classCallCheck(this, Wrapper); 59 | 60 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Wrapper).call(this, props)); 61 | window.component = window.component || {}; 62 | _this.iframeRef = _react["default"].createRef(); 63 | _this.handleChange = _this.handleChange.bind(_assertThisInitialized(_this)); 64 | _this.toggleEditor = _this.toggleEditor.bind(_assertThisInitialized(_this)); 65 | var example = props.example; 66 | example = example || 'return (
      Example
      )'; 67 | _this.state = { 68 | example: example, 69 | height: 200, 70 | showEditor: false 71 | }; 72 | 73 | _this.executeScript(example); 74 | 75 | return _this; 76 | } 77 | 78 | _createClass(Wrapper, [{ 79 | key: "executeScript", 80 | value: function executeScript(source) { 81 | var uniqId = this.props.uniqId; 82 | var script = document.createElement('script'); 83 | var self = this; 84 | 85 | script.onload = script.onerror = function () { 86 | this.remove(); 87 | self.setState(function (state) { 88 | return _objectSpread2({}, state, { 89 | component: window.component[uniqId] || '' 90 | }); 91 | }); 92 | }; 93 | 94 | var wrapper = "window.component['".concat(uniqId, "'] = (() => {\n ").concat(Object.keys(reactComponents).map(function (k) { 95 | return "const ".concat(k, " = reactComponents['").concat(k, "'];"); 96 | }).join('\n'), "\n try {\n ").concat(source, "\n } catch (error) {\n console.log(error)\n }\n })()"); 97 | 98 | try { 99 | var src = Babel.transform(wrapper, { 100 | presets: ['react', 'es2015'] 101 | }).code; 102 | script.src = 'data:text/plain;base64,' + btoa(src); 103 | } catch (error) { 104 | console.log(error); 105 | } 106 | 107 | document.body.appendChild(script); 108 | } 109 | }, { 110 | key: "handleChange", 111 | value: function handleChange(code) { 112 | this.executeScript(code); 113 | this.setState(function (state) { 114 | return _objectSpread2({}, state, { 115 | example: code 116 | }); 117 | }); 118 | } 119 | }, { 120 | key: "computeHeight", 121 | value: function computeHeight() { 122 | var height = this.state.height; 123 | var padding = 5; // buffer for any unstyled margins 124 | 125 | if (this.iframeRef.current && this.iframeRef.current.node.contentDocument && this.iframeRef.current.node.contentDocument.body.offsetHeight !== 0 && this.iframeRef.current.node.contentDocument.body.offsetHeight !== height - padding) { 126 | this.setState({ 127 | height: this.iframeRef.current.node.contentDocument.body.offsetHeight + padding 128 | }); 129 | } 130 | } 131 | }, { 132 | key: "componentDidUpdate", 133 | value: function componentDidUpdate() { 134 | this.computeHeight(); 135 | } 136 | }, { 137 | key: "componentDidMount", 138 | value: function componentDidMount() { 139 | var _this2 = this; 140 | 141 | this.heightInterval = setInterval(function () { 142 | _this2.computeHeight(); 143 | }, 1000); 144 | } 145 | }, { 146 | key: "componentWillUnmount", 147 | value: function componentWillUnmount() { 148 | clearInterval(this.heightInterval); 149 | } 150 | }, { 151 | key: "toggleEditor", 152 | value: function toggleEditor(event) { 153 | event.preventDefault(); 154 | this.setState(function (state) { 155 | return _objectSpread2({}, state, { 156 | showEditor: !state.showEditor 157 | }); 158 | }); 159 | } 160 | }, { 161 | key: "render", 162 | value: function render() { 163 | var _this3 = this; 164 | 165 | var _this$state = this.state, 166 | component = _this$state.component, 167 | height = _this$state.height, 168 | showEditor = _this$state.showEditor; 169 | return _react["default"].createElement("div", null, _react["default"].createElement(_reactFrameComponent["default"], { 170 | className: "component-wrapper", 171 | ref: this.iframeRef, 172 | style: { 173 | width: '100%', 174 | height: height 175 | }, 176 | onLoad: this.computeHeight() 177 | }, _react["default"].createElement("link", { 178 | type: "text/css", 179 | rel: "stylesheet", 180 | href: "./build/entry.css" 181 | }), _react["default"].createElement(_reactFrameComponent.FrameContextConsumer, null, function (frameContext) { 182 | return _react["default"].createElement(_componentRenderer["default"], { 183 | frameContext: frameContext 184 | }, component); 185 | })), _react["default"].createElement("div", { 186 | className: "bd__button" 187 | }, _react["default"].createElement("a", { 188 | href: "#", 189 | onClick: this.toggleEditor 190 | }, "Modify Example Code")), showEditor ? _react["default"].createElement("div", { 191 | className: "field" 192 | }, _react["default"].createElement(_reactAce["default"], { 193 | style: { 194 | width: '100%', 195 | height: '200px', 196 | marginBottom: '20px' 197 | }, 198 | value: this.state.example, 199 | mode: "jsx", 200 | theme: "monokai", 201 | onChange: function onChange(code) { 202 | return _this3.handleChange(code); 203 | }, 204 | name: "editor-div", 205 | editorProps: { 206 | $useSoftTabs: true 207 | } 208 | })) : ''); 209 | } 210 | }]); 211 | 212 | return Wrapper; 213 | }(_react["default"].Component); 214 | 215 | var _default = function _default(props) { 216 | return _react["default"].createElement(Wrapper, props); 217 | }; 218 | 219 | exports["default"] = _default; -------------------------------------------------------------------------------- /tmpl/container.tmpl: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
      21 | 22 |
      23 | 24 |

      26 | 30 |

      32 | 33 |
      34 | 35 | 36 | 37 | 38 |
      39 | 40 | 41 | 42 |

      43 | '' + p.name + '').join(' ') + ' />' ?> 44 |

      45 | 46 |
      47 | 48 | 49 |
      50 | 51 |
      52 |
      53 | 54 | 55 |
      56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
      64 |
      65 |
      66 |
      67 | Constructor 68 |
      69 | 70 |
      71 |
      72 |
      73 | 74 | 75 | 76 | 77 | 78 |
      79 | 80 | 81 | 82 | 83 | 84 |

      Example 1? 's':'' ?>

      85 | 86 | 87 | 88 |
      89 | 90 | 91 |
      92 |

      Slots

      93 |
      94 | 95 |
      96 |

      97 | 98 | 99 |

      100 |
      101 |

      102 |
      103 |
      104 | 105 |
      106 |
      107 | 108 | 109 | 110 |

      Extends

      111 | 112 | 113 | 114 | 115 | 116 |

      Requires

      117 | 118 |
        119 |
      • 120 |
      121 | 122 | 123 | 127 |

      Classes

      128 | 129 |
      130 |
      131 |
      132 |
      133 | 134 | 135 | 139 |

      Interfaces

      140 | 141 |
      142 |
      143 |
      144 |
      145 | 146 | 147 | 151 |

      Mixins

      152 | 153 |
      154 |
      155 |
      156 |
      157 | 158 | 159 | 163 |

      Namespaces

      164 | 165 |
      166 |
      167 |
      168 |
      169 | 170 | 171 | 184 |
      185 |

      Members

      186 |
      187 | 188 |
      189 | 190 |
      191 |
      192 | 193 | 194 | { 199 | if(method.access === 'private') return acc 200 | var index = acc.findIndex(m => m.id === method.id) 201 | index < 0 ? acc.push(method) : acc[index] = method 202 | return acc 203 | }, []) 204 | ?> 205 |
      206 |

      Methods

      207 |
      208 | 209 |
      210 | 211 |
      212 |
      213 | 214 | 215 | 219 |
      220 |

      Type Definitions

      221 |
      222 | 225 |
      226 | 230 |
      231 | 234 |
      235 |
      236 | 237 | 238 | 242 |
      243 |

      Events

      244 |
      245 | 246 |
      247 | 248 |
      249 | 250 |
      251 |
      252 | 253 |
      254 | 255 |
      256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /typescript/type-converter.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ts = require('typescript') 3 | 4 | const appendComment = (commentBlock, toAppend) => { 5 | return commentBlock.replace(/[\n,\s]*\*\//, toAppend.split('\n').map(line => `\n * ${line}`) + '\n */') 6 | } 7 | 8 | /** 9 | * Get type from a node 10 | * @param {ts.TypeNode} type which should be parsed to string 11 | * @param {string} src source for an entire parsed file 12 | * @returns {string} node type 13 | */ 14 | const getTypeName = (type, src) => { 15 | if(!type) { return ''} 16 | if (type.typeName && type.typeName.escapedText) { 17 | const typeName = type.typeName.escapedText 18 | if(type.typeArguments && type.typeArguments.length) { 19 | const args = type.typeArguments.map(subType => getTypeName(subType, src)).join(', ') 20 | return `${typeName}<${args}>` 21 | } else { 22 | return typeName 23 | } 24 | } 25 | if(ts.isFunctionTypeNode(type) || ts.isFunctionLike(type)) { 26 | // it replaces ():void => {} (and other) to simple function 27 | return 'function' 28 | } 29 | if (ts.isArrayTypeNode(type)) { 30 | return 'Array' 31 | } 32 | if (type.types) { 33 | return type.types.map(subType => getTypeName(subType, src)).join(' | ') 34 | } 35 | if (type.members && type.members.length) { 36 | return 'object' 37 | } 38 | return src.substring(type.pos, type.end).trim() 39 | } 40 | 41 | /** 42 | * Fetches name from a node. 43 | */ 44 | const getName = (node, src) => { 45 | let name = node.name?.escapedText 46 | || node.parameters && src.substring(node.parameters.pos, node.parameters.end) 47 | 48 | // changing type [key: string] to {...} - otherwise it wont be parsed by @jsdoc 49 | if (name === 'key: string') { return '{...}' } 50 | return name 51 | } 52 | 53 | /** 54 | * Check type of node (dev only) 55 | * @param {node} node 56 | * @return {void} Console log predicted types 57 | */ 58 | function checkType(node) { 59 | console.group(node.name?.escapedText); 60 | const predictedTypes = Object.keys(ts).reduce((acc, key) => { 61 | if (typeof ts[key] !== "function" && !key.startsWith("is")) { 62 | return acc; 63 | } 64 | try { 65 | if (ts[key](node) === true) { 66 | acc.push(key); 67 | } 68 | } catch (error) { 69 | return acc; 70 | } 71 | return acc; 72 | }, []); 73 | console.log(predictedTypes); 74 | console.groupEnd(); 75 | } 76 | 77 | 78 | /** 79 | * Fill missing method declaration 80 | * 81 | * @param {string} comment 82 | * @param member 83 | * @param {string} src 84 | * @return {string} 85 | */ 86 | const fillMethodComment = (comment, member, src) => { 87 | if (!comment.includes('@method')) { 88 | comment = appendComment(comment, '@method') 89 | } 90 | if (!comment.includes('@param')) { 91 | comment = convertParams(comment, member, src) 92 | } 93 | if (member.type && ts.isArrayTypeNode(member.type)) { 94 | comment = convertMembers(comment, member.type, src) 95 | } 96 | if (member.type && !comment.includes('@return')) { 97 | const returnType = getTypeName(member.type, src) 98 | comment = appendComment(comment, `@return {${returnType}}`) 99 | } 100 | return comment 101 | } 102 | 103 | /** 104 | * converts function parameters to @params 105 | * 106 | * @param {string} [jsDoc] existing jsdoc text where all @param comments should be appended 107 | * @param {ts.FunctionDeclaration} wrapper ts node which has to be parsed 108 | * @param {string} src source for an entire parsed file (we are fetching substrings from it) 109 | * @param {string} parentName name of a parent element - NOT IMPLEMENTED YET 110 | * @returns {string} modified jsDoc comment with appended @param tags 111 | * 112 | */ 113 | const convertParams = (jsDoc = '', node, src) => { 114 | const parameters = node.type?.parameters || node.parameters 115 | if(!parameters) { return } 116 | parameters.forEach(parameter => { 117 | let name = getName(parameter, src) 118 | let comment = getCommentAsString(parameter, src) 119 | if (parameter.questionToken) { 120 | name = ['[', name, ']'].join('') 121 | } 122 | let type = getTypeName(parameter.type, src) 123 | jsDoc = appendComment(jsDoc, `@param {${type}} ${name} ${comment}`) 124 | }) 125 | return jsDoc 126 | } 127 | 128 | /** 129 | * Convert type properties to @property 130 | * @param {string} [jsDoc] existing jsdoc text where all @param comments should be appended 131 | * @param {ts.TypeNode} wrapper ts node which has to be parsed 132 | * @param {string} src source for an entire parsed file (we are fetching substrings from it) 133 | * @param {string} parentName name of a parent element 134 | * @returns {string} modified jsDoc comment with appended @param tags 135 | */ 136 | let convertMembers = (jsDoc = '', type, src, parentName = null) => { 137 | // type could be an array of types like: `{sth: 1} | string` - so we parse 138 | // each type separately 139 | const typesToCheck = [type] 140 | if (type.types && type.types.length) { 141 | typesToCheck.push(...type.types) 142 | } 143 | typesToCheck.forEach(type => { 144 | // Handling array defined like this: {element1: 'something'}[] 145 | if(ts.isArrayTypeNode(type) && type.elementType) { 146 | jsDoc = convertMembers(jsDoc, type.elementType, src, parentName ? parentName + '[]' : '[]') 147 | } 148 | 149 | // Handling Array<{element1: 'something'}> 150 | if (type.typeName && type.typeName.escapedText === 'Array') { 151 | if(type.typeArguments && type.typeArguments.length) { 152 | type.typeArguments.forEach(subType => { 153 | 154 | jsDoc = convertMembers(jsDoc, subType, src, parentName 155 | ? parentName + '[]' 156 | : '' // when there is no parent - jsdoc cannot parse [].name 157 | ) 158 | }) 159 | } 160 | } 161 | // Handling {property1: "value"} 162 | (type.members || []).filter(m => ts.isTypeElement(m)).forEach(member => { 163 | let name = getName(member, src) 164 | let comment = getCommentAsString(member, src) 165 | const members = member.type.members || [] 166 | let typeName = members.length ? 'object' : getTypeName(member.type, src) 167 | if (parentName) { 168 | name = [parentName, name].join('.') 169 | } 170 | // optional 171 | const nameToPlace = member.questionToken ? `[${name}]` : name 172 | jsDoc = appendComment(jsDoc, `@property {${typeName}} ${nameToPlace} ${comment}`) 173 | jsDoc = convertMembers(jsDoc, member.type, src, name) 174 | }) 175 | }) 176 | return jsDoc 177 | } 178 | 179 | /** 180 | * Extract comment from member jsDoc as string 181 | * @param member 182 | * @param {string} src 183 | * @returns {string} 184 | */ 185 | function getCommentAsString(member, src) { 186 | if (member.jsDoc && member.jsDoc[0] && member.jsDoc[0].comment) { 187 | const comment = member.jsDoc[0].comment; 188 | if (Array.isArray(comment)) { 189 | return comment 190 | .map((c) => c.text.length ? c.text : src.substring(c.pos, c.end)) 191 | .join(''); 192 | } 193 | return member.jsDoc[0].comment; 194 | } 195 | return ''; 196 | } 197 | 198 | /** 199 | * Main function which converts types 200 | * 201 | * @param {string} src typescript code to convert to jsdoc comments 202 | * @param {string} [filename] filename which is required by typescript parser 203 | * @return {string} @jsdoc comments generated from given typescript code 204 | */ 205 | module.exports = function typeConverter(src, filename = 'test.ts') { 206 | let ast = ts.createSourceFile( 207 | path.basename(filename), 208 | src, 209 | ts.ScriptTarget.Latest, 210 | false, 211 | ts.ScriptKind.TS 212 | ) 213 | 214 | // iterate through all the statements in global scope 215 | // we are looking for `interface xxxx` and `type zzz` 216 | return ast.statements.map(statement => { 217 | let jsDocNode = statement.jsDoc && statement.jsDoc[0] 218 | // Parse only statements with jsdoc comments. 219 | if (jsDocNode) { 220 | let comment = src.substring(jsDocNode.pos, jsDocNode.end) 221 | const name = getName(statement, src) 222 | if (ts.isFunctionDeclaration(statement)) { 223 | return fillMethodComment(comment, statement, src); 224 | } 225 | if (ts.isTypeAliasDeclaration(statement)) { 226 | if (ts.isFunctionTypeNode(statement.type)) { 227 | comment = appendComment(comment, `@typedef {function} ${name}`) 228 | return convertParams(comment, statement, src) 229 | } 230 | if (ts.isTypeLiteralNode(statement.type)) { 231 | comment = appendComment(comment, `@typedef {object} ${name}`) 232 | return convertMembers(comment, statement.type, src) 233 | } 234 | if (ts.isIntersectionTypeNode(statement.type)) { 235 | comment = appendComment(comment, `@typedef {object} ${name}`) 236 | return convertMembers(comment, statement.type, src) 237 | } 238 | if (ts.isUnionTypeNode(statement.type) || ts.isSimpleInlineableExpression(statement.type)) { 239 | let typeName = getTypeName(statement.type, src) 240 | comment = appendComment(comment, `@typedef {${typeName}} ${name}`) 241 | return convertMembers(comment, statement.type, src) 242 | } 243 | } 244 | if (ts.isInterfaceDeclaration(statement)) { 245 | comment = appendComment(comment, `@interface ${name}`) 246 | 247 | statement.members.forEach(member => { 248 | if (!member.jsDoc) { return } 249 | let memberComment = src.substring(member.jsDoc[0].pos, member.jsDoc[0].end) 250 | let memberName = getName(member, src) 251 | memberComment = appendComment(memberComment, [ 252 | `@name ${name}#${memberName}` 253 | ].join('\n')) 254 | if (member.questionToken) { 255 | memberComment = appendComment(memberComment, '@optional') 256 | } 257 | if (!member.type && ts.isFunctionLike(member)) { 258 | let type = getTypeName(member, src) 259 | memberComment = appendComment(memberComment, `@type {${type}}`) 260 | memberComment = appendComment(memberComment, '@method') 261 | } else { 262 | memberComment = convertMembers(memberComment, member.type, src) 263 | let type = getTypeName(member.type, src) 264 | memberComment = appendComment(memberComment, `@type {${type}}`) 265 | } 266 | comment += '\n' + memberComment 267 | }) 268 | return comment 269 | } 270 | if (ts.isClassDeclaration(statement)) { 271 | comment = '' 272 | const className = getName(statement, src) 273 | statement.members.forEach(member => { 274 | if (!member.jsDoc) { return } 275 | let memberComment = src.substring(member.jsDoc[0].pos, member.jsDoc[0].end) 276 | const modifiers = (member.modifiers || []).map(m => m.getText({text: src})) 277 | modifiers.forEach(modifier => { 278 | const allowedModifiers = ['async', 'abstract', 'private', 'public', 'protected'] 279 | if (allowedModifiers.includes(modifier)) { 280 | memberComment = appendComment(memberComment, `@${modifier}`) 281 | } 282 | }) 283 | if (member.type && ts.isPropertyDeclaration(member)) { 284 | const type = getTypeName(member.type, src) 285 | memberComment = appendComment(memberComment, `@type {${type}}`) 286 | } 287 | if (ts.isFunctionLike(member)) { 288 | memberComment = fillMethodComment(memberComment, member, src) 289 | } 290 | if (ts.isConstructorDeclaration(member)) { 291 | memberComment = appendComment(memberComment, `@constructor`) 292 | memberComment += `\n${className}.prototype.${className}` 293 | } else { 294 | if (modifiers.find((m) => m === "static")) { 295 | memberComment += `\n${className}.${getName(member, src)}` 296 | } else { 297 | memberComment += `\n${className}.prototype.${getName(member, src)}` 298 | } 299 | } 300 | comment += "\n" + memberComment 301 | }) 302 | return comment 303 | } 304 | } 305 | return '' 306 | }).join('\n') 307 | } 308 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Documentation toolbox for your **javascript** / **typescript** projects based on JSDoc3 with **@category**, **@component** and **@optional** plugins. 4 | 5 | This is how it looks: 6 | 7 | 8 | 9 | 12 | 15 | 18 | 19 |
      10 | 11 | 13 | 14 | 16 | 17 |
      20 | 21 | # Example 22 | 23 | Example documentation can be found here: https://softwarebrothers.github.io/example-design-system/index.html 24 | 25 | # OpenSource SoftwareBrothers community 26 | 27 | - [Join the community](https://join.slack.com/t/adminbro/shared_invite/zt-czfb79t1-0U7pn_KCqd5Ts~lbJK0_RA) to get help and be inspired. 28 | - subscribe to our [newsletter](http://opensource.softwarebrothers.co) 29 | 30 | # Installation 31 | 32 | ```sh 33 | npm install --save-dev better-docs 34 | ``` 35 | 36 | # Theme Usage 37 | 38 | ## With command line 39 | 40 | Assuming that you have [jsdoc](https://github.com/jsdoc/jsdoc) installed globally: 41 | 42 | ``` 43 | jsdoc your-documented-file.js -t ./node_modules/better-docs 44 | ``` 45 | 46 | ## With npm and configuration file 47 | 48 | In your projects package.json file - add a new script: 49 | 50 | ``` 51 | "script": { 52 | "docs": "jsdoc -c jsdoc.json" 53 | } 54 | ``` 55 | 56 | in your `jsdoc.json` file, set the template: 57 | 58 | ```json 59 | "opts": { 60 | "template": "node_modules/better-docs" 61 | } 62 | ``` 63 | 64 | # TypeScript support 65 | 66 | better-docs has a plugin which allows you to generate documentation from your TypeScript codebase. 67 | 68 | ## Usage 69 | 70 | To use it update your `jsdoc.json` file 71 | 72 | ``` 73 | ... 74 | "tags": { 75 | "allowUnknownTags": ["optional"] //or true 76 | }, 77 | "plugins": [ 78 | "node_modules/better-docs/typescript" 79 | ], 80 | "source": { 81 | "includePattern": "\\.(jsx|js|ts|tsx)$", 82 | }, 83 | ... 84 | ``` 85 | 86 | And now you can run your `jsdoc` command and parse TypeScript files. 87 | 88 | ## How it works? 89 | 90 | It performs 4 operations: 91 | 92 | * First of all it transpiles all .ts and .tsx files to .js, so that all comments used by you are treated 93 | as a regular JSDoc comments. 94 | 95 | Furthermore it: 96 | 97 | * Converts all your commented `type` aliases to `@typedef` 98 | * Converts all your commented `interface` definitions to `@interface`, 99 | * Converts descriptions for your public, protected, static class members 100 | 101 | so they can be printed by JSDoc automatically. 102 | 103 | ## Examples 104 | 105 | ``` 106 | /** 107 | * ActionRequest 108 | * @memberof Action 109 | * @alias ActionRequest 110 | */ 111 | export type ActionRequest = { 112 | /** 113 | * parameters passed in an URL 114 | */ 115 | params: { 116 | /** 117 | * Id of current resource 118 | */ 119 | resourceId: string; 120 | /** 121 | * Id of current record 122 | */ 123 | recordId?: string; 124 | /** 125 | * Name of an action 126 | */ 127 | action: string; 128 | 129 | [key: string]: any; 130 | }; 131 | } 132 | ``` 133 | 134 | is converted to: 135 | 136 | ``` 137 | /** 138 | * ActionRequest' 139 | * @memberof Action' 140 | * @alias ActionRequest' 141 | * @typedef {object} ActionRequest' 142 | * @property {object} params parameters passed in an URL' 143 | * @property {string} params.resourceId Id of current resource' 144 | * @property {string} [params.recordId] Id of current record' 145 | * @property {string} params.action Name of an action' 146 | * @property {any} params.{...}' 147 | */ 148 | ``` 149 | 150 | Also you can comment the interface in a similar fashion: 151 | 152 | ``` 153 | /** 154 | * JSON representation of an {@link Action} 155 | * @see Action 156 | */ 157 | export default interface ActionJSON { 158 | /** 159 | * Unique action name 160 | */ 161 | name: string; 162 | /** 163 | * Type of an action 164 | */ 165 | actionType: 'record' | 'resource' | Array<'record' | 'resource'>; 166 | /** 167 | * Action icon 168 | */ 169 | icon?: string; 170 | /** 171 | * Action label - visible on the frontend 172 | */ 173 | label: string; 174 | /** 175 | * Guarding message 176 | */ 177 | guard?: string; 178 | /** 179 | * If action should have a filter (for resource actions) 180 | */ 181 | showFilter: boolean; 182 | /** 183 | * Action component. When set to false action will be invoked immediately after clicking it, 184 | * to put in another words: there wont be an action view 185 | */ 186 | component?: string | false | null; 187 | } 188 | ``` 189 | 190 | or describe your class properties like that: 191 | 192 | ``` 193 | /** 194 | * Class name 195 | */ 196 | class ClassName { 197 | /** 198 | * Some private member which WONT be in jsdoc (because it is private) 199 | */ 200 | private name: string 201 | 202 | /** 203 | * Some protected member which will go to the docs 204 | */ 205 | protected somethingIsA: number 206 | 207 | /** 208 | * And static member which will goes to the docs. 209 | */ 210 | static someStaticMember: number 211 | 212 | public notCommentedWontBeInJSDoc: string 213 | 214 | constructor(color: string) {} 215 | } 216 | ``` 217 | 218 | # @category plugin 219 | 220 | better-docs also allows you to nest your documentation into categories and subcategories in the sidebar menu. 221 | 222 | ## Usage 223 | 224 | To add a plugin - update `plugins` section in your `jsdoc.json` file: 225 | 226 | ``` 227 | ... 228 | "tags": { 229 | "allowUnknownTags": ["category"] //or true 230 | }, 231 | "plugins": [ 232 | "node_modules/better-docs/category" 233 | ], 234 | ... 235 | ``` 236 | 237 | and then you can use `@category` and/or `@subcategory` tag in your code: 238 | 239 | ``` 240 | /** 241 | * Class description 242 | * @category Category 243 | * @subcategory All 244 | */ 245 | class YourClass { 246 | .... 247 | } 248 | ``` 249 | 250 | # @component plugin [BETA] 251 | 252 | Better-docs also allows you to document your [React](https://reactjs.org/) and [Vue](https://vuejs.org/) components automatically. The only thing you have to do is to add a `@component` tag. It will take all props from your components and along with an `@example` tag - will generate a __live preview__. 253 | 254 | ## Installation instructions 255 | 256 | Similar as before to add a plugin - you have to update the `plugins` section in your `jsdoc.json` file: 257 | 258 | ``` 259 | ... 260 | "tags": { 261 | "allowUnknownTags": ["component"] //or true 262 | }, 263 | "plugins": [ 264 | "node_modules/better-docs/component" 265 | ], 266 | ... 267 | ``` 268 | 269 | Since __component__ plugin uses [parcel](https://parceljs.org) as a bundler you have to install it globally. To do this run: 270 | 271 | ``` 272 | # if you use npm 273 | npm install -g parcel-bundler 274 | 275 | # or yarn 276 | yarn global add parcel-bundler 277 | ``` 278 | 279 | ## Usage 280 | 281 | To document components simply add `@component` in your JSDoc documentation: 282 | 283 | ```jsx 284 | /** 285 | * Some documented component 286 | * 287 | * @component 288 | */ 289 | const Documented = (props) => { 290 | const { text } = props 291 | return ( 292 |
      {text}
      293 | ) 294 | } 295 | 296 | Documented.propTypes = { 297 | /** 298 | * Text is a text 299 | */ 300 | text: PropTypes.string.isRequired, 301 | } 302 | 303 | export default Documented 304 | ``` 305 | 306 | The plugin will take the information from your [PropTypes](https://reactjs.org/docs/typechecking-with-proptypes.html) and put them into an array. 307 | 308 | For Vue it looks similar: 309 | 310 | ```vue 311 | 329 | ``` 330 | 331 | In this case, props will be taken from `props` property. 332 | 333 | ## Preview 334 | 335 | `@component` plugin also modifies the behaviour of `@example` tag in a way that it can generate an actual __component preview__. What you have to do is to add an `@example` tag and return component from it: 336 | 337 | **React example:** 338 | 339 | ```jsx 340 | /** 341 | * Some documented component 342 | * 343 | * @component 344 | * @example 345 | * const text = 'some example text' 346 | * return ( 347 | * 348 | * ) 349 | */ 350 | const Documented = (props) => { 351 | ///... 352 | } 353 | ``` 354 | 355 | **Vue example 1:** 356 | 357 | ```vue 358 | 369 | ``` 370 | 371 | **Vue example 2:** 372 | 373 | ```vue 374 | 393 | ``` 394 | 395 | You can put as many `@example` tags as you like in one component and "caption" each of them like this: 396 | 397 | ```javascript 398 | /** 399 | * @component 400 | * @example Example usage of method1. 401 | * // your example here 402 | */ 403 | ``` 404 | 405 | ## Mixing components in preview 406 | 407 | Also you can use multiple components which are documented with `@component` tag together. So lets say you have 2 components and in the second component you want to use the first one as a wrapper like this: 408 | 409 | ```javascript 410 | // component-1.js 411 | /** 412 | * Component 1 413 | * @component 414 | * 415 | */ 416 | const Component1 = (props) => {...} 417 | 418 | // component-2.js 419 | /** 420 | * Component 2 421 | * @component 422 | * @example 423 | * return ( 424 | * 425 | * 426 | * 427 | * 428 | * ) 429 | */ 430 | const Component2 = (props) => {...} 431 | ``` 432 | 433 | ## Wrapper component [only React] 434 | 435 | Most probably your components will have to be run within a particular context, like within redux store provider or with custom CSS libraries. 436 | You can simulate this by passing a `component.wrapper` in your `jsdoc.json`: 437 | _(To read more about passing options - scroll down to __Customization__ section)_ 438 | 439 | ```json 440 | // jsdoc.json 441 | { 442 | "opts": {...}, 443 | "templates": { 444 | "better-docs": { 445 | "name": "Sample Documentation", 446 | "component": { 447 | "wrapper": "./path/to/your/wrapper-component.js", 448 | }, 449 | "...": "...", 450 | } 451 | } 452 | } 453 | ``` 454 | 455 | Wrapper component can look like this: 456 | 457 | ```javascript 458 | // wrapper-component.js 459 | import React from 'react' 460 | import { BrowserRouter } from 'react-router-dom' 461 | import { createStore } from 'redux' 462 | import { Provider } from 'react-redux' 463 | 464 | const store = createStore(() => ({}), {}) 465 | 466 | const Component = (props) => { 467 | return ( 468 | 469 | 470 | 471 | 472 | 473 | 474 | {props.children} 475 | 476 | 477 | 478 | ) 479 | } 480 | 481 | export default Component 482 | ``` 483 | 484 | ## Styling React examples 485 | 486 | Better-docs inserts all examples within an `iframe`. This results in the following styling options: 487 | 488 | 1. If you pass styles inline - they will work right away. 489 | 490 | 2. For `css modules` to work with `parcel` bundler - you have to install `postcss-modules` package: 491 | 492 | ``` 493 | yarn add postcss-modules 494 | ``` 495 | 496 | and create a `.postcssrc` file: 497 | 498 | 499 | ```json 500 | // .postcssrc 501 | { 502 | "modules": true 503 | } 504 | ``` 505 | 506 | 3. For [styled-components](https://www.styled-components.com/) you have to use wrapper component which looks like this: 507 | 508 | ```jsx 509 | import React from 'react' 510 | import { StyleSheetManager } from 'styled-components' 511 | 512 | const Component = (props) => { 513 | const { frameContext } = props 514 | return ( 515 | 516 | {props.children} 517 | 518 | ) 519 | } 520 | 521 | export default Component 522 | ``` 523 | 524 | ## Adding commands to bundle entry file 525 | 526 | `@component` plugin creates an entry file: `.entry.js` in your _docs_ output folder. Sometimes you might want to add something to it. You can do this by passing: `component.entry` option, which is an array of strings. 527 | 528 | So let's say you want to add `babel-polyfill` and 'bulma.css' framework to your bundle. You can do it like this: 529 | 530 | ```json 531 | // jsdoc.json 532 | { 533 | "opts": {...}, 534 | "templates": { 535 | "better-docs": { 536 | "name": "Sample Documentation", 537 | "component": { 538 | "entry": [ 539 | "import 'babel-polyfill';", 540 | "import 'bulma/css/bulma.css';" 541 | ] 542 | }, 543 | "...": "...", 544 | } 545 | } 546 | } 547 | ``` 548 | 549 | # Customization 550 | 551 | First of all, let me state that better-docs extends the `default` template. That is why default template parameters are also handled. 552 | 553 | [BETA]: You must explicitly set the `search` option of the `default` template to `true` to enable search 554 | 555 | To customize the better-docs pass `options` to `templates['better-docs']`. section in your `jsdoc configuration file`. 556 | 557 | Example configuration file with settings for both `default` and `better-docs` templates: 558 | 559 | ```json 560 | { 561 | "tags": { 562 | "allowUnknownTags": ["category"] 563 | }, 564 | "source": { 565 | "include": ["./src"], 566 | "includePattern": ".js$", 567 | "excludePattern": "(node_modules/|docs)" 568 | }, 569 | "plugins": [ 570 | "plugins/markdown", 571 | "jsdoc-mermaid", 572 | "node_modules/better-docs/category" 573 | ], 574 | "opts": { 575 | "encoding": "utf8", 576 | "destination": "docs/", 577 | "readme": "readme.md", 578 | "recurse": true, 579 | "verbose": true, 580 | "tutorials": "./docs-src/tutorials", 581 | "template": "better-docs" 582 | }, 583 | "templates": { 584 | "cleverLinks": false, 585 | "monospaceLinks": false, 586 | "search": true, 587 | "default": { 588 | "staticFiles": { 589 | "include": [ 590 | "./docs-src/statics" 591 | ] 592 | } 593 | }, 594 | "better-docs": { 595 | "name": "Sample Documentation", 596 | "logo": "images/logo.png", 597 | "title": "", // HTML title 598 | "css": "style.css", 599 | "trackingCode": "tracking-code-which-will-go-to-the-HEAD", 600 | "hideGenerator": false, 601 | "navLinks": [ 602 | { 603 | "label": "Github", 604 | "href": "https://github.com/SoftwareBrothers/admin-bro" 605 | }, 606 | { 607 | "label": "Example Application", 608 | "href": "https://admin-bro-example-app-staging.herokuapp.com/admin" 609 | } 610 | ] 611 | } 612 | } 613 | } 614 | ``` 615 | 616 | ## Extras 617 | 618 | ### typedef(import(...)) 619 | 620 | better-docs also has one extra plugin for handling typescript'like types imports like (it has to be one-liner): 621 | 622 | ``` 623 | /** @typedef {import('./some-other-file').ExportedType} ExportedType */ 624 | ``` 625 | 626 | It simply removes that from the code so JSDoc wont throw an error. In order to use it add this plugin to your plugins section: 627 | 628 | ``` 629 | "plugins": [ 630 | "node_modules/better-docs/typedef-import" 631 | ], 632 | ``` 633 | 634 | # Setting up for the development 635 | 636 | If you want to change the theme locally follow the steps: 637 | 638 | 1. Clone the repo to the folder where you have the project: 639 | 640 | ``` 641 | cd your-project 642 | git clone git@github.com:SoftwareBrothers/better-docs.git 643 | ``` 644 | 645 | or add it as a git submodule: 646 | 647 | ``` 648 | git submodule add git@github.com:SoftwareBrothers/better-docs.git 649 | ``` 650 | 651 | 2. Install the packages 652 | 653 | ``` 654 | cd better-docs 655 | 656 | npm install 657 | 658 | # or 659 | 660 | yarn 661 | ``` 662 | 663 | 3. Within the better-docs folder run the gulp script. It will regenerate documentation every time you change something. 664 | 665 | It supports following ENV variables: 666 | 667 | * `DOCS_COMMAND` - a command in your root repo which you use to generate documentation: i.e. `DOCS_COMMAND='jsdoc -c jsdoc.json'` or `npm run docs` if you have `docs` command defined in `package.json` file 668 | * `DOCS_OUTPUT` - where your documentation is generated. It should point to the same folder your jsdoc `--destination` conf. But make sure that it is relative to the path where you cloned `better-docs`. 669 | * `DOCS` - list of folders from your original repo what you want to watch for changes. Separated by comma. 670 | 671 | ``` 672 | cd better-docs 673 | DOCS_COMMAND='npm run docs' DOCS=../src/**/*,../config/**/* DOCS_OUTPUT=../docs cd better-docs && gulp 674 | ``` 675 | 676 | The script should launch the browser and refresh it whenever you change something in the template or in `DOCS`. 677 | 678 | # Setting up the jsdoc in your project 679 | 680 | If you want to see how to setup jsdoc in your project - take a look at these brief tutorials: 681 | 682 | - JSDoc - https://www.youtube.com/watch?v=Yl6WARA3IhQ 683 | - better-docs and Mermaid: https://www.youtube.com/watch?v=UBMYogTzsBk 684 | 685 | # License 686 | 687 | better-docs is Copyright © 2019 SoftwareBrothers.co. It is free software and may be redistributed under the terms specified in the [LICENSE](LICENSE) file - MIT. 688 | 689 | # About SoftwareBrothers.co 690 | 691 | 692 | 693 | 694 | We're an open, friendly team that helps clients from all over the world to transform their businesses and create astonishing products. 695 | 696 | * We are available for [hire](https://softwarebrothers.co/contact). 697 | * If you want to work for us - check out the [career page](https://softwarebrothers.co/career). 698 | --------------------------------------------------------------------------------