UNPKG

upfront-editable

Version:
172 lines (141 loc) 5.5 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _rangy = require('rangy'); var _rangy2 = _interopRequireDefault(_rangy); var _nodeIterator = require('./node-iterator'); var _nodeIterator2 = _interopRequireDefault(_nodeIterator); var _nodeType = require('./node-type'); var nodeType = _interopRequireWildcard(_nodeType); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = { // Get the text from an editable block with a NodeIterator. // This must work the same as when later iterating over the text // in highlightMatches(). extractText: function extractText(element) { var text = ''; getText(element, function (part) { text += part; }); return text; }, // Go through the element to highlight the matches while keeping the // existing html valid (highlighting a match may require inserting multiple // elements). // // @params // - matches // Array of positions in the string to highlight: // e.g [{startIndex: 0, endIndex: 1, match: 'The'}] highlightMatches: function highlightMatches(element, matches) { if (!matches || matches.length === 0) { return; } var iterator = new _nodeIterator2.default(element); var currentMatchIndex = 0; var totalOffset = 0; var currentMatch = matches[currentMatchIndex]; var portions = []; var next = void 0; var wordId = void 0; while (next = iterator.getNext()) { // Account for <br> elements if (next.nodeType === nodeType.textNode && next.data !== '') { var textNode = next; } else if (next.nodeType === nodeType.elementNode && next.nodeName === 'BR') { totalOffset += 1; continue; } else { continue; } var nodeText = textNode.data; var nodeEndOffset = totalOffset + nodeText.length; if (currentMatch.startIndex < nodeEndOffset && totalOffset < currentMatch.endIndex) { // get portion position (fist, last or in the middle) var isFirstPortion = totalOffset <= currentMatch.startIndex; var isLastPortion = nodeEndOffset >= currentMatch.endIndex; if (isFirstPortion) { wordId = currentMatch.id || currentMatch.startIndex; } // calculate offset and length var offset = void 0; if (isFirstPortion) { offset = currentMatch.startIndex - totalOffset; } else { offset = 0; } var length = void 0; if (isLastPortion) { length = currentMatch.endIndex - totalOffset - offset; } else { length = nodeText.length - offset; } // create portion object var portion = { element: textNode, text: nodeText.substring(offset, offset + length), offset: offset, length: length, isLastPortion: isLastPortion, wordId: wordId }; portions.push(portion); if (isLastPortion) { var lastNode = this.wrapMatch(portions, currentMatch.marker, currentMatch.title); iterator.replaceCurrent(lastNode); // recalculate nodeEndOffset if we have to replace the current node. nodeEndOffset = totalOffset + portion.length + portion.offset; portions = []; currentMatchIndex += 1; if (currentMatchIndex < matches.length) { currentMatch = matches[currentMatchIndex]; } } } totalOffset = nodeEndOffset; } }, // @return the last wrapped element wrapMatch: function wrapMatch(portions, stencilElement, title) { var _this = this; return portions.map(function (portion) { return _this.wrapPortion(portion, stencilElement, title); }).pop(); }, wrapPortion: function wrapPortion(portion, stencilElement, title) { var range = _rangy2.default.createRange(); range.setStart(portion.element, portion.offset); range.setEnd(portion.element, portion.offset + portion.length); var node = stencilElement.cloneNode(true); node.setAttribute('data-word-id', portion.wordId); if (title) node.setAttribute('title', title); range.surroundContents(node); // Fix a weird behaviour where an empty text node is inserted after the range if (node.nextSibling) { var next = node.nextSibling; if (next.nodeType === nodeType.textNode && next.data === '') { next.parentNode.removeChild(next); } } return node; } }; // Extract the text of an element. // This has two notable behaviours: // - It uses a NodeIterator which will skip elements // with data-editable="remove" // - It returns a space for <br> elements // (The only block level element allowed inside of editables) function getText(element, callback) { var iterator = new _nodeIterator2.default(element); var next = void 0; while (next = iterator.getNext()) { if (next.nodeType === nodeType.textNode && next.data !== '') { callback(next.data); } else if (next.nodeType === nodeType.elementNode && next.nodeName === 'BR') { callback(' '); // eslint-disable-line standard/no-callback-literal } } }