UNPKG

usfm-editor

Version:
405 lines (305 loc) 15.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createBasicUsfmEditor = exports.BasicUsfmEditor = void 0; var React = _interopRequireWildcard(require("react")); var _slateReact = require("slate-react"); var _slate = require("slate"); var _usfmRenderer = require("../transforms/usfmRenderer"); var _usfmToSlate = require("../transforms/usfmToSlate"); var _normalizeNode = require("../plugins/normalizeNode"); var _keyHandlers = require("../plugins/keyHandlers"); var _slateToUsfm = require("../transforms/slateToUsfm"); var _lodash = require("lodash"); var _MyTransforms = require("../plugins/helpers/MyTransforms"); var _SelectionTransforms = require("../plugins/helpers/SelectionTransforms"); var _identificationTransforms = require("../transforms/identificationTransforms"); var _MyEditor = require("../plugins/helpers/MyEditor"); var _UsfmEditor = require("../UsfmEditor"); var _NodeRules = _interopRequireDefault(require("../utils/NodeRules")); var _UsfmMarkers = require("../utils/UsfmMarkers"); var _NodeTypes = _interopRequireDefault(require("../utils/NodeTypes")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 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 _extends() { _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; }; return _extends.apply(this, arguments); } const createBasicUsfmEditor = () => { const e = /*#__PURE__*/React.forwardRef(({ ...props }, ref) => /*#__PURE__*/React.createElement(BasicUsfmEditor, _extends({}, props, { ref: ref }))); e.displayName = "BasicUsfmEditor"; return e; }; /** * A WYSIWYG editor component for USFM */ exports.createBasicUsfmEditor = createBasicUsfmEditor; class BasicUsfmEditor extends React.Component { constructor(props) { super(props); _defineProperty(this, "slateEditor", void 0); _defineProperty(this, "getMarksAtSelection", () => { if (!this.slateEditor.selection) return []; const markObject = _slate.Editor.marks(this.slateEditor); if (!markObject) return []; const markArray = Object.keys(markObject).filter(k => markObject[k] === true); return markArray; }); _defineProperty(this, "addMarkAtSelection", mark => { if (!this.slateEditor.selection) return; _slate.Editor.addMark(this.slateEditor, mark, true); }); _defineProperty(this, "removeMarkAtSelection", mark => { if (!this.slateEditor.selection) return; _slate.Editor.removeMark(this.slateEditor, mark); }); _defineProperty(this, "getParagraphTypesAtSelection", () => { if (!this.slateEditor.selection) return []; let types = []; for (const entry of _slate.Editor.nodes(this.slateEditor)) { const node = entry[0]; if (_slate.Element.isElement(node) && (_UsfmMarkers.UsfmMarkers.isParagraphType(node) || node.type == _NodeTypes.default.INLINE_CONTAINER)) { types = types.concat(node.type); } } return types; }); _defineProperty(this, "setParagraphTypeAtSelection", marker => { if (!this.slateEditor.selection) return; _slate.Transforms.setNodes(this.slateEditor, { type: marker }, { match: _NodeRules.default.isFormattableBlockType }); }); _defineProperty(this, "goToVerse", verseObject => { if (!verseObject) return; const { chapter, verse } = verseObject; const versePath = _MyEditor.MyEditor.findVersePath(this.slateEditor, chapter, verse); if (!versePath) return; const [verseNode] = _slate.Editor.node(this.slateEditor, versePath); if (!_slate.Element.isElement(verseNode)) return; const verseNumOrRange = _slate.Node.string(verseNode.children[0]); const inlineContainerPath = versePath.concat(1); _SelectionTransforms.SelectionTransforms.moveToStartOfFirstLeaf(this.slateEditor, inlineContainerPath); _slateReact.ReactEditor.focus(this.slateEditor); if (!this.props.onVerseChange || !this.didSelectedVerseChange(chapter, verseNumOrRange)) { return; } this.updateSelectedVerse(chapter, verseNumOrRange); }); _defineProperty(this, "handleChange", value => { console.debug("after change", value); this.fixSelectionOnChapterOrVerseNumber(); this.setState({ value: value }); this.scheduleOnChange(value); }); _defineProperty(this, "scheduleOnChange", (0, _lodash.debounce)(newValue => { if (this.props.onChange) { const usfm = (0, _slateToUsfm.slateToUsfm)(newValue); this.props.onChange(usfm); } if (this.props.onVerseChange) { // No need to keep track of selected chapter and verse if // onVerseChange is not given. this.updateSelectedVerseAfterEditorChange(); } }, 200)); _defineProperty(this, "onKeyDown", event => { (0, _keyHandlers.handleKeyPress)(event, this.slateEditor); }); _defineProperty(this, "updateIdentificationFromProp", () => { if (!this.props.identification) return; const current = _MyEditor.MyEditor.identification(this.slateEditor); const validUpdates = this.filterAndNormalize(this.props.identification); const updated = (0, _identificationTransforms.mergeIdentification)(current, validUpdates); if (!(0, _lodash.isEqual)(updated, current)) { _MyTransforms.MyTransforms.setIdentification(this.slateEditor, updated); if (this.props.onIdentificationChange) { this.props.onIdentificationChange(updated); } } }); _defineProperty(this, "updateIdentificationFromUsfm", () => { const parsedIdentification = (0, _identificationTransforms.parseIdentificationFromUsfm)(this.props.usfmString); const validParsed = this.filterAndNormalize(parsedIdentification); _MyTransforms.MyTransforms.setIdentification(this.slateEditor, validParsed); if (this.props.onIdentificationChange) { this.props.onIdentificationChange(validParsed); } }); _defineProperty(this, "updateIdentificationFromUsfmAndProp", () => { var _this$props$identific; const parsedIdentification = (0, _identificationTransforms.parseIdentificationFromUsfm)(this.props.usfmString); const validParsed = this.filterAndNormalize(parsedIdentification); const updateIdentification = (_this$props$identific = this.props.identification) !== null && _this$props$identific !== void 0 ? _this$props$identific : {}; const validUpdates = this.filterAndNormalize(updateIdentification); const updated = (0, _identificationTransforms.mergeIdentification)(validParsed, validUpdates); _MyTransforms.MyTransforms.setIdentification(this.slateEditor, updated); if (this.props.onIdentificationChange) { this.props.onIdentificationChange(updated); } }); _defineProperty(this, "filterAndNormalize", ids => { const filtered = (0, _identificationTransforms.filterInvalidIdentification)(ids); return (0, _identificationTransforms.normalizeIdentificationValues)(filtered); }); _defineProperty(this, "updateSelectedVerseAfterEditorChange", () => { var _MyEditor$getChapterN; if (!this.slateEditor.selection) return; const verseNodeEntry = _MyEditor.MyEditor.getVerseNode(this.slateEditor); if (!verseNodeEntry) return; const [verseNode] = verseNodeEntry; const chapterNode = (_MyEditor$getChapterN = _MyEditor.MyEditor.getChapterNode(this.slateEditor)) === null || _MyEditor$getChapterN === void 0 ? void 0 : _MyEditor$getChapterN[0]; if (!_slate.Element.isElement(chapterNode) || !_slate.Element.isElement(verseNode)) return; const chapterNum = parseInt(_slate.Node.string(chapterNode.children[0])); const verseNumOrRangeStr = _slate.Node.string(verseNode.children[0]); if (this.didSelectedVerseChange(chapterNum, verseNumOrRangeStr)) { this.updateSelectedVerse(chapterNum, verseNumOrRangeStr); } }); this.state = { value: (0, _usfmToSlate.usfmToSlate)(props.usfmString), selectedVerse: undefined, prevUsfmStringProp: props.usfmString }; this.slateEditor = (0, _lodash.flowRight)(_keyHandlers.withBackspace, _keyHandlers.withDelete, _keyHandlers.withEnter, _normalizeNode.withNormalize, _slateReact.withReact, _slate.createEditor)(); this.slateEditor.isInline = () => false; } // Since usfmString and goToVerse can be updated at the same time, we need to calculate the new // value before the editor renders. Once it renders, we can proceed. static getDerivedStateFromProps(props, state) { if (state.prevUsfmStringProp == props.usfmString) return null; return { value: (0, _usfmToSlate.usfmToSlate)(props.usfmString), prevUsfmStringProp: props.usfmString }; } /* UsfmEditor interface functions */ fixSelectionOnChapterOrVerseNumber() { var _MyEditor$getVerseNod; const editor = this.slateEditor; const selection = editor.selection; if (!selection || !_MyEditor.MyEditor.isVerseOrChapterNumberSelected(editor)) { return; } console.debug("selection before correction: ", selection); const anchorVersePath = (_MyEditor$getVerseNod = _MyEditor.MyEditor.getVerseNode(editor, selection.anchor.path)) === null || _MyEditor$getVerseNod === void 0 ? void 0 : _MyEditor$getVerseNod[1]; if (_slate.Range.isCollapsed(selection) && anchorVersePath) { // This can happen when nodes get merged after the user presses delete at the // start of a verse. The solution is to move to the start of the inline container. _SelectionTransforms.SelectionTransforms.moveToStartOfFirstLeaf(editor, anchorVersePath.concat(1)); } else if (_slate.Range.isBackward(selection)) { // There is currently no solution to the problem when the user selects backwards // through a verse number. Setting the focus to the start of the verse at which // the selection began seems reasonable, but it does not consistently work. _slate.Transforms.deselect(this.slateEditor); } else if (_slate.Range.isForward(selection) && anchorVersePath) { // When the user selects forwards through a verse number, we need to set // the focus to the end of the verse at which they started the selection. // If the errant selection was the result of a double/triple click, we can be assured // that the user's selection came from the left (see the jsdoc for SelectionSeparator), // so we take the same action here. _SelectionTransforms.SelectionTransforms.moveToEndOfLastLeaf(editor, anchorVersePath, { edge: "focus" }); } } updateSelectedVerse(chapter, verseNumOrRange) { const { verseStart, verseEnd } = getVerseStartAndEnd(verseNumOrRange); const newSelectedVerse = { chapter: chapter, verse: verseStart }; this.setState({ selectedVerse: newSelectedVerse }); if (!this.props.onVerseChange) return; this.props.onVerseChange({ chapter, verseStart, verseEnd }); } didSelectedVerseChange(chapter, verseNumOrRange) { const { verseStart } = getVerseStartAndEnd(verseNumOrRange); const newSelectedVerse = { chapter: chapter, verse: verseStart }; return !(0, _lodash.isEqual)(newSelectedVerse, this.state.selectedVerse); } componentDidMount() { this.updateIdentificationFromUsfmAndProp(); if (this.props.goToVerse) { this.goToVerse(this.props.goToVerse); } } componentDidUpdate(prevProps) { if (prevProps.usfmString != this.props.usfmString && prevProps.identification != this.props.identification) { this.updateIdentificationFromUsfmAndProp(); } else if (prevProps.identification != this.props.identification) { this.updateIdentificationFromProp(); } else if (prevProps.usfmString != this.props.usfmString) { this.updateIdentificationFromUsfm(); } if (prevProps.usfmString != this.props.usfmString) { _slate.Transforms.deselect(this.slateEditor); } if (!(0, _lodash.isEqual)(prevProps.goToVerse, this.props.goToVerse)) { this.goToVerse(this.props.goToVerse); } } render() { // The selection may be invalid if the slate value is updated by an external source, // e.g. when the usfmString property just changed. if (this.slateEditor.selection && isInvalidRange(this.slateEditor.selection, this.state.value)) { _slate.Transforms.deselect(this.slateEditor); } return /*#__PURE__*/React.createElement(_slateReact.Slate, { editor: this.slateEditor, value: this.state.value, onChange: this.handleChange }, /*#__PURE__*/React.createElement(_slateReact.Editable, { readOnly: this.props.readOnly, renderElement: _usfmRenderer.renderElementByType, renderLeaf: _usfmRenderer.renderLeafByProps, spellCheck: false, onKeyDown: this.onKeyDown, className: "usfm-editor" })); } } exports.BasicUsfmEditor = BasicUsfmEditor; _defineProperty(BasicUsfmEditor, "propTypes", _UsfmEditor.usfmEditorPropTypes); _defineProperty(BasicUsfmEditor, "defaultProps", _UsfmEditor.usfmEditorDefaultProps); function getVerseStartAndEnd(verseNumOrRange) { if (verseNumOrRange == "front") return { verseStart: 0, verseEnd: 0 }; const [startVerseStr, endVerseStrOrNull] = verseNumOrRange.split("-"); const verseStart = parseInt(startVerseStr); const verseEnd = parseInt(endVerseStrOrNull) || verseStart; return { verseStart, verseEnd }; } function isInvalidRange(range, nodes) { const anchorPath = range.anchor.path; const focusPath = range.focus.path; return nodes.length <= Math.min(anchorPath[0], focusPath[0]) || !_slate.Node.has(nodes[anchorPath[0]], anchorPath.slice(1)) || !_slate.Node.has(nodes[focusPath[0]], focusPath.slice(1)); }