UNPKG

@toast-ui/editor

Version:

GFM Markdown Wysiwyg Editor - Productive and Extensible

1,618 lines (1,495 loc) 723 kB
import { Fragment, Schema, Slice, NodeRange, Mark as Mark$1, DOMParser, Node as Node$3 } from 'prosemirror-model'; import { DecorationSet, Decoration, EditorView } from 'prosemirror-view'; import { ReplaceAroundStep, liftTarget, canSplit, StepMap } from 'prosemirror-transform'; import { TextSelection, Plugin, PluginKey, EditorState, AllSelection, Selection, SelectionRange, NodeSelection } from 'prosemirror-state'; import { keymap } from 'prosemirror-keymap'; import { deleteSelection, selectAll, baseKeymap, chainCommands, joinForward, newlineInCode, setBlockType, wrapIn, toggleMark as toggleMark$1, exitCode } from 'prosemirror-commands'; import { InputRule, inputRules } from 'prosemirror-inputrules'; import { undo, redo, history, undoDepth } from 'prosemirror-history'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from) { for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) to[j] = from[i]; return to; } function __makeTemplateObject(cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; } /** * @fileoverview Execute the provided callback once for each property of object which actually exist. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * Execute the provided callback once for each property of object which actually exist. * If the callback function returns false, the loop will be stopped. * Callback function(iteratee) is invoked with three arguments: * 1) The value of the property * 2) The name of the property * 3) The object being traversed * @param {Object} obj The object that will be traversed * @param {function} iteratee Callback function * @param {Object} [context] Context(this) of callback function * @memberof module:collection * @example * var forEachOwnProperties = require('tui-code-snippet/collection/forEachOwnProperties'); // node, commonjs * * var sum = 0; * * forEachOwnProperties({a:1,b:2,c:3}, function(value){ * sum += value; * }); * alert(sum); // 6 */ function forEachOwnProperties(obj, iteratee, context) { var key; context = context || null; for (key in obj) { if (obj.hasOwnProperty(key)) { if (iteratee.call(context, obj[key], key, obj) === false) { break; } } } } var forEachOwnProperties_1 = forEachOwnProperties; /** * @fileoverview Extend the target object from other objects. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * @module object */ /** * Extend the target object from other objects. * @param {object} target - Object that will be extended * @param {...object} objects - Objects as sources * @returns {object} Extended object * @memberof module:object */ function extend(target, objects) { // eslint-disable-line no-unused-vars var hasOwnProp = Object.prototype.hasOwnProperty; var source, prop, i, len; for (i = 1, len = arguments.length; i < len; i += 1) { source = arguments[i]; for (prop in source) { if (hasOwnProp.call(source, prop)) { target[prop] = source[prop]; } } } return target; } var extend_1 = extend; /** * @fileoverview Check whether the given variable is a string or not. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * Check whether the given variable is a string or not. * If the given variable is a string, return true. * @param {*} obj - Target for checking * @returns {boolean} Is string? * @memberof module:type */ function isString(obj) { return typeof obj === 'string' || obj instanceof String; } var isString_1 = isString; /** * @fileoverview Check whether the given variable is an instance of Array or not. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * Check whether the given variable is an instance of Array or not. * If the given variable is an instance of Array, return true. * @param {*} obj - Target for checking * @returns {boolean} Is array instance? * @memberof module:type */ function isArray(obj) { return obj instanceof Array; } var isArray_1 = isArray; /** * @fileoverview Execute the provided callback once for each element present in the array(or Array-like object) in ascending order. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * Execute the provided callback once for each element present * in the array(or Array-like object) in ascending order. * If the callback function returns false, the loop will be stopped. * Callback function(iteratee) is invoked with three arguments: * 1) The value of the element * 2) The index of the element * 3) The array(or Array-like object) being traversed * @param {Array|Arguments|NodeList} arr The array(or Array-like object) that will be traversed * @param {function} iteratee Callback function * @param {Object} [context] Context(this) of callback function * @memberof module:collection * @example * var forEachArray = require('tui-code-snippet/collection/forEachArray'); // node, commonjs * * var sum = 0; * * forEachArray([1,2,3], function(value){ * sum += value; * }); * alert(sum); // 6 */ function forEachArray(arr, iteratee, context) { var index = 0; var len = arr.length; context = context || null; for (; index < len; index += 1) { if (iteratee.call(context, arr[index], index, arr) === false) { break; } } } var forEachArray_1 = forEachArray; /** * @module collection */ /** * Execute the provided callback once for each property of object(or element of array) which actually exist. * If the object is Array-like object(ex-arguments object), It needs to transform to Array.(see 'ex2' of example). * If the callback function returns false, the loop will be stopped. * Callback function(iteratee) is invoked with three arguments: * 1) The value of the property(or The value of the element) * 2) The name of the property(or The index of the element) * 3) The object being traversed * @param {Object} obj The object that will be traversed * @param {function} iteratee Callback function * @param {Object} [context] Context(this) of callback function * @memberof module:collection * @example * var forEach = require('tui-code-snippet/collection/forEach'); // node, commonjs * * var sum = 0; * * forEach([1,2,3], function(value){ * sum += value; * }); * alert(sum); // 6 * * // In case of Array-like object * var array = Array.prototype.slice.call(arrayLike); // change to array * forEach(array, function(value){ * sum += value; * }); */ function forEach(obj, iteratee, context) { if (isArray_1(obj)) { forEachArray_1(obj, iteratee, context); } else { forEachOwnProperties_1(obj, iteratee, context); } } var forEach_1 = forEach; /** * Setting element style * @param {(HTMLElement|SVGElement)} element - element to setting style * @param {(string|object)} key - style prop name or {prop: value} pair object * @param {string} [value] - style value * @memberof module:domUtil */ function css(element, key, value) { var style = element.style; if (isString_1(key)) { style[key] = value; return; } forEach_1(key, function(v, k) { style[k] = v; }); } var css_1 = css; /** * @module array */ /** * Returns the first index at which a given element can be found in the array * from start index(default 0), or -1 if it is not present. * It compares searchElement to elements of the Array using strict equality * (the same method used by the ===, or triple-equals, operator). * @param {*} searchElement Element to locate in the array * @param {Array} array Array that will be traversed. * @param {number} startIndex Start index in array for searching (default 0) * @returns {number} the First index at which a given element, or -1 if it is not present * @memberof module:array * @example * var inArray = require('tui-code-snippet/array/inArray'); // node, commonjs * * var arr = ['one', 'two', 'three', 'four']; * var idx1 = inArray('one', arr, 3); // -1 * var idx2 = inArray('one', arr); // 0 */ function inArray(searchElement, array, startIndex) { var i; var length; startIndex = startIndex || 0; if (!isArray_1(array)) { return -1; } if (Array.prototype.indexOf) { return Array.prototype.indexOf.call(array, searchElement, startIndex); } length = array.length; for (i = startIndex; startIndex >= 0 && i < length; i += 1) { if (array[i] === searchElement) { return i; } } return -1; } var inArray_1 = inArray; /** * @fileoverview Check whether the given variable is undefined or not. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * Check whether the given variable is undefined or not. * If the given variable is undefined, returns true. * @param {*} obj - Target for checking * @returns {boolean} Is undefined? * @memberof module:type */ function isUndefined(obj) { return obj === undefined; // eslint-disable-line no-undefined } var isUndefined_1 = isUndefined; /** * Get HTML element's design classes. * @param {(HTMLElement|SVGElement)} element target element * @returns {string} element css class name * @memberof module:domUtil */ function getClass(element) { if (!element || !element.className) { return ''; } if (isUndefined_1(element.className.baseVal)) { return element.className; } return element.className.baseVal; } var getClass_1 = getClass; /** * Set className value * @param {(HTMLElement|SVGElement)} element - target element * @param {(string|string[])} cssClass - class names * @private */ function setClassName(element, cssClass) { cssClass = isArray_1(cssClass) ? cssClass.join(' ') : cssClass; cssClass = cssClass.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); if (isUndefined_1(element.className.baseVal)) { element.className = cssClass; return; } element.className.baseVal = cssClass; } var _setClassName = setClassName; /** * domUtil module * @module domUtil */ /** * Add css class to element * @param {(HTMLElement|SVGElement)} element - target element * @param {...string} cssClass - css classes to add * @memberof module:domUtil */ function addClass(element) { var cssClass = Array.prototype.slice.call(arguments, 1); var classList = element.classList; var newClass = []; var origin; if (classList) { forEach_1(cssClass, function(name) { element.classList.add(name); }); return; } origin = getClass_1(element); if (origin) { cssClass = [].concat(origin.split(/\s+/), cssClass); } forEach_1(cssClass, function(cls) { if (inArray_1(cls, newClass) < 0) { newClass.push(cls); } }); _setClassName(element, newClass); } var addClass_1 = addClass; /** * Remove css class from element * @param {(HTMLElement|SVGElement)} element - target element * @param {...string} cssClass - css classes to remove * @memberof module:domUtil */ function removeClass(element) { var cssClass = Array.prototype.slice.call(arguments, 1); var classList = element.classList; var origin, newClass; if (classList) { forEachArray_1(cssClass, function(name) { classList.remove(name); }); return; } origin = getClass_1(element).split(/\s+/); newClass = []; forEachArray_1(origin, function(name) { if (inArray_1(name, cssClass) < 0) { newClass.push(name); } }); _setClassName(element, newClass); } var removeClass_1 = removeClass; /** * @fileoverview Check whether the given variable is a number or not. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * Check whether the given variable is a number or not. * If the given variable is a number, return true. * @param {*} obj - Target for checking * @returns {boolean} Is number? * @memberof module:type */ function isNumber(obj) { return typeof obj === 'number' || obj instanceof Number; } var isNumber_1 = isNumber; /** * @fileoverview Check whether the given variable is null or not. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * Check whether the given variable is null or not. * If the given variable(arguments[0]) is null, returns true. * @param {*} obj - Target for checking * @returns {boolean} Is null? * @memberof module:type */ function isNull(obj) { return obj === null; } var isNull_1 = isNull; /** * @module request */ /** * Request image ping. * @param {String} url url for ping request * @param {Object} trackingInfo infos for make query string * @returns {HTMLElement} * @memberof module:request * @example * var imagePing = require('tui-code-snippet/request/imagePing'); // node, commonjs * * imagePing('https://www.google-analytics.com/collect', { * v: 1, * t: 'event', * tid: 'trackingid', * cid: 'cid', * dp: 'dp', * dh: 'dh' * }); */ function imagePing(url, trackingInfo) { var trackingElement = document.createElement('img'); var queryString = ''; forEachOwnProperties_1(trackingInfo, function(value, key) { queryString += '&' + key + '=' + value; }); queryString = queryString.substring(1); trackingElement.src = url + '?' + queryString; trackingElement.style.display = 'none'; document.body.appendChild(trackingElement); document.body.removeChild(trackingElement); return trackingElement; } var imagePing_1 = imagePing; var ms7days = 7 * 24 * 60 * 60 * 1000; /** * Check if the date has passed 7 days * @param {number} date - milliseconds * @returns {boolean} * @private */ function isExpired(date) { var now = new Date().getTime(); return now - date > ms7days; } /** * Send hostname on DOMContentLoaded. * To prevent hostname set tui.usageStatistics to false. * @param {string} appName - application name * @param {string} trackingId - GA tracking ID * @ignore */ function sendHostname(appName, trackingId) { var url = 'https://www.google-analytics.com/collect'; var hostname = location.hostname; var hitType = 'event'; var eventCategory = 'use'; var applicationKeyForStorage = 'TOAST UI ' + appName + ' for ' + hostname + ': Statistics'; var date = window.localStorage.getItem(applicationKeyForStorage); // skip if the flag is defined and is set to false explicitly if (!isUndefined_1(window.tui) && window.tui.usageStatistics === false) { return; } // skip if not pass seven days old if (date && !isExpired(date)) { return; } window.localStorage.setItem(applicationKeyForStorage, new Date().getTime()); setTimeout(function() { if (document.readyState === 'interactive' || document.readyState === 'complete') { imagePing_1(url, { v: 1, t: hitType, tid: trackingId, cid: hostname, dp: hostname, dh: appName, el: appName, ec: eventCategory }); } }, 1000); } var sendHostname_1 = sendHostname; /Mac/.test(navigator.platform); function sendHostName() { sendHostname_1('editor', 'UA-129966929-1'); } function includes(arr, targetItem) { return arr.indexOf(targetItem) !== -1; } var availableLinkAttributes = ['rel', 'target', 'hreflang', 'type']; function sanitizeLinkAttribute(attribute) { if (!attribute) { return null; } var linkAttributes = {}; availableLinkAttributes.forEach(function (key) { if (!isUndefined_1(attribute[key])) { linkAttributes[key] = attribute[key]; } }); return linkAttributes; } function repeat$1(text, count) { var result = ''; for (var i = 0; i < count; i += 1) { result += text; } return result; } function escape(text, startOfLine) { var result = text.replace(/[`*\\~[\]]/g, '\\$&'); if (startOfLine) { return result.replace(/^[:#\-*+]/, '\\$&').replace(/^(\d+)\./, '$1\\.'); } return result; } function quote(text) { var result; if (text.indexOf('"') === -1) { result = '""'; } else { result = text.indexOf("'") === -1 ? "''" : '()'; } return result[0] + text + result[1]; } function isNil(value) { return isNull_1(value) || isUndefined_1(value); } function shallowEqual(o1, o2) { if (o1 === null && o1 === o2) { return true; } if (typeof o1 !== 'object' || typeof o2 !== 'object' || isNil(o1) || isNil(o2)) { return o1 === o2; } for (var key in o1) { if (o1[key] !== o2[key]) { return false; } } for (var key in o2) { if (!(key in o1)) { return false; } } return true; } function last$1(arr) { return arr[arr.length - 1]; } function between$1(value, min, max) { return value >= min && value <= max; } function isObject$1(obj) { return typeof obj === 'object' && obj !== null; } function deepMergedCopy(targetObj, obj) { var resultObj = __assign({}, targetObj); if (targetObj && obj) { Object.keys(obj).forEach(function (prop) { if (isObject$1(resultObj[prop])) { if (Array.isArray(obj[prop])) { resultObj[prop] = deepCopyArray(obj[prop]); } else if (resultObj.hasOwnProperty(prop)) { resultObj[prop] = deepMergedCopy(resultObj[prop], obj[prop]); } else { resultObj[prop] = deepCopy(obj[prop]); } } else { resultObj[prop] = obj[prop]; } }); } return resultObj; } function deepCopyArray(items) { return items.map(function (item) { if (isObject$1(item)) { return Array.isArray(item) ? deepCopyArray(item) : deepCopy(item); } return item; }); } function deepCopy(obj) { var keys = Object.keys(obj); if (!keys.length) { return obj; } return keys.reduce(function (acc, prop) { if (isObject$1(obj[prop])) { acc[prop] = Array.isArray(obj[prop]) ? deepCopyArray(obj[prop]) : deepCopy(obj[prop]); } else { acc[prop] = obj[prop]; } return acc; }, {}); } function assign(targetObj, obj) { if (obj === void 0) { obj = {}; } Object.keys(obj).forEach(function (prop) { if (targetObj.hasOwnProperty(prop) && typeof targetObj[prop] === 'object') { if (Array.isArray(obj[prop])) { targetObj[prop] = obj[prop]; } else { assign(targetObj[prop], obj[prop]); } } else { targetObj[prop] = obj[prop]; } }); return targetObj; } function getSortedNumPair(valueA, valueB) { return valueA > valueB ? [valueB, valueA] : [valueA, valueB]; } function createParagraph(schema, content) { var paragraph = schema.nodes.paragraph; if (!content) { return paragraph.createAndFill(); } return paragraph.create(null, isString_1(content) ? schema.text(content) : content); } function createTextNode$1(schema, text, marks) { return schema.text(text, marks); } function createTextSelection(tr, from, to) { if (to === void 0) { to = from; } var contentSize = tr.doc.content.size; var size = contentSize > 0 ? contentSize - 1 : 1; return TextSelection.create(tr.doc, Math.min(from, size), Math.min(to, size)); } function addParagraph(tr, _a, schema) { var pos = _a.pos; tr.replaceWith(pos, pos, createParagraph(schema)); return tr.setSelection(createTextSelection(tr, pos + 1)); } function replaceTextNode(_a) { var state = _a.state, from = _a.from, startIndex = _a.startIndex, endIndex = _a.endIndex, createText = _a.createText; var tr = state.tr, doc = state.doc, schema = state.schema; for (var i = startIndex; i <= endIndex; i += 1) { var _b = doc.child(i), nodeSize = _b.nodeSize, textContent = _b.textContent, content = _b.content; var text = createText(textContent); var node = text ? createTextNode$1(schema, text) : Fragment.empty; var mappedFrom = tr.mapping.map(from); var mappedTo = mappedFrom + content.size; tr.replaceWith(mappedFrom, mappedTo, node); from += nodeSize; } return tr; } function splitAndExtendBlock(tr, pos, text, node) { var textLen = text.length; tr.split(pos) .delete(pos - textLen, pos) .insert(tr.mapping.map(pos), node) .setSelection(createTextSelection(tr, tr.mapping.map(pos) - textLen)); } function getMdStartLine(mdNode) { return mdNode.sourcepos[0][0]; } function getMdEndLine(mdNode) { return mdNode.sourcepos[1][0]; } function getMdStartCh(mdNode) { return mdNode.sourcepos[0][1]; } function getMdEndCh(mdNode) { return mdNode.sourcepos[1][1]; } function isHTMLNode(mdNode) { var type = mdNode.type; return type === 'htmlBlock' || type === 'htmlInline'; } function isStyledInlineNode(mdNode) { var type = mdNode.type; return (type === 'strike' || type === 'strong' || type === 'emph' || type === 'code' || type === 'link' || type === 'image'); } function isCodeBlockNode(mdNode) { return mdNode && mdNode.type === 'codeBlock'; } function isListNode$1(mdNode) { return mdNode && (mdNode.type === 'item' || mdNode.type === 'list'); } function isOrderedListNode(mdNode) { return isListNode$1(mdNode) && mdNode.listData.type === 'ordered'; } function isBulletListNode(mdNode) { return isListNode$1(mdNode) && mdNode.listData.type !== 'ordered'; } function isTableCellNode(mdNode) { return mdNode && (mdNode.type === 'tableCell' || mdNode.type === 'tableDelimCell'); } function isInlineNode$1(mdNode) { switch (mdNode.type) { case 'code': case 'text': case 'emph': case 'strong': case 'strike': case 'link': case 'image': case 'htmlInline': case 'linebreak': case 'softbreak': case 'customInline': return true; default: return false; } } function findClosestNode(mdNode, condition, includeSelf) { if (includeSelf === void 0) { includeSelf = true; } mdNode = includeSelf ? mdNode : mdNode.parent; while (mdNode && mdNode.type !== 'document') { if (condition(mdNode)) { return mdNode; } mdNode = mdNode.parent; } return null; } function traverseParentNodes(mdNode, iteratee, includeSelf) { if (includeSelf === void 0) { includeSelf = true; } mdNode = includeSelf ? mdNode : mdNode.parent; while (mdNode && mdNode.type !== 'document') { iteratee(mdNode); mdNode = mdNode.parent; } } function addOffsetPos(originPos, offset) { return [originPos[0], originPos[1] + offset]; } function setOffsetPos(originPos, newOffset) { return [originPos[0], newOffset]; } function getInlineMarkdownText(mdNode) { var text = mdNode.firstChild.literal; switch (mdNode.type) { case 'emph': return "*" + text + "*"; case 'strong': return "**" + text + "**"; case 'strike': return "~~" + text + "~~"; case 'code': return "`" + text + "`"; case 'link': case 'image': /* eslint-disable no-case-declarations */ var _a = mdNode, destination = _a.destination, title = _a.title; var delim = mdNode.type === 'link' ? '' : '!'; return delim + "[" + text + "](" + destination + (title ? " \"" + title + "\"" : '') + ")"; default: return null; } } function isContainer$2(node) { switch (node.type) { case 'document': case 'blockQuote': case 'list': case 'item': case 'paragraph': case 'heading': case 'emph': case 'strong': case 'strike': case 'link': case 'image': case 'table': case 'tableHead': case 'tableBody': case 'tableRow': case 'tableCell': case 'tableDelimRow': case 'customInline': return true; default: return false; } } function getChildrenText$1(node) { var buffer = []; var walker = node.walker(); var event = null; while ((event = walker.next())) { var childNode = event.node; if (childNode.type === 'text') { buffer.push(childNode.literal); } } return buffer.join(''); } var widgetRules = []; var widgetRuleMap = {}; var reWidgetPrefix = /\$\$widget\d+\s/; function unwrapWidgetSyntax(text) { var index = text.search(reWidgetPrefix); if (index !== -1) { var rest = text.substring(index); var replaced = rest.replace(reWidgetPrefix, '').replace('$$', ''); text = text.substring(0, index); text += unwrapWidgetSyntax(replaced); } return text; } function createWidgetContent(info, text) { return "$$" + info + " " + text + "$$"; } function widgetToDOM(info, text) { var _a = widgetRuleMap[info], rule = _a.rule, toDOM = _a.toDOM; text = unwrapWidgetSyntax(text).match(rule)[0]; return toDOM(text); } function getWidgetRules() { return widgetRules; } function setWidgetRules(rules) { widgetRules = rules; widgetRules.forEach(function (rule, index) { widgetRuleMap["widget" + index] = rule; }); } function mergeNodes(nodes, text, schema, ruleIndex) { return nodes.concat(createNodesWithWidget(text, schema, ruleIndex)); } /** * create nodes with plain text and replace text matched to the widget rules with the widget node * For example, in case the text and widget rules as below * * text: $test plain text #test * widget rules: [{ rule: /$.+/ }, { rule: /#.+/ }] * * The creating node process is recursive and is as follows. * * in first widget rule(/$.+/) * $test -> widget node * plain text -> match with next widget rule * #test -> match with next widget rule * * in second widget rule(/#.+/) * plain text -> text node(no rule for matching) * #test -> widget node */ function createNodesWithWidget(text, schema, ruleIndex) { if (ruleIndex === void 0) { ruleIndex = 0; } var nodes = []; var rule = (widgetRules[ruleIndex] || {}).rule; var nextRuleIndex = ruleIndex + 1; text = unwrapWidgetSyntax(text); if (rule && rule.test(text)) { var index = void 0; while ((index = text.search(rule)) !== -1) { var prev = text.substring(0, index); // get widget node on first splitted text using next widget rule if (prev) { nodes = mergeNodes(nodes, prev, schema, nextRuleIndex); } // build widget node using current widget rule text = text.substring(index); var literal = text.match(rule)[0]; var info = "widget" + ruleIndex; nodes.push(schema.nodes.widget.create({ info: info }, schema.text(createWidgetContent(info, literal)))); text = text.substring(literal.length); } // get widget node on last splitted text using next widget rule if (text) { nodes = mergeNodes(nodes, text, schema, nextRuleIndex); } } else if (text) { nodes = ruleIndex < widgetRules.length - 1 ? mergeNodes(nodes, text, schema, nextRuleIndex) : [schema.text(text)]; } return nodes; } function getWidgetContent(widgetNode) { var event; var text = ''; var walker = widgetNode.walker(); while ((event = walker.next())) { var node = event.node, entering = event.entering; if (entering) { if (node !== widgetNode && node.type !== 'text') { text += getInlineMarkdownText(node); // skip the children walker.resumeAt(widgetNode, false); walker.next(); } else if (node.type === 'text') { text += node.literal; } } } return text; } function getDefaultCommands() { return { deleteSelection: function () { return deleteSelection; }, selectAll: function () { return selectAll; }, undo: function () { return undo; }, redo: function () { return redo; }, }; } function placeholder(options) { return new Plugin({ props: { decorations: function (state) { var doc = state.doc; if (options.text && doc.childCount === 1 && doc.firstChild.isTextblock && doc.firstChild.content.size === 0) { var placeHolder = document.createElement('span'); addClass_1(placeHolder, 'placeholder'); if (options.className) { addClass_1(placeHolder, options.className); } placeHolder.textContent = options.text; return DecorationSet.create(doc, [Decoration.widget(1, placeHolder)]); } return null; }, }, }); } var pluginKey$1 = new PluginKey('widget'); var PopupWidget = /** @class */ (function () { function PopupWidget(eventEmitter) { var _this = this; this.popup = null; this.removeWidget = function () { if (_this.popup) { document.body.removeChild(_this.popup); _this.popup = null; } }; this.eventEmitter = eventEmitter; this.eventEmitter.listen('blur', this.removeWidget); } PopupWidget.prototype.update = function (view) { var widget = pluginKey$1.getState(view.state); this.removeWidget(); if (widget) { var node = widget.node, style = widget.style; var _a = view.coordsAtPos(widget.pos), top_1 = _a.top, left = _a.left, bottom = _a.bottom; var height = bottom - top_1; css_1(node, { position: 'absolute', left: left + "px", opacity: '0' }); document.body.appendChild(node); css_1(node, { top: (style === 'bottom' ? top_1 + height : top_1 - node.clientHeight - height) + "px", opacity: '1', }); this.popup = node; view.focus(); } }; PopupWidget.prototype.destroy = function () { this.eventEmitter.removeEventHandler('blur', this.removeWidget); }; return PopupWidget; }()); function addWidget(eventEmitter) { return new Plugin({ key: pluginKey$1, state: { init: function () { return null; }, apply: function (tr) { return tr.getMeta('widget'); }, }, view: function () { return new PopupWidget(eventEmitter); }, }); } /** * Transform the Array-like object to Array. * In low IE (below 8), Array.prototype.slice.call is not perfect. So, try-catch statement is used. * @param {*} arrayLike Array-like object * @returns {Array} Array * @memberof module:collection * @example * var toArray = require('tui-code-snippet/collection/toArray'); // node, commonjs * * var arrayLike = { * 0: 'one', * 1: 'two', * 2: 'three', * 3: 'four', * length: 4 * }; * var result = toArray(arrayLike); * * alert(result instanceof Array); // true * alert(result); // one,two,three,four */ function toArray(arrayLike) { var arr; try { arr = Array.prototype.slice.call(arrayLike); } catch (e) { arr = []; forEachArray_1(arrayLike, function(value) { arr.push(value); }); } return arr; } var toArray_1 = toArray; function addDefaultImageBlobHook(eventEmitter) { eventEmitter.listen('addImageBlobHook', function (blob, callback) { var reader = new FileReader(); reader.onload = function (_a) { var target = _a.target; return callback(target.result); }; reader.readAsDataURL(blob); }); } function emitImageBlobHook(eventEmitter, blob, type) { var hook = function (imageUrl, altText) { eventEmitter.emit('command', 'addImage', { imageUrl: imageUrl, altText: altText || blob.name || 'image', }); }; eventEmitter.emit('addImageBlobHook', blob, hook, type); } function pasteImageOnly(items) { var images = toArray_1(items).filter(function (_a) { var type = _a.type; return type.indexOf('image') !== -1; }); if (images.length === 1) { var item = images[0]; if (item) { return item.getAsFile(); } } return null; } function dropImage(_a) { var eventEmitter = _a.eventEmitter; return new Plugin({ props: { handleDOMEvents: { drop: function (_, ev) { var _a; var items = (_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.files; if (items) { forEachArray_1(items, function (item) { if (item.type.indexOf('image') !== -1) { ev.preventDefault(); ev.stopPropagation(); emitImageBlobHook(eventEmitter, item, ev.type); return false; } return true; }); } return true; }, }, }, }); } var Node$2 = /** @class */ (function () { function Node() { } Object.defineProperty(Node.prototype, "type", { get: function () { return 'node'; }, enumerable: false, configurable: true }); Node.prototype.setContext = function (context) { this.context = context; }; return Node; }()); function widgetNodeView(pmNode) { var dom = document.createElement('span'); var node = widgetToDOM(pmNode.attrs.info, pmNode.textContent); dom.className = 'tui-widget'; dom.appendChild(node); return { dom: dom }; } function isWidgetNode(pmNode) { return pmNode.type.name === 'widget'; } var Widget = /** @class */ (function (_super) { __extends(Widget, _super); function Widget() { return _super !== null && _super.apply(this, arguments) || this; } Object.defineProperty(Widget.prototype, "name", { get: function () { return 'widget'; }, enumerable: false, configurable: true }); Object.defineProperty(Widget.prototype, "schema", { get: function () { return { attrs: { info: { default: null }, }, group: 'inline', inline: true, content: 'text*', selectable: false, atom: true, toDOM: function () { return ['span', { class: 'tui-widget' }, 0]; }, parseDOM: [ { tag: 'span.tui-widget', getAttrs: function (dom) { var text = dom.textContent; var _a = text.match(/\$\$(widget\d+)/), info = _a[1]; return { info: info }; }, }, ], }; }, enumerable: false, configurable: true }); return Widget; }(Node$2)); var EditorBase = /** @class */ (function () { function EditorBase(eventEmitter) { this.el = document.createElement('div'); this.el.className = 'toastui-editor'; this.eventEmitter = eventEmitter; this.placeholder = { text: '' }; } EditorBase.prototype.createState = function () { return EditorState.create({ schema: this.schema, plugins: this.createPlugins(), }); }; EditorBase.prototype.initEvent = function () { var _a = this, eventEmitter = _a.eventEmitter, view = _a.view, editorType = _a.editorType; view.dom.addEventListener('focus', function () { return eventEmitter.emit('focus', editorType); }); view.dom.addEventListener('blur', function () { return eventEmitter.emit('blur', editorType); }); }; EditorBase.prototype.emitChangeEvent = function (tr) { this.eventEmitter.emit('caretChange', this.editorType); if (tr.docChanged) { this.eventEmitter.emit('change', this.editorType); } }; Object.defineProperty(EditorBase.prototype, "defaultPlugins", { get: function () { var _a = getDefaultCommands(), undo = _a.undo, redo = _a.redo; var rules = this.createInputRules(); var plugins = __spreadArray(__spreadArray([], this.keymaps), [ keymap(__assign({ 'Mod-z': undo(), 'Shift-Mod-z': redo() }, baseKeymap)), history(), placeholder(this.placeholder), addWidget(this.eventEmitter), dropImage(this.context), ]); return rules ? plugins.concat(rules) : plugins; }, enumerable: false, configurable: true }); EditorBase.prototype.createInputRules = function () { var widgetRules = getWidgetRules(); var rules = widgetRules.map(function (_a) { var rule = _a.rule; return new InputRule(rule, function (state, match, start, end) { var schema = state.schema, tr = state.tr, doc = state.doc; var allMatched = match.input.match(new RegExp(rule, 'g')); var pos = doc.resolve(start); var parent = pos.parent; var count = 0; if (isWidgetNode(parent)) { parent = pos.node(pos.depth - 1); } parent.forEach(function (child) { return isWidgetNode(child) && (count += 1); }); // replace the content only if the count of matched rules in whole text is greater than current widget node count if (allMatched.length > count) { var content = last$1(allMatched); var nodes = createNodesWithWidget(content, schema); // adjust start position based on widget content return tr.replaceWith(end - content.length + 1, end, nodes); } return null; }); }); return rules.length ? inputRules({ rules: rules }) : null; }; EditorBase.prototype.createSchema = function () { return new Schema({ nodes: this.specs.nodes, marks: this.specs.marks, }); }; EditorBase.prototype.createKeymaps = function (useCommandShortcut) { return useCommandShortcut ? this.specs.keymaps() : []; }; EditorBase.prototype.createCommands = function () { return this.specs.commands(this.view); }; EditorBase.prototype.createPluginProps = function () { var _this = this; return this.extraPlugins.map(function (plugin) { return plugin(_this.eventEmitter); }); }; EditorBase.prototype.focus = function () { this.view.focus(); this.view.dispatch(this.view.state.tr.scrollIntoView()); }; EditorBase.prototype.blur = function () { this.view.dom.blur(); }; EditorBase.prototype.destroy = function () { var _this = this; this.view.destroy(); Object.keys(this).forEach(function (prop) { delete _this[prop]; }); }; EditorBase.prototype.moveCursorToStart = function () { var tr = this.view.state.tr; this.view.dispatch(tr.setSelection(createTextSelection(tr, 1)).scrollIntoView()); this.focus(); }; EditorBase.prototype.moveCursorToEnd = function () { var tr = this.view.state.tr; this.view.dispatch(tr.setSelection(createTextSelection(tr, tr.doc.content.size - 1)).scrollIntoView()); this.focus(); }; EditorBase.prototype.setScrollTop = function (top) { this.view.dom.scrollTop = top; }; EditorBase.prototype.getScrollTop = function () { return this.view.dom.scrollTop; }; EditorBase.prototype.setPlaceholder = function (text) { this.placeholder.text = text; this.view.dispatch(this.view.state.tr.scrollIntoView()); }; EditorBase.prototype.setHeight = function (height) { css_1(this.el, { height: height + "px" }); }; EditorBase.prototype.setMinHeight = function (minHeight) { css_1(this.el, { minHeight: minHeight + "px" }); }; EditorBase.prototype.getElement = function () { return this.el; }; return EditorBase; }()); /** * @fileoverview Check whether the given variable is a function or not. * @author NHN FE Development Lab <dl_javascript@nhn.com> */ /** * Check whether the given variable is a function or not. * If the given variable is a function, return true. * @param {*} obj - Target for checking * @returns {boolean} Is function? * @memberof module:type */ function isFunction(obj) { return obj instanceof Function; } var isFunction_1 = isFunction; function execCommand(view, command, payload) { view.focus(); return command(payload)(view.state, view.dispatch, view); } var SpecManager = /** @class */ (function () { function SpecManager(specs) { this.specs = specs; } Object.defineProperty(SpecManager.prototype, "nodes", { get: function () { return this.specs .filter(function (spec) { return spec.type === 'node'; }) .reduce(function (nodes, _a) { var _b; var name = _a.name, schema = _a.schema; return __assign(__assign({}, nodes), (_b = {}, _b[name] = schema, _b)); }, {}); }, enumerable: false, configurable: true }); Object.defineProperty(SpecManager.prototype, "marks", { get: function () { return this.specs .filter(function (spec) { return spec.type === 'mark'; }) .reduce(function (marks, _a) { var _b; var name = _a.name, schema = _a.schema; return __assign(__assign({}, marks), (_b = {}, _b[name] = schema, _b)); }, {}); }, enumerable: false, configurable: true }); SpecManager.prototype.commands = function (view, addedCommands) { var specCommands = this.specs .filter(function (_a) { var commands = _a.commands; return commands; }) .reduce(function (allCommands, spec) { var commands = {}; var specCommand = spec.commands(); if (isFunction_1(specCommand)) { commands[spec.name] = function (payload) { return execCommand(view, specCommand, payload); }; } else { Object.keys(specCommand).forEach(function (name) { commands[name] = function (payload) { return execCommand(view, specCommand[name], payload); }; }); } return __assign(__assign({}, allCommands), commands); }, {}); var defaultCommands = getDefaultCommands(); Object.keys(defaultCommands).forEach(function (name) { specCommands[name] = function (payload) { return execCommand(view, defaultCommands[name], payload); }; }); if (addedCommands) { Object.keys(addedCommands).forEach(function (name) { specCommands[name] = function (payload) { return execCommand(view, addedCommands[name], payload); }; }); } return specCommands; }; SpecManager.prototype.keymaps = function () { var specKeymaps = this.specs.filter(function (spec) { return spec.keymaps; }).map(function (spec) { return spec.keymaps(); }); return specKeymaps.map(function (keys) { return keymap(keys); }); }; SpecManager.prototype.setContext = function (context) { this.specs.forEach(function (spec) { spec.setContext(context); }); }; return SpecManager; }()); /** * Check element has specific css class * @param {(HTMLElement|SVGElement)} element - target element * @param {string} cssClass - css class * @returns {boolean} * @memberof module:domUtil */ function hasClass(element, cssClass) { var origin; if (element.classList) { return element.classList.contains(cssClass); } origin = getClass_1(element).split(/\s+/); return inArray_1(cssClass, origin) > -1; } var hasClass_1 = hasClass; var elProto = Element.prototype; var matchSelector = elProto.matches || elProto.webkitMatchesSelector || elProto.mozMatchesSelector || elProto.msMatchesSelector || function(selector) { var doc = this.document || this.ownerDocument; return inArray_1(this, toArray_1(doc.querySelectorAll(selector))) > -1; }; /** * Check element match selector * @param {HTMLElement} element - element to check * @param {string} selector - selector to check * @returns {boolean} is selector matched to element? * @memberof module:domUtil */ function matches(element, selector) { return matchSelector.call(element, selector); } var matches_1 = matches; function isPositionInBox(style, offsetX, offsetY) { var left = parseInt(style.left, 10); var top = parseInt(style.top, 10); var width = parseInt(style.width, 10) + parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10); var height = parseInt(style.height, 10) + parseInt(style.paddingTop, 10) + parseInt(style.paddingBottom, 10); return offsetX >= left && offsetX <= left + width && offsetY >= top && offsetY <= top + height; } var CLS_PREFIX = 'toastui-editor-'; function cls() { var names = []; for (var _i = 0; _i < arguments.length; _i++) { names[_i] = arguments[_i]; } return names.map(function (className) { return "" + CLS_PREFIX + className; }).join(' '); } function clsWithMdPrefix() { var names = []; for (var _i = 0; _i < arguments.length; _i++) { names[_i] = arguments[_i]; } return names.map(function (className) { return CLS_PREFIX + "md-" + className; }).join(' '); } function isTextNode(node) { return (node === null || node === void 0 ? void 0 : node.nodeType) === Node.TEXT_NODE; } function isElemNode(node) { return node && node.nodeType === Node.ELEMENT_NODE; } function findNodes(element, selector) { var nodeList = toArray_1(element.querySelectorAll(selector)); if (nodeList.length) { return nodeList; } return []; } function appendNodes(node, nodesToAppend) { nodesToAppend = isArray_1(nodesToAppend) ? toArray_1(nodesToAppend) : [nodesToAppend]; nodesToAppend.forEach(function (nodeToAppend) { node.appendChild(nodeToAppend); }); } function insertBeforeNode(insertedNode, node) { if (node.parentNode) { node.parentNode.insertBefore(insertedNode, node); } } function removeNode$1(node) { if (node.parentNode) { node.parentNode.removeChild(node); } } function unwrapNode(node) { var result = []; while (node.firstChild) { result.push(node.firstChild); if (node.parentNode) { node.parentNode.insertBefore(node.firstChild, node); } } removeNode$1(node); return result; } function toggleClass(element, className, state) { if (isUndefined_1(state)) { state = !hasClass_1(element, className); } var toggleFn = state ? addClass_1 : removeClass_1; toggleFn(element, className); } function createElementWith(contents, target) { var contain