UNPKG

substance

Version:

Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing system. It is developed to power our online editing platform [Substance](http://substance.io).

181 lines (174 loc) 5.56 kB
import { $$ } from '../dom' import { isString } from '../util' import Button from './Button' import Icon from './Icon' import Menu from './Menu' import MenuItem from './MenuItem' import DropdownMenu from './DropdownMenu' import StackFill from './StackFill' import HorizontalStack from './HorizontalStack' import Separator from './Separator' import HorizontalSpace from './HorizontalSpace' // WIP: implementing this step-by-step as we need export default function renderMenu (requester, menuNameOrSpec, commandStates) { const { config, editorState } = requester.context if (!commandStates && editorState) { commandStates = editorState.commandStates } let spec if (isString(menuNameOrSpec)) { spec = config.getToolPanel(menuNameOrSpec, true) } else { spec = menuNameOrSpec } // create a shallow clone of the spec spec = Object.assign({ type: 'menu' }, spec) // NOTE: sometimes we want to pass props of a component to this method // but we need to make sure to omit the children // TODO: we should actually pick only what is a valid menu spec parameter if (spec.children) delete spec.children // context is used to inherit default behavior or style to children items let items if (spec.items) { items = spec.items.map(itemSpec => _renderItem(requester, config, itemSpec, commandStates, spec)) } switch (spec.type) { case 'toolbar': { return $$(HorizontalStack, {}, items) } case 'menu': { return $$(Menu, spec, items) } default: return _renderItem(requester, config, spec, commandStates) } } function _renderItem (requester, config, itemSpec, commandStates = {}, context = {}) { // Note: commands we define using a syntax like // { command: 'toggle-strong', icon: 'bold' } // i.e. no type, but with command given const type = _getItemType(itemSpec) switch (type) { case 'action': case 'url': case 'command': { const style = itemSpec.style || context.style const size = itemSpec.size || context.size const props = { style, size } let shortcut if (type === 'command') { const commandName = itemSpec.command const commandState = commandStates[commandName] || { disabled: true } Object.assign(props, commandState) const shortcutSpec = config.getKeyboardShortcutsByCommandName(commandName) shortcut = shortcutSpec ? shortcutSpec.label : null props.action = 'executeCommand' props.args = [commandName, { commandState }, requester.context, requester] } else if (type === 'action') { props.action = itemSpec.action props.args = itemSpec.args } else if (type === 'url') { props.url = itemSpec.url props.newTab = itemSpec.newTab } const { icon, label, tooltip } = itemSpec if (itemSpec.ComponentClass) { return $$(itemSpec.ComponentClass, Object.assign(props, { icon, label, shortcut })) } else if (context.type === 'menu') { return $$(MenuItem, Object.assign(props, { icon, label, shortcut })) } else { const buttonEl = $$(Button, props) if (icon) { buttonEl.append( $$(Icon, { icon: itemSpec.icon, size }) ) } if (label) { buttonEl.append( icon ? $$(HorizontalSpace) : null, label ) } const title = [tooltip, shortcut].filter(Boolean).join(' ') if (title) { buttonEl.attr('title', title) } return buttonEl } } case 'fill': { return $$(StackFill) } case 'separator': { return $$(Separator) } case 'submenu': case 'menu': { const hasEnabledItems = _hasEnabledItems(itemSpec, commandStates) const menuProps = Object.assign({}, itemSpec, { disabled: !hasEnabledItems }) if (context.type === 'menu') { return _renderNestedMenu(config, menuProps, commandStates, context) } else { return _renderDropdown(config, menuProps, commandStates, context) } } default: console.error('Unsupported menu item', itemSpec) throw new Error(`Unsupported menu item ${itemSpec.type}`) } } function _renderNestedMenu (config, itemSpec, commandStates) { throw new Error('TODO: implement nested menus') } function _renderDropdown (config, itemSpec, commandStates, context) { context = Object.assign({}, context, { type: 'menu' }) const DropdownClass = itemSpec.ComponentClass || DropdownMenu return $$(DropdownClass, itemSpec) } function _hasEnabledItems (spec, commandStates) { const items = spec.items || [] for (const item of items) { const type = _getItemType(item) switch (type) { case 'action': case 'url': return true case 'command': { const commandName = item.command const commandState = commandStates[commandName] if (commandState && !commandState.disabled) { return true } break } case 'menu': case 'dropdown': case 'submenu': if (_hasEnabledItems(item, commandStates)) { return true } break default: // nothing } } return false } function _getItemType (item) { if (item.type) { return item.type } if (item.command) { return 'command' } if (item.action) { return 'action' } if (item.url) { return 'url' } console.error('Unsupported item type', item) throw new Error('Unsupported item type') }