@gechiui/block-editor
Version:
417 lines (383 loc) • 13.3 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import { createElement, Fragment } from "@gechiui/element";
/**
* External dependencies
*/
import { identity } from 'lodash';
import { View, Platform, TouchableWithoutFeedback } from 'react-native';
/**
* GeChiUI dependencies
*/
import { Component, createContext } from '@gechiui/element';
import { withDispatch, withSelect } from '@gechiui/data';
import { compose, withPreferredColorScheme } from '@gechiui/compose';
import { createBlock } from '@gechiui/blocks';
import { KeyboardAwareFlatList, ReadableContentView, WIDE_ALIGNMENTS, alignmentHelpers } from '@gechiui/components';
import { __ } from '@gechiui/i18n';
/**
* Internal dependencies
*/
import styles from './style.scss';
import BlockListAppender from '../block-list-appender';
import BlockListItem from './block-list-item';
import { store as blockEditorStore } from '../../store';
const BlockListContext = createContext();
export const OnCaretVerticalPositionChange = createContext();
const stylesMemo = {};
const getStyles = (isRootList, isStackedHorizontally, horizontalAlignment) => {
if (isRootList) {
return;
}
const styleName = `${isStackedHorizontally}-${horizontalAlignment}`;
if (stylesMemo[styleName]) {
return stylesMemo[styleName];
}
const computedStyles = [isStackedHorizontally && styles.horizontal, horizontalAlignment && styles[`is-aligned-${horizontalAlignment}`], styles.overflowVisible];
stylesMemo[styleName] = computedStyles;
return computedStyles;
};
export class BlockList extends Component {
constructor() {
super(...arguments);
this.extraData = {
parentWidth: this.props.parentWidth,
renderFooterAppender: this.props.renderFooterAppender,
renderAppender: this.props.renderAppender,
onDeleteBlock: this.props.onDeleteBlock,
contentStyle: this.props.contentStyle
};
this.renderItem = this.renderItem.bind(this);
this.renderBlockListFooter = this.renderBlockListFooter.bind(this);
this.onCaretVerticalPositionChange = this.onCaretVerticalPositionChange.bind(this);
this.scrollViewInnerRef = this.scrollViewInnerRef.bind(this);
this.addBlockToEndOfPost = this.addBlockToEndOfPost.bind(this);
this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind(this);
this.shouldShowInnerBlockAppender = this.shouldShowInnerBlockAppender.bind(this);
this.renderEmptyList = this.renderEmptyList.bind(this);
this.getExtraData = this.getExtraData.bind(this);
this.onLayout = this.onLayout.bind(this);
this.state = {
blockWidth: this.props.blockWidth || 0
};
}
addBlockToEndOfPost(newBlock) {
this.props.insertBlock(newBlock, this.props.blockCount);
}
onCaretVerticalPositionChange(targetId, caretY, previousCaretY) {
KeyboardAwareFlatList.handleCaretVerticalPositionChange(this.scrollViewRef, targetId, caretY, previousCaretY);
}
scrollViewInnerRef(ref) {
this.scrollViewRef = ref;
}
shouldFlatListPreventAutomaticScroll() {
return this.props.isBlockInsertionPointVisible;
}
shouldShowInnerBlockAppender() {
const {
blockClientIds,
renderAppender
} = this.props;
return renderAppender && blockClientIds.length > 0;
}
renderEmptyList() {
return createElement(EmptyListComponentCompose, {
rootClientId: this.props.rootClientId,
renderAppender: this.props.renderAppender,
renderFooterAppender: this.props.renderFooterAppender
});
}
getExtraData() {
const {
parentWidth,
renderFooterAppender,
onDeleteBlock,
contentStyle,
renderAppender,
gridProperties
} = this.props;
const {
blockWidth
} = this.state;
if (this.extraData.parentWidth !== parentWidth || this.extraData.renderFooterAppender !== renderFooterAppender || this.extraData.onDeleteBlock !== onDeleteBlock || this.extraData.contentStyle !== contentStyle || this.extraData.renderAppender !== renderAppender || this.extraData.blockWidth !== blockWidth || this.extraData.gridProperties !== gridProperties) {
this.extraData = {
parentWidth,
renderFooterAppender,
onDeleteBlock,
contentStyle,
renderAppender,
blockWidth,
gridProperties
};
}
return this.extraData;
}
onLayout(_ref) {
let {
nativeEvent
} = _ref;
const {
layout
} = nativeEvent;
const {
blockWidth
} = this.state;
const {
isRootList,
maxWidth
} = this.props;
const layoutWidth = Math.floor(layout.width);
if (isRootList && blockWidth !== layoutWidth) {
this.setState({
blockWidth: Math.min(layoutWidth, maxWidth)
});
} else if (!isRootList && !blockWidth) {
this.setState({
blockWidth: Math.min(layoutWidth, maxWidth)
});
}
}
render() {
const {
isRootList
} = this.props; // Use of Context to propagate the main scroll ref to its children e.g InnerBlocks
const blockList = isRootList ? createElement(BlockListContext.Provider, {
value: this.scrollViewRef
}, this.renderList()) : createElement(BlockListContext.Consumer, null, ref => this.renderList({
parentScrollRef: ref
}));
return createElement(OnCaretVerticalPositionChange.Provider, {
value: this.onCaretVerticalPositionChange
}, blockList);
}
renderList() {
let extraProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
clearSelectedBlock,
blockClientIds,
title,
header,
isReadOnly,
isRootList,
horizontal,
marginVertical = styles.defaultBlock.marginTop,
marginHorizontal = styles.defaultBlock.marginLeft,
isFloatingToolbarVisible,
isStackedHorizontally,
horizontalAlignment,
contentResizeMode,
blockWidth
} = this.props;
const {
parentScrollRef
} = extraProps;
const {
blockToolbar,
blockBorder,
headerToolbar,
floatingToolbar
} = styles;
const containerStyle = {
flex: isRootList ? 1 : 0,
// We set negative margin in the parent to remove the edge spacing between parent block and child block in ineer blocks
marginVertical: isRootList ? 0 : -marginVertical,
marginHorizontal: isRootList ? 0 : -marginHorizontal
};
const isContentStretch = contentResizeMode === 'stretch';
const isMultiBlocks = blockClientIds.length > 1;
const {
isWider
} = alignmentHelpers;
return createElement(View, {
style: containerStyle,
onAccessibilityEscape: clearSelectedBlock,
onLayout: this.onLayout,
testID: "block-list-wrapper"
}, createElement(KeyboardAwareFlatList, _extends({}, Platform.OS === 'android' ? {
removeClippedSubviews: false
} : {}, {
// Disable clipping on Android to fix focus losing. See https://github.com/gechiui-mobile/gutenberg-mobile/pull/741#issuecomment-472746541
accessibilityLabel: "block-list",
autoScroll: this.props.autoScroll,
innerRef: ref => {
this.scrollViewInnerRef(parentScrollRef || ref);
},
extraScrollHeight: blockToolbar.height + blockBorder.width,
inputAccessoryViewHeight: headerToolbar.height + (isFloatingToolbarVisible ? floatingToolbar.height : 0),
keyboardShouldPersistTaps: "always",
scrollViewStyle: [{
flex: isRootList ? 1 : 0
}, !isRootList && styles.overflowVisible],
extraData: this.getExtraData(),
scrollEnabled: isRootList,
contentContainerStyle: [horizontal && styles.horizontalContentContainer, isWider(blockWidth, 'medium') && (isContentStretch && isMultiBlocks ? styles.horizontalContentContainerStretch : styles.horizontalContentContainerCenter)],
style: getStyles(isRootList, isStackedHorizontally, horizontalAlignment),
data: blockClientIds,
keyExtractor: identity,
renderItem: this.renderItem,
shouldPreventAutomaticScroll: this.shouldFlatListPreventAutomaticScroll,
title: title,
ListHeaderComponent: header,
ListEmptyComponent: !isReadOnly && this.renderEmptyList,
ListFooterComponent: this.renderBlockListFooter
})), this.shouldShowInnerBlockAppender() && createElement(View, {
style: {
marginHorizontal: marginHorizontal - styles.innerAppender.marginLeft
}
}, createElement(BlockListAppender, {
rootClientId: this.props.rootClientId,
renderAppender: this.props.renderAppender,
showSeparator: true
})));
}
renderItem(_ref2) {
let {
item: clientId
} = _ref2;
const {
contentResizeMode,
contentStyle,
onAddBlock,
onDeleteBlock,
rootClientId,
isStackedHorizontally,
blockClientIds,
parentWidth,
marginVertical = styles.defaultBlock.marginTop,
marginHorizontal = styles.defaultBlock.marginLeft,
gridProperties
} = this.props;
const {
blockWidth
} = this.state;
return createElement(BlockListItem, {
isStackedHorizontally: isStackedHorizontally,
rootClientId: rootClientId,
clientId: clientId,
parentWidth: parentWidth,
contentResizeMode: contentResizeMode,
contentStyle: contentStyle,
onAddBlock: onAddBlock,
marginVertical: marginVertical,
marginHorizontal: marginHorizontal,
onDeleteBlock: onDeleteBlock,
shouldShowInnerBlockAppender: this.shouldShowInnerBlockAppender,
blockWidth: blockWidth,
gridProperties: gridProperties,
items: blockClientIds
});
}
renderBlockListFooter() {
const paragraphBlock = createBlock('core/paragraph');
const {
isReadOnly,
withFooter = true,
renderFooterAppender
} = this.props;
if (!isReadOnly && withFooter) {
return createElement(Fragment, null, createElement(TouchableWithoutFeedback, {
accessibilityLabel: __('Add paragraph block'),
testID: __('Add paragraph block'),
onPress: () => {
this.addBlockToEndOfPost(paragraphBlock);
}
}, createElement(View, {
style: styles.blockListFooter
})));
} else if (renderFooterAppender) {
return renderFooterAppender();
}
return null;
}
}
export default compose([withSelect((select, _ref3) => {
let {
rootClientId,
orientation,
filterInnerBlocks
} = _ref3;
const {
getBlockCount,
getBlockOrder,
getSelectedBlockClientId,
isBlockInsertionPointVisible,
getSettings
} = select(blockEditorStore);
const isStackedHorizontally = orientation === 'horizontal';
const selectedBlockClientId = getSelectedBlockClientId();
let blockClientIds = getBlockOrder(rootClientId); // Display only block which fulfill the condition in passed `filterInnerBlocks` function
if (filterInnerBlocks) {
blockClientIds = filterInnerBlocks(blockClientIds);
}
const {
maxWidth
} = getSettings();
const isReadOnly = getSettings().readOnly;
const blockCount = getBlockCount();
const hasRootInnerBlocks = !!blockCount;
const isFloatingToolbarVisible = !!selectedBlockClientId && hasRootInnerBlocks;
return {
blockClientIds,
blockCount,
isBlockInsertionPointVisible: Platform.OS === 'ios' && isBlockInsertionPointVisible(),
isReadOnly,
isRootList: rootClientId === undefined,
isFloatingToolbarVisible,
isStackedHorizontally,
maxWidth
};
}), withDispatch(dispatch => {
const {
insertBlock,
replaceBlock,
clearSelectedBlock
} = dispatch(blockEditorStore);
return {
clearSelectedBlock,
insertBlock,
replaceBlock
};
}), withPreferredColorScheme])(BlockList);
class EmptyListComponent extends Component {
render() {
const {
shouldShowInsertionPoint,
rootClientId,
renderAppender,
renderFooterAppender
} = this.props;
if (renderFooterAppender) {
return null;
}
return createElement(View, {
style: styles.defaultAppender
}, createElement(ReadableContentView, {
align: renderAppender ? WIDE_ALIGNMENTS.alignments.full : undefined
}, createElement(BlockListAppender, {
rootClientId: rootClientId,
renderAppender: renderAppender,
showSeparator: shouldShowInsertionPoint
})));
}
}
const EmptyListComponentCompose = compose([withSelect((select, _ref4) => {
let {
rootClientId,
orientation
} = _ref4;
const {
getBlockOrder,
getBlockInsertionPoint,
isBlockInsertionPointVisible
} = select(blockEditorStore);
const isStackedHorizontally = orientation === 'horizontal';
const blockClientIds = getBlockOrder(rootClientId);
const insertionPoint = getBlockInsertionPoint();
const blockInsertionPointIsVisible = isBlockInsertionPointVisible();
const shouldShowInsertionPoint = !isStackedHorizontally && blockInsertionPointIsVisible && insertionPoint.rootClientId === rootClientId && ( // if list is empty, show the insertion point (via the default appender)
blockClientIds.length === 0 || // or if the insertion point is right before the denoted block
!blockClientIds[insertionPoint.index]);
return {
shouldShowInsertionPoint
};
})])(EmptyListComponent);
//# sourceMappingURL=index.native.js.map