docz
Version:
It's has never been so easy to documents your things!
527 lines (436 loc) • 14.9 kB
JavaScript
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;
;