UNPKG

wix-style-react

Version:
531 lines (436 loc) • 18.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeHrefAbsolute = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _class, _temp, _initialiseProps; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _FormFieldError = require('wix-ui-icons-common/system/FormFieldError'); var _FormFieldError2 = _interopRequireDefault(_FormFieldError); var _WixComponent2 = require('../BaseComponents/WixComponent'); var _WixComponent3 = _interopRequireDefault(_WixComponent2); var _slate = require('slate'); var _Tooltip = require('../Tooltip'); var _Tooltip2 = _interopRequireDefault(_Tooltip); var _RichTextAreaToolbar = require('./RichTextAreaToolbar'); var _RichTextAreaToolbar2 = _interopRequireDefault(_RichTextAreaToolbar); var _htmlSerializer = require('./htmlSerializer'); var _htmlSerializer2 = _interopRequireDefault(_htmlSerializer); var _RichTextArea = require('./RichTextArea.scss'); var _RichTextArea2 = _interopRequireDefault(_RichTextArea); var _isImage = require('is-image'); var _isImage2 = _interopRequireDefault(_isImage); var _isUrl = require('is-url'); var _isUrl2 = _interopRequireDefault(_isUrl); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var DEFAULT_NODE = 'paragraph'; var defaultBlock = { type: 'paragraph', isVoid: false, data: {}, key: 'defaultBlock' }; /* here we are checking is link absolute(if it contain 'https' or http or '//') and if it not absolute, then we add '//' at the beginning of it, to make link absolute */ var makeHrefAbsolute = exports.makeHrefAbsolute = function makeHrefAbsolute(href) { return (/^(https?:)?\/\//.test(href) ? href : '//' + href ); }; var RichTextArea = (_temp = _class = function (_WixComponent) { _inherits(RichTextArea, _WixComponent); /* eslint-disable */ function RichTextArea(props) { _classCallCheck(this, RichTextArea); var _this = _possibleConstructorReturn(this, (RichTextArea.__proto__ || Object.getPrototypeOf(RichTextArea)).call(this, props)); _initialiseProps.call(_this); var editorState = _htmlSerializer2.default.deserialize(props.value); _this.state = { editorState: editorState }; _this.lastValue = props.value; return _this; } /* eslint-disable react/prop-types */ _createClass(RichTextArea, [{ key: 'componentWillReceiveProps', value: function componentWillReceiveProps(props) { var isPlaceholderChanged = props.placeholder !== this.props.placeholder; var isValueChanged = props.value && props.value !== this.props.value && props.value !== this.lastValue; if (isPlaceholderChanged || isValueChanged) { if (props.isAppend) { var newEditorState = this.state.editorState.transform().insertText(props.value).apply(); this.setEditorState(newEditorState); } else { var editorState = _htmlSerializer2.default.deserialize(props.value); this.setEditorState(editorState); } } } }, { key: 'triggerChange', value: function triggerChange() { var isTextChanged = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; var serialized = _htmlSerializer2.default.serialize(this.state.editorState); this.lastValue = serialized; if (isTextChanged) { var onChange = this.props.onChange; onChange && onChange(serialized); } } }]); return RichTextArea; }(_WixComponent3.default), _class.displayName = 'RichTextArea', _class.propTypes = { /** Is the rich text area automatically transforming relative links to absolute after user insert */ absoluteLinks: _propTypes2.default.bool, buttons: _propTypes2.default.arrayOf(_propTypes2.default.string), // TODO: use PropTypes.oneOf(), dataHook: _propTypes2.default.string, /** Is the rich text area disabled */ disabled: _propTypes2.default.bool, /** Is input value erroneous */ error: _propTypes2.default.bool, /** The error message to display when hovering the error icon, if not given or empty there will be no tooltip */ errorMessage: _propTypes2.default.string, /** Placeholder text */ placeholder: _propTypes2.default.string, /** Max height of the text editor */ maxHeight: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]), resizable: _propTypes2.default.bool, /** Content HTML. Supported tags: `p`, `strong`, `em`, `u`, `ul`, `ol`, `li` */ value: _propTypes2.default.string, /** Change callback */ onChange: _propTypes2.default.func, /** Image icon click callback. * It is a function which recieves a callback. * The callback function should be called * when we obtain the url text (callback(text)), causing the image to reflect in the editor */ onImageRequest: _propTypes2.default.func }, _class.defaultProps = { absoluteLinks: false, errorMessage: '', value: '<p></p>' }, _initialiseProps = function _initialiseProps() { var _this2 = this; this.schema = { nodes: { 'unordered-list': function unorderedList(props) { return _react2.default.createElement( 'ul', props.attributes, props.children ); }, 'list-item': function listItem(props) { return _react2.default.createElement( 'li', props.attributes, props.children ); }, 'ordered-list': function orderedList(props) { return _react2.default.createElement( 'ol', props.attributes, props.children ); }, link: function link(props) { var data = props.node.data; var href = data.get('href'); return _react2.default.createElement( 'a', _extends({ className: _RichTextArea2.default.link }, props.attributes, { rel: 'noopener noreferrer', target: '_blank', href: href }), props.children ); }, image: function image(props) { var node = props.node, state = props.state; var isFocused = state.selection.hasEdgeIn(node); var src = node.data.get('src'); return _react2.default.createElement('img', { 'data-hook': 'editor-image', src: src, className: (0, _classnames2.default)(_RichTextArea2.default.editorImage, _defineProperty({}, _RichTextArea2.default.activeEditorImage, isFocused)) }); } }, marks: { bold: { fontWeight: 'bold' }, italic: { fontStyle: 'italic' }, underline: { textDecoration: 'underline' } }, rules: [ // Rule to insert a paragraph block if the document is empty. { match: function match(node) { return node.kind === 'document'; }, validate: function validate(document) { return document.nodes.size ? null : true; }, normalize: function normalize(transform, document) { var block = _slate.Block.create(defaultBlock); transform.insertNodeByKey(document.key, 0, block); } }, // Rule to insert a paragraph below a void node (the image) if that node is // the last one in the document. { match: function match(node) { return node.kind === 'document'; }, validate: function validate(document) { var lastNode = document.nodes.last(); return lastNode && lastNode.isVoid ? true : null; }, normalize: function normalize(transform, document) { var block = _slate.Block.create(defaultBlock); transform.insertNodeByKey(document.key, document.nodes.size, block); } }] }; this.setEditorState = function (editorState) { var isTextChanged = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; _this2.setState({ editorState: editorState }, function () { return _this2.triggerChange(isTextChanged); }); }; this.hasBlock = function (type) { return _this2.state.editorState.blocks.some(function (node) { return node.type == type; }); }; this.hasListBlock = function (type) { var editorState = _this2.state.editorState; return editorState.blocks.some(function (node) { var parent = editorState.document.getParent(node.key); return parent && parent.type === type; }); }; this.hasMark = function (type) { return _this2.state.editorState.marks.some(function (mark) { return mark.type == type; }); }; this.hasLink = function () { return _this2.state.editorState.inlines.some(function (inline) { return inline.type === "link"; }); }; this.handleButtonClick = function (action, type) { _this2.setState({ activeToolbarButton: type }); switch (action) { case "mark": return _this2.handleMarkButtonClick(type); case "block": return _this2.handleBlockButtonClick(type); case "link": return _this2.handleLinkButtonClick(type); case "image": return _this2.handleImageButtonClick(type); } }; this.handleMarkButtonClick = function (type) { var editorState = _this2.state.editorState.transform().toggleMark(type).apply(); _this2.setEditorState(editorState); }; this.handleImageButtonClick = function (type) { _this2.props.onImageRequest(_this2.handleImageInput.bind(_this2)); }; this.handleImageInput = function (text) { if (_this2.isValidImage(text)) { var editorState = _this2.insertImage(_this2.state.editorState, text); _this2.setEditorState(editorState); } }; this.onPaste = function (e, data, state, editor) { switch (data.type) { case "text": return _this2.onPasteText(data.text, state); } }; this.onPasteText = function (text, state) { if (_this2.isValidImage(text)) { return _this2.insertImage(state, text); } return; }; this.isValidImage = function (text) { return (0, _isUrl2.default)(text) && (0, _isImage2.default)(text); }; this.insertImage = function (state, src) { return state.transform().insertBlock({ type: "image", isVoid: true, data: { src: src } }).apply(); }; this.handleBlockButtonClick = function (type) { var editorState = _this2.state.editorState; var transform = editorState.transform(); var _editorState = editorState, document = _editorState.document; // Handle everything but list buttons. if (type !== "unordered-list" && type !== "ordered-list") { var isActive = _this2.hasBlock(type); var isList = _this2.hasBlock("list-item"); if (isList) { transform.setBlock(isActive ? "" : type).unwrapBlock("unordered-list").unwrapBlock("ordered-list"); } else { transform.setBlock(isActive ? "" : type); } } // Handle the extra wrapping required for list buttons. else { var _isList = _this2.hasBlock("list-item"); var isType = editorState.blocks.some(function (block) { return !!document.getClosest(block.key, function (parent) { return parent.type == type; }); }); if (_isList && isType) { transform.setBlock(DEFAULT_NODE).unwrapBlock("unordered-list").unwrapBlock("ordered-list"); } else if (_isList) { transform.unwrapBlock(type == "unordered-list" ? "ordered-list" : "unordered-list").wrapBlock(type); } else { transform.setBlock("list-item").wrapBlock(type); } } editorState = transform.apply(); _this2.setState({ editorState: editorState }); }; this.handleLinkButtonClick = function () { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, href = _ref.href, text = _ref.text; var editorState = _this2.state.editorState; var transform = editorState.transform(); var decoratedHref = _this2.props.absoluteLinks ? makeHrefAbsolute(href) : href; if (_this2.hasLink()) { transform.unwrapInline("link"); } else { var linkContent = text || decoratedHref; var startPos = editorState.anchorOffset; transform.insertText(linkContent).select({ anchorOffset: startPos, focusOffset: startPos + linkContent.length, isFocused: true, isBackward: false }).wrapInline({ type: "link", data: { href: decoratedHref } }).focus().collapseToEnd(); } _this2.setEditorState(transform.apply()); }; this.render = function () { var _classNames2, _classNames4; var editorState = _this2.state.editorState; var _props = _this2.props, error = _props.error, placeholder = _props.placeholder, disabled = _props.disabled, resizable = _props.resizable, onImageRequest = _props.onImageRequest, dataHook = _props.dataHook; var className = (0, _classnames2.default)(_RichTextArea2.default.container, (_classNames2 = {}, _defineProperty(_classNames2, _RichTextArea2.default.withError, error), _defineProperty(_classNames2, _RichTextArea2.default.isEditorFocused, editorState.isFocused), _classNames2)); var isScrollable = resizable || _this2.props.maxHeight; return _react2.default.createElement( 'div', { className: className, 'data-hook': dataHook }, _react2.default.createElement( 'div', { className: (0, _classnames2.default)(_RichTextArea2.default.toolbar, _defineProperty({}, _RichTextArea2.default.disabled, disabled)), 'data-hook': 'toolbar' }, _react2.default.createElement(_RichTextAreaToolbar2.default /* activeToolbarButton prop required to trigger RichTextEditorToolbar re-render after toolbar button click */ , { activeToolbarButton: _this2.state.activeToolbarButton, selection: editorState.fragment.text, disabled: disabled, onClick: _this2.handleButtonClick, onLinkButtonClick: _this2.handleLinkButtonClick, onImageButtonClick: onImageRequest ? _this2.handleImageButtonClick : null, hasMark: _this2.hasMark, hasListBlock: _this2.hasListBlock, hasLink: _this2.hasLink, isSelectionExpanded: editorState.isExpanded }) ), _react2.default.createElement( 'div', { className: (0, _classnames2.default)(_RichTextArea2.default.editorWrapper, (_classNames4 = {}, _defineProperty(_classNames4, _RichTextArea2.default.resizable, resizable), _defineProperty(_classNames4, _RichTextArea2.default.scrollable, isScrollable), _defineProperty(_classNames4, _RichTextArea2.default.disabled, disabled), _classNames4)), 'data-hook': 'editor-wrapper', style: { maxHeight: _this2.props.maxHeight } }, _react2.default.createElement(_slate.Editor, { readOnly: disabled, placeholder: placeholder, placeholderClassName: _RichTextArea2.default.placeholder, className: (0, _classnames2.default)(_RichTextArea2.default.editor, _defineProperty({}, _RichTextArea2.default.disabled, disabled)), schema: _this2.schema, state: editorState, onPaste: _this2.onPaste, onChange: function onChange(e) { var serialized = _htmlSerializer2.default.serialize(e); var isValueChanged = serialized !== _this2.lastValue; _this2.lastValue = serialized; _this2.setEditorState(e, isValueChanged); } }), _this2.renderError() ) ); }; this.renderError = function () { var errorMessage = _this2.props.errorMessage; return _react2.default.createElement( _Tooltip2.default, { disabled: !errorMessage, placement: 'top', moveBy: { x: 2, y: 0 }, alignment: 'center', content: errorMessage, theme: 'dark' }, _react2.default.createElement( 'div', { className: _RichTextArea2.default.exclamation }, _react2.default.createElement(_FormFieldError2.default, null) ) ); }; }, _temp); exports.default = RichTextArea;