@gechiui/block-editor
Version:
408 lines (337 loc) • 11.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.Inserter = void 0;
var _element = require("@gechiui/element");
var _reactNative = require("react-native");
var _lodash = require("lodash");
var _i18n = require("@gechiui/i18n");
var _components = require("@gechiui/components");
var _data = require("@gechiui/data");
var _compose = require("@gechiui/compose");
var _blocks = require("@gechiui/blocks");
var _icons = require("@gechiui/icons");
var _reactNativeBridge = require("@gechiui/react-native-bridge");
var _style = _interopRequireDefault(require("./style.scss"));
var _menu = _interopRequireDefault(require("./menu"));
var _insertionPoint = _interopRequireDefault(require("../block-list/insertion-point"));
var _store = require("../../store");
/**
* External dependencies
*/
/**
* GeChiUI dependencies
*/
/**
* Internal dependencies
*/
const VOICE_OVER_ANNOUNCEMENT_DELAY = 1000;
const defaultRenderToggle = _ref => {
let {
onToggle,
disabled,
style,
onLongPress
} = _ref;
return (0, _element.createElement)(_components.ToolbarButton, {
title: (0, _i18n.__)('添加区块'),
icon: (0, _element.createElement)(_icons.Icon, {
icon: _icons.plusCircleFilled,
style: style,
color: style.color
}),
onClick: onToggle,
extraProps: {
hint: (0, _i18n.__)('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
});
};
class Inserter extends _element.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: (0, _i18n.__)('Add Block Before'),
icon: _icons.plusCircle
};
const replaceCurrentOption = {
value: 'replace',
label: (0, _i18n.__)('Replace Current Block'),
icon: _icons.plusCircleFilled
};
const addAfterOption = {
value: 'after',
label: (0, _i18n.__)('Add Block After'),
icon: _icons.plusCircle
};
const addToBeginningOption = {
value: 'start',
label: (0, _i18n.__)('Add To Beginning'),
icon: _icons.insertBefore
};
const addToEndOption = {
value: 'end',
label: (0, _i18n.__)('Add To End'),
icon: _icons.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
(0, _reactNativeBridge.setBlockTypeImpressions)(decrementedImpressions);
}
} // Surface toggle callback to parent component
if (onToggle) {
onToggle(isOpen);
}
this.onInserterToggledAnnouncement(isOpen);
}
onInserterToggledAnnouncement(isOpen) {
_reactNative.AccessibilityInfo.isScreenReaderEnabled().done(isEnabled => {
if (isEnabled) {
const isIOS = _reactNative.Platform.OS === 'ios';
const announcement = isOpen ? (0, _i18n.__)('Scrollable block menu opened. Select a block.') : (0, _i18n.__)('Scrollable block menu closed.');
(0, _lodash.delay)(() => _reactNative.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 (0, _element.createElement)(_insertionPoint.default, null);
}
const style = getStylesFromColorScheme(_style.default.addBlockButton, _style.default.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 (0, _element.createElement)(_element.Fragment, null, renderToggle({
onToggle: onPress,
isOpen,
disabled,
style,
onLongPress
}), (0, _element.createElement)(_components.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 (0, _element.createElement)(_menu.default, {
isOpen: isOpen,
onSelect: onClose,
onDismiss: onClose,
rootClientId: destinationRootClientId,
clientId: clientId,
isAppender: isAppender,
shouldReplaceBlock: shouldReplaceBlock,
insertionIndex: insertionIndex
});
}
render() {
return (0, _element.createElement)(_components.Dropdown, {
onToggle: this.onToggle,
headerTitle: (0, _i18n.__)('添加区块'),
renderToggle: this.renderInserterToggle,
renderContent: this.renderContent
});
}
}
exports.Inserter = Inserter;
var _default = (0, _compose.compose)([(0, _data.withDispatch)(dispatch => {
const {
updateSettings
} = dispatch(_store.store);
return {
updateSettings
};
}), (0, _data.withSelect)((select, _ref5) => {
let {
clientId,
isAppender,
rootClientId
} = _ref5;
const {
getBlockRootClientId,
getBlockSelectionEnd,
getBlockOrder,
getBlockIndex,
getBlock,
getSettings: getBlockEditorSettings
} = select(_store.store);
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 ? (0, _blocks.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
};
}), _compose.withPreferredColorScheme])(Inserter);
exports.default = _default;
//# sourceMappingURL=index.native.js.map