UNPKG

@gechiui/block-editor

Version:
382 lines (330 loc) 10.7 kB
import { createElement, Fragment } from "@gechiui/element"; /** * External dependencies */ import { AccessibilityInfo, Platform } from 'react-native'; import { delay } from 'lodash'; /** * GeChiUI dependencies */ import { __ } from '@gechiui/i18n'; import { Dropdown, ToolbarButton, Picker } from '@gechiui/components'; import { Component } from '@gechiui/element'; import { withDispatch, withSelect } from '@gechiui/data'; import { compose, withPreferredColorScheme } from '@gechiui/compose'; import { isUnmodifiedDefaultBlock } from '@gechiui/blocks'; import { Icon, plusCircle, plusCircleFilled, insertAfter, insertBefore } from '@gechiui/icons'; import { setBlockTypeImpressions } from '@gechiui/react-native-bridge'; /** * Internal dependencies */ import styles from './style.scss'; import InserterMenu from './menu'; import BlockInsertionPoint from '../block-list/insertion-point'; import { store as blockEditorStore } from '../../store'; const VOICE_OVER_ANNOUNCEMENT_DELAY = 1000; const defaultRenderToggle = _ref => { let { onToggle, disabled, style, onLongPress } = _ref; return createElement(ToolbarButton, { title: __('添加区块'), icon: createElement(Icon, { icon: plusCircleFilled, style: style, color: style.color }), onClick: onToggle, extraProps: { hint: __('Double tap to add a block'), // testID is present to disambiguate this element for native UI tests. It's not // usually required for components. See: https://git.io/JeQ7G. testID: 'add-block-button', onLongPress }, isDisabled: disabled }); }; export class Inserter extends Component { constructor() { super(...arguments); this.onToggle = this.onToggle.bind(this); this.renderInserterToggle = this.renderInserterToggle.bind(this); this.renderContent = this.renderContent.bind(this); } getInsertionOptions() { const addBeforeOption = { value: 'before', label: __('Add Block Before'), icon: plusCircle }; const replaceCurrentOption = { value: 'replace', label: __('Replace Current Block'), icon: plusCircleFilled }; const addAfterOption = { value: 'after', label: __('Add Block After'), icon: plusCircle }; const addToBeginningOption = { value: 'start', label: __('Add To Beginning'), icon: insertBefore }; const addToEndOption = { value: 'end', label: __('Add To End'), icon: insertAfter }; const { isAnyBlockSelected, isSelectedBlockReplaceable } = this.props; if (isAnyBlockSelected) { if (isSelectedBlockReplaceable) { return [addToBeginningOption, addBeforeOption, replaceCurrentOption, addAfterOption, addToEndOption]; } return [addToBeginningOption, addBeforeOption, addAfterOption, addToEndOption]; } return [addToBeginningOption, addToEndOption]; } getInsertionIndex(insertionType) { const { insertionIndexDefault, insertionIndexStart, insertionIndexBefore, insertionIndexAfter, insertionIndexEnd } = this.props; if (insertionType === 'start') { return insertionIndexStart; } if (insertionType === 'before' || insertionType === 'replace') { return insertionIndexBefore; } if (insertionType === 'after') { return insertionIndexAfter; } if (insertionType === 'end') { return insertionIndexEnd; } return insertionIndexDefault; } shouldReplaceBlock(insertionType) { const { isSelectedBlockReplaceable } = this.props; if (insertionType === 'replace') { return true; } if (insertionType === 'default' && isSelectedBlockReplaceable) { return true; } return false; } onToggle(isOpen) { const { blockTypeImpressions, onToggle, updateSettings } = this.props; if (!isOpen) { const impressionsRemain = Object.values(blockTypeImpressions).some(count => count > 0); if (impressionsRemain) { const decrementedImpressions = Object.entries(blockTypeImpressions).reduce((acc, _ref2) => { let [blockName, count] = _ref2; return { ...acc, [blockName]: Math.max(count - 1, 0) }; }, {}); // Persist block type impression to JavaScript store updateSettings({ impressions: decrementedImpressions }); // Persist block type impression count to native app store setBlockTypeImpressions(decrementedImpressions); } } // Surface toggle callback to parent component if (onToggle) { onToggle(isOpen); } this.onInserterToggledAnnouncement(isOpen); } onInserterToggledAnnouncement(isOpen) { AccessibilityInfo.isScreenReaderEnabled().done(isEnabled => { if (isEnabled) { const isIOS = Platform.OS === 'ios'; const announcement = isOpen ? __('Scrollable block menu opened. Select a block.') : __('Scrollable block menu closed.'); delay(() => AccessibilityInfo.announceForAccessibility(announcement), isIOS ? VOICE_OVER_ANNOUNCEMENT_DELAY : 0); } }); } /** * Render callback to display Dropdown toggle element. * * @param {Object} options * @param {Function} options.onToggle Callback to invoke when toggle is * pressed. * @param {boolean} options.isOpen Whether dropdown is currently open. * * @return {GCElement} Dropdown toggle element. */ renderInserterToggle(_ref3) { let { onToggle, isOpen } = _ref3; const { disabled, renderToggle = defaultRenderToggle, getStylesFromColorScheme, showSeparator } = this.props; if (showSeparator && isOpen) { return createElement(BlockInsertionPoint, null); } const style = getStylesFromColorScheme(styles.addBlockButton, styles.addBlockButtonDark); const onPress = () => { this.setState({ destinationRootClientId: this.props.destinationRootClientId, shouldReplaceBlock: this.shouldReplaceBlock('default'), insertionIndex: this.getInsertionIndex('default') }, onToggle); }; const onLongPress = () => { if (this.picker) { this.picker.presentPicker(); } }; const onPickerSelect = insertionType => { this.setState({ destinationRootClientId: this.props.destinationRootClientId, shouldReplaceBlock: this.shouldReplaceBlock(insertionType), insertionIndex: this.getInsertionIndex(insertionType) }, onToggle); }; return createElement(Fragment, null, renderToggle({ onToggle: onPress, isOpen, disabled, style, onLongPress }), createElement(Picker, { ref: instance => this.picker = instance, options: this.getInsertionOptions(), onChange: onPickerSelect, hideCancelButton: true })); } /** * Render callback to display Dropdown content element. * * @param {Object} options * @param {Function} options.onClose Callback to invoke when dropdown is * closed. * @param {boolean} options.isOpen Whether dropdown is currently open. * * @return {GCElement} Dropdown content element. */ renderContent(_ref4) { let { onClose, isOpen } = _ref4; const { clientId, isAppender } = this.props; const { destinationRootClientId, shouldReplaceBlock, insertionIndex } = this.state; return createElement(InserterMenu, { isOpen: isOpen, onSelect: onClose, onDismiss: onClose, rootClientId: destinationRootClientId, clientId: clientId, isAppender: isAppender, shouldReplaceBlock: shouldReplaceBlock, insertionIndex: insertionIndex }); } render() { return createElement(Dropdown, { onToggle: this.onToggle, headerTitle: __('添加区块'), renderToggle: this.renderInserterToggle, renderContent: this.renderContent }); } } export default compose([withDispatch(dispatch => { const { updateSettings } = dispatch(blockEditorStore); return { updateSettings }; }), withSelect((select, _ref5) => { let { clientId, isAppender, rootClientId } = _ref5; const { getBlockRootClientId, getBlockSelectionEnd, getBlockOrder, getBlockIndex, getBlock, getSettings: getBlockEditorSettings } = select(blockEditorStore); const end = getBlockSelectionEnd(); // `end` argument (id) can refer to the component which is removed // due to pressing `undo` button, that's why we need to check // if `getBlock( end) is valid, otherwise `null` is passed const isAnyBlockSelected = !isAppender && end && getBlock(end); const destinationRootClientId = isAnyBlockSelected ? getBlockRootClientId(end) : rootClientId; const selectedBlockIndex = getBlockIndex(end); const endOfRootIndex = getBlockOrder(rootClientId).length; const isSelectedUnmodifiedDefaultBlock = isAnyBlockSelected ? isUnmodifiedDefaultBlock(getBlock(end)) : undefined; function getDefaultInsertionIndex() { const { __experimentalShouldInsertAtTheTop: shouldInsertAtTheTop } = getBlockEditorSettings(); // if post title is selected insert as first block if (shouldInsertAtTheTop) { return 0; } // If the clientId is defined, we insert at the position of the block. if (clientId) { return getBlockIndex(clientId); } // If there is a selected block, if (isAnyBlockSelected) { // and the last selected block is unmodified (empty), it will be replaced if (isSelectedUnmodifiedDefaultBlock) { return selectedBlockIndex; } // we insert after the selected block. return selectedBlockIndex + 1; } // Otherwise, we insert at the end of the current rootClientId return endOfRootIndex; } const insertionIndexStart = 0; const insertionIndexBefore = isAnyBlockSelected ? selectedBlockIndex : insertionIndexStart; const insertionIndexAfter = isAnyBlockSelected ? selectedBlockIndex + 1 : endOfRootIndex; const insertionIndexEnd = endOfRootIndex; return { blockTypeImpressions: getBlockEditorSettings().impressions, destinationRootClientId, insertionIndexDefault: getDefaultInsertionIndex(), insertionIndexBefore, insertionIndexAfter, insertionIndexStart, insertionIndexEnd, isAnyBlockSelected: !!isAnyBlockSelected, isSelectedBlockReplaceable: isSelectedUnmodifiedDefaultBlock }; }), withPreferredColorScheme])(Inserter); //# sourceMappingURL=index.native.js.map