UNPKG

docz

Version:

It's has never been so easy to documents your things!

527 lines (436 loc) 14.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var gatsby = require('gatsby'); var React = require('react'); var React__default = _interopDefault(React); var _merge = _interopDefault(require('lodash/fp/merge')); var equal = _interopDefault(require('fast-deep-equal')); var _kebabCase = _interopDefault(require('lodash/fp/kebabCase')); var _mapValues = _interopDefault(require('lodash/fp/mapValues')); var _get = _interopDefault(require('lodash/fp/get')); var _first = _interopDefault(require('lodash/fp/first')); var _assoc = _interopDefault(require('lodash/fp/assoc')); var pascalCase = require('pascal-case'); var marksy = _interopDefault(require('marksy')); var sort = _interopDefault(require('array-sort')); var _unionBy = _interopDefault(require('lodash/fp/unionBy')); var _flattenDepth = _interopDefault(require('lodash/fp/flattenDepth')); var _omit = _interopDefault(require('lodash/fp/omit')); var _pipe = _interopDefault(require('lodash/fp/pipe')); var ulid = require('ulid'); var match = _interopDefault(require('match-sorter')); var _throttle = _interopDefault(require('lodash/fp/throttle')); var tslib = require('tslib'); var capitalize = _interopDefault(require('capitalize')); const DefNotFound = () => React__default.createElement(React__default.Fragment, null, "Not found"); const DefLayout = ({ children }) => React__default.createElement(React__default.Fragment, null, children); const DefPlayground = ({ component, code }) => React__default.createElement("div", null, component, React__default.createElement("pre", null, code)); const defaultComponents = { layout: DefLayout, notFound: DefNotFound, playground: DefPlayground }; const ctx = React.createContext(defaultComponents); const ComponentsProvider = ({ components: themeComponents = {}, children }) => React__default.createElement(ctx.Provider, { value: Object.assign(Object.assign({}, defaultComponents), themeComponents) }, children); const useComponents = () => { return React.useContext(ctx); }; function create(initial) { var _a; const ctx = React.createContext(initial); const listeners = new Set(); const dispatch = fn => { listeners.forEach(listener => listener(fn)); }; return { context: ctx, set: fn => dispatch(fn), Provider: (_a = class Provider extends React.Component { constructor() { super(...arguments); this.state = this.props.initial || initial || {}; } static getDerivedStateFromProps(props, state) { if (!equal(props.initial, state)) return props.initial; return null; } componentDidMount() { listeners.add(fn => this.setState(fn)); } componentWillUnmount() { listeners.clear(); } render() { return React__default.createElement(ctx.Provider, { value: this.state }, this.props.children); } }, _a.displayName = 'DoczStateProvider', _a) }; } const doczState = create({}); const useConfig = () => { const state = React.useContext(doczState.context); const { transform, config, themeConfig = {} } = state; const newConfig = _merge(themeConfig, config ? config.themeConfig : {}); const transformed = transform ? transform(newConfig) : newConfig; return Object.assign(Object.assign({}, config), { themeConfig: transformed }); }; const useComponentProps = ({ componentName, fileName }) => { const components = useComponents(); const { props: stateProps } = React.useContext(doczState.context); const componentMatcher = (componentName, item) => { const matchingPatterns = [fileName, `/${componentName}.`, `/${_kebabCase(componentName)}.`, `/${pascalCase.pascalCase(componentName)}.`]; return !!matchingPatterns.find(pattern => item.key.includes(pattern)); }; const found = stateProps && stateProps.length > 0 && stateProps.find(item => componentMatcher(componentName, item)); const value = _get('value', found) || []; const firstDefinition = _first(value); const definition = value.find(i => i.displayName === componentName); const compile = React.useMemo(() => marksy({ createElement: React.createElement, elements: components }), [components]); const props = React.useMemo(() => { const props = _get('props', definition || firstDefinition); const parseDescs = _mapValues(prop => { const desc = _get('description', prop); return !desc ? prop : _assoc('description', compile(desc).tree, prop); }); return parseDescs(props); }, [compile, definition || firstDefinition]); return props; }; const useCurrentDoc = () => { const state = React.useContext(doczState.context); return _get('currentEntry.value', state); }; const updateState = ev => { const { type, payload } = JSON.parse(ev.data); const prop = type.startsWith('state.') && type.split('.')[1]; if (prop) { doczState.set(state => Object.assign(Object.assign({}, state), { [prop]: payload })); } }; const useDataServer = url => { React.useEffect(() => { if (!url) return; const socket = new WebSocket(url); socket.onmessage = updateState; return () => socket.close(); }, []); }; function flatArrFromObject(arr, prop) { const reducer = (arr, obj) => { const value = _get(prop)(obj); return value ? arr.concat([value]) : arr; }; return Array.from(new Set(arr.reduce(reducer, []))); } function compare(a, b, reverse) { if (a < b) return reverse ? 1 : -1; if (a > b) return reverse ? -1 : 1; return 0; } const useDocs = () => { const { entries = [] } = React.useContext(doczState.context); const arr = entries.map(({ value }) => value); return sort(arr, (a, b) => compare(a.name, b.name)); }; const noMenu = entry => !entry.menu; const fromMenu = menu => entry => entry.menu === menu; const entriesOfMenu = (menu, entries) => entries.filter(fromMenu(menu)); const parseMenu = entries => name => ({ name, menu: entriesOfMenu(name, entries) }); const menusFromEntries = entries => { const entriesWithoutMenu = entries.filter(noMenu); const menus = flatArrFromObject(entries, 'menu').map(parseMenu(entries)); return _unionBy('name', menus, entriesWithoutMenu); }; const parseItemStr = item => typeof item === 'string' ? { name: item } : item; const normalize = item => { const selected = parseItemStr(item); return Object.assign(Object.assign({}, selected), { id: selected.id || ulid.ulid(), parent: _get('parent', selected) || _get('parent', item), menu: Array.isArray(selected.menu) ? selected.menu.map(normalize) : selected.menu }); }; const clean = item => item.href || item.route ? _omit('menu', item) : item; const normalizeAndClean = _pipe(normalize, clean); const mergeMenus = (entriesMenu, configMenu) => { const first = entriesMenu.map(normalizeAndClean); const second = configMenu.map(normalizeAndClean); const merged = _unionBy('name', first, second); return merged.map(item => { if (!item.menu) return item; const found = second.find(i => i.name === item.name); const foundMenu = found && found.menu; return Object.assign(Object.assign({}, item), { menu: foundMenu ? mergeMenus(item.menu, foundMenu) : item.menu || found.menu }); }); }; const UNKNOWN_POS = Infinity; const findPos = (item, orderedList = []) => { const name = typeof item !== 'string' ? _get('name', item) : item; const pos = orderedList.findIndex(item => item === name); return pos !== -1 ? pos : UNKNOWN_POS; }; const compareWithMenu = (to = []) => (a, b) => { const list = to.map(i => i.name || i); return compare(findPos(a, list), findPos(b, list)); }; const sortByName = (a, b) => { return a.name < b.name ? -1 : a.name > b.name ? 1 : 0; }; const sortMenus = (first, second = []) => { const sorted = sort(first, compareWithMenu(second), sortByName); return sorted.map(item => { if (!item.menu) return item; const found = second.find(menu => menu.name === item.name); const foundMenu = found && found.menu; return Object.assign(Object.assign({}, item), { menu: foundMenu ? sortMenus(item.menu, foundMenu) : sort(item.menu, sortByName) }); }); }; const search = (val, menu) => { const items = menu.map(item => [item].concat(item.menu || [])); const flattened = _flattenDepth(2, items); const flattenedDeduplicated = Array.from(new Set(flattened)); return match(flattenedDeduplicated, val, { keys: ['name'] }); }; const filterMenus = (items, filter) => { if (!filter) return items; return items.filter(filter).map(item => { if (!item.menu) return item; return Object.assign(Object.assign({}, item), { menu: item.menu.filter(filter) }); }); }; const useMenus = opts => { const { query = '' } = opts || {}; const { entries, config } = React.useContext(doczState.context); if (!entries) return null; const arr = entries.map(({ value }) => value); const entriesMenu = menusFromEntries(arr); const sorted = React.useMemo(() => { const merged = mergeMenus(entriesMenu, config.menu); const result = sortMenus(merged, config.menu); return filterMenus(result, opts && opts.filter); }, [entries, config]); return query && query.length > 0 ? search(query, sorted) : sorted; }; const usePrevious = (value, defaultValue) => { const ref = React.useRef(defaultValue); React.useEffect(() => { ref.current = value; }); return ref.current; }; const isClient = typeof window === 'object'; const getSize = (initialWidth, initialHeight) => ({ innerHeight: isClient ? window.innerHeight : initialHeight, innerWidth: isClient ? window.innerWidth : initialWidth, outerHeight: isClient ? window.outerHeight : initialHeight, outerWidth: isClient ? window.outerWidth : initialWidth }); const useWindowSize = (throttleMs = 300, _initialWidth = Infinity, initialHeight = Infinity) => { const [windowSize, setWindowSize] = React.useState(getSize(initialHeight, initialHeight)); const tSetWindowResize = _throttle(throttleMs, () => setWindowSize(getSize(initialHeight, initialHeight))); React.useEffect(() => { window.addEventListener('resize', tSetWindowResize); return () => void window.removeEventListener('resize', tSetWindowResize); }, []); return windowSize; }; const Playground = ({ className, children, style, wrapper, __scope, __position, __code, language, useScoping }) => { const components = useComponents(); const PlaygroundComponent = components.playground; if (!PlaygroundComponent) return null; return React__default.createElement(PlaygroundComponent, { components: components, component: children, className: className, style: style, wrapper: wrapper, scope: __scope, position: __position, code: __code, language: language, useScoping: useScoping }); }; const RE_OBJECTOF = /(?:React\.)?(?:PropTypes\.)?objectOf\((?:React\.)?(?:PropTypes\.)?(\w+)\)/; const getTypeStr = type => { switch (type.name.toLowerCase()) { case 'instanceof': return `Class(${type.value})`; case 'enum': if (type.computed) return type.value; return type.value ? type.value.map(v => `${v.value}`).join(' │ ') : type.raw; case 'union': return type.value ? type.value.map(t => `${getTypeStr(t)}`).join(' │ ') : type.raw; case 'array': return type.raw; case 'arrayof': return `Array<${getTypeStr(type.value)}>`; case 'custom': if (type.raw.indexOf('function') !== -1 || type.raw.indexOf('=>') !== -1) return 'Custom(Function)';else if (type.raw.toLowerCase().indexOf('objectof') !== -1) { const m = type.raw.match(RE_OBJECTOF); if (m && m[1]) return `ObjectOf(${capitalize(m[1])})`; return 'ObjectOf'; } return 'Custom'; case 'bool': return 'Boolean'; case 'func': return 'Function'; case 'shape': const shape = type.value; const rst = {}; Object.keys(shape).forEach(key => { rst[key] = getTypeStr(shape[key]); }); return JSON.stringify(rst, null, 2); default: return type.name; } }; const humanize = type => getTypeStr(type); const getPropType = prop => { const propName = _get('name', prop.flowType || prop.type); if (!propName) return null; const isEnum = propName.startsWith('"') || propName === 'enum'; const name = isEnum ? 'enum' : propName; const value = _get('type.value', prop); if (!name) return null; if (isEnum && typeof value === 'string' || !prop.flowType && !isEnum && !value || prop.flowType && !prop.flowType.elements) { return name; } return prop.flowType ? humanize(prop.flowType) : humanize(prop.type); }; const Props = _a => { var { title, isToggle, isRaw, of: component } = _a, rest = tslib.__rest(_a, ["title", "isToggle", "isRaw", "of"]); const components = useComponents(); const PropsComponent = components.props; const fileName = _get('__filemeta.filename', component); const filemetaName = _get('__filemeta.name', component); const componentName = filemetaName || _get('displayName', component) || _get('name', component); const props = useComponentProps({ componentName, fileName }); if (!PropsComponent) return null; return React__default.createElement(PropsComponent, Object.assign({ title: title, isRaw: isRaw, isToggle: isToggle, props: props, getPropType: getPropType, of: component }, rest)); }; function theme(themeConfig, transform = c => c) { return WrappedComponent => { const Theme = React.memo(props => { const { db, currentEntry, children } = props; const initial = Object.assign(Object.assign({}, db), { currentEntry, themeConfig, transform }); return React__default.createElement(doczState.Provider, { initial: initial }, React__default.createElement(WrappedComponent, null, children)); }); Theme.displayName = WrappedComponent.displayName || 'DoczTheme'; return Theme; }; } Object.defineProperty(exports, 'Link', { enumerable: true, get: function () { return gatsby.Link; } }); exports.ComponentsProvider = ComponentsProvider; exports.Playground = Playground; exports.Props = Props; exports.doczState = doczState; exports.theme = theme; exports.useComponentProps = useComponentProps; exports.useComponents = useComponents; exports.useConfig = useConfig; exports.useCurrentDoc = useCurrentDoc; exports.useDataServer = useDataServer; exports.useDocs = useDocs; exports.useMenus = useMenus; exports.usePrevious = usePrevious; exports.useWindowSize = useWindowSize;