diff --git a/.vscode/settings.json b/.vscode/settings.json index 56453056a..a32c00042 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { "http.proxyStrictSSL": false -} \ No newline at end of file +} diff --git a/README.md b/README.md index 720d3c023..a8d45f9fe 100755 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # [Users Guide](https://bit.ly/2JaSlQd) for GURPS 4e Game Aid for Foundry VTT + If you can't access the Google doc, here is a [PDF](https://github.com/crnormand/gurps/raw/main/docs/Guide%20for%20GURPS%204e%20on%20Foundry%20VTT.pdf) of the latest version. # Current Release Version 0.14.10 (compatible with Foundry 0.9.x) + With support for the [Nordlondr Ovinabokin: Bestiary and Enemies module](https://foundryvtt.com/packages/nordlond-bestiary)! ### [Change Log](changelog.md) -The list was getting just too long, so it has been moved to a separate file. Click above to see what has changed. +The list was getting just too long, so it has been moved to a separate file. Click above to see what has changed. If you like our work [Sponsor our development](https://github.com/sponsors/crnormand) or @@ -14,9 +16,8 @@ Join us on Discord: [GURPS Foundry-VTT Discord](https://discord.gg/6xJBcYWyED) [Current GCA4 Export version: 'GCA-11' 12/23/2021 / Current GCA5 Export version: 'GCA5-13' 4/26/2022 / Current GCS Export version: 'GCS-5' 3/8/2021](https://drive.google.com/file/d/1vbDb9WtYQiZI78Pwa_TlEvYpJnR_S67B/view?usp=sharing) - - #### Legal + The material presented here is my original creation, intended for use with the [GURPS](http://www.sjgames.com/gurps) system from [Steve Jackson Games](ttp://www.sjgames.com). This material is not official and is not endorsed by Steve Jackson Games. [GURPS](http://www.sjgames.com/gurps) is a trademark of Steve Jackson Games, and its rules and art are copyrighted by Steve Jackson Games. All rights are reserved by Steve Jackson Games. This game aid is the original creation of Chris Normand/Nose66 and is released for free distribution, and not for resale, under the permissions granted in the [Steve Jackson Games Online Policy](http://www.sjgames.com/general/online_policy.html) diff --git a/changelog.md b/changelog.md index 20d8540f1..39cb619e5 100644 --- a/changelog.md +++ b/changelog.md @@ -16,7 +16,7 @@ Release 0.14.10 - 9/07/2022 - Fixed, reinstalled individual dice results on targetted rolls. - Enhanced /show to accept -a (sort alphabetically), -pc and -npc flags - Fixed import of usage notes for melee and ranged weapons -- Fixed /anim file selection algorithm (to determine best fit). NOTE: You may need to adjust your current targeted /anim commands to get the right "look". +- Fixed /anim file selection algorithm (to determine best fit). NOTE: You may need to adjust your current targeted /anim commands to get the right "look". - Added 'Show?' button to tooltip - Holding CTRL/CMD and clicking a "send to" will add, not replace player's bucket - Update to JB2A 0.4.9 @@ -39,7 +39,7 @@ Release 0.14.7 - 7/28/2022 - Added 'disarmed' status effect icon - Fixed /pr (private roll), and /psr (private selected roll) -- Update JB2A to 0.4.6 +- Update JB2A to 0.4.6 Release 0.14.6 - 7/24/2022 @@ -56,14 +56,14 @@ Release 0.14.5 - 7/24/2022 - Holding CTRL shows changes in roll mode (GM roll for GMs, Blind roll for Players), w/system setting - Fix /hp +1 @target for good? - Damage column can now execute OTFs (ex: PDF:B405) -- OTF now handles HTTP URLs. [http://google.com], as well as labeled ["Google!"http://google.com] +- OTF now handles HTTP URLs. [http://google.com], as well as labeled ["Google!"http://google.com] - Drag and drop PDF Journal links now open PDFoundry, and not the placeholder Journal - Show "flavor" text below roll (issue #1426) - Add ability to roll dice or damage multiple times from chat "/r [3d] 5" or "/r [3d cut] 5" - Add ability to roll dice or damage multiple times from chat (using compact syntax) "/3d 5" or "/3d cut 5" - Fixed initial vtt-notes import from GCS - Added warning for /repeat X /anim ... when actor not linked -- Added "selected roll", /sr [otf], /psr [otf] . "rolls" (executes) OTF against the selected actors. /sr [per], /sr [/hp -1d-3!], /sr [/hp reset] +- Added "selected roll", /sr [otf], /psr [otf] . "rolls" (executes) OTF against the selected actors. /sr [per], /sr [/hp -1d-3!], /sr [/hp reset] Release 0.14.4 7/7/2022 diff --git a/lib/change-log.js b/lib/change-log.js index 5ca41176b..b4580c913 100755 --- a/lib/change-log.js +++ b/lib/change-log.js @@ -1,69 +1,69 @@ -import "./markdown-it.js"; -import { SemanticVersion } from "./semver.js"; +import './markdown-it.js' +import { SemanticVersion } from './semver.js' export class ChangeLogWindow extends FormApplication { constructor(lastVersion) { - super({}, {}); - - this.lastVersion = lastVersion; + super({}, {}) + + this.lastVersion = lastVersion } static get defaultOptions() { - const options = super.defaultOptions; + const options = super.defaultOptions return mergeObject(options, { - id: "changelog", - classes: ["gurps", "changelog"], - template: "systems/gurps/templates/changelog.html", + id: 'changelog', + classes: ['gurps', 'changelog'], + template: 'systems/gurps/templates/changelog.html', width: 700, submitOnChange: true, closeOnSubmit: false, - }); + }) } get title() { - return `${game.i18n.localize("GURPS.title")} ~ ${game.i18n.localize("GURPS.changelog")}`; + return `${game.i18n.localize('GURPS.title')} ~ ${game.i18n.localize('GURPS.changelog')}` } async getData() { - let data = await super.getData(); + let data = await super.getData() - let xhr = new XMLHttpRequest(); - xhr.open("GET", "systems/gurps/changelog.md"); + let xhr = new XMLHttpRequest() + xhr.open('GET', 'systems/gurps/changelog.md') let promise = new Promise(resolve => { xhr.onload = () => { if (xhr.status === 200) { - data.changelog = this._processChangelog(xhr.response); - resolve(data); + data.changelog = this._processChangelog(xhr.response) + resolve(data) } - }; - }); - xhr.send(null); + } + }) + xhr.send(null) - return promise; + return promise } _processChangelog(md) { - const MD = window.markdownit(); - md = md.replace(//g,""); // Remove HTML link from internal changelog display + const MD = window.markdownit() + md = md.replace(//g, '') // Remove HTML link from internal changelog display // Cut off irrelevant changelog entries - let lines = md.split(/[\n\r]/); - let count = 0 // Max at 5 + let lines = md.split(/[\n\r]/) + let count = 0 // Max at 5 if (this.lastVersion) { for (let a = 0; a < lines.length; a++) { - let line = lines[a]; + let line = lines[a] if (line.match(/([0-9]+\.[0-9]+\.[0-9]+)/)) { count++ - const version = SemanticVersion.fromString(RegExp.$1); + const version = SemanticVersion.fromString(RegExp.$1) if (count > 5 || !version.isHigherThan(this.lastVersion)) { - lines = lines.slice(0, a); - break; + lines = lines.slice(0, a) + break } } } } - return MD.render(lines.join("\n")); + return MD.render(lines.join('\n')) } } diff --git a/lib/hitpoints.js b/lib/hitpoints.js index 55f25b6aa..c7915de86 100755 --- a/lib/hitpoints.js +++ b/lib/hitpoints.js @@ -17,7 +17,7 @@ const hpConditions = { style: 'normal', }, REELING: { - breakpoint: HP => Math.ceil(HP.max / 3)-1, + breakpoint: HP => Math.ceil(HP.max / 3) - 1, label: 'GURPS.STATUSReeling', style: 'reeling', }, @@ -66,14 +66,14 @@ const fpConditions = { style: 'normal', }, REELING: { - breakpoint: FP => Math.ceil(FP.max / 3)-1, + breakpoint: FP => Math.ceil(FP.max / 3) - 1, label: 'GURPS.tired', style: 'tired', }, COLLAPSE: { breakpoint: _ => 0, label: 'GURPS.collapse', - + style: 'collapse', }, UNCONSCIOUS: { diff --git a/lib/initiative.js b/lib/initiative.js index 1ad6b2d29..bc29a2b3e 100755 --- a/lib/initiative.js +++ b/lib/initiative.js @@ -6,16 +6,16 @@ export default class Initiative { constructor() { this.setup() } - + static defaultFormula() { - return "((@basicspeed.value*100) + (@attributes.DX.value / 100) + (1d6 / 1000)) / 100" + return '((@basicspeed.value*100) + (@attributes.DX.value / 100) + (1d6 / 1000)) / 100' } setup() { - Hooks.once("init", () => { + Hooks.once('init', () => { CONFIG.Combat.initiative = { formula: Initiative.defaultFormula(), - decimals: 5 // Important to be able to maintain resolution + decimals: 5, // Important to be able to maintain resolution } }) } diff --git a/lib/markdown-it.js b/lib/markdown-it.js index dc2db5f76..dac66ae8d 100755 --- a/lib/markdown-it.js +++ b/lib/markdown-it.js @@ -1,8160 +1,10978 @@ -/*! markdown-it 11.0.1 https://github.com//markdown-it/markdown-it @license MIT */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.markdownit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i utf16string } -// -'use strict'; - -/*eslint quotes:0*/ -module.exports = require('entities/lib/maps/entities.json'); - -},{"entities/lib/maps/entities.json":52}],2:[function(require,module,exports){ -// List of valid html blocks names, accorting to commonmark spec -// http://jgm.github.io/CommonMark/spec.html#html-blocks - -'use strict'; - - -module.exports = [ - 'address', - 'article', - 'aside', - 'base', - 'basefont', - 'blockquote', - 'body', - 'caption', - 'center', - 'col', - 'colgroup', - 'dd', - 'details', - 'dialog', - 'dir', - 'div', - 'dl', - 'dt', - 'fieldset', - 'figcaption', - 'figure', - 'footer', - 'form', - 'frame', - 'frameset', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'head', - 'header', - 'hr', - 'html', - 'iframe', - 'legend', - 'li', - 'link', - 'main', - 'menu', - 'menuitem', - 'meta', - 'nav', - 'noframes', - 'ol', - 'optgroup', - 'option', - 'p', - 'param', - 'section', - 'source', - 'summary', - 'table', - 'tbody', - 'td', - 'tfoot', - 'th', - 'thead', - 'title', - 'tr', - 'track', - 'ul' -]; - -},{}],3:[function(require,module,exports){ -// Regexps to match html elements - -'use strict'; - -var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; - -var unquoted = '[^"\'=<>`\\x00-\\x20]+'; -var single_quoted = "'[^']*'"; -var double_quoted = '"[^"]*"'; - -var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'; - -var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'; - -var open_tag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>'; - -var close_tag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>'; -var comment = '|'; -var processing = '<[?].*?[?]>'; -var declaration = ']*>'; -var cdata = ''; - -var HTML_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + '|' + comment + - '|' + processing + '|' + declaration + '|' + cdata + ')'); -var HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')'); - -module.exports.HTML_TAG_RE = HTML_TAG_RE; -module.exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE; - -},{}],4:[function(require,module,exports){ -// Utilities -// -'use strict'; - - -function _class(obj) { return Object.prototype.toString.call(obj); } - -function isString(obj) { return _class(obj) === '[object String]'; } - -var _hasOwnProperty = Object.prototype.hasOwnProperty; - -function has(object, key) { - return _hasOwnProperty.call(object, key); -} - -// Merge objects -// -function assign(obj /*from1, from2, from3, ...*/) { - var sources = Array.prototype.slice.call(arguments, 1); - - sources.forEach(function (source) { - if (!source) { return; } - - if (typeof source !== 'object') { - throw new TypeError(source + 'must be object'); - } - - Object.keys(source).forEach(function (key) { - obj[key] = source[key]; - }); - }); - - return obj; -} - -// Remove element from array and put another array at those position. -// Useful for some operations with tokens -function arrayReplaceAt(src, pos, newElements) { - return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); -} - -//////////////////////////////////////////////////////////////////////////////// - -function isValidEntityCode(c) { - /*eslint no-bitwise:0*/ - // broken sequence - if (c >= 0xD800 && c <= 0xDFFF) { return false; } - // never used - if (c >= 0xFDD0 && c <= 0xFDEF) { return false; } - if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; } - // control codes - if (c >= 0x00 && c <= 0x08) { return false; } - if (c === 0x0B) { return false; } - if (c >= 0x0E && c <= 0x1F) { return false; } - if (c >= 0x7F && c <= 0x9F) { return false; } - // out of range - if (c > 0x10FFFF) { return false; } - return true; -} - -function fromCodePoint(c) { - /*eslint no-bitwise:0*/ - if (c > 0xffff) { - c -= 0x10000; - var surrogate1 = 0xd800 + (c >> 10), - surrogate2 = 0xdc00 + (c & 0x3ff); +/*! markdown-it 11.0.1 https://github.com//markdown-it/markdown-it @license MIT */ ;(function (f) { + if (typeof exports === 'object' && typeof module !== 'undefined') { + module.exports = f() + } else if (typeof define === 'function' && define.amd) { + define([], f) + } else { + var g + if (typeof window !== 'undefined') { + g = window + } else if (typeof global !== 'undefined') { + g = global + } else if (typeof self !== 'undefined') { + g = self + } else { + g = this + } + g.markdownit = f() + } +})(function () { + var define, module, exports + return (function () { + function r(e, n, t) { + function o(i, f) { + if (!n[i]) { + if (!e[i]) { + var c = 'function' == typeof require && require + if (!f && c) return c(i, !0) + if (u) return u(i, !0) + var a = new Error("Cannot find module '" + i + "'") + throw ((a.code = 'MODULE_NOT_FOUND'), a) + } + var p = (n[i] = { exports: {} }) + e[i][0].call( + p.exports, + function (r) { + var n = e[i][1][r] + return o(n || r) + }, + p, + p.exports, + r, + e, + n, + t + ) + } + return n[i].exports + } + for (var u = 'function' == typeof require && require, i = 0; i < t.length; i++) o(t[i]) + return o + } + return r + })()( + { + 1: [ + function (require, module, exports) { + // HTML5 entities map: { name -> utf16string } + // + 'use strict' - return String.fromCharCode(surrogate1, surrogate2); - } - return String.fromCharCode(c); -} + /*eslint quotes:0*/ + module.exports = require('entities/lib/maps/entities.json') + }, + { 'entities/lib/maps/entities.json': 52 }, + ], + 2: [ + function (require, module, exports) { + // List of valid html blocks names, accorting to commonmark spec + // http://jgm.github.io/CommonMark/spec.html#html-blocks + + 'use strict' + + module.exports = [ + 'address', + 'article', + 'aside', + 'base', + 'basefont', + 'blockquote', + 'body', + 'caption', + 'center', + 'col', + 'colgroup', + 'dd', + 'details', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hr', + 'html', + 'iframe', + 'legend', + 'li', + 'link', + 'main', + 'menu', + 'menuitem', + 'meta', + 'nav', + 'noframes', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'section', + 'source', + 'summary', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'title', + 'tr', + 'track', + 'ul', + ] + }, + {}, + ], + 3: [ + function (require, module, exports) { + // Regexps to match html elements + + 'use strict' + + var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*' + + var unquoted = '[^"\'=<>`\\x00-\\x20]+' + var single_quoted = "'[^']*'" + var double_quoted = '"[^"]*"' + + var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')' + + var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)' + + var open_tag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>' + + var close_tag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>' + var comment = '|' + var processing = '<[?].*?[?]>' + var declaration = ']*>' + var cdata = '' + + var HTML_TAG_RE = new RegExp( + '^(?:' + + open_tag + + '|' + + close_tag + + '|' + + comment + + '|' + + processing + + '|' + + declaration + + '|' + + cdata + + ')' + ) + var HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')') + + module.exports.HTML_TAG_RE = HTML_TAG_RE + module.exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE + }, + {}, + ], + 4: [ + function (require, module, exports) { + // Utilities + // + 'use strict' + function _class(obj) { + return Object.prototype.toString.call(obj) + } -var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g; -var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; -var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi'); + function isString(obj) { + return _class(obj) === '[object String]' + } -var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; + var _hasOwnProperty = Object.prototype.hasOwnProperty -var entities = require('./entities'); + function has(object, key) { + return _hasOwnProperty.call(object, key) + } -function replaceEntityPattern(match, name) { - var code = 0; + // Merge objects + // + function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1) - if (has(entities, name)) { - return entities[name]; - } + sources.forEach(function (source) { + if (!source) { + return + } - if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { - code = name[1].toLowerCase() === 'x' ? - parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); + if (typeof source !== 'object') { + throw new TypeError(source + 'must be object') + } - if (isValidEntityCode(code)) { - return fromCodePoint(code); - } - } + Object.keys(source).forEach(function (key) { + obj[key] = source[key] + }) + }) - return match; -} + return obj + } -/*function replaceEntities(str) { - if (str.indexOf('&') < 0) { return str; } + // Remove element from array and put another array at those position. + // Useful for some operations with tokens + function arrayReplaceAt(src, pos, newElements) { + return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)) + } - return str.replace(ENTITY_RE, replaceEntityPattern); -}*/ + //////////////////////////////////////////////////////////////////////////////// -function unescapeMd(str) { - if (str.indexOf('\\') < 0) { return str; } - return str.replace(UNESCAPE_MD_RE, '$1'); -} - -function unescapeAll(str) { - if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { return str; } - - return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { - if (escaped) { return escaped; } - return replaceEntityPattern(match, entity); - }); -} - -//////////////////////////////////////////////////////////////////////////////// - -var HTML_ESCAPE_TEST_RE = /[&<>"]/; -var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; -var HTML_REPLACEMENTS = { - '&': '&', - '<': '<', - '>': '>', - '"': '"' -}; - -function replaceUnsafeChar(ch) { - return HTML_REPLACEMENTS[ch]; -} - -function escapeHtml(str) { - if (HTML_ESCAPE_TEST_RE.test(str)) { - return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); - } - return str; -} + function isValidEntityCode(c) { + /*eslint no-bitwise:0*/ + // broken sequence + if (c >= 0xd800 && c <= 0xdfff) { + return false + } + // never used + if (c >= 0xfdd0 && c <= 0xfdef) { + return false + } + if ((c & 0xffff) === 0xffff || (c & 0xffff) === 0xfffe) { + return false + } + // control codes + if (c >= 0x00 && c <= 0x08) { + return false + } + if (c === 0x0b) { + return false + } + if (c >= 0x0e && c <= 0x1f) { + return false + } + if (c >= 0x7f && c <= 0x9f) { + return false + } + // out of range + if (c > 0x10ffff) { + return false + } + return true + } -//////////////////////////////////////////////////////////////////////////////// + function fromCodePoint(c) { + /*eslint no-bitwise:0*/ + if (c > 0xffff) { + c -= 0x10000 + var surrogate1 = 0xd800 + (c >> 10), + surrogate2 = 0xdc00 + (c & 0x3ff) -var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; + return String.fromCharCode(surrogate1, surrogate2) + } + return String.fromCharCode(c) + } -function escapeRE(str) { - return str.replace(REGEXP_ESCAPE_RE, '\\$&'); -} + var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g + var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi + var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi') -//////////////////////////////////////////////////////////////////////////////// + var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i -function isSpace(code) { - switch (code) { - case 0x09: - case 0x20: - return true; - } - return false; -} - -// Zs (unicode class) || [\t\f\v\r\n] -function isWhiteSpace(code) { - if (code >= 0x2000 && code <= 0x200A) { return true; } - switch (code) { - case 0x09: // \t - case 0x0A: // \n - case 0x0B: // \v - case 0x0C: // \f - case 0x0D: // \r - case 0x20: - case 0xA0: - case 0x1680: - case 0x202F: - case 0x205F: - case 0x3000: - return true; - } - return false; -} - -//////////////////////////////////////////////////////////////////////////////// - -/*eslint-disable max-len*/ -var UNICODE_PUNCT_RE = require('uc.micro/categories/P/regex'); - -// Currently without astral characters support. -function isPunctChar(ch) { - return UNICODE_PUNCT_RE.test(ch); -} - - -// Markdown ASCII punctuation characters. -// -// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ -// http://spec.commonmark.org/0.15/#ascii-punctuation-character -// -// Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. -// -function isMdAsciiPunct(ch) { - switch (ch) { - case 0x21/* ! */: - case 0x22/* " */: - case 0x23/* # */: - case 0x24/* $ */: - case 0x25/* % */: - case 0x26/* & */: - case 0x27/* ' */: - case 0x28/* ( */: - case 0x29/* ) */: - case 0x2A/* * */: - case 0x2B/* + */: - case 0x2C/* , */: - case 0x2D/* - */: - case 0x2E/* . */: - case 0x2F/* / */: - case 0x3A/* : */: - case 0x3B/* ; */: - case 0x3C/* < */: - case 0x3D/* = */: - case 0x3E/* > */: - case 0x3F/* ? */: - case 0x40/* @ */: - case 0x5B/* [ */: - case 0x5C/* \ */: - case 0x5D/* ] */: - case 0x5E/* ^ */: - case 0x5F/* _ */: - case 0x60/* ` */: - case 0x7B/* { */: - case 0x7C/* | */: - case 0x7D/* } */: - case 0x7E/* ~ */: - return true; - default: - return false; - } -} - -// Hepler to unify [reference labels]. -// -function normalizeReference(str) { - // Trim and collapse whitespace - // - str = str.trim().replace(/\s+/g, ' '); - - // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug - // fixed in v12 (couldn't find any details). - // - // So treat this one as a special case - // (remove this when node v10 is no longer supported). - // - if ('ẞ'.toLowerCase() === 'Ṿ') { - str = str.replace(/ẞ/g, 'ß'); - } + var entities = require('./entities') - // .toLowerCase().toUpperCase() should get rid of all differences - // between letter variants. - // - // Simple .toLowerCase() doesn't normalize 125 code points correctly, - // and .toUpperCase doesn't normalize 6 of them (list of exceptions: - // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently - // uppercased versions). - // - // Here's an example showing how it happens. Lets take greek letter omega: - // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) - // - // Unicode entries: - // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; - // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 - // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 - // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; - // - // Case-insensitive comparison should treat all of them as equivalent. - // - // But .toLowerCase() doesn't change ϑ (it's already lowercase), - // and .toUpperCase() doesn't change ϴ (already uppercase). - // - // Applying first lower then upper case normalizes any character: - // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' - // - // Note: this is equivalent to unicode case folding; unicode normalization - // is a different step that is not required here. - // - // Final result should be uppercased, because it's later stored in an object - // (this avoid a conflict with Object.prototype members, - // most notably, `__proto__`) - // - return str.toLowerCase().toUpperCase(); -} - -//////////////////////////////////////////////////////////////////////////////// - -// Re-export libraries commonly used in both markdown-it and its plugins, -// so plugins won't have to depend on them explicitly, which reduces their -// bundled size (e.g. a browser build). -// -exports.lib = {}; -exports.lib.mdurl = require('mdurl'); -exports.lib.ucmicro = require('uc.micro'); - -exports.assign = assign; -exports.isString = isString; -exports.has = has; -exports.unescapeMd = unescapeMd; -exports.unescapeAll = unescapeAll; -exports.isValidEntityCode = isValidEntityCode; -exports.fromCodePoint = fromCodePoint; -// exports.replaceEntities = replaceEntities; -exports.escapeHtml = escapeHtml; -exports.arrayReplaceAt = arrayReplaceAt; -exports.isSpace = isSpace; -exports.isWhiteSpace = isWhiteSpace; -exports.isMdAsciiPunct = isMdAsciiPunct; -exports.isPunctChar = isPunctChar; -exports.escapeRE = escapeRE; -exports.normalizeReference = normalizeReference; - -},{"./entities":1,"mdurl":58,"uc.micro":65,"uc.micro/categories/P/regex":63}],5:[function(require,module,exports){ -// Just a shortcut for bulk export -'use strict'; - - -exports.parseLinkLabel = require('./parse_link_label'); -exports.parseLinkDestination = require('./parse_link_destination'); -exports.parseLinkTitle = require('./parse_link_title'); - -},{"./parse_link_destination":6,"./parse_link_label":7,"./parse_link_title":8}],6:[function(require,module,exports){ -// Parse link destination -// -'use strict'; - - -var unescapeAll = require('../common/utils').unescapeAll; - - -module.exports = function parseLinkDestination(str, pos, max) { - var code, level, - lines = 0, - start = pos, - result = { - ok: false, - pos: 0, - lines: 0, - str: '' - }; - - if (str.charCodeAt(pos) === 0x3C /* < */) { - pos++; - while (pos < max) { - code = str.charCodeAt(pos); - if (code === 0x0A /* \n */) { return result; } - if (code === 0x3E /* > */) { - result.pos = pos + 1; - result.str = unescapeAll(str.slice(start + 1, pos)); - result.ok = true; - return result; - } - if (code === 0x5C /* \ */ && pos + 1 < max) { - pos += 2; - continue; - } + function replaceEntityPattern(match, name) { + var code = 0 - pos++; - } + if (has(entities, name)) { + return entities[name] + } - // no closing '>' - return result; - } + if (name.charCodeAt(0) === 0x23 /* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + code = name[1].toLowerCase() === 'x' ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10) - // this should be ... } else { ... branch + if (isValidEntityCode(code)) { + return fromCodePoint(code) + } + } - level = 0; - while (pos < max) { - code = str.charCodeAt(pos); + return match + } - if (code === 0x20) { break; } + /*function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } - // ascii control characters - if (code < 0x20 || code === 0x7F) { break; } + return str.replace(ENTITY_RE, replaceEntityPattern); +}*/ - if (code === 0x5C /* \ */ && pos + 1 < max) { - pos += 2; - continue; - } + function unescapeMd(str) { + if (str.indexOf('\\') < 0) { + return str + } + return str.replace(UNESCAPE_MD_RE, '$1') + } - if (code === 0x28 /* ( */) { - level++; - } + function unescapeAll(str) { + if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { + return str + } - if (code === 0x29 /* ) */) { - if (level === 0) { break; } - level--; - } + return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { + if (escaped) { + return escaped + } + return replaceEntityPattern(match, entity) + }) + } - pos++; - } + //////////////////////////////////////////////////////////////////////////////// - if (start === pos) { return result; } - if (level !== 0) { return result; } - - result.str = unescapeAll(str.slice(start, pos)); - result.lines = lines; - result.pos = pos; - result.ok = true; - return result; -}; - -},{"../common/utils":4}],7:[function(require,module,exports){ -// Parse link label -// -// this function assumes that first character ("[") already matches; -// returns the end of the label -// -'use strict'; - -module.exports = function parseLinkLabel(state, start, disableNested) { - var level, found, marker, prevPos, - labelEnd = -1, - max = state.posMax, - oldPos = state.pos; - - state.pos = start + 1; - level = 1; - - while (state.pos < max) { - marker = state.src.charCodeAt(state.pos); - if (marker === 0x5D /* ] */) { - level--; - if (level === 0) { - found = true; - break; - } - } + var HTML_ESCAPE_TEST_RE = /[&<>"]/ + var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g + var HTML_REPLACEMENTS = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + } - prevPos = state.pos; - state.md.inline.skipToken(state); - if (marker === 0x5B /* [ */) { - if (prevPos === state.pos - 1) { - // increase level if we find text `[`, which is not a part of any token - level++; - } else if (disableNested) { - state.pos = oldPos; - return -1; - } - } - } + function replaceUnsafeChar(ch) { + return HTML_REPLACEMENTS[ch] + } - if (found) { - labelEnd = state.pos; - } + function escapeHtml(str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar) + } + return str + } - // restore old state - state.pos = oldPos; + //////////////////////////////////////////////////////////////////////////////// - return labelEnd; -}; + var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g -},{}],8:[function(require,module,exports){ -// Parse link title -// -'use strict'; + function escapeRE(str) { + return str.replace(REGEXP_ESCAPE_RE, '\\$&') + } + //////////////////////////////////////////////////////////////////////////////// -var unescapeAll = require('../common/utils').unescapeAll; + function isSpace(code) { + switch (code) { + case 0x09: + case 0x20: + return true + } + return false + } + // Zs (unicode class) || [\t\f\v\r\n] + function isWhiteSpace(code) { + if (code >= 0x2000 && code <= 0x200a) { + return true + } + switch (code) { + case 0x09: // \t + case 0x0a: // \n + case 0x0b: // \v + case 0x0c: // \f + case 0x0d: // \r + case 0x20: + case 0xa0: + case 0x1680: + case 0x202f: + case 0x205f: + case 0x3000: + return true + } + return false + } -module.exports = function parseLinkTitle(str, pos, max) { - var code, - marker, - lines = 0, - start = pos, - result = { - ok: false, - pos: 0, - lines: 0, - str: '' - }; + //////////////////////////////////////////////////////////////////////////////// - if (pos >= max) { return result; } + /*eslint-disable max-len*/ + var UNICODE_PUNCT_RE = require('uc.micro/categories/P/regex') - marker = str.charCodeAt(pos); + // Currently without astral characters support. + function isPunctChar(ch) { + return UNICODE_PUNCT_RE.test(ch) + } - if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return result; } + // Markdown ASCII punctuation characters. + // + // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + // http://spec.commonmark.org/0.15/#ascii-punctuation-character + // + // Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. + // + function isMdAsciiPunct(ch) { + switch (ch) { + case 0x21 /* ! */: + case 0x22 /* " */: + case 0x23 /* # */: + case 0x24 /* $ */: + case 0x25 /* % */: + case 0x26 /* & */: + case 0x27 /* ' */: + case 0x28 /* ( */: + case 0x29 /* ) */: + case 0x2a /* * */: + case 0x2b /* + */: + case 0x2c /* , */: + case 0x2d /* - */: + case 0x2e /* . */: + case 0x2f /* / */: + case 0x3a /* : */: + case 0x3b /* ; */: + case 0x3c /* < */: + case 0x3d /* = */: + case 0x3e /* > */: + case 0x3f /* ? */: + case 0x40 /* @ */: + case 0x5b /* [ */: + case 0x5c /* \ */: + case 0x5d /* ] */: + case 0x5e /* ^ */: + case 0x5f /* _ */: + case 0x60 /* ` */: + case 0x7b /* { */: + case 0x7c /* | */: + case 0x7d /* } */: + case 0x7e /* ~ */: + return true + default: + return false + } + } - pos++; + // Hepler to unify [reference labels]. + // + function normalizeReference(str) { + // Trim and collapse whitespace + // + str = str.trim().replace(/\s+/g, ' ') + + // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug + // fixed in v12 (couldn't find any details). + // + // So treat this one as a special case + // (remove this when node v10 is no longer supported). + // + if ('ẞ'.toLowerCase() === 'Ṿ') { + str = str.replace(/ẞ/g, 'ß') + } - // if opening marker is "(", switch it to closing marker ")" - if (marker === 0x28) { marker = 0x29; } + // .toLowerCase().toUpperCase() should get rid of all differences + // between letter variants. + // + // Simple .toLowerCase() doesn't normalize 125 code points correctly, + // and .toUpperCase doesn't normalize 6 of them (list of exceptions: + // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently + // uppercased versions). + // + // Here's an example showing how it happens. Lets take greek letter omega: + // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) + // + // Unicode entries: + // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; + // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 + // 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 + // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8; + // + // Case-insensitive comparison should treat all of them as equivalent. + // + // But .toLowerCase() doesn't change ϑ (it's already lowercase), + // and .toUpperCase() doesn't change ϴ (already uppercase). + // + // Applying first lower then upper case normalizes any character: + // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' + // + // Note: this is equivalent to unicode case folding; unicode normalization + // is a different step that is not required here. + // + // Final result should be uppercased, because it's later stored in an object + // (this avoid a conflict with Object.prototype members, + // most notably, `__proto__`) + // + return str.toLowerCase().toUpperCase() + } - while (pos < max) { - code = str.charCodeAt(pos); - if (code === marker) { - result.pos = pos + 1; - result.lines = lines; - result.str = unescapeAll(str.slice(start + 1, pos)); - result.ok = true; - return result; - } else if (code === 0x0A) { - lines++; - } else if (code === 0x5C /* \ */ && pos + 1 < max) { - pos++; - if (str.charCodeAt(pos) === 0x0A) { - lines++; - } - } + //////////////////////////////////////////////////////////////////////////////// - pos++; - } + // Re-export libraries commonly used in both markdown-it and its plugins, + // so plugins won't have to depend on them explicitly, which reduces their + // bundled size (e.g. a browser build). + // + exports.lib = {} + exports.lib.mdurl = require('mdurl') + exports.lib.ucmicro = require('uc.micro') + + exports.assign = assign + exports.isString = isString + exports.has = has + exports.unescapeMd = unescapeMd + exports.unescapeAll = unescapeAll + exports.isValidEntityCode = isValidEntityCode + exports.fromCodePoint = fromCodePoint + // exports.replaceEntities = replaceEntities; + exports.escapeHtml = escapeHtml + exports.arrayReplaceAt = arrayReplaceAt + exports.isSpace = isSpace + exports.isWhiteSpace = isWhiteSpace + exports.isMdAsciiPunct = isMdAsciiPunct + exports.isPunctChar = isPunctChar + exports.escapeRE = escapeRE + exports.normalizeReference = normalizeReference + }, + { './entities': 1, mdurl: 58, 'uc.micro': 65, 'uc.micro/categories/P/regex': 63 }, + ], + 5: [ + function (require, module, exports) { + // Just a shortcut for bulk export + 'use strict' + + exports.parseLinkLabel = require('./parse_link_label') + exports.parseLinkDestination = require('./parse_link_destination') + exports.parseLinkTitle = require('./parse_link_title') + }, + { './parse_link_destination': 6, './parse_link_label': 7, './parse_link_title': 8 }, + ], + 6: [ + function (require, module, exports) { + // Parse link destination + // + 'use strict' + + var unescapeAll = require('../common/utils').unescapeAll + + module.exports = function parseLinkDestination(str, pos, max) { + var code, + level, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '', + } + + if (str.charCodeAt(pos) === 0x3c /* < */) { + pos++ + while (pos < max) { + code = str.charCodeAt(pos) + if (code === 0x0a /* \n */) { + return result + } + if (code === 0x3e /* > */) { + result.pos = pos + 1 + result.str = unescapeAll(str.slice(start + 1, pos)) + result.ok = true + return result + } + if (code === 0x5c /* \ */ && pos + 1 < max) { + pos += 2 + continue + } + + pos++ + } + + // no closing '>' + return result + } - return result; -}; + // this should be ... } else { ... branch -},{"../common/utils":4}],9:[function(require,module,exports){ -// Main parser class + level = 0 + while (pos < max) { + code = str.charCodeAt(pos) -'use strict'; + if (code === 0x20) { + break + } + // ascii control characters + if (code < 0x20 || code === 0x7f) { + break + } -var utils = require('./common/utils'); -var helpers = require('./helpers'); -var Renderer = require('./renderer'); -var ParserCore = require('./parser_core'); -var ParserBlock = require('./parser_block'); -var ParserInline = require('./parser_inline'); -var LinkifyIt = require('linkify-it'); -var mdurl = require('mdurl'); -var punycode = require('punycode'); + if (code === 0x5c /* \ */ && pos + 1 < max) { + pos += 2 + continue + } + if (code === 0x28 /* ( */) { + level++ + } -var config = { - 'default': require('./presets/default'), - zero: require('./presets/zero'), - commonmark: require('./presets/commonmark') -}; + if (code === 0x29 /* ) */) { + if (level === 0) { + break + } + level-- + } -//////////////////////////////////////////////////////////////////////////////// -// -// This validator can prohibit more than really needed to prevent XSS. It's a -// tradeoff to keep code simple and to be secure by default. -// -// If you need different setup - override validator method as you wish. Or -// replace it with dummy function and use external sanitizer. -// + pos++ + } -var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/; -var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/; + if (start === pos) { + return result + } + if (level !== 0) { + return result + } -function validateLink(url) { - // url should be normalized at this point, and existing entities are decoded - var str = url.trim().toLowerCase(); + result.str = unescapeAll(str.slice(start, pos)) + result.lines = lines + result.pos = pos + result.ok = true + return result + } + }, + { '../common/utils': 4 }, + ], + 7: [ + function (require, module, exports) { + // Parse link label + // + // this function assumes that first character ("[") already matches; + // returns the end of the label + // + 'use strict' + + module.exports = function parseLinkLabel(state, start, disableNested) { + var level, + found, + marker, + prevPos, + labelEnd = -1, + max = state.posMax, + oldPos = state.pos + + state.pos = start + 1 + level = 1 + + while (state.pos < max) { + marker = state.src.charCodeAt(state.pos) + if (marker === 0x5d /* ] */) { + level-- + if (level === 0) { + found = true + break + } + } + + prevPos = state.pos + state.md.inline.skipToken(state) + if (marker === 0x5b /* [ */) { + if (prevPos === state.pos - 1) { + // increase level if we find text `[`, which is not a part of any token + level++ + } else if (disableNested) { + state.pos = oldPos + return -1 + } + } + } - return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true; -} + if (found) { + labelEnd = state.pos + } -//////////////////////////////////////////////////////////////////////////////// + // restore old state + state.pos = oldPos + return labelEnd + } + }, + {}, + ], + 8: [ + function (require, module, exports) { + // Parse link title + // + 'use strict' + + var unescapeAll = require('../common/utils').unescapeAll + + module.exports = function parseLinkTitle(str, pos, max) { + var code, + marker, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '', + } + + if (pos >= max) { + return result + } -var RECODE_HOSTNAME_FOR = [ 'http:', 'https:', 'mailto:' ]; + marker = str.charCodeAt(pos) -function normalizeLink(url) { - var parsed = mdurl.parse(url, true); + if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { + return result + } - if (parsed.hostname) { - // Encode hostnames in urls like: - // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` - // - // We don't encode unknown schemas, because it's likely that we encode - // something we shouldn't (e.g. `skype:name` treated as `skype:host`) - // - if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { - try { - parsed.hostname = punycode.toASCII(parsed.hostname); - } catch (er) { /**/ } - } - } + pos++ - return mdurl.encode(mdurl.format(parsed)); -} - -function normalizeLinkText(url) { - var parsed = mdurl.parse(url, true); - - if (parsed.hostname) { - // Encode hostnames in urls like: - // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` - // - // We don't encode unknown schemas, because it's likely that we encode - // something we shouldn't (e.g. `skype:name` treated as `skype:host`) - // - if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { - try { - parsed.hostname = punycode.toUnicode(parsed.hostname); - } catch (er) { /**/ } - } - } + // if opening marker is "(", switch it to closing marker ")" + if (marker === 0x28) { + marker = 0x29 + } - return mdurl.decode(mdurl.format(parsed)); -} - - -/** - * class MarkdownIt - * - * Main parser/renderer class. - * - * ##### Usage - * - * ```javascript - * // node.js, "classic" way: - * var MarkdownIt = require('markdown-it'), - * md = new MarkdownIt(); - * var result = md.render('# markdown-it rulezz!'); - * - * // node.js, the same, but with sugar: - * var md = require('markdown-it')(); - * var result = md.render('# markdown-it rulezz!'); - * - * // browser without AMD, added to "window" on script load - * // Note, there are no dash. - * var md = window.markdownit(); - * var result = md.render('# markdown-it rulezz!'); - * ``` - * - * Single line rendering, without paragraph wrap: - * - * ```javascript - * var md = require('markdown-it')(); - * var result = md.renderInline('__markdown-it__ rulezz!'); - * ``` - **/ - -/** - * new MarkdownIt([presetName, options]) - * - presetName (String): optional, `commonmark` / `zero` - * - options (Object) - * - * Creates parser instanse with given config. Can be called without `new`. - * - * ##### presetName - * - * MarkdownIt provides named presets as a convenience to quickly - * enable/disable active syntax rules and options for common use cases. - * - * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - - * configures parser to strict [CommonMark](http://commonmark.org/) mode. - * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - - * similar to GFM, used when no preset name given. Enables all available rules, - * but still without html, typographer & autolinker. - * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - - * all rules disabled. Useful to quickly setup your config via `.enable()`. - * For example, when you need only `bold` and `italic` markup and nothing else. - * - * ##### options: - * - * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! - * That's not safe! You may need external sanitizer to protect output from XSS. - * It's better to extend features via plugins, instead of enabling HTML. - * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags - * (`
`). This is needed only for full CommonMark compatibility. In real - * world you will need HTML output. - * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `
`. - * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. - * Can be useful for external highlighters. - * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. - * - __typographer__ - `false`. Set `true` to enable [some language-neutral - * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + - * quotes beautification (smartquotes). - * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement - * pairs, when typographer enabled and smartquotes on. For example, you can - * use `'«»„“'` for Russian, `'„“‚‘'` for German, and - * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). - * - __highlight__ - `null`. Highlighter function for fenced code blocks. - * Highlighter `function (str, lang)` should return escaped HTML. It can also - * return empty string if the source was not changed and should be escaped - * externaly. If result starts with `): - * - * ```javascript - * var hljs = require('highlight.js') // https://highlightjs.org/ - * - * // Actual default values - * var md = require('markdown-it')({ - * highlight: function (str, lang) { - * if (lang && hljs.getLanguage(lang)) { - * try { - * return '
' +
- *                hljs.highlight(lang, str, true).value +
- *                '
'; - * } catch (__) {} - * } - * - * return '
' + md.utils.escapeHtml(str) + '
'; - * } - * }); - * ``` - * - **/ -function MarkdownIt(presetName, options) { - if (!(this instanceof MarkdownIt)) { - return new MarkdownIt(presetName, options); - } + while (pos < max) { + code = str.charCodeAt(pos) + if (code === marker) { + result.pos = pos + 1 + result.lines = lines + result.str = unescapeAll(str.slice(start + 1, pos)) + result.ok = true + return result + } else if (code === 0x0a) { + lines++ + } else if (code === 0x5c /* \ */ && pos + 1 < max) { + pos++ + if (str.charCodeAt(pos) === 0x0a) { + lines++ + } + } + + pos++ + } - if (!options) { - if (!utils.isString(presetName)) { - options = presetName || {}; - presetName = 'default'; - } - } + return result + } + }, + { '../common/utils': 4 }, + ], + 9: [ + function (require, module, exports) { + // Main parser class + + 'use strict' + + var utils = require('./common/utils') + var helpers = require('./helpers') + var Renderer = require('./renderer') + var ParserCore = require('./parser_core') + var ParserBlock = require('./parser_block') + var ParserInline = require('./parser_inline') + var LinkifyIt = require('linkify-it') + var mdurl = require('mdurl') + var punycode = require('punycode') + + var config = { + default: require('./presets/default'), + zero: require('./presets/zero'), + commonmark: require('./presets/commonmark'), + } - /** - * MarkdownIt#inline -> ParserInline - * - * Instance of [[ParserInline]]. You may need it to add new rules when - * writing plugins. For simple rules control use [[MarkdownIt.disable]] and - * [[MarkdownIt.enable]]. - **/ - this.inline = new ParserInline(); - - /** - * MarkdownIt#block -> ParserBlock - * - * Instance of [[ParserBlock]]. You may need it to add new rules when - * writing plugins. For simple rules control use [[MarkdownIt.disable]] and - * [[MarkdownIt.enable]]. - **/ - this.block = new ParserBlock(); - - /** - * MarkdownIt#core -> Core - * - * Instance of [[Core]] chain executor. You may need it to add new rules when - * writing plugins. For simple rules control use [[MarkdownIt.disable]] and - * [[MarkdownIt.enable]]. - **/ - this.core = new ParserCore(); - - /** - * MarkdownIt#renderer -> Renderer - * - * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering - * rules for new token types, generated by plugins. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * function myToken(tokens, idx, options, env, self) { - * //... - * return result; - * }; - * - * md.renderer.rules['my_token'] = myToken - * ``` - * - * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). - **/ - this.renderer = new Renderer(); - - /** - * MarkdownIt#linkify -> LinkifyIt - * - * [linkify-it](https://github.com/markdown-it/linkify-it) instance. - * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) - * rule. - **/ - this.linkify = new LinkifyIt(); - - /** - * MarkdownIt#validateLink(url) -> Boolean - * - * Link validation function. CommonMark allows too much in links. By default - * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas - * except some embedded image types. - * - * You can change this behaviour: - * - * ```javascript - * var md = require('markdown-it')(); - * // enable everything - * md.validateLink = function () { return true; } - * ``` - **/ - this.validateLink = validateLink; - - /** - * MarkdownIt#normalizeLink(url) -> String - * - * Function used to encode link url to a machine-readable format, - * which includes url-encoding, punycode, etc. - **/ - this.normalizeLink = normalizeLink; - - /** - * MarkdownIt#normalizeLinkText(url) -> String - * - * Function used to decode link url to a human-readable format` - **/ - this.normalizeLinkText = normalizeLinkText; - - - // Expose utils & helpers for easy acces from plugins - - /** - * MarkdownIt#utils -> utils - * - * Assorted utility functions, useful to write plugins. See details - * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). - **/ - this.utils = utils; - - /** - * MarkdownIt#helpers -> helpers - * - * Link components parser functions, useful to write plugins. See details - * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). - **/ - this.helpers = utils.assign({}, helpers); - - - this.options = {}; - this.configure(presetName); - - if (options) { this.set(options); } -} - - -/** chainable - * MarkdownIt.set(options) - * - * Set parser options (in the same format as in constructor). Probably, you - * will never need it, but you can change options after constructor call. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')() - * .set({ html: true, breaks: true }) - * .set({ typographer, true }); - * ``` - * - * __Note:__ To achieve the best possible performance, don't modify a - * `markdown-it` instance options on the fly. If you need multiple configurations - * it's best to create multiple instances and initialize each with separate - * config. - **/ -MarkdownIt.prototype.set = function (options) { - utils.assign(this.options, options); - return this; -}; - - -/** chainable, internal - * MarkdownIt.configure(presets) - * - * Batch load of all options and compenent settings. This is internal method, - * and you probably will not need it. But if you will - see available presets - * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) - * - * We strongly recommend to use presets instead of direct config loads. That - * will give better compatibility with next versions. - **/ -MarkdownIt.prototype.configure = function (presets) { - var self = this, presetName; - - if (utils.isString(presets)) { - presetName = presets; - presets = config[presetName]; - if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); } - } + //////////////////////////////////////////////////////////////////////////////// + // + // This validator can prohibit more than really needed to prevent XSS. It's a + // tradeoff to keep code simple and to be secure by default. + // + // If you need different setup - override validator method as you wish. Or + // replace it with dummy function and use external sanitizer. + // - if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); } + var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/ + var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/ - if (presets.options) { self.set(presets.options); } + function validateLink(url) { + // url should be normalized at this point, and existing entities are decoded + var str = url.trim().toLowerCase() - if (presets.components) { - Object.keys(presets.components).forEach(function (name) { - if (presets.components[name].rules) { - self[name].ruler.enableOnly(presets.components[name].rules); - } - if (presets.components[name].rules2) { - self[name].ruler2.enableOnly(presets.components[name].rules2); - } - }); - } - return this; -}; - - -/** chainable - * MarkdownIt.enable(list, ignoreInvalid) - * - list (String|Array): rule name or list of rule names to enable - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * Enable list or rules. It will automatically find appropriate components, - * containing rules with given names. If rule not found, and `ignoreInvalid` - * not set - throws exception. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')() - * .enable(['sub', 'sup']) - * .disable('smartquotes'); - * ``` - **/ -MarkdownIt.prototype.enable = function (list, ignoreInvalid) { - var result = []; - - if (!Array.isArray(list)) { list = [ list ]; } - - [ 'core', 'block', 'inline' ].forEach(function (chain) { - result = result.concat(this[chain].ruler.enable(list, true)); - }, this); - - result = result.concat(this.inline.ruler2.enable(list, true)); - - var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); - - if (missed.length && !ignoreInvalid) { - throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed); - } + return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true + } - return this; -}; + //////////////////////////////////////////////////////////////////////////////// + + var RECODE_HOSTNAME_FOR = ['http:', 'https:', 'mailto:'] + + function normalizeLink(url) { + var parsed = mdurl.parse(url, true) + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toASCII(parsed.hostname) + } catch (er) { + /**/ + } + } + } + return mdurl.encode(mdurl.format(parsed)) + } -/** chainable - * MarkdownIt.disable(list, ignoreInvalid) - * - list (String|Array): rule name or list of rule names to disable. - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * The same as [[MarkdownIt.enable]], but turn specified rules off. - **/ -MarkdownIt.prototype.disable = function (list, ignoreInvalid) { - var result = []; + function normalizeLinkText(url) { + var parsed = mdurl.parse(url, true) + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toUnicode(parsed.hostname) + } catch (er) { + /**/ + } + } + } - if (!Array.isArray(list)) { list = [ list ]; } + return mdurl.decode(mdurl.format(parsed)) + } - [ 'core', 'block', 'inline' ].forEach(function (chain) { - result = result.concat(this[chain].ruler.disable(list, true)); - }, this); + /** + * class MarkdownIt + * + * Main parser/renderer class. + * + * ##### Usage + * + * ```javascript + * // node.js, "classic" way: + * var MarkdownIt = require('markdown-it'), + * md = new MarkdownIt(); + * var result = md.render('# markdown-it rulezz!'); + * + * // node.js, the same, but with sugar: + * var md = require('markdown-it')(); + * var result = md.render('# markdown-it rulezz!'); + * + * // browser without AMD, added to "window" on script load + * // Note, there are no dash. + * var md = window.markdownit(); + * var result = md.render('# markdown-it rulezz!'); + * ``` + * + * Single line rendering, without paragraph wrap: + * + * ```javascript + * var md = require('markdown-it')(); + * var result = md.renderInline('__markdown-it__ rulezz!'); + * ``` + **/ + + /** + * new MarkdownIt([presetName, options]) + * - presetName (String): optional, `commonmark` / `zero` + * - options (Object) + * + * Creates parser instanse with given config. Can be called without `new`. + * + * ##### presetName + * + * MarkdownIt provides named presets as a convenience to quickly + * enable/disable active syntax rules and options for common use cases. + * + * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - + * configures parser to strict [CommonMark](http://commonmark.org/) mode. + * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - + * similar to GFM, used when no preset name given. Enables all available rules, + * but still without html, typographer & autolinker. + * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - + * all rules disabled. Useful to quickly setup your config via `.enable()`. + * For example, when you need only `bold` and `italic` markup and nothing else. + * + * ##### options: + * + * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! + * That's not safe! You may need external sanitizer to protect output from XSS. + * It's better to extend features via plugins, instead of enabling HTML. + * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags + * (`
`). This is needed only for full CommonMark compatibility. In real + * world you will need HTML output. + * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `
`. + * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. + * Can be useful for external highlighters. + * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. + * - __typographer__ - `false`. Set `true` to enable [some language-neutral + * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + + * quotes beautification (smartquotes). + * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement + * pairs, when typographer enabled and smartquotes on. For example, you can + * use `'«»„“'` for Russian, `'„“‚‘'` for German, and + * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). + * - __highlight__ - `null`. Highlighter function for fenced code blocks. + * Highlighter `function (str, lang)` should return escaped HTML. It can also + * return empty string if the source was not changed and should be escaped + * externaly. If result starts with `): + * + * ```javascript + * var hljs = require('highlight.js') // https://highlightjs.org/ + * + * // Actual default values + * var md = require('markdown-it')({ + * highlight: function (str, lang) { + * if (lang && hljs.getLanguage(lang)) { + * try { + * return '
' +
+           *                hljs.highlight(lang, str, true).value +
+           *                '
'; + * } catch (__) {} + * } + * + * return '
' + md.utils.escapeHtml(str) + '
'; + * } + * }); + * ``` + * + **/ + function MarkdownIt(presetName, options) { + if (!(this instanceof MarkdownIt)) { + return new MarkdownIt(presetName, options) + } - result = result.concat(this.inline.ruler2.disable(list, true)); + if (!options) { + if (!utils.isString(presetName)) { + options = presetName || {} + presetName = 'default' + } + } - var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + /** + * MarkdownIt#inline -> ParserInline + * + * Instance of [[ParserInline]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.inline = new ParserInline() + + /** + * MarkdownIt#block -> ParserBlock + * + * Instance of [[ParserBlock]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.block = new ParserBlock() + + /** + * MarkdownIt#core -> Core + * + * Instance of [[Core]] chain executor. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.core = new ParserCore() + + /** + * MarkdownIt#renderer -> Renderer + * + * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering + * rules for new token types, generated by plugins. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * function myToken(tokens, idx, options, env, self) { + * //... + * return result; + * }; + * + * md.renderer.rules['my_token'] = myToken + * ``` + * + * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). + **/ + this.renderer = new Renderer() + + /** + * MarkdownIt#linkify -> LinkifyIt + * + * [linkify-it](https://github.com/markdown-it/linkify-it) instance. + * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) + * rule. + **/ + this.linkify = new LinkifyIt() + + /** + * MarkdownIt#validateLink(url) -> Boolean + * + * Link validation function. CommonMark allows too much in links. By default + * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas + * except some embedded image types. + * + * You can change this behaviour: + * + * ```javascript + * var md = require('markdown-it')(); + * // enable everything + * md.validateLink = function () { return true; } + * ``` + **/ + this.validateLink = validateLink + + /** + * MarkdownIt#normalizeLink(url) -> String + * + * Function used to encode link url to a machine-readable format, + * which includes url-encoding, punycode, etc. + **/ + this.normalizeLink = normalizeLink + + /** + * MarkdownIt#normalizeLinkText(url) -> String + * + * Function used to decode link url to a human-readable format` + **/ + this.normalizeLinkText = normalizeLinkText + + // Expose utils & helpers for easy acces from plugins + + /** + * MarkdownIt#utils -> utils + * + * Assorted utility functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). + **/ + this.utils = utils + + /** + * MarkdownIt#helpers -> helpers + * + * Link components parser functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). + **/ + this.helpers = utils.assign({}, helpers) + + this.options = {} + this.configure(presetName) + + if (options) { + this.set(options) + } + } - if (missed.length && !ignoreInvalid) { - throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed); - } - return this; -}; - - -/** chainable - * MarkdownIt.use(plugin, params) - * - * Load specified plugin with given params into current parser instance. - * It's just a sugar to call `plugin(md, params)` with curring. - * - * ##### Example - * - * ```javascript - * var iterator = require('markdown-it-for-inline'); - * var md = require('markdown-it')() - * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { - * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); - * }); - * ``` - **/ -MarkdownIt.prototype.use = function (plugin /*, params, ... */) { - var args = [ this ].concat(Array.prototype.slice.call(arguments, 1)); - plugin.apply(plugin, args); - return this; -}; - - -/** internal - * MarkdownIt.parse(src, env) -> Array - * - src (String): source string - * - env (Object): environment sandbox - * - * Parse input string and return list of block tokens (special token type - * "inline" will contain list of inline tokens). You should not call this - * method directly, until you write custom renderer (for example, to produce - * AST). - * - * `env` is used to pass data between "distributed" rules and return additional - * metadata like reference info, needed for the renderer. It also can be used to - * inject data in specific cases. Usually, you will be ok to pass `{}`, - * and then pass updated object to renderer. - **/ -MarkdownIt.prototype.parse = function (src, env) { - if (typeof src !== 'string') { - throw new Error('Input data should be a String'); - } + /** chainable + * MarkdownIt.set(options) + * + * Set parser options (in the same format as in constructor). Probably, you + * will never need it, but you can change options after constructor call. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .set({ html: true, breaks: true }) + * .set({ typographer, true }); + * ``` + * + * __Note:__ To achieve the best possible performance, don't modify a + * `markdown-it` instance options on the fly. If you need multiple configurations + * it's best to create multiple instances and initialize each with separate + * config. + **/ + MarkdownIt.prototype.set = function (options) { + utils.assign(this.options, options) + return this + } - var state = new this.core.State(src, this, env); - - this.core.process(state); - - return state.tokens; -}; - - -/** - * MarkdownIt.render(src [, env]) -> String - * - src (String): source string - * - env (Object): environment sandbox - * - * Render markdown string into html. It does all magic for you :). - * - * `env` can be used to inject additional metadata (`{}` by default). - * But you will not need it with high probability. See also comment - * in [[MarkdownIt.parse]]. - **/ -MarkdownIt.prototype.render = function (src, env) { - env = env || {}; - - return this.renderer.render(this.parse(src, env), this.options, env); -}; - - -/** internal - * MarkdownIt.parseInline(src, env) -> Array - * - src (String): source string - * - env (Object): environment sandbox - * - * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the - * block tokens list with the single `inline` element, containing parsed inline - * tokens in `children` property. Also updates `env` object. - **/ -MarkdownIt.prototype.parseInline = function (src, env) { - var state = new this.core.State(src, this, env); - - state.inlineMode = true; - this.core.process(state); - - return state.tokens; -}; - - -/** - * MarkdownIt.renderInline(src [, env]) -> String - * - src (String): source string - * - env (Object): environment sandbox - * - * Similar to [[MarkdownIt.render]] but for single paragraph content. Result - * will NOT be wrapped into `

` tags. - **/ -MarkdownIt.prototype.renderInline = function (src, env) { - env = env || {}; - - return this.renderer.render(this.parseInline(src, env), this.options, env); -}; - - -module.exports = MarkdownIt; - -},{"./common/utils":4,"./helpers":5,"./parser_block":10,"./parser_core":11,"./parser_inline":12,"./presets/commonmark":13,"./presets/default":14,"./presets/zero":15,"./renderer":16,"linkify-it":53,"mdurl":58,"punycode":60}],10:[function(require,module,exports){ -/** internal - * class ParserBlock - * - * Block-level tokenizer. - **/ -'use strict'; - - -var Ruler = require('./ruler'); - - -var _rules = [ - // First 2 params - rule name & source. Secondary array - list of rules, - // which can be terminated by this one. - [ 'table', require('./rules_block/table'), [ 'paragraph', 'reference' ] ], - [ 'code', require('./rules_block/code') ], - [ 'fence', require('./rules_block/fence'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], - [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], - [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], - [ 'list', require('./rules_block/list'), [ 'paragraph', 'reference', 'blockquote' ] ], - [ 'reference', require('./rules_block/reference') ], - [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'reference', 'blockquote' ] ], - [ 'lheading', require('./rules_block/lheading') ], - [ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ], - [ 'paragraph', require('./rules_block/paragraph') ] -]; - - -/** - * new ParserBlock() - **/ -function ParserBlock() { - /** - * ParserBlock#ruler -> Ruler - * - * [[Ruler]] instance. Keep configuration of block rules. - **/ - this.ruler = new Ruler(); - - for (var i = 0; i < _rules.length; i++) { - this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); - } -} - - -// Generate tokens for input range -// -ParserBlock.prototype.tokenize = function (state, startLine, endLine) { - var ok, i, - rules = this.ruler.getRules(''), - len = rules.length, - line = startLine, - hasEmptyLines = false, - maxNesting = state.md.options.maxNesting; - - while (line < endLine) { - state.line = line = state.skipEmptyLines(line); - if (line >= endLine) { break; } - - // Termination condition for nested calls. - // Nested calls currently used for blockquotes & lists - if (state.sCount[line] < state.blkIndent) { break; } - - // If nesting level exceeded - skip tail to the end. That's not ordinary - // situation and we should not care about content. - if (state.level >= maxNesting) { - state.line = endLine; - break; - } + /** chainable, internal + * MarkdownIt.configure(presets) + * + * Batch load of all options and compenent settings. This is internal method, + * and you probably will not need it. But if you will - see available presets + * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) + * + * We strongly recommend to use presets instead of direct config loads. That + * will give better compatibility with next versions. + **/ + MarkdownIt.prototype.configure = function (presets) { + var self = this, + presetName + + if (utils.isString(presets)) { + presetName = presets + presets = config[presetName] + if (!presets) { + throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name') + } + } - // Try all possible rules. - // On success, rule should: - // - // - update `state.line` - // - update `state.tokens` - // - return true + if (!presets) { + throw new Error("Wrong `markdown-it` preset, can't be empty") + } - for (i = 0; i < len; i++) { - ok = rules[i](state, line, endLine, false); - if (ok) { break; } - } + if (presets.options) { + self.set(presets.options) + } - // set state.tight if we had an empty line before current tag - // i.e. latest empty line should not count - state.tight = !hasEmptyLines; + if (presets.components) { + Object.keys(presets.components).forEach(function (name) { + if (presets.components[name].rules) { + self[name].ruler.enableOnly(presets.components[name].rules) + } + if (presets.components[name].rules2) { + self[name].ruler2.enableOnly(presets.components[name].rules2) + } + }) + } + return this + } - // paragraph might "eat" one newline after it in nested lists - if (state.isEmpty(state.line - 1)) { - hasEmptyLines = true; - } + /** chainable + * MarkdownIt.enable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to enable + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable list or rules. It will automatically find appropriate components, + * containing rules with given names. If rule not found, and `ignoreInvalid` + * not set - throws exception. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .enable(['sub', 'sup']) + * .disable('smartquotes'); + * ``` + **/ + MarkdownIt.prototype.enable = function (list, ignoreInvalid) { + var result = [] + + if (!Array.isArray(list)) { + list = [list] + } - line = state.line; + ;['core', 'block', 'inline'].forEach(function (chain) { + result = result.concat(this[chain].ruler.enable(list, true)) + }, this) - if (line < endLine && state.isEmpty(line)) { - hasEmptyLines = true; - line++; - state.line = line; - } - } -}; + result = result.concat(this.inline.ruler2.enable(list, true)) + var missed = list.filter(function (name) { + return result.indexOf(name) < 0 + }) -/** - * ParserBlock.parse(str, md, env, outTokens) - * - * Process input string and push block tokens into `outTokens` - **/ -ParserBlock.prototype.parse = function (src, md, env, outTokens) { - var state; + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed) + } - if (!src) { return; } + return this + } - state = new this.State(src, md, env, outTokens); + /** chainable + * MarkdownIt.disable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * The same as [[MarkdownIt.enable]], but turn specified rules off. + **/ + MarkdownIt.prototype.disable = function (list, ignoreInvalid) { + var result = [] + + if (!Array.isArray(list)) { + list = [list] + } - this.tokenize(state, state.line, state.lineMax); -}; + ;['core', 'block', 'inline'].forEach(function (chain) { + result = result.concat(this[chain].ruler.disable(list, true)) + }, this) + result = result.concat(this.inline.ruler2.disable(list, true)) -ParserBlock.prototype.State = require('./rules_block/state_block'); + var missed = list.filter(function (name) { + return result.indexOf(name) < 0 + }) + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed) + } + return this + } -module.exports = ParserBlock; + /** chainable + * MarkdownIt.use(plugin, params) + * + * Load specified plugin with given params into current parser instance. + * It's just a sugar to call `plugin(md, params)` with curring. + * + * ##### Example + * + * ```javascript + * var iterator = require('markdown-it-for-inline'); + * var md = require('markdown-it')() + * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { + * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); + * }); + * ``` + **/ + MarkdownIt.prototype.use = function (plugin /*, params, ... */) { + var args = [this].concat(Array.prototype.slice.call(arguments, 1)) + plugin.apply(plugin, args) + return this + } -},{"./ruler":17,"./rules_block/blockquote":18,"./rules_block/code":19,"./rules_block/fence":20,"./rules_block/heading":21,"./rules_block/hr":22,"./rules_block/html_block":23,"./rules_block/lheading":24,"./rules_block/list":25,"./rules_block/paragraph":26,"./rules_block/reference":27,"./rules_block/state_block":28,"./rules_block/table":29}],11:[function(require,module,exports){ -/** internal - * class Core - * - * Top-level rules executor. Glues block/inline parsers and does intermediate - * transformations. - **/ -'use strict'; + /** internal + * MarkdownIt.parse(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * Parse input string and return list of block tokens (special token type + * "inline" will contain list of inline tokens). You should not call this + * method directly, until you write custom renderer (for example, to produce + * AST). + * + * `env` is used to pass data between "distributed" rules and return additional + * metadata like reference info, needed for the renderer. It also can be used to + * inject data in specific cases. Usually, you will be ok to pass `{}`, + * and then pass updated object to renderer. + **/ + MarkdownIt.prototype.parse = function (src, env) { + if (typeof src !== 'string') { + throw new Error('Input data should be a String') + } + var state = new this.core.State(src, this, env) -var Ruler = require('./ruler'); + this.core.process(state) + return state.tokens + } -var _rules = [ - [ 'normalize', require('./rules_core/normalize') ], - [ 'block', require('./rules_core/block') ], - [ 'inline', require('./rules_core/inline') ], - [ 'linkify', require('./rules_core/linkify') ], - [ 'replacements', require('./rules_core/replacements') ], - [ 'smartquotes', require('./rules_core/smartquotes') ] -]; + /** + * MarkdownIt.render(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Render markdown string into html. It does all magic for you :). + * + * `env` can be used to inject additional metadata (`{}` by default). + * But you will not need it with high probability. See also comment + * in [[MarkdownIt.parse]]. + **/ + MarkdownIt.prototype.render = function (src, env) { + env = env || {} + + return this.renderer.render(this.parse(src, env), this.options, env) + } + /** internal + * MarkdownIt.parseInline(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the + * block tokens list with the single `inline` element, containing parsed inline + * tokens in `children` property. Also updates `env` object. + **/ + MarkdownIt.prototype.parseInline = function (src, env) { + var state = new this.core.State(src, this, env) + + state.inlineMode = true + this.core.process(state) + + return state.tokens + } -/** - * new Core() - **/ -function Core() { - /** - * Core#ruler -> Ruler - * - * [[Ruler]] instance. Keep configuration of core rules. - **/ - this.ruler = new Ruler(); + /** + * MarkdownIt.renderInline(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Similar to [[MarkdownIt.render]] but for single paragraph content. Result + * will NOT be wrapped into `

` tags. + **/ + MarkdownIt.prototype.renderInline = function (src, env) { + env = env || {} + + return this.renderer.render(this.parseInline(src, env), this.options, env) + } - for (var i = 0; i < _rules.length; i++) { - this.ruler.push(_rules[i][0], _rules[i][1]); - } -} + module.exports = MarkdownIt + }, + { + './common/utils': 4, + './helpers': 5, + './parser_block': 10, + './parser_core': 11, + './parser_inline': 12, + './presets/commonmark': 13, + './presets/default': 14, + './presets/zero': 15, + './renderer': 16, + 'linkify-it': 53, + mdurl: 58, + punycode: 60, + }, + ], + 10: [ + function (require, module, exports) { + /** internal + * class ParserBlock + * + * Block-level tokenizer. + **/ + 'use strict' + + var Ruler = require('./ruler') + + var _rules = [ + // First 2 params - rule name & source. Secondary array - list of rules, + // which can be terminated by this one. + ['table', require('./rules_block/table'), ['paragraph', 'reference']], + ['code', require('./rules_block/code')], + ['fence', require('./rules_block/fence'), ['paragraph', 'reference', 'blockquote', 'list']], + ['blockquote', require('./rules_block/blockquote'), ['paragraph', 'reference', 'blockquote', 'list']], + ['hr', require('./rules_block/hr'), ['paragraph', 'reference', 'blockquote', 'list']], + ['list', require('./rules_block/list'), ['paragraph', 'reference', 'blockquote']], + ['reference', require('./rules_block/reference')], + ['heading', require('./rules_block/heading'), ['paragraph', 'reference', 'blockquote']], + ['lheading', require('./rules_block/lheading')], + ['html_block', require('./rules_block/html_block'), ['paragraph', 'reference', 'blockquote']], + ['paragraph', require('./rules_block/paragraph')], + ] + + /** + * new ParserBlock() + **/ + function ParserBlock() { + /** + * ParserBlock#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of block rules. + **/ + this.ruler = new Ruler() + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }) + } + } + // Generate tokens for input range + // + ParserBlock.prototype.tokenize = function (state, startLine, endLine) { + var ok, + i, + rules = this.ruler.getRules(''), + len = rules.length, + line = startLine, + hasEmptyLines = false, + maxNesting = state.md.options.maxNesting + + while (line < endLine) { + state.line = line = state.skipEmptyLines(line) + if (line >= endLine) { + break + } + + // Termination condition for nested calls. + // Nested calls currently used for blockquotes & lists + if (state.sCount[line] < state.blkIndent) { + break + } + + // If nesting level exceeded - skip tail to the end. That's not ordinary + // situation and we should not care about content. + if (state.level >= maxNesting) { + state.line = endLine + break + } + + // Try all possible rules. + // On success, rule should: + // + // - update `state.line` + // - update `state.tokens` + // - return true + + for (i = 0; i < len; i++) { + ok = rules[i](state, line, endLine, false) + if (ok) { + break + } + } + + // set state.tight if we had an empty line before current tag + // i.e. latest empty line should not count + state.tight = !hasEmptyLines + + // paragraph might "eat" one newline after it in nested lists + if (state.isEmpty(state.line - 1)) { + hasEmptyLines = true + } + + line = state.line + + if (line < endLine && state.isEmpty(line)) { + hasEmptyLines = true + line++ + state.line = line + } + } + } -/** - * Core.process(state) - * - * Executes core chain rules. - **/ -Core.prototype.process = function (state) { - var i, l, rules; + /** + * ParserBlock.parse(str, md, env, outTokens) + * + * Process input string and push block tokens into `outTokens` + **/ + ParserBlock.prototype.parse = function (src, md, env, outTokens) { + var state - rules = this.ruler.getRules(''); + if (!src) { + return + } - for (i = 0, l = rules.length; i < l; i++) { - rules[i](state); - } -}; + state = new this.State(src, md, env, outTokens) -Core.prototype.State = require('./rules_core/state_core'); + this.tokenize(state, state.line, state.lineMax) + } + ParserBlock.prototype.State = require('./rules_block/state_block') + + module.exports = ParserBlock + }, + { + './ruler': 17, + './rules_block/blockquote': 18, + './rules_block/code': 19, + './rules_block/fence': 20, + './rules_block/heading': 21, + './rules_block/hr': 22, + './rules_block/html_block': 23, + './rules_block/lheading': 24, + './rules_block/list': 25, + './rules_block/paragraph': 26, + './rules_block/reference': 27, + './rules_block/state_block': 28, + './rules_block/table': 29, + }, + ], + 11: [ + function (require, module, exports) { + /** internal + * class Core + * + * Top-level rules executor. Glues block/inline parsers and does intermediate + * transformations. + **/ + 'use strict' + + var Ruler = require('./ruler') + + var _rules = [ + ['normalize', require('./rules_core/normalize')], + ['block', require('./rules_core/block')], + ['inline', require('./rules_core/inline')], + ['linkify', require('./rules_core/linkify')], + ['replacements', require('./rules_core/replacements')], + ['smartquotes', require('./rules_core/smartquotes')], + ] + + /** + * new Core() + **/ + function Core() { + /** + * Core#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of core rules. + **/ + this.ruler = new Ruler() + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]) + } + } -module.exports = Core; + /** + * Core.process(state) + * + * Executes core chain rules. + **/ + Core.prototype.process = function (state) { + var i, l, rules -},{"./ruler":17,"./rules_core/block":30,"./rules_core/inline":31,"./rules_core/linkify":32,"./rules_core/normalize":33,"./rules_core/replacements":34,"./rules_core/smartquotes":35,"./rules_core/state_core":36}],12:[function(require,module,exports){ -/** internal - * class ParserInline - * - * Tokenizes paragraph content. - **/ -'use strict'; + rules = this.ruler.getRules('') + for (i = 0, l = rules.length; i < l; i++) { + rules[i](state) + } + } -var Ruler = require('./ruler'); + Core.prototype.State = require('./rules_core/state_core') + + module.exports = Core + }, + { + './ruler': 17, + './rules_core/block': 30, + './rules_core/inline': 31, + './rules_core/linkify': 32, + './rules_core/normalize': 33, + './rules_core/replacements': 34, + './rules_core/smartquotes': 35, + './rules_core/state_core': 36, + }, + ], + 12: [ + function (require, module, exports) { + /** internal + * class ParserInline + * + * Tokenizes paragraph content. + **/ + 'use strict' + + var Ruler = require('./ruler') + + //////////////////////////////////////////////////////////////////////////////// + // Parser rules + + var _rules = [ + ['text', require('./rules_inline/text')], + ['newline', require('./rules_inline/newline')], + ['escape', require('./rules_inline/escape')], + ['backticks', require('./rules_inline/backticks')], + ['strikethrough', require('./rules_inline/strikethrough').tokenize], + ['emphasis', require('./rules_inline/emphasis').tokenize], + ['link', require('./rules_inline/link')], + ['image', require('./rules_inline/image')], + ['autolink', require('./rules_inline/autolink')], + ['html_inline', require('./rules_inline/html_inline')], + ['entity', require('./rules_inline/entity')], + ] + + var _rules2 = [ + ['balance_pairs', require('./rules_inline/balance_pairs')], + ['strikethrough', require('./rules_inline/strikethrough').postProcess], + ['emphasis', require('./rules_inline/emphasis').postProcess], + ['text_collapse', require('./rules_inline/text_collapse')], + ] + + /** + * new ParserInline() + **/ + function ParserInline() { + var i + + /** + * ParserInline#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of inline rules. + **/ + this.ruler = new Ruler() + + for (i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]) + } + /** + * ParserInline#ruler2 -> Ruler + * + * [[Ruler]] instance. Second ruler used for post-processing + * (e.g. in emphasis-like rules). + **/ + this.ruler2 = new Ruler() -//////////////////////////////////////////////////////////////////////////////// -// Parser rules + for (i = 0; i < _rules2.length; i++) { + this.ruler2.push(_rules2[i][0], _rules2[i][1]) + } + } -var _rules = [ - [ 'text', require('./rules_inline/text') ], - [ 'newline', require('./rules_inline/newline') ], - [ 'escape', require('./rules_inline/escape') ], - [ 'backticks', require('./rules_inline/backticks') ], - [ 'strikethrough', require('./rules_inline/strikethrough').tokenize ], - [ 'emphasis', require('./rules_inline/emphasis').tokenize ], - [ 'link', require('./rules_inline/link') ], - [ 'image', require('./rules_inline/image') ], - [ 'autolink', require('./rules_inline/autolink') ], - [ 'html_inline', require('./rules_inline/html_inline') ], - [ 'entity', require('./rules_inline/entity') ] -]; + // Skip single token by running all rules in validation mode; + // returns `true` if any rule reported success + // + ParserInline.prototype.skipToken = function (state) { + var ok, + i, + pos = state.pos, + rules = this.ruler.getRules(''), + len = rules.length, + maxNesting = state.md.options.maxNesting, + cache = state.cache + + if (typeof cache[pos] !== 'undefined') { + state.pos = cache[pos] + return + } -var _rules2 = [ - [ 'balance_pairs', require('./rules_inline/balance_pairs') ], - [ 'strikethrough', require('./rules_inline/strikethrough').postProcess ], - [ 'emphasis', require('./rules_inline/emphasis').postProcess ], - [ 'text_collapse', require('./rules_inline/text_collapse') ] -]; + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + // Increment state.level and decrement it later to limit recursion. + // It's harmless to do here, because no tokens are created. But ideally, + // we'd need a separate private state variable for this purpose. + // + state.level++ + ok = rules[i](state, true) + state.level-- + + if (ok) { + break + } + } + } else { + // Too much nesting, just skip until the end of the paragraph. + // + // NOTE: this will cause links to behave incorrectly in the following case, + // when an amount of `[` is exactly equal to `maxNesting + 1`: + // + // [[[[[[[[[[[[[[[[[[[[[foo]() + // + // TODO: remove this workaround when CM standard will allow nested links + // (we can replace it by preventing links from being parsed in + // validation mode) + // + state.pos = state.posMax + } + if (!ok) { + state.pos++ + } + cache[pos] = state.pos + } -/** - * new ParserInline() - **/ -function ParserInline() { - var i; + // Generate tokens for input range + // + ParserInline.prototype.tokenize = function (state) { + var ok, + i, + rules = this.ruler.getRules(''), + len = rules.length, + end = state.posMax, + maxNesting = state.md.options.maxNesting + + while (state.pos < end) { + // Try all possible rules. + // On success, rule should: + // + // - update `state.pos` + // - update `state.tokens` + // - return true + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + ok = rules[i](state, false) + if (ok) { + break + } + } + } + + if (ok) { + if (state.pos >= end) { + break + } + continue + } + + state.pending += state.src[state.pos++] + } - /** - * ParserInline#ruler -> Ruler - * - * [[Ruler]] instance. Keep configuration of inline rules. - **/ - this.ruler = new Ruler(); + if (state.pending) { + state.pushPending() + } + } - for (i = 0; i < _rules.length; i++) { - this.ruler.push(_rules[i][0], _rules[i][1]); - } + /** + * ParserInline.parse(str, md, env, outTokens) + * + * Process input string and push inline tokens into `outTokens` + **/ + ParserInline.prototype.parse = function (str, md, env, outTokens) { + var i, rules, len + var state = new this.State(str, md, env, outTokens) - /** - * ParserInline#ruler2 -> Ruler - * - * [[Ruler]] instance. Second ruler used for post-processing - * (e.g. in emphasis-like rules). - **/ - this.ruler2 = new Ruler(); + this.tokenize(state) - for (i = 0; i < _rules2.length; i++) { - this.ruler2.push(_rules2[i][0], _rules2[i][1]); - } -} + rules = this.ruler2.getRules('') + len = rules.length + for (i = 0; i < len; i++) { + rules[i](state) + } + } -// Skip single token by running all rules in validation mode; -// returns `true` if any rule reported success -// -ParserInline.prototype.skipToken = function (state) { - var ok, i, pos = state.pos, - rules = this.ruler.getRules(''), - len = rules.length, - maxNesting = state.md.options.maxNesting, - cache = state.cache; + ParserInline.prototype.State = require('./rules_inline/state_inline') + + module.exports = ParserInline + }, + { + './ruler': 17, + './rules_inline/autolink': 37, + './rules_inline/backticks': 38, + './rules_inline/balance_pairs': 39, + './rules_inline/emphasis': 40, + './rules_inline/entity': 41, + './rules_inline/escape': 42, + './rules_inline/html_inline': 43, + './rules_inline/image': 44, + './rules_inline/link': 45, + './rules_inline/newline': 46, + './rules_inline/state_inline': 47, + './rules_inline/strikethrough': 48, + './rules_inline/text': 49, + './rules_inline/text_collapse': 50, + }, + ], + 13: [ + function (require, module, exports) { + // Commonmark default options + + 'use strict' + + module.exports = { + options: { + html: true, // Enable HTML tags in source + xhtmlOut: true, // Use '/' to close single tags (
) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019' /* “”‘’ */, + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019' /* “”‘’ */, + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with ) + breaks: false, // Convert '\n' in paragraphs into
+ langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019' /* “”‘’ */, + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with = end) { break; } - continue; - } + return '' + escapeHtml(tokens[idx].content) + '' + } - state.pending += state.src[state.pos++]; - } + default_rules.code_block = function (tokens, idx, options, env, slf) { + var token = tokens[idx] - if (state.pending) { - state.pushPending(); - } -}; + return '' + escapeHtml(tokens[idx].content) + '\n' + } + default_rules.fence = function (tokens, idx, options, env, slf) { + var token = tokens[idx], + info = token.info ? unescapeAll(token.info).trim() : '', + langName = '', + highlighted, + i, + tmpAttrs, + tmpToken + + if (info) { + langName = info.split(/\s+/g)[0] + } -/** - * ParserInline.parse(str, md, env, outTokens) - * - * Process input string and push inline tokens into `outTokens` - **/ -ParserInline.prototype.parse = function (str, md, env, outTokens) { - var i, rules, len; - var state = new this.State(str, md, env, outTokens); + if (options.highlight) { + highlighted = options.highlight(token.content, langName) || escapeHtml(token.content) + } else { + highlighted = escapeHtml(token.content) + } - this.tokenize(state); + if (highlighted.indexOf('' + highlighted + '\n' + } - for (i = 0; i < len; i++) { - rules[i](state); - } -}; + return '

' + highlighted + '
\n' + } + default_rules.image = function (tokens, idx, options, env, slf) { + var token = tokens[idx] -ParserInline.prototype.State = require('./rules_inline/state_inline'); + // "alt" attr MUST be set, even if empty. Because it's mandatory and + // should be placed on proper position for tests. + // + // Replace content with actual value + token.attrs[token.attrIndex('alt')][1] = slf.renderInlineAsText(token.children, options, env) -module.exports = ParserInline; + return slf.renderToken(tokens, idx, options) + } -},{"./ruler":17,"./rules_inline/autolink":37,"./rules_inline/backticks":38,"./rules_inline/balance_pairs":39,"./rules_inline/emphasis":40,"./rules_inline/entity":41,"./rules_inline/escape":42,"./rules_inline/html_inline":43,"./rules_inline/image":44,"./rules_inline/link":45,"./rules_inline/newline":46,"./rules_inline/state_inline":47,"./rules_inline/strikethrough":48,"./rules_inline/text":49,"./rules_inline/text_collapse":50}],13:[function(require,module,exports){ -// Commonmark default options + default_rules.hardbreak = function (tokens, idx, options /*, env */) { + return options.xhtmlOut ? '
\n' : '
\n' + } + default_rules.softbreak = function (tokens, idx, options /*, env */) { + return options.breaks ? (options.xhtmlOut ? '
\n' : '
\n') : '\n' + } -'use strict'; + default_rules.text = function (tokens, idx /*, options, env */) { + return escapeHtml(tokens[idx].content) + } + default_rules.html_block = function (tokens, idx /*, options, env */) { + return tokens[idx].content + } + default_rules.html_inline = function (tokens, idx /*, options, env */) { + return tokens[idx].content + } -module.exports = { - options: { - html: true, // Enable HTML tags in source - xhtmlOut: true, // Use '/' to close single tags (
) - breaks: false, // Convert '\n' in paragraphs into
- langPrefix: 'language-', // CSS language prefix for fenced blocks - linkify: false, // autoconvert URL-like texts to links + /** + * new Renderer() + * + * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. + **/ + function Renderer() { + /** + * Renderer#rules -> Object + * + * Contains render rules for tokens. Can be updated and extended. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.renderer.rules.strong_open = function () { return ''; }; + * md.renderer.rules.strong_close = function () { return ''; }; + * + * var result = md.renderInline(...); + * ``` + * + * Each rule is called as independent static function with fixed signature: + * + * ```javascript + * function my_token_render(tokens, idx, options, env, renderer) { + * // ... + * return renderedHTML; + * } + * ``` + * + * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) + * for more details and examples. + **/ + this.rules = assign({}, default_rules) + } - // Enable some language-neutral replacements + quotes beautification - typographer: false, + /** + * Renderer.renderAttrs(token) -> String + * + * Render token attributes to string. + **/ + Renderer.prototype.renderAttrs = function renderAttrs(token) { + var i, l, result - // Double + single quotes replacement pairs, when typographer enabled, - // and smartquotes on. Could be either a String or an Array. - // - // For example, you can use '«»„“' for Russian, '„“‚‘' for German, - // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). - quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + if (!token.attrs) { + return '' + } - // Highlighter function. Should return escaped HTML, - // or '' if the source string is not changed and should be escaped externaly. - // If result starts with String + * - tokens (Array): list of tokens + * - idx (Numbed): token index to render + * - options (Object): params of parser instance + * + * Default token renderer. Can be overriden by custom function + * in [[Renderer#rules]]. + **/ + Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { + var nextToken, + result = '', + needLf = false, + token = tokens[idx] + + // Tight list paragraphs + if (token.hidden) { + return '' + } - block: { - rules: [ - 'blockquote', - 'code', - 'fence', - 'heading', - 'hr', - 'html_block', - 'lheading', - 'list', - 'reference', - 'paragraph' - ] - }, + // Insert a newline between hidden paragraph and subsequent opening + // block-level tag. + // + // For example, here we should insert a newline before blockquote: + // - a + // > + // + if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { + result += '\n' + } - inline: { - rules: [ - 'autolink', - 'backticks', - 'emphasis', - 'entity', - 'escape', - 'html_inline', - 'image', - 'link', - 'newline', - 'text' - ], - rules2: [ - 'balance_pairs', - 'emphasis', - 'text_collapse' - ] - } - } -}; + // Add token name, e.g. ``. + // + needLf = false + } + } + } + } -module.exports = { - options: { - html: false, // Enable HTML tags in source - xhtmlOut: false, // Use '/' to close single tags (
) - breaks: false, // Convert '\n' in paragraphs into
- langPrefix: 'language-', // CSS language prefix for fenced blocks - linkify: false, // autoconvert URL-like texts to links + result += needLf ? '>\n' : '>' - // Enable some language-neutral replacements + quotes beautification - typographer: false, + return result + } - // Double + single quotes replacement pairs, when typographer enabled, - // and smartquotes on. Could be either a String or an Array. - // - // For example, you can use '«»„“' for Russian, '„“‚‘' for German, - // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). - quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + /** + * Renderer.renderInline(tokens, options, env) -> String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * The same as [[Renderer.render]], but for single token of `inline` type. + **/ + Renderer.prototype.renderInline = function (tokens, options, env) { + var type, + result = '', + rules = this.rules + + for (var i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type + + if (typeof rules[type] !== 'undefined') { + result += rules[type](tokens, i, options, env, this) + } else { + result += this.renderToken(tokens, i, options) + } + } - // Highlighter function. Should return escaped HTML, - // or '' if the source string is not changed and should be escaped externaly. - // If result starts with String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Special kludge for image `alt` attributes to conform CommonMark spec. + * Don't try to use it! Spec requires to show `alt` content with stripped markup, + * instead of simple escaping. + **/ + Renderer.prototype.renderInlineAsText = function (tokens, options, env) { + var result = '' + + for (var i = 0, len = tokens.length; i < len; i++) { + if (tokens[i].type === 'text') { + result += tokens[i].content + } else if (tokens[i].type === 'image') { + result += this.renderInlineAsText(tokens[i].children, options, env) + } + } - components: { + return result + } - core: {}, - block: {}, - inline: {} - } -}; - -},{}],15:[function(require,module,exports){ -// "Zero" preset, with nothing enabled. Useful for manual configuring of simple -// modes. For example, to parse bold/italic only. - -'use strict'; - - -module.exports = { - options: { - html: false, // Enable HTML tags in source - xhtmlOut: false, // Use '/' to close single tags (
) - breaks: false, // Convert '\n' in paragraphs into
- langPrefix: 'language-', // CSS language prefix for fenced blocks - linkify: false, // autoconvert URL-like texts to links - - // Enable some language-neutral replacements + quotes beautification - typographer: false, - - // Double + single quotes replacement pairs, when typographer enabled, - // and smartquotes on. Could be either a String or an Array. - // - // For example, you can use '«»„“' for Russian, '„“‚‘' for German, - // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). - quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ - - // Highlighter function. Should return escaped HTML, - // or '' if the source string is not changed and should be escaped externaly. - // If result starts with String + * - tokens (Array): list on block tokens to renter + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Takes token stream and generates HTML. Probably, you will never need to call + * this method directly. + **/ + Renderer.prototype.render = function (tokens, options, env) { + var i, + len, + type, + result = '', + rules = this.rules + + for (i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type + + if (type === 'inline') { + result += this.renderInline(tokens[i].children, options, env) + } else if (typeof rules[type] !== 'undefined') { + result += rules[tokens[i].type](tokens, i, options, env, this) + } else { + result += this.renderToken(tokens, i, options, env) + } + } - block: { - rules: [ - 'paragraph' - ] - }, + return result + } - inline: { - rules: [ - 'text' + module.exports = Renderer + }, + { './common/utils': 4 }, ], - rules2: [ - 'balance_pairs', - 'text_collapse' - ] - } - } -}; + 17: [ + function (require, module, exports) { + /** + * class Ruler + * + * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and + * [[MarkdownIt#inline]] to manage sequences of functions (rules): + * + * - keep rules in defined order + * - assign the name to each rule + * - enable/disable rules + * - add/replace rules + * - allow assign rules to additional named chains (in the same) + * - cacheing lists of active rules + * + * You will not need use this class directly until write plugins. For simple + * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and + * [[MarkdownIt.use]]. + **/ + 'use strict' + + /** + * new Ruler() + **/ + function Ruler() { + // List of added rules. Each element is: + // + // { + // name: XXX, + // enabled: Boolean, + // fn: Function(), + // alt: [ name2, name3 ] + // } + // + this.__rules__ = [] + + // Cached rule chains. + // + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + // + this.__cache__ = null + } -},{}],16:[function(require,module,exports){ -/** - * class Renderer - * - * Generates HTML from parsed token stream. Each instance has independent - * copy of rules. Those can be rewritten with ease. Also, you can add new - * rules if you create plugin and adds new token types. - **/ -'use strict'; + //////////////////////////////////////////////////////////////////////////////// + // Helper methods, should not be used directly + // Find rule index by name + // + Ruler.prototype.__find__ = function (name) { + for (var i = 0; i < this.__rules__.length; i++) { + if (this.__rules__[i].name === name) { + return i + } + } + return -1 + } -var assign = require('./common/utils').assign; -var unescapeAll = require('./common/utils').unescapeAll; -var escapeHtml = require('./common/utils').escapeHtml; + // Build rules lookup cache + // + Ruler.prototype.__compile__ = function () { + var self = this + var chains = [''] + + // collect unique names + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { + return + } + + rule.alt.forEach(function (altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName) + } + }) + }) + + self.__cache__ = {} + + chains.forEach(function (chain) { + self.__cache__[chain] = [] + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { + return + } + + if (chain && rule.alt.indexOf(chain) < 0) { + return + } + + self.__cache__[chain].push(rule.fn) + }) + }) + } + /** + * Ruler.at(name, fn [, options]) + * - name (String): rule name to replace. + * - fn (Function): new rule function. + * - options (Object): new rule options (not mandatory). + * + * Replace rule by name with new function & options. Throws error if name not + * found. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * Replace existing typographer replacement rule with new one: + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.at('replacements', function replace(state) { + * //... + * }); + * ``` + **/ + Ruler.prototype.at = function (name, fn, options) { + var index = this.__find__(name) + var opt = options || {} + + if (index === -1) { + throw new Error('Parser rule not found: ' + name) + } -//////////////////////////////////////////////////////////////////////////////// + this.__rules__[index].fn = fn + this.__rules__[index].alt = opt.alt || [] + this.__cache__ = null + } -var default_rules = {}; + /** + * Ruler.before(beforeName, ruleName, fn [, options]) + * - beforeName (String): new rule will be added before this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain before one with given name. See also + * [[Ruler.after]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ + Ruler.prototype.before = function (beforeName, ruleName, fn, options) { + var index = this.__find__(beforeName) + var opt = options || {} + + if (index === -1) { + throw new Error('Parser rule not found: ' + beforeName) + } + this.__rules__.splice(index, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [], + }) -default_rules.code_inline = function (tokens, idx, options, env, slf) { - var token = tokens[idx]; + this.__cache__ = null + } - return '' + - escapeHtml(tokens[idx].content) + - ''; -}; + /** + * Ruler.after(afterName, ruleName, fn [, options]) + * - afterName (String): new rule will be added after this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain after one with given name. See also + * [[Ruler.before]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.inline.ruler.after('text', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ + Ruler.prototype.after = function (afterName, ruleName, fn, options) { + var index = this.__find__(afterName) + var opt = options || {} + + if (index === -1) { + throw new Error('Parser rule not found: ' + afterName) + } + this.__rules__.splice(index + 1, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [], + }) -default_rules.code_block = function (tokens, idx, options, env, slf) { - var token = tokens[idx]; + this.__cache__ = null + } - return '' + - escapeHtml(tokens[idx].content) + - '\n'; -}; + /** + * Ruler.push(ruleName, fn [, options]) + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Push new rule to the end of chain. See also + * [[Ruler.before]], [[Ruler.after]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.push('my_rule', function replace(state) { + * //... + * }); + * ``` + **/ + Ruler.prototype.push = function (ruleName, fn, options) { + var opt = options || {} + + this.__rules__.push({ + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [], + }) + + this.__cache__ = null + } + /** + * Ruler.enable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to enable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.disable]], [[Ruler.enableOnly]]. + **/ + Ruler.prototype.enable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { + list = [list] + } -default_rules.fence = function (tokens, idx, options, env, slf) { - var token = tokens[idx], - info = token.info ? unescapeAll(token.info).trim() : '', - langName = '', - highlighted, i, tmpAttrs, tmpToken; + var result = [] - if (info) { - langName = info.split(/\s+/g)[0]; - } + // Search by name and enable + list.forEach(function (name) { + var idx = this.__find__(name) - if (options.highlight) { - highlighted = options.highlight(token.content, langName) || escapeHtml(token.content); - } else { - highlighted = escapeHtml(token.content); - } + if (idx < 0) { + if (ignoreInvalid) { + return + } + throw new Error('Rules manager: invalid rule name ' + name) + } + this.__rules__[idx].enabled = true + result.push(name) + }, this) - if (highlighted.indexOf('' - + highlighted - + '\n'; - } + /** + * Ruler.disable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Disable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.enable]], [[Ruler.enableOnly]]. + **/ + Ruler.prototype.disable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { + list = [list] + } + var result = [] - return '
'
-        + highlighted
-        + '
\n'; -}; - - -default_rules.image = function (tokens, idx, options, env, slf) { - var token = tokens[idx]; - - // "alt" attr MUST be set, even if empty. Because it's mandatory and - // should be placed on proper position for tests. - // - // Replace content with actual value - - token.attrs[token.attrIndex('alt')][1] = - slf.renderInlineAsText(token.children, options, env); - - return slf.renderToken(tokens, idx, options); -}; - - -default_rules.hardbreak = function (tokens, idx, options /*, env */) { - return options.xhtmlOut ? '
\n' : '
\n'; -}; -default_rules.softbreak = function (tokens, idx, options /*, env */) { - return options.breaks ? (options.xhtmlOut ? '
\n' : '
\n') : '\n'; -}; - - -default_rules.text = function (tokens, idx /*, options, env */) { - return escapeHtml(tokens[idx].content); -}; - - -default_rules.html_block = function (tokens, idx /*, options, env */) { - return tokens[idx].content; -}; -default_rules.html_inline = function (tokens, idx /*, options, env */) { - return tokens[idx].content; -}; - - -/** - * new Renderer() - * - * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. - **/ -function Renderer() { - - /** - * Renderer#rules -> Object - * - * Contains render rules for tokens. Can be updated and extended. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.renderer.rules.strong_open = function () { return ''; }; - * md.renderer.rules.strong_close = function () { return ''; }; - * - * var result = md.renderInline(...); - * ``` - * - * Each rule is called as independent static function with fixed signature: - * - * ```javascript - * function my_token_render(tokens, idx, options, env, renderer) { - * // ... - * return renderedHTML; - * } - * ``` - * - * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) - * for more details and examples. - **/ - this.rules = assign({}, default_rules); -} - - -/** - * Renderer.renderAttrs(token) -> String - * - * Render token attributes to string. - **/ -Renderer.prototype.renderAttrs = function renderAttrs(token) { - var i, l, result; - - if (!token.attrs) { return ''; } - - result = ''; - - for (i = 0, l = token.attrs.length; i < l; i++) { - result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"'; - } + // Search by name and disable + list.forEach(function (name) { + var idx = this.__find__(name) - return result; -}; - - -/** - * Renderer.renderToken(tokens, idx, options) -> String - * - tokens (Array): list of tokens - * - idx (Numbed): token index to render - * - options (Object): params of parser instance - * - * Default token renderer. Can be overriden by custom function - * in [[Renderer#rules]]. - **/ -Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { - var nextToken, - result = '', - needLf = false, - token = tokens[idx]; - - // Tight list paragraphs - if (token.hidden) { - return ''; - } + if (idx < 0) { + if (ignoreInvalid) { + return + } + throw new Error('Rules manager: invalid rule name ' + name) + } + this.__rules__[idx].enabled = false + result.push(name) + }, this) - // Insert a newline between hidden paragraph and subsequent opening - // block-level tag. - // - // For example, here we should insert a newline before blockquote: - // - a - // > - // - if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { - result += '\n'; - } + this.__cache__ = null + return result + } - // Add token name, e.g. ` Array + * + * Return array of active functions (rules) for given chain name. It analyzes + * rules configuration, compiles caches if not exists and returns result. + * + * Default chain name is `''` (empty string). It can't be skipped. That's + * done intentionally, to keep signature monomorphic for high speed. + **/ + Ruler.prototype.getRules = function (chainName) { + if (this.__cache__ === null) { + this.__compile__() + } - // Encode attributes, e.g. `= 4) { + return false + } - // Check if we need to add a newline after this tag - if (token.block) { - needLf = true; + // check the block quote marker + if (state.src.charCodeAt(pos++) !== 0x3e /* > */) { + return false + } - if (token.nesting === 1) { - if (idx + 1 < tokens.length) { - nextToken = tokens[idx + 1]; + // we know that it's going to be a valid blockquote, + // so no point trying to find the end of it in silent mode + if (silent) { + return true + } - if (nextToken.type === 'inline' || nextToken.hidden) { - // Block-level tag containing an inline tag. - // - needLf = false; + // set offset past spaces and ">" + initial = offset = state.sCount[startLine] + 1 + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++ + initial++ + offset++ + adjustTab = false + spaceAfterMarker = true + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true + + if ((state.bsCount[startLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++ + initial++ + offset++ + adjustTab = false + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true + } + } else { + spaceAfterMarker = false + } - } else if (nextToken.nesting === -1 && nextToken.tag === token.tag) { - // Opening tag + closing tag of the same type. E.g. `
  • `. - // - needLf = false; - } - } - } - } + oldBMarks = [state.bMarks[startLine]] + state.bMarks[startLine] = pos - result += needLf ? '>\n' : '>'; + while (pos < max) { + ch = state.src.charCodeAt(pos) - return result; -}; + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - ((offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4) + } else { + offset++ + } + } else { + break + } + pos++ + } -/** - * Renderer.renderInline(tokens, options, env) -> String - * - tokens (Array): list on block tokens to renter - * - options (Object): params of parser instance - * - env (Object): additional data from parsed input (references, for example) - * - * The same as [[Renderer.render]], but for single token of `inline` type. - **/ -Renderer.prototype.renderInline = function (tokens, options, env) { - var type, - result = '', - rules = this.rules; + oldBSCount = [state.bsCount[startLine]] + state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0) + + lastLineEmpty = pos >= max + + oldSCount = [state.sCount[startLine]] + state.sCount[startLine] = offset - initial + + oldTShift = [state.tShift[startLine]] + state.tShift[startLine] = pos - state.bMarks[startLine] + + terminatorRules = state.md.block.ruler.getRules('blockquote') + + oldParentType = state.parentType + state.parentType = 'blockquote' + + // Search the end of the block + // + // Block ends with either: + // 1. an empty line outside: + // ``` + // > test + // + // ``` + // 2. an empty line inside: + // ``` + // > + // test + // ``` + // 3. another tag: + // ``` + // > test + // - - - + // ``` + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + // check if it's outdented, i.e. it's inside list item and indented + // less than said list item: + // + // ``` + // 1. anything + // > current blockquote + // 2. checking this line + // ``` + isOutdented = state.sCount[nextLine] < state.blkIndent + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + + if (pos >= max) { + // Case 1: line is not inside the blockquote, and this line is empty. + break + } + + if (state.src.charCodeAt(pos++) === 0x3e /* > */ && !isOutdented) { + // This line is inside the blockquote. + + // set offset past spaces and ">" + initial = offset = state.sCount[nextLine] + 1 + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++ + initial++ + offset++ + adjustTab = false + spaceAfterMarker = true + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true + + if ((state.bsCount[nextLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++ + initial++ + offset++ + adjustTab = false + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true + } + } else { + spaceAfterMarker = false + } + + oldBMarks.push(state.bMarks[nextLine]) + state.bMarks[nextLine] = pos + + while (pos < max) { + ch = state.src.charCodeAt(pos) + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - ((offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4) + } else { + offset++ + } + } else { + break + } + + pos++ + } + + lastLineEmpty = pos >= max + + oldBSCount.push(state.bsCount[nextLine]) + state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0) + + oldSCount.push(state.sCount[nextLine]) + state.sCount[nextLine] = offset - initial + + oldTShift.push(state.tShift[nextLine]) + state.tShift[nextLine] = pos - state.bMarks[nextLine] + continue + } + + // Case 2: line is not inside the blockquote, and the last line was empty. + if (lastLineEmpty) { + break + } + + // Case 3: another tag found. + terminate = false + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true + break + } + } + + if (terminate) { + // Quirk to enforce "hard termination mode" for paragraphs; + // normally if you call `tokenize(state, startLine, nextLine)`, + // paragraphs will look below nextLine for paragraph continuation, + // but if blockquote is terminated by another tag, they shouldn't + state.lineMax = nextLine + + if (state.blkIndent !== 0) { + // state.blkIndent was non-zero, we now set it to zero, + // so we need to re-calculate all offsets to appear as + // if indent wasn't changed + oldBMarks.push(state.bMarks[nextLine]) + oldBSCount.push(state.bsCount[nextLine]) + oldTShift.push(state.tShift[nextLine]) + oldSCount.push(state.sCount[nextLine]) + state.sCount[nextLine] -= state.blkIndent + } + + break + } + + oldBMarks.push(state.bMarks[nextLine]) + oldBSCount.push(state.bsCount[nextLine]) + oldTShift.push(state.tShift[nextLine]) + oldSCount.push(state.sCount[nextLine]) + + // A negative indentation means that this is a paragraph continuation + // + state.sCount[nextLine] = -1 + } - for (var i = 0, len = tokens.length; i < len; i++) { - type = tokens[i].type; + oldIndent = state.blkIndent + state.blkIndent = 0 - if (typeof rules[type] !== 'undefined') { - result += rules[type](tokens, i, options, env, this); - } else { - result += this.renderToken(tokens, i, options); - } - } + token = state.push('blockquote_open', 'blockquote', 1) + token.markup = '>' + token.map = lines = [startLine, 0] - return result; -}; - - -/** internal - * Renderer.renderInlineAsText(tokens, options, env) -> String - * - tokens (Array): list on block tokens to renter - * - options (Object): params of parser instance - * - env (Object): additional data from parsed input (references, for example) - * - * Special kludge for image `alt` attributes to conform CommonMark spec. - * Don't try to use it! Spec requires to show `alt` content with stripped markup, - * instead of simple escaping. - **/ -Renderer.prototype.renderInlineAsText = function (tokens, options, env) { - var result = ''; - - for (var i = 0, len = tokens.length; i < len; i++) { - if (tokens[i].type === 'text') { - result += tokens[i].content; - } else if (tokens[i].type === 'image') { - result += this.renderInlineAsText(tokens[i].children, options, env); - } - } + state.md.block.tokenize(state, startLine, nextLine) - return result; -}; - - -/** - * Renderer.render(tokens, options, env) -> String - * - tokens (Array): list on block tokens to renter - * - options (Object): params of parser instance - * - env (Object): additional data from parsed input (references, for example) - * - * Takes token stream and generates HTML. Probably, you will never need to call - * this method directly. - **/ -Renderer.prototype.render = function (tokens, options, env) { - var i, len, type, - result = '', - rules = this.rules; - - for (i = 0, len = tokens.length; i < len; i++) { - type = tokens[i].type; - - if (type === 'inline') { - result += this.renderInline(tokens[i].children, options, env); - } else if (typeof rules[type] !== 'undefined') { - result += rules[tokens[i].type](tokens, i, options, env, this); - } else { - result += this.renderToken(tokens, i, options, env); - } - } + token = state.push('blockquote_close', 'blockquote', -1) + token.markup = '>' - return result; -}; - -module.exports = Renderer; - -},{"./common/utils":4}],17:[function(require,module,exports){ -/** - * class Ruler - * - * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and - * [[MarkdownIt#inline]] to manage sequences of functions (rules): - * - * - keep rules in defined order - * - assign the name to each rule - * - enable/disable rules - * - add/replace rules - * - allow assign rules to additional named chains (in the same) - * - cacheing lists of active rules - * - * You will not need use this class directly until write plugins. For simple - * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and - * [[MarkdownIt.use]]. - **/ -'use strict'; - - -/** - * new Ruler() - **/ -function Ruler() { - // List of added rules. Each element is: - // - // { - // name: XXX, - // enabled: Boolean, - // fn: Function(), - // alt: [ name2, name3 ] - // } - // - this.__rules__ = []; - - // Cached rule chains. - // - // First level - chain name, '' for default. - // Second level - diginal anchor for fast filtering by charcodes. - // - this.__cache__ = null; -} - -//////////////////////////////////////////////////////////////////////////////// -// Helper methods, should not be used directly - - -// Find rule index by name -// -Ruler.prototype.__find__ = function (name) { - for (var i = 0; i < this.__rules__.length; i++) { - if (this.__rules__[i].name === name) { - return i; - } - } - return -1; -}; + state.lineMax = oldLineMax + state.parentType = oldParentType + lines[1] = state.line + // Restore original tShift; this might not be necessary since the parser + // has already been here, but just to make sure we can do that. + for (i = 0; i < oldTShift.length; i++) { + state.bMarks[i + startLine] = oldBMarks[i] + state.tShift[i + startLine] = oldTShift[i] + state.sCount[i + startLine] = oldSCount[i] + state.bsCount[i + startLine] = oldBSCount[i] + } + state.blkIndent = oldIndent -// Build rules lookup cache -// -Ruler.prototype.__compile__ = function () { - var self = this; - var chains = [ '' ]; + return true + } + }, + { '../common/utils': 4 }, + ], + 19: [ + function (require, module, exports) { + // Code block (4 spaces padded) - // collect unique names - self.__rules__.forEach(function (rule) { - if (!rule.enabled) { return; } + 'use strict' - rule.alt.forEach(function (altName) { - if (chains.indexOf(altName) < 0) { - chains.push(altName); - } - }); - }); - - self.__cache__ = {}; - - chains.forEach(function (chain) { - self.__cache__[chain] = []; - self.__rules__.forEach(function (rule) { - if (!rule.enabled) { return; } - - if (chain && rule.alt.indexOf(chain) < 0) { return; } - - self.__cache__[chain].push(rule.fn); - }); - }); -}; - - -/** - * Ruler.at(name, fn [, options]) - * - name (String): rule name to replace. - * - fn (Function): new rule function. - * - options (Object): new rule options (not mandatory). - * - * Replace rule by name with new function & options. Throws error if name not - * found. - * - * ##### Options: - * - * - __alt__ - array with names of "alternate" chains. - * - * ##### Example - * - * Replace existing typographer replacement rule with new one: - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.core.ruler.at('replacements', function replace(state) { - * //... - * }); - * ``` - **/ -Ruler.prototype.at = function (name, fn, options) { - var index = this.__find__(name); - var opt = options || {}; - - if (index === -1) { throw new Error('Parser rule not found: ' + name); } - - this.__rules__[index].fn = fn; - this.__rules__[index].alt = opt.alt || []; - this.__cache__ = null; -}; - - -/** - * Ruler.before(beforeName, ruleName, fn [, options]) - * - beforeName (String): new rule will be added before this one. - * - ruleName (String): name of added rule. - * - fn (Function): rule function. - * - options (Object): rule options (not mandatory). - * - * Add new rule to chain before one with given name. See also - * [[Ruler.after]], [[Ruler.push]]. - * - * ##### Options: - * - * - __alt__ - array with names of "alternate" chains. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { - * //... - * }); - * ``` - **/ -Ruler.prototype.before = function (beforeName, ruleName, fn, options) { - var index = this.__find__(beforeName); - var opt = options || {}; - - if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); } - - this.__rules__.splice(index, 0, { - name: ruleName, - enabled: true, - fn: fn, - alt: opt.alt || [] - }); - - this.__cache__ = null; -}; - - -/** - * Ruler.after(afterName, ruleName, fn [, options]) - * - afterName (String): new rule will be added after this one. - * - ruleName (String): name of added rule. - * - fn (Function): rule function. - * - options (Object): rule options (not mandatory). - * - * Add new rule to chain after one with given name. See also - * [[Ruler.before]], [[Ruler.push]]. - * - * ##### Options: - * - * - __alt__ - array with names of "alternate" chains. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.inline.ruler.after('text', 'my_rule', function replace(state) { - * //... - * }); - * ``` - **/ -Ruler.prototype.after = function (afterName, ruleName, fn, options) { - var index = this.__find__(afterName); - var opt = options || {}; - - if (index === -1) { throw new Error('Parser rule not found: ' + afterName); } - - this.__rules__.splice(index + 1, 0, { - name: ruleName, - enabled: true, - fn: fn, - alt: opt.alt || [] - }); - - this.__cache__ = null; -}; - -/** - * Ruler.push(ruleName, fn [, options]) - * - ruleName (String): name of added rule. - * - fn (Function): rule function. - * - options (Object): rule options (not mandatory). - * - * Push new rule to the end of chain. See also - * [[Ruler.before]], [[Ruler.after]]. - * - * ##### Options: - * - * - __alt__ - array with names of "alternate" chains. - * - * ##### Example - * - * ```javascript - * var md = require('markdown-it')(); - * - * md.core.ruler.push('my_rule', function replace(state) { - * //... - * }); - * ``` - **/ -Ruler.prototype.push = function (ruleName, fn, options) { - var opt = options || {}; - - this.__rules__.push({ - name: ruleName, - enabled: true, - fn: fn, - alt: opt.alt || [] - }); - - this.__cache__ = null; -}; - - -/** - * Ruler.enable(list [, ignoreInvalid]) -> Array - * - list (String|Array): list of rule names to enable. - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * Enable rules with given names. If any rule name not found - throw Error. - * Errors can be disabled by second param. - * - * Returns list of found rule names (if no exception happened). - * - * See also [[Ruler.disable]], [[Ruler.enableOnly]]. - **/ -Ruler.prototype.enable = function (list, ignoreInvalid) { - if (!Array.isArray(list)) { list = [ list ]; } - - var result = []; - - // Search by name and enable - list.forEach(function (name) { - var idx = this.__find__(name); - - if (idx < 0) { - if (ignoreInvalid) { return; } - throw new Error('Rules manager: invalid rule name ' + name); - } - this.__rules__[idx].enabled = true; - result.push(name); - }, this); - - this.__cache__ = null; - return result; -}; - - -/** - * Ruler.enableOnly(list [, ignoreInvalid]) - * - list (String|Array): list of rule names to enable (whitelist). - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * Enable rules with given names, and disable everything else. If any rule name - * not found - throw Error. Errors can be disabled by second param. - * - * See also [[Ruler.disable]], [[Ruler.enable]]. - **/ -Ruler.prototype.enableOnly = function (list, ignoreInvalid) { - if (!Array.isArray(list)) { list = [ list ]; } - - this.__rules__.forEach(function (rule) { rule.enabled = false; }); - - this.enable(list, ignoreInvalid); -}; - - -/** - * Ruler.disable(list [, ignoreInvalid]) -> Array - * - list (String|Array): list of rule names to disable. - * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. - * - * Disable rules with given names. If any rule name not found - throw Error. - * Errors can be disabled by second param. - * - * Returns list of found rule names (if no exception happened). - * - * See also [[Ruler.enable]], [[Ruler.enableOnly]]. - **/ -Ruler.prototype.disable = function (list, ignoreInvalid) { - if (!Array.isArray(list)) { list = [ list ]; } - - var result = []; - - // Search by name and disable - list.forEach(function (name) { - var idx = this.__find__(name); - - if (idx < 0) { - if (ignoreInvalid) { return; } - throw new Error('Rules manager: invalid rule name ' + name); - } - this.__rules__[idx].enabled = false; - result.push(name); - }, this); - - this.__cache__ = null; - return result; -}; - - -/** - * Ruler.getRules(chainName) -> Array - * - * Return array of active functions (rules) for given chain name. It analyzes - * rules configuration, compiles caches if not exists and returns result. - * - * Default chain name is `''` (empty string). It can't be skipped. That's - * done intentionally, to keep signature monomorphic for high speed. - **/ -Ruler.prototype.getRules = function (chainName) { - if (this.__cache__ === null) { - this.__compile__(); - } + module.exports = function code(state, startLine, endLine /*, silent*/) { + var nextLine, last, token - // Chain can be empty, if rules disabled. But we still have to return Array. - return this.__cache__[chainName] || []; -}; - -module.exports = Ruler; - -},{}],18:[function(require,module,exports){ -// Block quotes - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - - -module.exports = function blockquote(state, startLine, endLine, silent) { - var adjustTab, - ch, - i, - initial, - l, - lastLineEmpty, - lines, - nextLine, - offset, - oldBMarks, - oldBSCount, - oldIndent, - oldParentType, - oldSCount, - oldTShift, - spaceAfterMarker, - terminate, - terminatorRules, - token, - isOutdented, - oldLineMax = state.lineMax, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - // check the block quote marker - if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; } - - // we know that it's going to be a valid blockquote, - // so no point trying to find the end of it in silent mode - if (silent) { return true; } - - // set offset past spaces and ">" - initial = offset = state.sCount[startLine] + 1; - - // skip one optional space after '>' - if (state.src.charCodeAt(pos) === 0x20 /* space */) { - // ' > test ' - // ^ -- position start of line here: - pos++; - initial++; - offset++; - adjustTab = false; - spaceAfterMarker = true; - } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { - spaceAfterMarker = true; - - if ((state.bsCount[startLine] + offset) % 4 === 3) { - // ' >\t test ' - // ^ -- position start of line here (tab has width===1) - pos++; - initial++; - offset++; - adjustTab = false; - } else { - // ' >\t test ' - // ^ -- position start of line here + shift bsCount slightly - // to make extra space appear - adjustTab = true; - } - } else { - spaceAfterMarker = false; - } + if (state.sCount[startLine] - state.blkIndent < 4) { + return false + } - oldBMarks = [ state.bMarks[startLine] ]; - state.bMarks[startLine] = pos; + last = nextLine = startLine + 1 - while (pos < max) { - ch = state.src.charCodeAt(pos); + while (nextLine < endLine) { + if (state.isEmpty(nextLine)) { + nextLine++ + continue + } - if (isSpace(ch)) { - if (ch === 0x09) { - offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; - } else { - offset++; - } - } else { - break; - } + if (state.sCount[nextLine] - state.blkIndent >= 4) { + nextLine++ + last = nextLine + continue + } + break + } - pos++; - } + state.line = last - oldBSCount = [ state.bsCount[startLine] ]; - state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); - - lastLineEmpty = pos >= max; - - oldSCount = [ state.sCount[startLine] ]; - state.sCount[startLine] = offset - initial; - - oldTShift = [ state.tShift[startLine] ]; - state.tShift[startLine] = pos - state.bMarks[startLine]; - - terminatorRules = state.md.block.ruler.getRules('blockquote'); - - oldParentType = state.parentType; - state.parentType = 'blockquote'; - - // Search the end of the block - // - // Block ends with either: - // 1. an empty line outside: - // ``` - // > test - // - // ``` - // 2. an empty line inside: - // ``` - // > - // test - // ``` - // 3. another tag: - // ``` - // > test - // - - - - // ``` - for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { - // check if it's outdented, i.e. it's inside list item and indented - // less than said list item: - // - // ``` - // 1. anything - // > current blockquote - // 2. checking this line - // ``` - isOutdented = state.sCount[nextLine] < state.blkIndent; - - pos = state.bMarks[nextLine] + state.tShift[nextLine]; - max = state.eMarks[nextLine]; - - if (pos >= max) { - // Case 1: line is not inside the blockquote, and this line is empty. - break; - } + token = state.push('code_block', 'code', 0) + token.content = state.getLines(startLine, last, 4 + state.blkIndent, true) + token.map = [startLine, state.line] - if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !isOutdented) { - // This line is inside the blockquote. - - // set offset past spaces and ">" - initial = offset = state.sCount[nextLine] + 1; - - // skip one optional space after '>' - if (state.src.charCodeAt(pos) === 0x20 /* space */) { - // ' > test ' - // ^ -- position start of line here: - pos++; - initial++; - offset++; - adjustTab = false; - spaceAfterMarker = true; - } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { - spaceAfterMarker = true; - - if ((state.bsCount[nextLine] + offset) % 4 === 3) { - // ' >\t test ' - // ^ -- position start of line here (tab has width===1) - pos++; - initial++; - offset++; - adjustTab = false; - } else { - // ' >\t test ' - // ^ -- position start of line here + shift bsCount slightly - // to make extra space appear - adjustTab = true; - } - } else { - spaceAfterMarker = false; - } + return true + } + }, + {}, + ], + 20: [ + function (require, module, exports) { + // fences (``` lang, ~~~ lang) + + 'use strict' + + module.exports = function fence(state, startLine, endLine, silent) { + var marker, + len, + params, + nextLine, + mem, + token, + markup, + haveEndMarker = false, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine] + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } - oldBMarks.push(state.bMarks[nextLine]); - state.bMarks[nextLine] = pos; + if (pos + 3 > max) { + return false + } - while (pos < max) { - ch = state.src.charCodeAt(pos); + marker = state.src.charCodeAt(pos) - if (isSpace(ch)) { - if (ch === 0x09) { - offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; - } else { - offset++; - } - } else { - break; - } + if (marker !== 0x7e /* ~ */ && marker !== 0x60 /* ` */) { + return false + } - pos++; - } + // scan marker length + mem = pos + pos = state.skipChars(pos, marker) - lastLineEmpty = pos >= max; + len = pos - mem - oldBSCount.push(state.bsCount[nextLine]); - state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); + if (len < 3) { + return false + } - oldSCount.push(state.sCount[nextLine]); - state.sCount[nextLine] = offset - initial; + markup = state.src.slice(mem, pos) + params = state.src.slice(pos, max) - oldTShift.push(state.tShift[nextLine]); - state.tShift[nextLine] = pos - state.bMarks[nextLine]; - continue; - } + if (marker === 0x60 /* ` */) { + if (params.indexOf(String.fromCharCode(marker)) >= 0) { + return false + } + } - // Case 2: line is not inside the blockquote, and the last line was empty. - if (lastLineEmpty) { break; } + // Since start is found, we can report success here in validation mode + if (silent) { + return true + } - // Case 3: another tag found. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; - } - } + // search end of block + nextLine = startLine + + for (;;) { + nextLine++ + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break + } + + if (state.src.charCodeAt(pos) !== marker) { + continue + } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue + } + + pos = state.skipChars(pos, marker) + + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { + continue + } + + // make sure tail has spaces only + pos = state.skipSpaces(pos) + + if (pos < max) { + continue + } + + haveEndMarker = true + // found! + break + } - if (terminate) { - // Quirk to enforce "hard termination mode" for paragraphs; - // normally if you call `tokenize(state, startLine, nextLine)`, - // paragraphs will look below nextLine for paragraph continuation, - // but if blockquote is terminated by another tag, they shouldn't - state.lineMax = nextLine; - - if (state.blkIndent !== 0) { - // state.blkIndent was non-zero, we now set it to zero, - // so we need to re-calculate all offsets to appear as - // if indent wasn't changed - oldBMarks.push(state.bMarks[nextLine]); - oldBSCount.push(state.bsCount[nextLine]); - oldTShift.push(state.tShift[nextLine]); - oldSCount.push(state.sCount[nextLine]); - state.sCount[nextLine] -= state.blkIndent; - } + // If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine] - break; - } + state.line = nextLine + (haveEndMarker ? 1 : 0) - oldBMarks.push(state.bMarks[nextLine]); - oldBSCount.push(state.bsCount[nextLine]); - oldTShift.push(state.tShift[nextLine]); - oldSCount.push(state.sCount[nextLine]); + token = state.push('fence', 'code', 0) + token.info = params + token.content = state.getLines(startLine + 1, nextLine, len, true) + token.markup = markup + token.map = [startLine, state.line] - // A negative indentation means that this is a paragraph continuation - // - state.sCount[nextLine] = -1; - } + return true + } + }, + {}, + ], + 21: [ + function (require, module, exports) { + // heading (#, ##, ...) - oldIndent = state.blkIndent; - state.blkIndent = 0; + 'use strict' - token = state.push('blockquote_open', 'blockquote', 1); - token.markup = '>'; - token.map = lines = [ startLine, 0 ]; + var isSpace = require('../common/utils').isSpace - state.md.block.tokenize(state, startLine, nextLine); + module.exports = function heading(state, startLine, endLine, silent) { + var ch, + level, + tmp, + token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine] - token = state.push('blockquote_close', 'blockquote', -1); - token.markup = '>'; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } - state.lineMax = oldLineMax; - state.parentType = oldParentType; - lines[1] = state.line; + ch = state.src.charCodeAt(pos) - // Restore original tShift; this might not be necessary since the parser - // has already been here, but just to make sure we can do that. - for (i = 0; i < oldTShift.length; i++) { - state.bMarks[i + startLine] = oldBMarks[i]; - state.tShift[i + startLine] = oldTShift[i]; - state.sCount[i + startLine] = oldSCount[i]; - state.bsCount[i + startLine] = oldBSCount[i]; - } - state.blkIndent = oldIndent; + if (ch !== 0x23 /* # */ || pos >= max) { + return false + } - return true; -}; + // count heading level + level = 1 + ch = state.src.charCodeAt(++pos) + while (ch === 0x23 /* # */ && pos < max && level <= 6) { + level++ + ch = state.src.charCodeAt(++pos) + } -},{"../common/utils":4}],19:[function(require,module,exports){ -// Code block (4 spaces padded) + if (level > 6 || (pos < max && !isSpace(ch))) { + return false + } -'use strict'; + if (silent) { + return true + } + // Let's cut tails like ' ### ' from the end of string -module.exports = function code(state, startLine, endLine/*, silent*/) { - var nextLine, last, token; + max = state.skipSpacesBack(max, pos) + tmp = state.skipCharsBack(max, 0x23, pos) // # + if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) { + max = tmp + } - if (state.sCount[startLine] - state.blkIndent < 4) { return false; } + state.line = startLine + 1 - last = nextLine = startLine + 1; + token = state.push('heading_open', 'h' + String(level), 1) + token.markup = '########'.slice(0, level) + token.map = [startLine, state.line] - while (nextLine < endLine) { - if (state.isEmpty(nextLine)) { - nextLine++; - continue; - } + token = state.push('inline', '', 0) + token.content = state.src.slice(pos, max).trim() + token.map = [startLine, state.line] + token.children = [] - if (state.sCount[nextLine] - state.blkIndent >= 4) { - nextLine++; - last = nextLine; - continue; - } - break; - } + token = state.push('heading_close', 'h' + String(level), -1) + token.markup = '########'.slice(0, level) - state.line = last; + return true + } + }, + { '../common/utils': 4 }, + ], + 22: [ + function (require, module, exports) { + // Horizontal rule - token = state.push('code_block', 'code', 0); - token.content = state.getLines(startLine, last, 4 + state.blkIndent, true); - token.map = [ startLine, state.line ]; + 'use strict' - return true; -}; + var isSpace = require('../common/utils').isSpace -},{}],20:[function(require,module,exports){ -// fences (``` lang, ~~~ lang) + module.exports = function hr(state, startLine, endLine, silent) { + var marker, + cnt, + ch, + token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine] -'use strict'; + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } + marker = state.src.charCodeAt(pos++) -module.exports = function fence(state, startLine, endLine, silent) { - var marker, len, params, nextLine, mem, token, markup, - haveEndMarker = false, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; + // Check hr marker + if (marker !== 0x2a /* * */ && marker !== 0x2d /* - */ && marker !== 0x5f /* _ */) { + return false + } - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + // markers can be mixed with spaces, but there should be at least 3 of them + + cnt = 1 + while (pos < max) { + ch = state.src.charCodeAt(pos++) + if (ch !== marker && !isSpace(ch)) { + return false + } + if (ch === marker) { + cnt++ + } + } - if (pos + 3 > max) { return false; } + if (cnt < 3) { + return false + } - marker = state.src.charCodeAt(pos); + if (silent) { + return true + } - if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { - return false; - } + state.line = startLine + 1 - // scan marker length - mem = pos; - pos = state.skipChars(pos, marker); + token = state.push('hr', 'hr', 0) + token.map = [startLine, state.line] + token.markup = Array(cnt + 1).join(String.fromCharCode(marker)) - len = pos - mem; + return true + } + }, + { '../common/utils': 4 }, + ], + 23: [ + function (require, module, exports) { + // HTML block - if (len < 3) { return false; } + 'use strict' - markup = state.src.slice(mem, pos); - params = state.src.slice(pos, max); + var block_names = require('../common/html_blocks') + var HTML_OPEN_CLOSE_TAG_RE = require('../common/html_re').HTML_OPEN_CLOSE_TAG_RE - if (marker === 0x60 /* ` */) { - if (params.indexOf(String.fromCharCode(marker)) >= 0) { - return false; - } - } + // An array of opening and corresponding closing sequences for html tags, + // last argument defines whether it can terminate a paragraph or not + // + var HTML_SEQUENCES = [ + [/^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true], + [/^/, true], + [/^<\?/, /\?>/, true], + [/^/, true], + [/^/, true], + [new RegExp('^|$))', 'i'), /^$/, true], + [new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false], + ] + + module.exports = function html_block(state, startLine, endLine, silent) { + var i, + nextLine, + token, + lineText, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine] + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } - // Since start is found, we can report success here in validation mode - if (silent) { return true; } + if (!state.md.options.html) { + return false + } - // search end of block - nextLine = startLine; + if (state.src.charCodeAt(pos) !== 0x3c /* < */) { + return false + } - for (;;) { - nextLine++; - if (nextLine >= endLine) { - // unclosed block should be autoclosed by end of document. - // also block seems to be autoclosed by end of parent - break; - } + lineText = state.src.slice(pos, max) - pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; - max = state.eMarks[nextLine]; + for (i = 0; i < HTML_SEQUENCES.length; i++) { + if (HTML_SEQUENCES[i][0].test(lineText)) { + break + } + } - if (pos < max && state.sCount[nextLine] < state.blkIndent) { - // non-empty line with negative indent should stop the list: - // - ``` - // test - break; - } + if (i === HTML_SEQUENCES.length) { + return false + } - if (state.src.charCodeAt(pos) !== marker) { continue; } + if (silent) { + // true if this sequence can be a terminator, false otherwise + return HTML_SEQUENCES[i][2] + } - if (state.sCount[nextLine] - state.blkIndent >= 4) { - // closing fence should be indented less than 4 spaces - continue; - } + nextLine = startLine + 1 + + // If we are here - we detected HTML block. + // Let's roll down till block end. + if (!HTML_SEQUENCES[i][1].test(lineText)) { + for (; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { + break + } + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + lineText = state.src.slice(pos, max) + + if (HTML_SEQUENCES[i][1].test(lineText)) { + if (lineText.length !== 0) { + nextLine++ + } + break + } + } + } - pos = state.skipChars(pos, marker); + state.line = nextLine - // closing code fence must be at least as long as the opening one - if (pos - mem < len) { continue; } + token = state.push('html_block', '', 0) + token.map = [startLine, nextLine] + token.content = state.getLines(startLine, nextLine, state.blkIndent, true) - // make sure tail has spaces only - pos = state.skipSpaces(pos); + return true + } + }, + { '../common/html_blocks': 2, '../common/html_re': 3 }, + ], + 24: [ + function (require, module, exports) { + // lheading (---, ===) + + 'use strict' + + module.exports = function lheading(state, startLine, endLine /*, silent*/) { + var content, + terminate, + i, + l, + token, + pos, + max, + level, + marker, + nextLine = startLine + 1, + oldParentType, + terminatorRules = state.md.block.ruler.getRules('paragraph') + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } - if (pos < max) { continue; } + oldParentType = state.parentType + state.parentType = 'paragraph' // use paragraph to match terminatorRules + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { + continue + } + + // + // Check for underline in setext header + // + if (state.sCount[nextLine] >= state.blkIndent) { + pos = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + + if (pos < max) { + marker = state.src.charCodeAt(pos) + + if (marker === 0x2d /* - */ || marker === 0x3d /* = */) { + pos = state.skipChars(pos, marker) + pos = state.skipSpaces(pos) + + if (pos >= max) { + level = marker === 0x3d /* = */ ? 1 : 2 + break + } + } + } + } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { + continue + } + + // Some tags can terminate paragraph without empty line. + terminate = false + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true + break + } + } + if (terminate) { + break + } + } - haveEndMarker = true; - // found! - break; - } + if (!level) { + // Didn't find valid underline + return false + } - // If a fence has heading spaces, they should be removed from its inner block - len = state.sCount[startLine]; + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim() - state.line = nextLine + (haveEndMarker ? 1 : 0); + state.line = nextLine + 1 - token = state.push('fence', 'code', 0); - token.info = params; - token.content = state.getLines(startLine + 1, nextLine, len, true); - token.markup = markup; - token.map = [ startLine, state.line ]; + token = state.push('heading_open', 'h' + String(level), 1) + token.markup = String.fromCharCode(marker) + token.map = [startLine, state.line] - return true; -}; + token = state.push('inline', '', 0) + token.content = content + token.map = [startLine, state.line - 1] + token.children = [] -},{}],21:[function(require,module,exports){ -// heading (#, ##, ...) + token = state.push('heading_close', 'h' + String(level), -1) + token.markup = String.fromCharCode(marker) -'use strict'; + state.parentType = oldParentType -var isSpace = require('../common/utils').isSpace; + return true + } + }, + {}, + ], + 25: [ + function (require, module, exports) { + // Lists + 'use strict' -module.exports = function heading(state, startLine, endLine, silent) { - var ch, level, tmp, token, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; + var isSpace = require('../common/utils').isSpace - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + // Search `[-+*][\n ]`, returns next pos after marker on success + // or -1 on fail. + function skipBulletListMarker(state, startLine) { + var marker, pos, max, ch - ch = state.src.charCodeAt(pos); + pos = state.bMarks[startLine] + state.tShift[startLine] + max = state.eMarks[startLine] - if (ch !== 0x23/* # */ || pos >= max) { return false; } + marker = state.src.charCodeAt(pos++) + // Check bullet + if (marker !== 0x2a /* * */ && marker !== 0x2d /* - */ && marker !== 0x2b /* + */) { + return -1 + } - // count heading level - level = 1; - ch = state.src.charCodeAt(++pos); - while (ch === 0x23/* # */ && pos < max && level <= 6) { - level++; - ch = state.src.charCodeAt(++pos); - } + if (pos < max) { + ch = state.src.charCodeAt(pos) - if (level > 6 || (pos < max && !isSpace(ch))) { return false; } + if (!isSpace(ch)) { + // " -test " - is not a list item + return -1 + } + } - if (silent) { return true; } + return pos + } - // Let's cut tails like ' ### ' from the end of string + // Search `\d+[.)][\n ]`, returns next pos after marker on success + // or -1 on fail. + function skipOrderedListMarker(state, startLine) { + var ch, + start = state.bMarks[startLine] + state.tShift[startLine], + pos = start, + max = state.eMarks[startLine] + + // List marker should have at least 2 chars (digit + dot) + if (pos + 1 >= max) { + return -1 + } - max = state.skipSpacesBack(max, pos); - tmp = state.skipCharsBack(max, 0x23, pos); // # - if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) { - max = tmp; - } + ch = state.src.charCodeAt(pos++) - state.line = startLine + 1; + if (ch < 0x30 /* 0 */ || ch > 0x39 /* 9 */) { + return -1 + } - token = state.push('heading_open', 'h' + String(level), 1); - token.markup = '########'.slice(0, level); - token.map = [ startLine, state.line ]; + for (;;) { + // EOL -> fail + if (pos >= max) { + return -1 + } - token = state.push('inline', '', 0); - token.content = state.src.slice(pos, max).trim(); - token.map = [ startLine, state.line ]; - token.children = []; + ch = state.src.charCodeAt(pos++) - token = state.push('heading_close', 'h' + String(level), -1); - token.markup = '########'.slice(0, level); + if (ch >= 0x30 /* 0 */ && ch <= 0x39 /* 9 */) { + // List marker should have no more than 9 digits + // (prevents integer overflow in browsers) + if (pos - start >= 10) { + return -1 + } - return true; -}; + continue + } -},{"../common/utils":4}],22:[function(require,module,exports){ -// Horizontal rule + // found valid marker + if (ch === 0x29 /* ) */ || ch === 0x2e /* . */) { + break + } -'use strict'; + return -1 + } -var isSpace = require('../common/utils').isSpace; + if (pos < max) { + ch = state.src.charCodeAt(pos) + if (!isSpace(ch)) { + // " 1.test " - is not a list item + return -1 + } + } + return pos + } -module.exports = function hr(state, startLine, endLine, silent) { - var marker, cnt, ch, token, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; + function markTightParagraphs(state, idx) { + var i, + l, + level = state.level + 2 + + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].hidden = true + state.tokens[i].hidden = true + i += 2 + } + } + } - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + module.exports = function list(state, startLine, endLine, silent) { + var ch, + contentStart, + i, + indent, + indentAfterMarker, + initial, + isOrdered, + itemLines, + l, + listLines, + listTokIdx, + markerCharCode, + markerValue, + max, + nextLine, + offset, + oldListIndent, + oldParentType, + oldSCount, + oldTShift, + oldTight, + pos, + posAfterMarker, + prevEmptyEnd, + start, + terminate, + terminatorRules, + token, + isTerminatingParagraph = false, + tight = true + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } - marker = state.src.charCodeAt(pos++); + // Special case: + // - item 1 + // - item 2 + // - item 3 + // - item 4 + // - this one is a paragraph continuation + if ( + state.listIndent >= 0 && + state.sCount[startLine] - state.listIndent >= 4 && + state.sCount[startLine] < state.blkIndent + ) { + return false + } - // Check hr marker - if (marker !== 0x2A/* * */ && - marker !== 0x2D/* - */ && - marker !== 0x5F/* _ */) { - return false; - } + // limit conditions when list can interrupt + // a paragraph (validation mode only) + if (silent && state.parentType === 'paragraph') { + // Next list item should still terminate previous list item; + // + // This code can fail if plugins use blkIndent as well as lists, + // but I hope the spec gets fixed long before that happens. + // + if (state.tShift[startLine] >= state.blkIndent) { + isTerminatingParagraph = true + } + } - // markers can be mixed with spaces, but there should be at least 3 of them + // Detect list type and position after marker + if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { + isOrdered = true + start = state.bMarks[startLine] + state.tShift[startLine] + markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)) + + // If we're starting a new ordered list right after + // a paragraph, it should start with 1. + if (isTerminatingParagraph && markerValue !== 1) return false + } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { + isOrdered = false + } else { + return false + } - cnt = 1; - while (pos < max) { - ch = state.src.charCodeAt(pos++); - if (ch !== marker && !isSpace(ch)) { return false; } - if (ch === marker) { cnt++; } - } + // If we're starting a new unordered list right after + // a paragraph, first line should not be empty. + if (isTerminatingParagraph) { + if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false + } - if (cnt < 3) { return false; } + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1) - if (silent) { return true; } + // For validation mode we can terminate immediately + if (silent) { + return true + } - state.line = startLine + 1; + // Start list + listTokIdx = state.tokens.length - token = state.push('hr', 'hr', 0); - token.map = [ startLine, state.line ]; - token.markup = Array(cnt + 1).join(String.fromCharCode(marker)); + if (isOrdered) { + token = state.push('ordered_list_open', 'ol', 1) + if (markerValue !== 1) { + token.attrs = [['start', markerValue]] + } + } else { + token = state.push('bullet_list_open', 'ul', 1) + } - return true; -}; + token.map = listLines = [startLine, 0] + token.markup = String.fromCharCode(markerCharCode) + + // + // Iterate list items + // + + nextLine = startLine + prevEmptyEnd = false + terminatorRules = state.md.block.ruler.getRules('list') + + oldParentType = state.parentType + state.parentType = 'list' + + while (nextLine < endLine) { + pos = posAfterMarker + max = state.eMarks[nextLine] + + initial = offset = + state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]) + + while (pos < max) { + ch = state.src.charCodeAt(pos) + + if (ch === 0x09) { + offset += 4 - ((offset + state.bsCount[nextLine]) % 4) + } else if (ch === 0x20) { + offset++ + } else { + break + } + + pos++ + } + + contentStart = pos + + if (contentStart >= max) { + // trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1 + } else { + indentAfterMarker = offset - initial + } + + // If we have more than 4 spaces, the indent is 1 + // (the rest is just indented code block) + if (indentAfterMarker > 4) { + indentAfterMarker = 1 + } + + // " - test" + // ^^^^^ - calculating total length of this thing + indent = initial + indentAfterMarker + + // Run subparser & write tokens + token = state.push('list_item_open', 'li', 1) + token.markup = String.fromCharCode(markerCharCode) + token.map = itemLines = [startLine, 0] + + // change current state, then restore it after parser subcall + oldTight = state.tight + oldTShift = state.tShift[startLine] + oldSCount = state.sCount[startLine] + + // - example list + // ^ listIndent position will be here + // ^ blkIndent position will be here + // + oldListIndent = state.listIndent + state.listIndent = state.blkIndent + state.blkIndent = indent + + state.tight = true + state.tShift[startLine] = contentStart - state.bMarks[startLine] + state.sCount[startLine] = offset + + if (contentStart >= max && state.isEmpty(startLine + 1)) { + // workaround for this case + // (list item is empty, list terminates before "foo"): + // ~~~~~~~~ + // - + // + // foo + // ~~~~~~~~ + state.line = Math.min(state.line + 2, endLine) + } else { + state.md.block.tokenize(state, startLine, endLine, true) + } + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = state.line - startLine > 1 && state.isEmpty(state.line - 1) + + state.blkIndent = state.listIndent + state.listIndent = oldListIndent + state.tShift[startLine] = oldTShift + state.sCount[startLine] = oldSCount + state.tight = oldTight + + token = state.push('list_item_close', 'li', -1) + token.markup = String.fromCharCode(markerCharCode) + + nextLine = startLine = state.line + itemLines[1] = nextLine + contentStart = state.bMarks[startLine] + + if (nextLine >= endLine) { + break + } + + // + // Try to check if list is terminated or continued. + // + if (state.sCount[nextLine] < state.blkIndent) { + break + } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + break + } + + // fail if terminating block found + terminate = false + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true + break + } + } + if (terminate) { + break + } + + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine) + if (posAfterMarker < 0) { + break + } + } else { + posAfterMarker = skipBulletListMarker(state, nextLine) + if (posAfterMarker < 0) { + break + } + } + + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { + break + } + } -},{"../common/utils":4}],23:[function(require,module,exports){ -// HTML block + // Finalize list + if (isOrdered) { + token = state.push('ordered_list_close', 'ol', -1) + } else { + token = state.push('bullet_list_close', 'ul', -1) + } + token.markup = String.fromCharCode(markerCharCode) -'use strict'; + listLines[1] = nextLine + state.line = nextLine + state.parentType = oldParentType -var block_names = require('../common/html_blocks'); -var HTML_OPEN_CLOSE_TAG_RE = require('../common/html_re').HTML_OPEN_CLOSE_TAG_RE; + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx) + } -// An array of opening and corresponding closing sequences for html tags, -// last argument defines whether it can terminate a paragraph or not -// -var HTML_SEQUENCES = [ - [ /^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true ], - [ /^/, true ], - [ /^<\?/, /\?>/, true ], - [ /^/, true ], - [ /^/, true ], - [ new RegExp('^|$))', 'i'), /^$/, true ], - [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false ] -]; + return true + } + }, + { '../common/utils': 4 }, + ], + 26: [ + function (require, module, exports) { + // Paragraph + + 'use strict' + + module.exports = function paragraph(state, startLine /*, endLine*/) { + var content, + terminate, + i, + l, + token, + oldParentType, + nextLine = startLine + 1, + terminatorRules = state.md.block.ruler.getRules('paragraph'), + endLine = state.lineMax + + oldParentType = state.parentType + state.parentType = 'paragraph' + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { + continue + } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { + continue + } + + // Some tags can terminate paragraph without empty line. + terminate = false + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true + break + } + } + if (terminate) { + break + } + } + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim() -module.exports = function html_block(state, startLine, endLine, silent) { - var i, nextLine, token, lineText, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine]; + state.line = nextLine - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + token = state.push('paragraph_open', 'p', 1) + token.map = [startLine, state.line] - if (!state.md.options.html) { return false; } + token = state.push('inline', '', 0) + token.content = content + token.map = [startLine, state.line] + token.children = [] - if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + token = state.push('paragraph_close', 'p', -1) - lineText = state.src.slice(pos, max); + state.parentType = oldParentType - for (i = 0; i < HTML_SEQUENCES.length; i++) { - if (HTML_SEQUENCES[i][0].test(lineText)) { break; } - } + return true + } + }, + {}, + ], + 27: [ + function (require, module, exports) { + 'use strict' + + var normalizeReference = require('../common/utils').normalizeReference + var isSpace = require('../common/utils').isSpace + + module.exports = function reference(state, startLine, _endLine, silent) { + var ch, + destEndPos, + destEndLineNo, + endLine, + href, + i, + l, + label, + labelEnd, + oldParentType, + res, + start, + str, + terminate, + terminatorRules, + title, + lines = 0, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine], + nextLine = startLine + 1 + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } - if (i === HTML_SEQUENCES.length) { return false; } + if (state.src.charCodeAt(pos) !== 0x5b /* [ */) { + return false + } - if (silent) { - // true if this sequence can be a terminator, false otherwise - return HTML_SEQUENCES[i][2]; - } + // Simple check to quickly interrupt scan on [link](url) at the start of line. + // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 + while (++pos < max) { + if (state.src.charCodeAt(pos) === 0x5d /* ] */ && state.src.charCodeAt(pos - 1) !== 0x5c /* \ */) { + if (pos + 1 === max) { + return false + } + if (state.src.charCodeAt(pos + 1) !== 0x3a /* : */) { + return false + } + break + } + } - nextLine = startLine + 1; + endLine = state.lineMax + + // jump line-by-line until empty one or EOF + terminatorRules = state.md.block.ruler.getRules('reference') + + oldParentType = state.parentType + state.parentType = 'reference' + + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { + continue + } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { + continue + } + + // Some tags can terminate paragraph without empty line. + terminate = false + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true + break + } + } + if (terminate) { + break + } + } - // If we are here - we detected HTML block. - // Let's roll down till block end. - if (!HTML_SEQUENCES[i][1].test(lineText)) { - for (; nextLine < endLine; nextLine++) { - if (state.sCount[nextLine] < state.blkIndent) { break; } + str = state.getLines(startLine, nextLine, state.blkIndent, false).trim() + max = str.length + + for (pos = 1; pos < max; pos++) { + ch = str.charCodeAt(pos) + if (ch === 0x5b /* [ */) { + return false + } else if (ch === 0x5d /* ] */) { + labelEnd = pos + break + } else if (ch === 0x0a /* \n */) { + lines++ + } else if (ch === 0x5c /* \ */) { + pos++ + if (pos < max && str.charCodeAt(pos) === 0x0a) { + lines++ + } + } + } - pos = state.bMarks[nextLine] + state.tShift[nextLine]; - max = state.eMarks[nextLine]; - lineText = state.src.slice(pos, max); + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3a /* : */) { + return false + } - if (HTML_SEQUENCES[i][1].test(lineText)) { - if (lineText.length !== 0) { nextLine++; } - break; - } - } - } + // [label]: destination 'title' + // ^^^ skip optional whitespace here + for (pos = labelEnd + 2; pos < max; pos++) { + ch = str.charCodeAt(pos) + if (ch === 0x0a) { + lines++ + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break + } + } - state.line = nextLine; + // [label]: destination 'title' + // ^^^^^^^^^^^ parse this + res = state.md.helpers.parseLinkDestination(str, pos, max) + if (!res.ok) { + return false + } - token = state.push('html_block', '', 0); - token.map = [ startLine, nextLine ]; - token.content = state.getLines(startLine, nextLine, state.blkIndent, true); + href = state.md.normalizeLink(res.str) + if (!state.md.validateLink(href)) { + return false + } - return true; -}; + pos = res.pos + lines += res.lines + + // save cursor state, we could require to rollback later + destEndPos = pos + destEndLineNo = lines + + // [label]: destination 'title' + // ^^^ skipping those spaces + start = pos + for (; pos < max; pos++) { + ch = str.charCodeAt(pos) + if (ch === 0x0a) { + lines++ + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break + } + } -},{"../common/html_blocks":2,"../common/html_re":3}],24:[function(require,module,exports){ -// lheading (---, ===) + // [label]: destination 'title' + // ^^^^^^^ parse this + res = state.md.helpers.parseLinkTitle(str, pos, max) + if (pos < max && start !== pos && res.ok) { + title = res.str + pos = res.pos + lines += res.lines + } else { + title = '' + pos = destEndPos + lines = destEndLineNo + } -'use strict'; + // skip trailing spaces until the rest of the line + while (pos < max) { + ch = str.charCodeAt(pos) + if (!isSpace(ch)) { + break + } + pos++ + } + if (pos < max && str.charCodeAt(pos) !== 0x0a) { + if (title) { + // garbage at the end of the line after title, + // but it could still be a valid reference if we roll back + title = '' + pos = destEndPos + lines = destEndLineNo + while (pos < max) { + ch = str.charCodeAt(pos) + if (!isSpace(ch)) { + break + } + pos++ + } + } + } -module.exports = function lheading(state, startLine, endLine/*, silent*/) { - var content, terminate, i, l, token, pos, max, level, marker, - nextLine = startLine + 1, oldParentType, - terminatorRules = state.md.block.ruler.getRules('paragraph'); + if (pos < max && str.charCodeAt(pos) !== 0x0a) { + // garbage at the end of the line + return false + } - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + label = normalizeReference(str.slice(1, labelEnd)) + if (!label) { + // CommonMark 0.20 disallows empty labels + return false + } - oldParentType = state.parentType; - state.parentType = 'paragraph'; // use paragraph to match terminatorRules + // Reference can not terminate anything. This check is for safety only. + /*istanbul ignore if*/ + if (silent) { + return true + } - // jump line-by-line until empty one or EOF - for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { - // this would be a code block normally, but after paragraph - // it's considered a lazy continuation regardless of what's there - if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + if (typeof state.env.references === 'undefined') { + state.env.references = {} + } + if (typeof state.env.references[label] === 'undefined') { + state.env.references[label] = { title: title, href: href } + } - // - // Check for underline in setext header - // - if (state.sCount[nextLine] >= state.blkIndent) { - pos = state.bMarks[nextLine] + state.tShift[nextLine]; - max = state.eMarks[nextLine]; + state.parentType = oldParentType - if (pos < max) { - marker = state.src.charCodeAt(pos); + state.line = startLine + lines + 1 + return true + } + }, + { '../common/utils': 4 }, + ], + 28: [ + function (require, module, exports) { + // Parser state class + + 'use strict' + + var Token = require('../token') + var isSpace = require('../common/utils').isSpace + + function StateBlock(src, md, env, tokens) { + var ch, s, start, pos, len, indent, offset, indent_found + + this.src = src + + // link to parser instance + this.md = md + + this.env = env + + // + // Internal state vartiables + // + + this.tokens = tokens + + this.bMarks = [] // line begin offsets for fast jumps + this.eMarks = [] // line end offsets for fast jumps + this.tShift = [] // offsets of the first non-space characters (tabs not expanded) + this.sCount = [] // indents for each line (tabs expanded) + + // An amount of virtual spaces (tabs expanded) between beginning + // of each line (bMarks) and real beginning of that line. + // + // It exists only as a hack because blockquotes override bMarks + // losing information in the process. + // + // It's used only when expanding tabs, you can think about it as + // an initial tab length, e.g. bsCount=21 applied to string `\t123` + // means first tab should be expanded to 4-21%4 === 3 spaces. + // + this.bsCount = [] + + // block parser variables + this.blkIndent = 0 // required block content indent (for example, if we are + // inside a list, it would be positioned after list marker) + this.line = 0 // line index in src + this.lineMax = 0 // lines count + this.tight = false // loose/tight mode for lists + this.ddIndent = -1 // indent of the current dd block (-1 if there isn't any) + this.listIndent = -1 // indent of the current list block (-1 if there isn't any) + + // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' + // used in lists to determine if they interrupt a paragraph + this.parentType = 'root' + + this.level = 0 + + // renderer + this.result = '' + + // Create caches + // Generate markers. + s = this.src + indent_found = false + + for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { + ch = s.charCodeAt(pos) + + if (!indent_found) { + if (isSpace(ch)) { + indent++ + + if (ch === 0x09) { + offset += 4 - (offset % 4) + } else { + offset++ + } + continue + } else { + indent_found = true + } + } + + if (ch === 0x0a || pos === len - 1) { + if (ch !== 0x0a) { + pos++ + } + this.bMarks.push(start) + this.eMarks.push(pos) + this.tShift.push(indent) + this.sCount.push(offset) + this.bsCount.push(0) + + indent_found = false + indent = 0 + offset = 0 + start = pos + 1 + } + } - if (marker === 0x2D/* - */ || marker === 0x3D/* = */) { - pos = state.skipChars(pos, marker); - pos = state.skipSpaces(pos); + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length) + this.eMarks.push(s.length) + this.tShift.push(0) + this.sCount.push(0) + this.bsCount.push(0) - if (pos >= max) { - level = (marker === 0x3D/* = */ ? 1 : 2); - break; + this.lineMax = this.bMarks.length - 1 // don't count last fake line } - } - } - } - // quirk for blockquotes, this line should already be checked by that rule - if (state.sCount[nextLine] < 0) { continue; } + // Push new token to "stream". + // + StateBlock.prototype.push = function (type, tag, nesting) { + var token = new Token(type, tag, nesting) + token.block = true - // Some tags can terminate paragraph without empty line. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; - } - } - if (terminate) { break; } - } + if (nesting < 0) this.level-- // closing tag + token.level = this.level + if (nesting > 0) this.level++ // opening tag - if (!level) { - // Didn't find valid underline - return false; - } + this.tokens.push(token) + return token + } - content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + StateBlock.prototype.isEmpty = function isEmpty(line) { + return this.bMarks[line] + this.tShift[line] >= this.eMarks[line] + } - state.line = nextLine + 1; + StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { + for (var max = this.lineMax; from < max; from++) { + if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { + break + } + } + return from + } - token = state.push('heading_open', 'h' + String(level), 1); - token.markup = String.fromCharCode(marker); - token.map = [ startLine, state.line ]; + // Skip spaces from given position. + StateBlock.prototype.skipSpaces = function skipSpaces(pos) { + var ch - token = state.push('inline', '', 0); - token.content = content; - token.map = [ startLine, state.line - 1 ]; - token.children = []; + for (var max = this.src.length; pos < max; pos++) { + ch = this.src.charCodeAt(pos) + if (!isSpace(ch)) { + break + } + } + return pos + } - token = state.push('heading_close', 'h' + String(level), -1); - token.markup = String.fromCharCode(marker); + // Skip spaces from given position in reverse. + StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { + if (pos <= min) { + return pos + } - state.parentType = oldParentType; + while (pos > min) { + if (!isSpace(this.src.charCodeAt(--pos))) { + return pos + 1 + } + } + return pos + } - return true; -}; + // Skip char codes from given position + StateBlock.prototype.skipChars = function skipChars(pos, code) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== code) { + break + } + } + return pos + } -},{}],25:[function(require,module,exports){ -// Lists + // Skip char codes reverse from given position - 1 + StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { + if (pos <= min) { + return pos + } -'use strict'; + while (pos > min) { + if (code !== this.src.charCodeAt(--pos)) { + return pos + 1 + } + } + return pos + } -var isSpace = require('../common/utils').isSpace; + // cut lines range from source. + StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { + var i, + lineIndent, + ch, + first, + last, + queue, + lineStart, + line = begin + + if (begin >= end) { + return '' + } + queue = new Array(end - begin) + + for (i = 0; line < end; line++, i++) { + lineIndent = 0 + lineStart = first = this.bMarks[line] + + if (line + 1 < end || keepLastLF) { + // No need for bounds check because we have fake entry on tail. + last = this.eMarks[line] + 1 + } else { + last = this.eMarks[line] + } + + while (first < last && lineIndent < indent) { + ch = this.src.charCodeAt(first) + + if (isSpace(ch)) { + if (ch === 0x09) { + lineIndent += 4 - ((lineIndent + this.bsCount[line]) % 4) + } else { + lineIndent++ + } + } else if (first - lineStart < this.tShift[line]) { + // patched tShift masked characters to look like spaces (blockquotes, list markers) + lineIndent++ + } else { + break + } + + first++ + } + + if (lineIndent > indent) { + // partially expanding tabs in code blocks, e.g '\t\tfoobar' + // with indent=2 becomes ' \tfoobar' + queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last) + } else { + queue[i] = this.src.slice(first, last) + } + } -// Search `[-+*][\n ]`, returns next pos after marker on success -// or -1 on fail. -function skipBulletListMarker(state, startLine) { - var marker, pos, max, ch; + return queue.join('') + } - pos = state.bMarks[startLine] + state.tShift[startLine]; - max = state.eMarks[startLine]; + // re-export Token class to use in block rules + StateBlock.prototype.Token = Token - marker = state.src.charCodeAt(pos++); - // Check bullet - if (marker !== 0x2A/* * */ && - marker !== 0x2D/* - */ && - marker !== 0x2B/* + */) { - return -1; - } + module.exports = StateBlock + }, + { '../common/utils': 4, '../token': 51 }, + ], + 29: [ + function (require, module, exports) { + // GFM table, non-standard - if (pos < max) { - ch = state.src.charCodeAt(pos); + 'use strict' - if (!isSpace(ch)) { - // " -test " - is not a list item - return -1; - } - } + var isSpace = require('../common/utils').isSpace - return pos; -} + function getLine(state, line) { + var pos = state.bMarks[line] + state.blkIndent, + max = state.eMarks[line] -// Search `\d+[.)][\n ]`, returns next pos after marker on success -// or -1 on fail. -function skipOrderedListMarker(state, startLine) { - var ch, - start = state.bMarks[startLine] + state.tShift[startLine], - pos = start, - max = state.eMarks[startLine]; + return state.src.substr(pos, max - pos) + } - // List marker should have at least 2 chars (digit + dot) - if (pos + 1 >= max) { return -1; } + function escapedSplit(str) { + var result = [], + pos = 0, + max = str.length, + ch, + escapes = 0, + lastPos = 0, + backTicked = false, + lastBackTick = 0 + + ch = str.charCodeAt(pos) + + while (pos < max) { + if (ch === 0x60 /* ` */) { + if (backTicked) { + // make \` close code sequence, but not open it; + // the reason is: `\` is correct code block + backTicked = false + lastBackTick = pos + } else if (escapes % 2 === 0) { + backTicked = true + lastBackTick = pos + } + } else if (ch === 0x7c /* | */ && escapes % 2 === 0 && !backTicked) { + result.push(str.substring(lastPos, pos)) + lastPos = pos + 1 + } + + if (ch === 0x5c /* \ */) { + escapes++ + } else { + escapes = 0 + } + + pos++ + + // If there was an un-closed backtick, go back to just after + // the last backtick, but as if it was a normal character + if (pos === max && backTicked) { + backTicked = false + pos = lastBackTick + 1 + } + + ch = str.charCodeAt(pos) + } - ch = state.src.charCodeAt(pos++); + result.push(str.substring(lastPos)) - if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; } + return result + } - for (;;) { - // EOL -> fail - if (pos >= max) { return -1; } + module.exports = function table(state, startLine, endLine, silent) { + var ch, lineText, pos, i, nextLine, columns, columnCount, token, aligns, t, tableLines, tbodyLines - ch = state.src.charCodeAt(pos++); + // should have at least two lines + if (startLine + 2 > endLine) { + return false + } - if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { + nextLine = startLine + 1 - // List marker should have no more than 9 digits - // (prevents integer overflow in browsers) - if (pos - start >= 10) { return -1; } + if (state.sCount[nextLine] < state.blkIndent) { + return false + } - continue; - } + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[nextLine] - state.blkIndent >= 4) { + return false + } - // found valid marker - if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { - break; - } + // first character of the second line should be '|', '-', ':', + // and no other characters are allowed but spaces; + // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp - return -1; - } + pos = state.bMarks[nextLine] + state.tShift[nextLine] + if (pos >= state.eMarks[nextLine]) { + return false + } + ch = state.src.charCodeAt(pos++) + if (ch !== 0x7c /* | */ && ch !== 0x2d /* - */ && ch !== 0x3a /* : */) { + return false + } - if (pos < max) { - ch = state.src.charCodeAt(pos); + while (pos < state.eMarks[nextLine]) { + ch = state.src.charCodeAt(pos) - if (!isSpace(ch)) { - // " 1.test " - is not a list item - return -1; - } - } - return pos; -} - -function markTightParagraphs(state, idx) { - var i, l, - level = state.level + 2; - - for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { - if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { - state.tokens[i + 2].hidden = true; - state.tokens[i].hidden = true; - i += 2; - } - } -} - - -module.exports = function list(state, startLine, endLine, silent) { - var ch, - contentStart, - i, - indent, - indentAfterMarker, - initial, - isOrdered, - itemLines, - l, - listLines, - listTokIdx, - markerCharCode, - markerValue, - max, - nextLine, - offset, - oldListIndent, - oldParentType, - oldSCount, - oldTShift, - oldTight, - pos, - posAfterMarker, - prevEmptyEnd, - start, - terminate, - terminatorRules, - token, - isTerminatingParagraph = false, - tight = true; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - // Special case: - // - item 1 - // - item 2 - // - item 3 - // - item 4 - // - this one is a paragraph continuation - if (state.listIndent >= 0 && - state.sCount[startLine] - state.listIndent >= 4 && - state.sCount[startLine] < state.blkIndent) { - return false; - } + if (ch !== 0x7c /* | */ && ch !== 0x2d /* - */ && ch !== 0x3a /* : */ && !isSpace(ch)) { + return false + } - // limit conditions when list can interrupt - // a paragraph (validation mode only) - if (silent && state.parentType === 'paragraph') { - // Next list item should still terminate previous list item; - // - // This code can fail if plugins use blkIndent as well as lists, - // but I hope the spec gets fixed long before that happens. - // - if (state.tShift[startLine] >= state.blkIndent) { - isTerminatingParagraph = true; - } - } + pos++ + } - // Detect list type and position after marker - if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { - isOrdered = true; - start = state.bMarks[startLine] + state.tShift[startLine]; - markerValue = Number(state.src.substr(start, posAfterMarker - start - 1)); + lineText = getLine(state, startLine + 1) + + columns = lineText.split('|') + aligns = [] + for (i = 0; i < columns.length; i++) { + t = columns[i].trim() + if (!t) { + // allow empty columns before and after table, but not in between columns; + // e.g. allow ` |---| `, disallow ` ---||--- ` + if (i === 0 || i === columns.length - 1) { + continue + } else { + return false + } + } + + if (!/^:?-+:?$/.test(t)) { + return false + } + if (t.charCodeAt(t.length - 1) === 0x3a /* : */) { + aligns.push(t.charCodeAt(0) === 0x3a /* : */ ? 'center' : 'right') + } else if (t.charCodeAt(0) === 0x3a /* : */) { + aligns.push('left') + } else { + aligns.push('') + } + } - // If we're starting a new ordered list right after - // a paragraph, it should start with 1. - if (isTerminatingParagraph && markerValue !== 1) return false; + lineText = getLine(state, startLine).trim() + if (lineText.indexOf('|') === -1) { + return false + } + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')) - } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { - isOrdered = false; + // header row will define an amount of columns in the entire table, + // and align row shouldn't be smaller than that (the rest of the rows can) + columnCount = columns.length + if (columnCount > aligns.length) { + return false + } - } else { - return false; - } + if (silent) { + return true + } - // If we're starting a new unordered list right after - // a paragraph, first line should not be empty. - if (isTerminatingParagraph) { - if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false; - } + token = state.push('table_open', 'table', 1) + token.map = tableLines = [startLine, 0] - // We should terminate list on style change. Remember first one to compare. - markerCharCode = state.src.charCodeAt(posAfterMarker - 1); + token = state.push('thead_open', 'thead', 1) + token.map = [startLine, startLine + 1] - // For validation mode we can terminate immediately - if (silent) { return true; } + token = state.push('tr_open', 'tr', 1) + token.map = [startLine, startLine + 1] - // Start list - listTokIdx = state.tokens.length; + for (i = 0; i < columns.length; i++) { + token = state.push('th_open', 'th', 1) + token.map = [startLine, startLine + 1] + if (aligns[i]) { + token.attrs = [['style', 'text-align:' + aligns[i]]] + } - if (isOrdered) { - token = state.push('ordered_list_open', 'ol', 1); - if (markerValue !== 1) { - token.attrs = [ [ 'start', markerValue ] ]; - } + token = state.push('inline', '', 0) + token.content = columns[i].trim() + token.map = [startLine, startLine + 1] + token.children = [] - } else { - token = state.push('bullet_list_open', 'ul', 1); - } + token = state.push('th_close', 'th', -1) + } - token.map = listLines = [ startLine, 0 ]; - token.markup = String.fromCharCode(markerCharCode); + token = state.push('tr_close', 'tr', -1) + token = state.push('thead_close', 'thead', -1) + + token = state.push('tbody_open', 'tbody', 1) + token.map = tbodyLines = [startLine + 2, 0] + + for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { + break + } + + lineText = getLine(state, nextLine).trim() + if (lineText.indexOf('|') === -1) { + break + } + if (state.sCount[nextLine] - state.blkIndent >= 4) { + break + } + columns = escapedSplit(lineText.replace(/^\||\|$/g, '')) + + token = state.push('tr_open', 'tr', 1) + for (i = 0; i < columnCount; i++) { + token = state.push('td_open', 'td', 1) + token.map = [nextLine, nextLine + 1] + if (aligns[i]) { + token.attrs = [['style', 'text-align:' + aligns[i]]] + } + + token = state.push('inline', '', 0) + token.map = [nextLine, nextLine + 1] + token.content = columns[i] ? columns[i].trim() : '' + token.children = [] + + token = state.push('td_close', 'td', -1) + } + token = state.push('tr_close', 'tr', -1) + } + token = state.push('tbody_close', 'tbody', -1) + token = state.push('table_close', 'table', -1) - // - // Iterate list items - // + tableLines[1] = tbodyLines[1] = nextLine + state.line = nextLine + return true + } + }, + { '../common/utils': 4 }, + ], + 30: [ + function (require, module, exports) { + 'use strict' + + module.exports = function block(state) { + var token + + if (state.inlineMode) { + token = new state.Token('inline', '', 0) + token.content = state.src + token.map = [0, 1] + token.children = [] + state.tokens.push(token) + } else { + state.md.block.parse(state.src, state.md, state.env, state.tokens) + } + } + }, + {}, + ], + 31: [ + function (require, module, exports) { + 'use strict' + + module.exports = function inline(state) { + var tokens = state.tokens, + tok, + i, + l + + // Parse inlines + for (i = 0, l = tokens.length; i < l; i++) { + tok = tokens[i] + if (tok.type === 'inline') { + state.md.inline.parse(tok.content, state.md, state.env, tok.children) + } + } + } + }, + {}, + ], + 32: [ + function (require, module, exports) { + // Replace link-like texts with link nodes. + // + // Currently restricted by `md.validateLink()` to http/https/ftp + // + 'use strict' - nextLine = startLine; - prevEmptyEnd = false; - terminatorRules = state.md.block.ruler.getRules('list'); + var arrayReplaceAt = require('../common/utils').arrayReplaceAt - oldParentType = state.parentType; - state.parentType = 'list'; + function isLinkOpen(str) { + return /^\s]/i.test(str) + } + function isLinkClose(str) { + return /^<\/a\s*>/i.test(str) + } - while (nextLine < endLine) { - pos = posAfterMarker; - max = state.eMarks[nextLine]; + module.exports = function linkify(state) { + var i, + j, + l, + tokens, + token, + currentToken, + nodes, + ln, + text, + pos, + lastPos, + level, + htmlLinkLevel, + url, + fullUrl, + urlText, + blockTokens = state.tokens, + links + + if (!state.md.options.linkify) { + return + } - initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]); + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline' || !state.md.linkify.pretest(blockTokens[j].content)) { + continue + } + + tokens = blockTokens[j].children + + htmlLinkLevel = 0 + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + currentToken = tokens[i] + + // Skip content of markdown links + if (currentToken.type === 'link_close') { + i-- + while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { + i-- + } + continue + } + + // Skip content of html tag links + if (currentToken.type === 'html_inline') { + if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { + htmlLinkLevel-- + } + if (isLinkClose(currentToken.content)) { + htmlLinkLevel++ + } + } + if (htmlLinkLevel > 0) { + continue + } + + if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { + text = currentToken.content + links = state.md.linkify.match(text) + + // Now split string to nodes + nodes = [] + level = currentToken.level + lastPos = 0 + + for (ln = 0; ln < links.length; ln++) { + url = links[ln].url + fullUrl = state.md.normalizeLink(url) + if (!state.md.validateLink(fullUrl)) { + continue + } + + urlText = links[ln].text + + // Linkifier might send raw hostnames like "example.com", where url + // starts with domain name. So we prepend http:// in those cases, + // and remove it afterwards. + // + if (!links[ln].schema) { + urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, '') + } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { + urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, '') + } else { + urlText = state.md.normalizeLinkText(urlText) + } + + pos = links[ln].index + + if (pos > lastPos) { + token = new state.Token('text', '', 0) + token.content = text.slice(lastPos, pos) + token.level = level + nodes.push(token) + } + + token = new state.Token('link_open', 'a', 1) + token.attrs = [['href', fullUrl]] + token.level = level++ + token.markup = 'linkify' + token.info = 'auto' + nodes.push(token) + + token = new state.Token('text', '', 0) + token.content = urlText + token.level = level + nodes.push(token) + + token = new state.Token('link_close', 'a', -1) + token.level = --level + token.markup = 'linkify' + token.info = 'auto' + nodes.push(token) + + lastPos = links[ln].lastIndex + } + if (lastPos < text.length) { + token = new state.Token('text', '', 0) + token.content = text.slice(lastPos) + token.level = level + nodes.push(token) + } + + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes) + } + } + } + } + }, + { '../common/utils': 4 }, + ], + 33: [ + function (require, module, exports) { + // Normalize input string - while (pos < max) { - ch = state.src.charCodeAt(pos); + 'use strict' - if (ch === 0x09) { - offset += 4 - (offset + state.bsCount[nextLine]) % 4; - } else if (ch === 0x20) { - offset++; - } else { - break; - } + // https://spec.commonmark.org/0.29/#line-ending + var NEWLINES_RE = /\r\n?|\n/g + var NULL_RE = /\0/g - pos++; - } + module.exports = function normalize(state) { + var str - contentStart = pos; + // Normalize newlines + str = state.src.replace(NEWLINES_RE, '\n') - if (contentStart >= max) { - // trimming space in "- \n 3" case, indent is 1 here - indentAfterMarker = 1; - } else { - indentAfterMarker = offset - initial; - } + // Replace NULL characters + str = str.replace(NULL_RE, '\uFFFD') - // If we have more than 4 spaces, the indent is 1 - // (the rest is just indented code block) - if (indentAfterMarker > 4) { indentAfterMarker = 1; } - - // " - test" - // ^^^^^ - calculating total length of this thing - indent = initial + indentAfterMarker; - - // Run subparser & write tokens - token = state.push('list_item_open', 'li', 1); - token.markup = String.fromCharCode(markerCharCode); - token.map = itemLines = [ startLine, 0 ]; - - // change current state, then restore it after parser subcall - oldTight = state.tight; - oldTShift = state.tShift[startLine]; - oldSCount = state.sCount[startLine]; - - // - example list - // ^ listIndent position will be here - // ^ blkIndent position will be here - // - oldListIndent = state.listIndent; - state.listIndent = state.blkIndent; - state.blkIndent = indent; - - state.tight = true; - state.tShift[startLine] = contentStart - state.bMarks[startLine]; - state.sCount[startLine] = offset; - - if (contentStart >= max && state.isEmpty(startLine + 1)) { - // workaround for this case - // (list item is empty, list terminates before "foo"): - // ~~~~~~~~ - // - - // - // foo - // ~~~~~~~~ - state.line = Math.min(state.line + 2, endLine); - } else { - state.md.block.tokenize(state, startLine, endLine, true); - } + state.src = str + } + }, + {}, + ], + 34: [ + function (require, module, exports) { + // Simple typographic replacements + // + // (c) (C) → © + // (tm) (TM) → ™ + // (r) (R) → ® + // +- → ± + // (p) (P) -> § + // ... → … (also ?.... → ?.., !.... → !..) + // ???????? → ???, !!!!! → !!!, `,,` → `,` + // -- → –, --- → — + // + 'use strict' - // If any of list item is tight, mark list as tight - if (!state.tight || prevEmptyEnd) { - tight = false; - } - // Item become loose if finish with empty line, - // but we should filter last element, because it means list finish - prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1); - - state.blkIndent = state.listIndent; - state.listIndent = oldListIndent; - state.tShift[startLine] = oldTShift; - state.sCount[startLine] = oldSCount; - state.tight = oldTight; - - token = state.push('list_item_close', 'li', -1); - token.markup = String.fromCharCode(markerCharCode); - - nextLine = startLine = state.line; - itemLines[1] = nextLine; - contentStart = state.bMarks[startLine]; - - if (nextLine >= endLine) { break; } - - // - // Try to check if list is terminated or continued. - // - if (state.sCount[nextLine] < state.blkIndent) { break; } - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { break; } - - // fail if terminating block found - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; - } - } - if (terminate) { break; } + // TODO: + // - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ + // - miltiplication 2 x 4 -> 2 × 4 - // fail if list has another type - if (isOrdered) { - posAfterMarker = skipOrderedListMarker(state, nextLine); - if (posAfterMarker < 0) { break; } - } else { - posAfterMarker = skipBulletListMarker(state, nextLine); - if (posAfterMarker < 0) { break; } - } + var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/ - if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } - } + // Workaround for phantomjs - need regex without /g flag, + // or root check will fail every second time + var SCOPED_ABBR_TEST_RE = /\((c|tm|r|p)\)/i - // Finalize list - if (isOrdered) { - token = state.push('ordered_list_close', 'ol', -1); - } else { - token = state.push('bullet_list_close', 'ul', -1); - } - token.markup = String.fromCharCode(markerCharCode); + var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/gi + var SCOPED_ABBR = { + c: '©', + r: '®', + p: '§', + tm: '™', + } - listLines[1] = nextLine; - state.line = nextLine; + function replaceFn(match, name) { + return SCOPED_ABBR[name.toLowerCase()] + } - state.parentType = oldParentType; + function replace_scoped(inlineTokens) { + var i, + token, + inside_autolink = 0 - // mark paragraphs tight if needed - if (tight) { - markTightParagraphs(state, listTokIdx); - } + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i] - return true; -}; + if (token.type === 'text' && !inside_autolink) { + token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn) + } -},{"../common/utils":4}],26:[function(require,module,exports){ -// Paragraph + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink-- + } -'use strict'; + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++ + } + } + } + function replace_rare(inlineTokens) { + var i, + token, + inside_autolink = 0 + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i] + + if (token.type === 'text' && !inside_autolink) { + if (RARE_RE.test(token.content)) { + token.content = token.content + .replace(/\+-/g, '±') + // .., ..., ....... -> … + // but ?..... & !..... -> ?.. & !.. + .replace(/\.{2,}/g, '…') + .replace(/([?!])…/g, '$1..') + .replace(/([?!]){4,}/g, '$1$1$1') + .replace(/,{2,}/g, ',') + // em-dash + .replace(/(^|[^-])---(?=[^-]|$)/gm, '$1\u2014') + // en-dash + .replace(/(^|\s)--(?=\s|$)/gm, '$1\u2013') + .replace(/(^|[^-\s])--(?=[^-\s]|$)/gm, '$1\u2013') + } + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink-- + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++ + } + } + } -module.exports = function paragraph(state, startLine/*, endLine*/) { - var content, terminate, i, l, token, oldParentType, - nextLine = startLine + 1, - terminatorRules = state.md.block.ruler.getRules('paragraph'), - endLine = state.lineMax; + module.exports = function replace(state) { + var blkIdx - oldParentType = state.parentType; - state.parentType = 'paragraph'; + if (!state.md.options.typographer) { + return + } - // jump line-by-line until empty one or EOF - for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { - // this would be a code block normally, but after paragraph - // it's considered a lazy continuation regardless of what's there - if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + if (state.tokens[blkIdx].type !== 'inline') { + continue + } - // quirk for blockquotes, this line should already be checked by that rule - if (state.sCount[nextLine] < 0) { continue; } + if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { + replace_scoped(state.tokens[blkIdx].children) + } - // Some tags can terminate paragraph without empty line. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; - } - } - if (terminate) { break; } - } + if (RARE_RE.test(state.tokens[blkIdx].content)) { + replace_rare(state.tokens[blkIdx].children) + } + } + } + }, + {}, + ], + 35: [ + function (require, module, exports) { + // Convert straight quotation marks to typographic ones + // + 'use strict' - content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + var isWhiteSpace = require('../common/utils').isWhiteSpace + var isPunctChar = require('../common/utils').isPunctChar + var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct - state.line = nextLine; + var QUOTE_TEST_RE = /['"]/ + var QUOTE_RE = /['"]/g + var APOSTROPHE = '\u2019' /* ’ */ - token = state.push('paragraph_open', 'p', 1); - token.map = [ startLine, state.line ]; + function replaceAt(str, index, ch) { + return str.substr(0, index) + ch + str.substr(index + 1) + } - token = state.push('inline', '', 0); - token.content = content; - token.map = [ startLine, state.line ]; - token.children = []; + function process_inlines(tokens, state) { + var i, + token, + text, + t, + pos, + max, + thisLevel, + item, + lastChar, + nextChar, + isLastPunctChar, + isNextPunctChar, + isLastWhiteSpace, + isNextWhiteSpace, + canOpen, + canClose, + j, + isSingle, + stack, + openQuote, + closeQuote + + stack = [] + + for (i = 0; i < tokens.length; i++) { + token = tokens[i] + + thisLevel = tokens[i].level + + for (j = stack.length - 1; j >= 0; j--) { + if (stack[j].level <= thisLevel) { + break + } + } + stack.length = j + 1 + + if (token.type !== 'text') { + continue + } + + text = token.content + pos = 0 + max = text.length + + /*eslint no-labels:0,block-scoped-var:0*/ + OUTER: while (pos < max) { + QUOTE_RE.lastIndex = pos + t = QUOTE_RE.exec(text) + if (!t) { + break + } + + canOpen = canClose = true + pos = t.index + 1 + isSingle = t[0] === "'" + + // Find previous character, + // default to space if it's the beginning of the line + // + lastChar = 0x20 + + if (t.index - 1 >= 0) { + lastChar = text.charCodeAt(t.index - 1) + } else { + for (j = i - 1; j >= 0; j--) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break // lastChar defaults to 0x20 + if (!tokens[j].content) continue // should skip all tokens except 'text', 'html_inline' or 'code_inline' + + lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1) + break + } + } + + // Find next character, + // default to space if it's the end of the line + // + nextChar = 0x20 + + if (pos < max) { + nextChar = text.charCodeAt(pos) + } else { + for (j = i + 1; j < tokens.length; j++) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break // nextChar defaults to 0x20 + if (!tokens[j].content) continue // should skip all tokens except 'text', 'html_inline' or 'code_inline' + + nextChar = tokens[j].content.charCodeAt(0) + break + } + } + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)) + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)) + + isLastWhiteSpace = isWhiteSpace(lastChar) + isNextWhiteSpace = isWhiteSpace(nextChar) + + if (isNextWhiteSpace) { + canOpen = false + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + canOpen = false + } + } + + if (isLastWhiteSpace) { + canClose = false + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + canClose = false + } + } + + if (nextChar === 0x22 /* " */ && t[0] === '"') { + if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { + // special case: 1"" - count first quote as an inch + canClose = canOpen = false + } + } + + if (canOpen && canClose) { + // Replace quotes in the middle of punctuation sequence, but not + // in the middle of the words, i.e.: + // + // 1. foo " bar " baz - not replaced + // 2. foo-"-bar-"-baz - replaced + // 3. foo"bar"baz - not replaced + // + canOpen = isLastPunctChar + canClose = isNextPunctChar + } + + if (!canOpen && !canClose) { + // middle of word + if (isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE) + } + continue + } + + if (canClose) { + // this could be a closing quote, rewind the stack to get a match + for (j = stack.length - 1; j >= 0; j--) { + item = stack[j] + if (stack[j].level < thisLevel) { + break + } + if (item.single === isSingle && stack[j].level === thisLevel) { + item = stack[j] + + if (isSingle) { + openQuote = state.md.options.quotes[2] + closeQuote = state.md.options.quotes[3] + } else { + openQuote = state.md.options.quotes[0] + closeQuote = state.md.options.quotes[1] + } + + // replace token.content *before* tokens[item.token].content, + // because, if they are pointing at the same token, replaceAt + // could mess up indices when quote length != 1 + token.content = replaceAt(token.content, t.index, closeQuote) + tokens[item.token].content = replaceAt(tokens[item.token].content, item.pos, openQuote) + + pos += closeQuote.length - 1 + if (item.token === i) { + pos += openQuote.length - 1 + } + + text = token.content + max = text.length + + stack.length = j + continue OUTER + } + } + } + + if (canOpen) { + stack.push({ + token: i, + pos: t.index, + single: isSingle, + level: thisLevel, + }) + } else if (canClose && isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE) + } + } + } + } - token = state.push('paragraph_close', 'p', -1); + module.exports = function smartquotes(state) { + /*eslint max-depth:0*/ + var blkIdx - state.parentType = oldParentType; + if (!state.md.options.typographer) { + return + } - return true; -}; - -},{}],27:[function(require,module,exports){ -'use strict'; - - -var normalizeReference = require('../common/utils').normalizeReference; -var isSpace = require('../common/utils').isSpace; - - -module.exports = function reference(state, startLine, _endLine, silent) { - var ch, - destEndPos, - destEndLineNo, - endLine, - href, - i, - l, - label, - labelEnd, - oldParentType, - res, - start, - str, - terminate, - terminatorRules, - title, - lines = 0, - pos = state.bMarks[startLine] + state.tShift[startLine], - max = state.eMarks[startLine], - nextLine = startLine + 1; - - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - - if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; } - - // Simple check to quickly interrupt scan on [link](url) at the start of line. - // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 - while (++pos < max) { - if (state.src.charCodeAt(pos) === 0x5D /* ] */ && - state.src.charCodeAt(pos - 1) !== 0x5C/* \ */) { - if (pos + 1 === max) { return false; } - if (state.src.charCodeAt(pos + 1) !== 0x3A/* : */) { return false; } - break; - } - } + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + if (state.tokens[blkIdx].type !== 'inline' || !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { + continue + } - endLine = state.lineMax; + process_inlines(state.tokens[blkIdx].children, state) + } + } + }, + { '../common/utils': 4 }, + ], + 36: [ + function (require, module, exports) { + // Core state object + // + 'use strict' - // jump line-by-line until empty one or EOF - terminatorRules = state.md.block.ruler.getRules('reference'); + var Token = require('../token') - oldParentType = state.parentType; - state.parentType = 'reference'; + function StateCore(src, md, env) { + this.src = src + this.env = env + this.tokens = [] + this.inlineMode = false + this.md = md // link to parser instance + } - for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { - // this would be a code block normally, but after paragraph - // it's considered a lazy continuation regardless of what's there - if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + // re-export Token class to use in core rules + StateCore.prototype.Token = Token - // quirk for blockquotes, this line should already be checked by that rule - if (state.sCount[nextLine] < 0) { continue; } + module.exports = StateCore + }, + { '../token': 51 }, + ], + 37: [ + function (require, module, exports) { + // Process autolinks '' + + 'use strict' + + /*eslint max-len:0*/ + var EMAIL_RE = + /^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/ + var AUTOLINK_RE = /^<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)>/ + + module.exports = function autolink(state, silent) { + var tail, + linkMatch, + emailMatch, + url, + fullUrl, + token, + pos = state.pos + + if (state.src.charCodeAt(pos) !== 0x3c /* < */) { + return false + } - // Some tags can terminate paragraph without empty line. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; - } - } - if (terminate) { break; } - } + tail = state.src.slice(pos) - str = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); - max = str.length; - - for (pos = 1; pos < max; pos++) { - ch = str.charCodeAt(pos); - if (ch === 0x5B /* [ */) { - return false; - } else if (ch === 0x5D /* ] */) { - labelEnd = pos; - break; - } else if (ch === 0x0A /* \n */) { - lines++; - } else if (ch === 0x5C /* \ */) { - pos++; - if (pos < max && str.charCodeAt(pos) === 0x0A) { - lines++; - } - } - } + if (tail.indexOf('>') < 0) { + return false + } - if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; } + if (AUTOLINK_RE.test(tail)) { + linkMatch = tail.match(AUTOLINK_RE) - // [label]: destination 'title' - // ^^^ skip optional whitespace here - for (pos = labelEnd + 2; pos < max; pos++) { - ch = str.charCodeAt(pos); - if (ch === 0x0A) { - lines++; - } else if (isSpace(ch)) { - /*eslint no-empty:0*/ - } else { - break; - } - } + url = linkMatch[0].slice(1, -1) + fullUrl = state.md.normalizeLink(url) + if (!state.md.validateLink(fullUrl)) { + return false + } - // [label]: destination 'title' - // ^^^^^^^^^^^ parse this - res = state.md.helpers.parseLinkDestination(str, pos, max); - if (!res.ok) { return false; } - - href = state.md.normalizeLink(res.str); - if (!state.md.validateLink(href)) { return false; } - - pos = res.pos; - lines += res.lines; - - // save cursor state, we could require to rollback later - destEndPos = pos; - destEndLineNo = lines; - - // [label]: destination 'title' - // ^^^ skipping those spaces - start = pos; - for (; pos < max; pos++) { - ch = str.charCodeAt(pos); - if (ch === 0x0A) { - lines++; - } else if (isSpace(ch)) { - /*eslint no-empty:0*/ - } else { - break; - } - } + if (!silent) { + token = state.push('link_open', 'a', 1) + token.attrs = [['href', fullUrl]] + token.markup = 'autolink' + token.info = 'auto' - // [label]: destination 'title' - // ^^^^^^^ parse this - res = state.md.helpers.parseLinkTitle(str, pos, max); - if (pos < max && start !== pos && res.ok) { - title = res.str; - pos = res.pos; - lines += res.lines; - } else { - title = ''; - pos = destEndPos; - lines = destEndLineNo; - } + token = state.push('text', '', 0) + token.content = state.md.normalizeLinkText(url) - // skip trailing spaces until the rest of the line - while (pos < max) { - ch = str.charCodeAt(pos); - if (!isSpace(ch)) { break; } - pos++; - } + token = state.push('link_close', 'a', -1) + token.markup = 'autolink' + token.info = 'auto' + } - if (pos < max && str.charCodeAt(pos) !== 0x0A) { - if (title) { - // garbage at the end of the line after title, - // but it could still be a valid reference if we roll back - title = ''; - pos = destEndPos; - lines = destEndLineNo; - while (pos < max) { - ch = str.charCodeAt(pos); - if (!isSpace(ch)) { break; } - pos++; - } - } - } + state.pos += linkMatch[0].length + return true + } - if (pos < max && str.charCodeAt(pos) !== 0x0A) { - // garbage at the end of the line - return false; - } + if (EMAIL_RE.test(tail)) { + emailMatch = tail.match(EMAIL_RE) - label = normalizeReference(str.slice(1, labelEnd)); - if (!label) { - // CommonMark 0.20 disallows empty labels - return false; - } + url = emailMatch[0].slice(1, -1) + fullUrl = state.md.normalizeLink('mailto:' + url) + if (!state.md.validateLink(fullUrl)) { + return false + } - // Reference can not terminate anything. This check is for safety only. - /*istanbul ignore if*/ - if (silent) { return true; } + if (!silent) { + token = state.push('link_open', 'a', 1) + token.attrs = [['href', fullUrl]] + token.markup = 'autolink' + token.info = 'auto' - if (typeof state.env.references === 'undefined') { - state.env.references = {}; - } - if (typeof state.env.references[label] === 'undefined') { - state.env.references[label] = { title: title, href: href }; - } + token = state.push('text', '', 0) + token.content = state.md.normalizeLinkText(url) - state.parentType = oldParentType; + token = state.push('link_close', 'a', -1) + token.markup = 'autolink' + token.info = 'auto' + } - state.line = startLine + lines + 1; - return true; -}; + state.pos += emailMatch[0].length + return true + } -},{"../common/utils":4}],28:[function(require,module,exports){ -// Parser state class + return false + } + }, + {}, + ], + 38: [ + function (require, module, exports) { + // Parse backticks + + 'use strict' + + module.exports = function backtick(state, silent) { + var start, + max, + marker, + matchStart, + matchEnd, + token, + pos = state.pos, + ch = state.src.charCodeAt(pos) + + if (ch !== 0x60 /* ` */) { + return false + } -'use strict'; + start = pos + pos++ + max = state.posMax -var Token = require('../token'); -var isSpace = require('../common/utils').isSpace; + while (pos < max && state.src.charCodeAt(pos) === 0x60 /* ` */) { + pos++ + } + marker = state.src.slice(start, pos) + + matchStart = matchEnd = pos + + while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { + matchEnd = matchStart + 1 + + while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60 /* ` */) { + matchEnd++ + } + + if (matchEnd - matchStart === marker.length) { + if (!silent) { + token = state.push('code_inline', 'code', 0) + token.markup = marker + token.content = state.src + .slice(pos, matchStart) + .replace(/\n/g, ' ') + .replace(/^ (.+) $/, '$1') + } + state.pos = matchEnd + return true + } + } -function StateBlock(src, md, env, tokens) { - var ch, s, start, pos, len, indent, offset, indent_found; + if (!silent) { + state.pending += marker + } + state.pos += marker.length + return true + } + }, + {}, + ], + 39: [ + function (require, module, exports) { + // For each opening emphasis-like marker find a matching closing one + // + 'use strict' + + function processDelimiters(state, delimiters) { + var closerIdx, + openerIdx, + closer, + opener, + minOpenerIdx, + newMinOpenerIdx, + isOddMatch, + lastJump, + openersBottom = {}, + max = delimiters.length + + for (closerIdx = 0; closerIdx < max; closerIdx++) { + closer = delimiters[closerIdx] + + // Length is only used for emphasis-specific "rule of 3", + // if it's not defined (in strikethrough or 3rd party plugins), + // we can default it to 0 to disable those checks. + // + closer.length = closer.length || 0 + + if (!closer.close) continue + + // Previously calculated lower bounds (previous fails) + // for each marker and each delimiter length modulo 3. + if (!openersBottom.hasOwnProperty(closer.marker)) { + openersBottom[closer.marker] = [-1, -1, -1] + } + + minOpenerIdx = openersBottom[closer.marker][closer.length % 3] + newMinOpenerIdx = -1 + + openerIdx = closerIdx - closer.jump - 1 + + for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) { + opener = delimiters[openerIdx] + + if (opener.marker !== closer.marker) continue + + if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx + + if (opener.open && opener.end < 0) { + isOddMatch = false + + // from spec: + // + // If one of the delimiters can both open and close emphasis, then the + // sum of the lengths of the delimiter runs containing the opening and + // closing delimiters must not be a multiple of 3 unless both lengths + // are multiples of 3. + // + if (opener.close || closer.open) { + if ((opener.length + closer.length) % 3 === 0) { + if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { + isOddMatch = true + } + } + } + + if (!isOddMatch) { + // If previous delimiter cannot be an opener, we can safely skip + // the entire sequence in future checks. This is required to make + // sure algorithm has linear complexity (see *_*_*_*_*_... case). + // + lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? delimiters[openerIdx - 1].jump + 1 : 0 + + closer.jump = closerIdx - openerIdx + lastJump + closer.open = false + opener.end = closerIdx + opener.jump = lastJump + opener.close = false + newMinOpenerIdx = -1 + break + } + } + } + + if (newMinOpenerIdx !== -1) { + // If match for this delimiter run failed, we want to set lower bound for + // future lookups. This is required to make sure algorithm has linear + // complexity. + // + // See details here: + // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 + // + openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx + } + } + } - this.src = src; + module.exports = function link_pairs(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length - // link to parser instance - this.md = md; + processDelimiters(state, state.delimiters) - this.env = env; + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + processDelimiters(state, tokens_meta[curr].delimiters) + } + } + } + }, + {}, + ], + 40: [ + function (require, module, exports) { + // Process *this* and _that_ + // + 'use strict' - // - // Internal state vartiables - // + // Insert each marker as a separate text token, and add it to delimiter list + // + module.exports.tokenize = function emphasis(state, silent) { + var i, + scanned, + token, + start = state.pos, + marker = state.src.charCodeAt(start) + + if (silent) { + return false + } - this.tokens = tokens; + if (marker !== 0x5f /* _ */ && marker !== 0x2a /* * */) { + return false + } - this.bMarks = []; // line begin offsets for fast jumps - this.eMarks = []; // line end offsets for fast jumps - this.tShift = []; // offsets of the first non-space characters (tabs not expanded) - this.sCount = []; // indents for each line (tabs expanded) + scanned = state.scanDelims(state.pos, marker === 0x2a) + + for (i = 0; i < scanned.length; i++) { + token = state.push('text', '', 0) + token.content = String.fromCharCode(marker) + + state.delimiters.push({ + // Char code of the starting marker (number). + // + marker: marker, + + // Total length of these series of delimiters. + // + length: scanned.length, + + // An amount of characters before this one that's equivalent to + // current one. In plain English: if this delimiter does not open + // an emphasis, neither do previous `jump` characters. + // + // Used to skip sequences like "*****" in one step, for 1st asterisk + // value will be 0, for 2nd it's 1 and so on. + // + jump: i, + + // A position of the token this delimiter corresponds to. + // + token: state.tokens.length - 1, + + // If this delimiter is matched as a valid opener, `end` will be + // equal to its position, otherwise it's `-1`. + // + end: -1, + + // Boolean flags that determine if this delimiter could open or close + // an emphasis. + // + open: scanned.can_open, + close: scanned.can_close, + }) + } - // An amount of virtual spaces (tabs expanded) between beginning - // of each line (bMarks) and real beginning of that line. - // - // It exists only as a hack because blockquotes override bMarks - // losing information in the process. - // - // It's used only when expanding tabs, you can think about it as - // an initial tab length, e.g. bsCount=21 applied to string `\t123` - // means first tab should be expanded to 4-21%4 === 3 spaces. - // - this.bsCount = []; + state.pos += scanned.length - // block parser variables - this.blkIndent = 0; // required block content indent (for example, if we are - // inside a list, it would be positioned after list marker) - this.line = 0; // line index in src - this.lineMax = 0; // lines count - this.tight = false; // loose/tight mode for lists - this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any) - this.listIndent = -1; // indent of the current list block (-1 if there isn't any) + return true + } - // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' - // used in lists to determine if they interrupt a paragraph - this.parentType = 'root'; + function postProcess(state, delimiters) { + var i, + startDelim, + endDelim, + token, + ch, + isStrong, + max = delimiters.length + + for (i = max - 1; i >= 0; i--) { + startDelim = delimiters[i] + + if (startDelim.marker !== 0x5f /* _ */ && startDelim.marker !== 0x2a /* * */) { + continue + } + + // Process only opening markers + if (startDelim.end === -1) { + continue + } + + endDelim = delimiters[startDelim.end] + + // If the previous delimiter has the same marker and is adjacent to this one, + // merge those into one strong delimiter. + // + // `whatever` -> `whatever` + // + isStrong = + i > 0 && + delimiters[i - 1].end === startDelim.end + 1 && + delimiters[i - 1].token === startDelim.token - 1 && + delimiters[startDelim.end + 1].token === endDelim.token + 1 && + delimiters[i - 1].marker === startDelim.marker + + ch = String.fromCharCode(startDelim.marker) + + token = state.tokens[startDelim.token] + token.type = isStrong ? 'strong_open' : 'em_open' + token.tag = isStrong ? 'strong' : 'em' + token.nesting = 1 + token.markup = isStrong ? ch + ch : ch + token.content = '' + + token = state.tokens[endDelim.token] + token.type = isStrong ? 'strong_close' : 'em_close' + token.tag = isStrong ? 'strong' : 'em' + token.nesting = -1 + token.markup = isStrong ? ch + ch : ch + token.content = '' + + if (isStrong) { + state.tokens[delimiters[i - 1].token].content = '' + state.tokens[delimiters[startDelim.end + 1].token].content = '' + i-- + } + } + } - this.level = 0; + // Walk through delimiter list and replace text tokens with tags + // + module.exports.postProcess = function emphasis(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length - // renderer - this.result = ''; + postProcess(state, state.delimiters) - // Create caches - // Generate markers. - s = this.src; - indent_found = false; + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters) + } + } + } + }, + {}, + ], + 41: [ + function (require, module, exports) { + // Process html entity - {, ¯, ", ... - for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { - ch = s.charCodeAt(pos); + 'use strict' - if (!indent_found) { - if (isSpace(ch)) { - indent++; + var entities = require('../common/entities') + var has = require('../common/utils').has + var isValidEntityCode = require('../common/utils').isValidEntityCode + var fromCodePoint = require('../common/utils').fromCodePoint - if (ch === 0x09) { - offset += 4 - offset % 4; - } else { - offset++; - } - continue; - } else { - indent_found = true; - } - } + var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i + var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i - if (ch === 0x0A || pos === len - 1) { - if (ch !== 0x0A) { pos++; } - this.bMarks.push(start); - this.eMarks.push(pos); - this.tShift.push(indent); - this.sCount.push(offset); - this.bsCount.push(0); - - indent_found = false; - indent = 0; - offset = 0; - start = pos + 1; - } - } + module.exports = function entity(state, silent) { + var ch, + code, + match, + pos = state.pos, + max = state.posMax - // Push fake entry to simplify cache bounds checks - this.bMarks.push(s.length); - this.eMarks.push(s.length); - this.tShift.push(0); - this.sCount.push(0); - this.bsCount.push(0); - - this.lineMax = this.bMarks.length - 1; // don't count last fake line -} - -// Push new token to "stream". -// -StateBlock.prototype.push = function (type, tag, nesting) { - var token = new Token(type, tag, nesting); - token.block = true; - - if (nesting < 0) this.level--; // closing tag - token.level = this.level; - if (nesting > 0) this.level++; // opening tag - - this.tokens.push(token); - return token; -}; - -StateBlock.prototype.isEmpty = function isEmpty(line) { - return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; -}; - -StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { - for (var max = this.lineMax; from < max; from++) { - if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { - break; - } - } - return from; -}; + if (state.src.charCodeAt(pos) !== 0x26 /* & */) { + return false + } -// Skip spaces from given position. -StateBlock.prototype.skipSpaces = function skipSpaces(pos) { - var ch; + if (pos + 1 < max) { + ch = state.src.charCodeAt(pos + 1) + + if (ch === 0x23 /* # */) { + match = state.src.slice(pos).match(DIGITAL_RE) + if (match) { + if (!silent) { + code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10) + state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xfffd) + } + state.pos += match[0].length + return true + } + } else { + match = state.src.slice(pos).match(NAMED_RE) + if (match) { + if (has(entities, match[1])) { + if (!silent) { + state.pending += entities[match[1]] + } + state.pos += match[0].length + return true + } + } + } + } - for (var max = this.src.length; pos < max; pos++) { - ch = this.src.charCodeAt(pos); - if (!isSpace(ch)) { break; } - } - return pos; -}; + if (!silent) { + state.pending += '&' + } + state.pos++ + return true + } + }, + { '../common/entities': 1, '../common/utils': 4 }, + ], + 42: [ + function (require, module, exports) { + // Process escaped chars and hardbreaks -// Skip spaces from given position in reverse. -StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { - if (pos <= min) { return pos; } + 'use strict' - while (pos > min) { - if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1; } - } - return pos; -}; + var isSpace = require('../common/utils').isSpace -// Skip char codes from given position -StateBlock.prototype.skipChars = function skipChars(pos, code) { - for (var max = this.src.length; pos < max; pos++) { - if (this.src.charCodeAt(pos) !== code) { break; } - } - return pos; -}; + var ESCAPED = [] -// Skip char codes reverse from given position - 1 -StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { - if (pos <= min) { return pos; } + for (var i = 0; i < 256; i++) { + ESCAPED.push(0) + } - while (pos > min) { - if (code !== this.src.charCodeAt(--pos)) { return pos + 1; } - } - return pos; -}; + '\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-'.split('').forEach(function (ch) { + ESCAPED[ch.charCodeAt(0)] = 1 + }) -// cut lines range from source. -StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { - var i, lineIndent, ch, first, last, queue, lineStart, - line = begin; + module.exports = function escape(state, silent) { + var ch, + pos = state.pos, + max = state.posMax - if (begin >= end) { - return ''; - } + if (state.src.charCodeAt(pos) !== 0x5c /* \ */) { + return false + } - queue = new Array(end - begin); + pos++ + + if (pos < max) { + ch = state.src.charCodeAt(pos) + + if (ch < 256 && ESCAPED[ch] !== 0) { + if (!silent) { + state.pending += state.src[pos] + } + state.pos += 2 + return true + } + + if (ch === 0x0a) { + if (!silent) { + state.push('hardbreak', 'br', 0) + } + + pos++ + // skip leading whitespaces from next line + while (pos < max) { + ch = state.src.charCodeAt(pos) + if (!isSpace(ch)) { + break + } + pos++ + } + + state.pos = pos + return true + } + } - for (i = 0; line < end; line++, i++) { - lineIndent = 0; - lineStart = first = this.bMarks[line]; + if (!silent) { + state.pending += '\\' + } + state.pos++ + return true + } + }, + { '../common/utils': 4 }, + ], + 43: [ + function (require, module, exports) { + // Process html tags - if (line + 1 < end || keepLastLF) { - // No need for bounds check because we have fake entry on tail. - last = this.eMarks[line] + 1; - } else { - last = this.eMarks[line]; - } + 'use strict' - while (first < last && lineIndent < indent) { - ch = this.src.charCodeAt(first); + var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE - if (isSpace(ch)) { - if (ch === 0x09) { - lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; - } else { - lineIndent++; - } - } else if (first - lineStart < this.tShift[line]) { - // patched tShift masked characters to look like spaces (blockquotes, list markers) - lineIndent++; - } else { - break; - } + function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20 // to lower case + return lc >= 0x61 /* a */ && lc <= 0x7a /* z */ + } - first++; - } + module.exports = function html_inline(state, silent) { + var ch, + match, + max, + token, + pos = state.pos - if (lineIndent > indent) { - // partially expanding tabs in code blocks, e.g '\t\tfoobar' - // with indent=2 becomes ' \tfoobar' - queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last); - } else { - queue[i] = this.src.slice(first, last); - } - } + if (!state.md.options.html) { + return false + } - return queue.join(''); -}; + // Check start + max = state.posMax + if (state.src.charCodeAt(pos) !== 0x3c /* < */ || pos + 2 >= max) { + return false + } -// re-export Token class to use in block rules -StateBlock.prototype.Token = Token; + // Quick fail on second char + ch = state.src.charCodeAt(pos + 1) + if (ch !== 0x21 /* ! */ && ch !== 0x3f /* ? */ && ch !== 0x2f /* / */ && !isLetter(ch)) { + return false + } + match = state.src.slice(pos).match(HTML_TAG_RE) + if (!match) { + return false + } -module.exports = StateBlock; + if (!silent) { + token = state.push('html_inline', '', 0) + token.content = state.src.slice(pos, pos + match[0].length) + } + state.pos += match[0].length + return true + } + }, + { '../common/html_re': 3 }, + ], + 44: [ + function (require, module, exports) { + // Process ![image]( "title") + + 'use strict' + + var normalizeReference = require('../common/utils').normalizeReference + var isSpace = require('../common/utils').isSpace + + module.exports = function image(state, silent) { + var attrs, + code, + content, + label, + labelEnd, + labelStart, + pos, + ref, + res, + title, + token, + tokens, + start, + href = '', + oldPos = state.pos, + max = state.posMax + + if (state.src.charCodeAt(state.pos) !== 0x21 /* ! */) { + return false + } + if (state.src.charCodeAt(state.pos + 1) !== 0x5b /* [ */) { + return false + } -},{"../common/utils":4,"../token":51}],29:[function(require,module,exports){ -// GFM table, non-standard + labelStart = state.pos + 2 + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false) -'use strict'; + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { + return false + } -var isSpace = require('../common/utils').isSpace; + pos = labelEnd + 1 + if (pos < max && state.src.charCodeAt(pos) === 0x28 /* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++ + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (!isSpace(code) && code !== 0x0a) { + break + } + } + if (pos >= max) { + return false + } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax) + if (res.ok) { + href = state.md.normalizeLink(res.str) + if (state.md.validateLink(href)) { + pos = res.pos + } else { + href = '' + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (!isSpace(code) && code !== 0x0a) { + break + } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax) + if (pos < max && start !== pos && res.ok) { + title = res.str + pos = res.pos + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (!isSpace(code) && code !== 0x0a) { + break + } + } + } else { + title = '' + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29 /* ) */) { + state.pos = oldPos + return false + } + pos++ + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { + return false + } + + if (pos < max && state.src.charCodeAt(pos) === 0x5b /* [ */) { + start = pos + 1 + pos = state.md.helpers.parseLinkLabel(state, pos) + if (pos >= 0) { + label = state.src.slice(start, pos++) + } else { + pos = labelEnd + 1 + } + } else { + pos = labelEnd + 1 + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { + label = state.src.slice(labelStart, labelEnd) + } + + ref = state.env.references[normalizeReference(label)] + if (!ref) { + state.pos = oldPos + return false + } + href = ref.href + title = ref.title + } + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + content = state.src.slice(labelStart, labelEnd) + + state.md.inline.parse(content, state.md, state.env, (tokens = [])) + + token = state.push('image', 'img', 0) + token.attrs = attrs = [ + ['src', href], + ['alt', ''], + ] + token.children = tokens + token.content = content + + if (title) { + attrs.push(['title', title]) + } + } -function getLine(state, line) { - var pos = state.bMarks[line] + state.blkIndent, - max = state.eMarks[line]; + state.pos = pos + state.posMax = max + return true + } + }, + { '../common/utils': 4 }, + ], + 45: [ + function (require, module, exports) { + // Process [link]( "stuff") + + 'use strict' + + var normalizeReference = require('../common/utils').normalizeReference + var isSpace = require('../common/utils').isSpace + + module.exports = function link(state, silent) { + var attrs, + code, + label, + labelEnd, + labelStart, + pos, + res, + ref, + title, + token, + href = '', + oldPos = state.pos, + max = state.posMax, + start = state.pos, + parseReference = true + + if (state.src.charCodeAt(state.pos) !== 0x5b /* [ */) { + return false + } - return state.src.substr(pos, max - pos); -} + labelStart = state.pos + 1 + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true) -function escapedSplit(str) { - var result = [], - pos = 0, - max = str.length, - ch, - escapes = 0, - lastPos = 0, - backTicked = false, - lastBackTick = 0; + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { + return false + } - ch = str.charCodeAt(pos); + pos = labelEnd + 1 + if (pos < max && state.src.charCodeAt(pos) === 0x28 /* ( */) { + // + // Inline link + // + + // might have found a valid shortcut link, disable reference parsing + parseReference = false + + // [link]( "title" ) + // ^^ skipping these spaces + pos++ + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (!isSpace(code) && code !== 0x0a) { + break + } + } + if (pos >= max) { + return false + } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax) + if (res.ok) { + href = state.md.normalizeLink(res.str) + if (state.md.validateLink(href)) { + pos = res.pos + } else { + href = '' + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (!isSpace(code) && code !== 0x0a) { + break + } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax) + if (pos < max && start !== pos && res.ok) { + title = res.str + pos = res.pos + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos) + if (!isSpace(code) && code !== 0x0a) { + break + } + } + } else { + title = '' + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29 /* ) */) { + // parsing a valid shortcut link failed, fallback to reference + parseReference = true + } + pos++ + } - while (pos < max) { - if (ch === 0x60/* ` */) { - if (backTicked) { - // make \` close code sequence, but not open it; - // the reason is: `\` is correct code block - backTicked = false; - lastBackTick = pos; - } else if (escapes % 2 === 0) { - backTicked = true; - lastBackTick = pos; - } - } else if (ch === 0x7c/* | */ && (escapes % 2 === 0) && !backTicked) { - result.push(str.substring(lastPos, pos)); - lastPos = pos + 1; - } + if (parseReference) { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { + return false + } + + if (pos < max && state.src.charCodeAt(pos) === 0x5b /* [ */) { + start = pos + 1 + pos = state.md.helpers.parseLinkLabel(state, pos) + if (pos >= 0) { + label = state.src.slice(start, pos++) + } else { + pos = labelEnd + 1 + } + } else { + pos = labelEnd + 1 + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { + label = state.src.slice(labelStart, labelEnd) + } + + ref = state.env.references[normalizeReference(label)] + if (!ref) { + state.pos = oldPos + return false + } + href = ref.href + title = ref.title + } - if (ch === 0x5c/* \ */) { - escapes++; - } else { - escapes = 0; - } + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart + state.posMax = labelEnd - pos++; + token = state.push('link_open', 'a', 1) + token.attrs = attrs = [['href', href]] + if (title) { + attrs.push(['title', title]) + } - // If there was an un-closed backtick, go back to just after - // the last backtick, but as if it was a normal character - if (pos === max && backTicked) { - backTicked = false; - pos = lastBackTick + 1; - } + state.md.inline.tokenize(state) - ch = str.charCodeAt(pos); - } + token = state.push('link_close', 'a', -1) + } - result.push(str.substring(lastPos)); + state.pos = pos + state.posMax = max + return true + } + }, + { '../common/utils': 4 }, + ], + 46: [ + function (require, module, exports) { + // Proceess '\n' - return result; -} + 'use strict' + var isSpace = require('../common/utils').isSpace -module.exports = function table(state, startLine, endLine, silent) { - var ch, lineText, pos, i, nextLine, columns, columnCount, token, - aligns, t, tableLines, tbodyLines; + module.exports = function newline(state, silent) { + var pmax, + max, + pos = state.pos - // should have at least two lines - if (startLine + 2 > endLine) { return false; } + if (state.src.charCodeAt(pos) !== 0x0a /* \n */) { + return false + } - nextLine = startLine + 1; + pmax = state.pending.length - 1 + max = state.posMax + + // ' \n' -> hardbreak + // Lookup in pending chars is bad practice! Don't copy to other rules! + // Pending string is stored in concat mode, indexed lookups will cause + // convertion to flat mode. + if (!silent) { + if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { + if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { + state.pending = state.pending.replace(/ +$/, '') + state.push('hardbreak', 'br', 0) + } else { + state.pending = state.pending.slice(0, -1) + state.push('softbreak', 'br', 0) + } + } else { + state.push('softbreak', 'br', 0) + } + } - if (state.sCount[nextLine] < state.blkIndent) { return false; } + pos++ - // if it's indented more than 3 spaces, it should be a code block - if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; } + // skip heading spaces for next line + while (pos < max && isSpace(state.src.charCodeAt(pos))) { + pos++ + } - // first character of the second line should be '|', '-', ':', - // and no other characters are allowed but spaces; - // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp + state.pos = pos + return true + } + }, + { '../common/utils': 4 }, + ], + 47: [ + function (require, module, exports) { + // Inline parser state + + 'use strict' + + var Token = require('../token') + var isWhiteSpace = require('../common/utils').isWhiteSpace + var isPunctChar = require('../common/utils').isPunctChar + var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct + + function StateInline(src, md, env, outTokens) { + this.src = src + this.env = env + this.md = md + this.tokens = outTokens + this.tokens_meta = Array(outTokens.length) + + this.pos = 0 + this.posMax = this.src.length + this.level = 0 + this.pending = '' + this.pendingLevel = 0 + + // Stores { start: end } pairs. Useful for backtrack + // optimization of pairs parse (emphasis, strikes). + this.cache = {} + + // List of emphasis-like delimiters for current tag + this.delimiters = [] + + // Stack of delimiter lists for upper level tags + this._prev_delimiters = [] + } - pos = state.bMarks[nextLine] + state.tShift[nextLine]; - if (pos >= state.eMarks[nextLine]) { return false; } + // Flush pending text + // + StateInline.prototype.pushPending = function () { + var token = new Token('text', '', 0) + token.content = this.pending + token.level = this.pendingLevel + this.tokens.push(token) + this.pending = '' + return token + } - ch = state.src.charCodeAt(pos++); - if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; } + // Push new token to "stream". + // If pending text exists - flush it as text token + // + StateInline.prototype.push = function (type, tag, nesting) { + if (this.pending) { + this.pushPending() + } - while (pos < state.eMarks[nextLine]) { - ch = state.src.charCodeAt(pos); + var token = new Token(type, tag, nesting) + var token_meta = null - if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; } + if (nesting < 0) { + // closing tag + this.level-- + this.delimiters = this._prev_delimiters.pop() + } - pos++; - } + token.level = this.level - lineText = getLine(state, startLine + 1); - - columns = lineText.split('|'); - aligns = []; - for (i = 0; i < columns.length; i++) { - t = columns[i].trim(); - if (!t) { - // allow empty columns before and after table, but not in between columns; - // e.g. allow ` |---| `, disallow ` ---||--- ` - if (i === 0 || i === columns.length - 1) { - continue; - } else { - return false; - } - } + if (nesting > 0) { + // opening tag + this.level++ + this._prev_delimiters.push(this.delimiters) + this.delimiters = [] + token_meta = { delimiters: this.delimiters } + } - if (!/^:?-+:?$/.test(t)) { return false; } - if (t.charCodeAt(t.length - 1) === 0x3A/* : */) { - aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right'); - } else if (t.charCodeAt(0) === 0x3A/* : */) { - aligns.push('left'); - } else { - aligns.push(''); - } - } + this.pendingLevel = this.level + this.tokens.push(token) + this.tokens_meta.push(token_meta) + return token + } - lineText = getLine(state, startLine).trim(); - if (lineText.indexOf('|') === -1) { return false; } - if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } - columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + // Scan a sequence of emphasis-like markers, and determine whether + // it can start an emphasis sequence or end an emphasis sequence. + // + // - start - position to scan from (it should point at a valid marker); + // - canSplitWord - determine if these markers can be found inside a word + // + StateInline.prototype.scanDelims = function (start, canSplitWord) { + var pos = start, + lastChar, + nextChar, + count, + can_open, + can_close, + isLastWhiteSpace, + isLastPunctChar, + isNextWhiteSpace, + isNextPunctChar, + left_flanking = true, + right_flanking = true, + max = this.posMax, + marker = this.src.charCodeAt(start) + + // treat beginning of the line as a whitespace + lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20 + + while (pos < max && this.src.charCodeAt(pos) === marker) { + pos++ + } - // header row will define an amount of columns in the entire table, - // and align row shouldn't be smaller than that (the rest of the rows can) - columnCount = columns.length; - if (columnCount > aligns.length) { return false; } + count = pos - start - if (silent) { return true; } + // treat end of the line as a whitespace + nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20 - token = state.push('table_open', 'table', 1); - token.map = tableLines = [ startLine, 0 ]; + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)) + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)) - token = state.push('thead_open', 'thead', 1); - token.map = [ startLine, startLine + 1 ]; + isLastWhiteSpace = isWhiteSpace(lastChar) + isNextWhiteSpace = isWhiteSpace(nextChar) - token = state.push('tr_open', 'tr', 1); - token.map = [ startLine, startLine + 1 ]; + if (isNextWhiteSpace) { + left_flanking = false + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + left_flanking = false + } + } - for (i = 0; i < columns.length; i++) { - token = state.push('th_open', 'th', 1); - token.map = [ startLine, startLine + 1 ]; - if (aligns[i]) { - token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; - } + if (isLastWhiteSpace) { + right_flanking = false + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + right_flanking = false + } + } - token = state.push('inline', '', 0); - token.content = columns[i].trim(); - token.map = [ startLine, startLine + 1 ]; - token.children = []; + if (!canSplitWord) { + can_open = left_flanking && (!right_flanking || isLastPunctChar) + can_close = right_flanking && (!left_flanking || isNextPunctChar) + } else { + can_open = left_flanking + can_close = right_flanking + } - token = state.push('th_close', 'th', -1); - } + return { + can_open: can_open, + can_close: can_close, + length: count, + } + } - token = state.push('tr_close', 'tr', -1); - token = state.push('thead_close', 'thead', -1); + // re-export Token class to use in block rules + StateInline.prototype.Token = Token - token = state.push('tbody_open', 'tbody', 1); - token.map = tbodyLines = [ startLine + 2, 0 ]; + module.exports = StateInline + }, + { '../common/utils': 4, '../token': 51 }, + ], + 48: [ + function (require, module, exports) { + // ~~strike through~~ + // + 'use strict' - for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { - if (state.sCount[nextLine] < state.blkIndent) { break; } + // Insert each marker as a separate text token, and add it to delimiter list + // + module.exports.tokenize = function strikethrough(state, silent) { + var i, + scanned, + token, + len, + ch, + start = state.pos, + marker = state.src.charCodeAt(start) + + if (silent) { + return false + } - lineText = getLine(state, nextLine).trim(); - if (lineText.indexOf('|') === -1) { break; } - if (state.sCount[nextLine] - state.blkIndent >= 4) { break; } - columns = escapedSplit(lineText.replace(/^\||\|$/g, '')); + if (marker !== 0x7e /* ~ */) { + return false + } - token = state.push('tr_open', 'tr', 1); - for (i = 0; i < columnCount; i++) { - token = state.push('td_open', 'td', 1); - token.map = [ nextLine, nextLine + 1 ]; - if (aligns[i]) { - token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; - } + scanned = state.scanDelims(state.pos, true) + len = scanned.length + ch = String.fromCharCode(marker) - token = state.push('inline', '', 0); - token.map = [ nextLine, nextLine + 1 ]; - token.content = columns[i] ? columns[i].trim() : ''; - token.children = []; + if (len < 2) { + return false + } - token = state.push('td_close', 'td', -1); - } - token = state.push('tr_close', 'tr', -1); - } - token = state.push('tbody_close', 'tbody', -1); - token = state.push('table_close', 'table', -1); + if (len % 2) { + token = state.push('text', '', 0) + token.content = ch + len-- + } - tableLines[1] = tbodyLines[1] = nextLine; - state.line = nextLine; - return true; -}; + for (i = 0; i < len; i += 2) { + token = state.push('text', '', 0) + token.content = ch + ch + + state.delimiters.push({ + marker: marker, + length: 0, // disable "rule of 3" length checks meant for emphasis + jump: i, + token: state.tokens.length - 1, + end: -1, + open: scanned.can_open, + close: scanned.can_close, + }) + } -},{"../common/utils":4}],30:[function(require,module,exports){ -'use strict'; + state.pos += scanned.length + return true + } -module.exports = function block(state) { - var token; + function postProcess(state, delimiters) { + var i, + j, + startDelim, + endDelim, + token, + loneMarkers = [], + max = delimiters.length + + for (i = 0; i < max; i++) { + startDelim = delimiters[i] + + if (startDelim.marker !== 0x7e /* ~ */) { + continue + } + + if (startDelim.end === -1) { + continue + } + + endDelim = delimiters[startDelim.end] + + token = state.tokens[startDelim.token] + token.type = 's_open' + token.tag = 's' + token.nesting = 1 + token.markup = '~~' + token.content = '' + + token = state.tokens[endDelim.token] + token.type = 's_close' + token.tag = 's' + token.nesting = -1 + token.markup = '~~' + token.content = '' + + if ( + state.tokens[endDelim.token - 1].type === 'text' && + state.tokens[endDelim.token - 1].content === '~' + ) { + loneMarkers.push(endDelim.token - 1) + } + } - if (state.inlineMode) { - token = new state.Token('inline', '', 0); - token.content = state.src; - token.map = [ 0, 1 ]; - token.children = []; - state.tokens.push(token); - } else { - state.md.block.parse(state.src, state.md, state.env, state.tokens); - } -}; + // If a marker sequence has an odd number of characters, it's splitted + // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the + // start of the sequence. + // + // So, we have to move all those markers after subsequent s_close tags. + // + while (loneMarkers.length) { + i = loneMarkers.pop() + j = i + 1 + + while (j < state.tokens.length && state.tokens[j].type === 's_close') { + j++ + } + + j-- + + if (i !== j) { + token = state.tokens[j] + state.tokens[j] = state.tokens[i] + state.tokens[i] = token + } + } + } -},{}],31:[function(require,module,exports){ -'use strict'; + // Walk through delimiter list and replace text tokens with tags + // + module.exports.postProcess = function strikethrough(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length -module.exports = function inline(state) { - var tokens = state.tokens, tok, i, l; + postProcess(state, state.delimiters) - // Parse inlines - for (i = 0, l = tokens.length; i < l; i++) { - tok = tokens[i]; - if (tok.type === 'inline') { - state.md.inline.parse(tok.content, state.md, state.env, tok.children); - } - } -}; + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters) + } + } + } + }, + {}, + ], + 49: [ + function (require, module, exports) { + // Skip text characters for text token, place those to pending buffer + // and increment current pos + + 'use strict' + + // Rule to skip pure text + // '{}$%@~+=:' reserved for extentions + + // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + + // !!!! Don't confuse with "Markdown ASCII Punctuation" chars + // http://spec.commonmark.org/0.15/#ascii-punctuation-character + function isTerminatorChar(ch) { + switch (ch) { + case 0x0a /* \n */: + case 0x21 /* ! */: + case 0x23 /* # */: + case 0x24 /* $ */: + case 0x25 /* % */: + case 0x26 /* & */: + case 0x2a /* * */: + case 0x2b /* + */: + case 0x2d /* - */: + case 0x3a /* : */: + case 0x3c /* < */: + case 0x3d /* = */: + case 0x3e /* > */: + case 0x40 /* @ */: + case 0x5b /* [ */: + case 0x5c /* \ */: + case 0x5d /* ] */: + case 0x5e /* ^ */: + case 0x5f /* _ */: + case 0x60 /* ` */: + case 0x7b /* { */: + case 0x7d /* } */: + case 0x7e /* ~ */: + return true + default: + return false + } + } -},{}],32:[function(require,module,exports){ -// Replace link-like texts with link nodes. -// -// Currently restricted by `md.validateLink()` to http/https/ftp -// -'use strict'; + module.exports = function text(state, silent) { + var pos = state.pos + while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { + pos++ + } -var arrayReplaceAt = require('../common/utils').arrayReplaceAt; + if (pos === state.pos) { + return false + } + if (!silent) { + state.pending += state.src.slice(state.pos, pos) + } -function isLinkOpen(str) { - return /^\s]/i.test(str); -} -function isLinkClose(str) { - return /^<\/a\s*>/i.test(str); -} + state.pos = pos + return true + } -module.exports = function linkify(state) { - var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, - level, htmlLinkLevel, url, fullUrl, urlText, - blockTokens = state.tokens, - links; + // Alternative implementation, for memory. + // + // It costs 10% of performance, but allows extend terminators list, if place it + // to `ParcerInline` property. Probably, will switch to it sometime, such + // flexibility required. - if (!state.md.options.linkify) { return; } + /* +var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/; - for (j = 0, l = blockTokens.length; j < l; j++) { - if (blockTokens[j].type !== 'inline' || - !state.md.linkify.pretest(blockTokens[j].content)) { - continue; - } +module.exports = function text(state, silent) { + var pos = state.pos, + idx = state.src.slice(pos).search(TERMINATOR_RE); - tokens = blockTokens[j].children; + // first char is terminator -> empty text + if (idx === 0) { return false; } - htmlLinkLevel = 0; + // no terminator -> text till end of string + if (idx < 0) { + if (!silent) { state.pending += state.src.slice(pos); } + state.pos = state.src.length; + return true; + } - // We scan from the end, to keep position when new tags added. - // Use reversed logic in links start/end match - for (i = tokens.length - 1; i >= 0; i--) { - currentToken = tokens[i]; + if (!silent) { state.pending += state.src.slice(pos, pos + idx); } - // Skip content of markdown links - if (currentToken.type === 'link_close') { - i--; - while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { - i--; - } - continue; - } - - // Skip content of html tag links - if (currentToken.type === 'html_inline') { - if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { - htmlLinkLevel--; - } - if (isLinkClose(currentToken.content)) { - htmlLinkLevel++; - } - } - if (htmlLinkLevel > 0) { continue; } - - if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { - - text = currentToken.content; - links = state.md.linkify.match(text); - - // Now split string to nodes - nodes = []; - level = currentToken.level; - lastPos = 0; - - for (ln = 0; ln < links.length; ln++) { - - url = links[ln].url; - fullUrl = state.md.normalizeLink(url); - if (!state.md.validateLink(fullUrl)) { continue; } - - urlText = links[ln].text; - - // Linkifier might send raw hostnames like "example.com", where url - // starts with domain name. So we prepend http:// in those cases, - // and remove it afterwards. - // - if (!links[ln].schema) { - urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, ''); - } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { - urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, ''); - } else { - urlText = state.md.normalizeLinkText(urlText); - } - - pos = links[ln].index; - - if (pos > lastPos) { - token = new state.Token('text', '', 0); - token.content = text.slice(lastPos, pos); - token.level = level; - nodes.push(token); - } - - token = new state.Token('link_open', 'a', 1); - token.attrs = [ [ 'href', fullUrl ] ]; - token.level = level++; - token.markup = 'linkify'; - token.info = 'auto'; - nodes.push(token); - - token = new state.Token('text', '', 0); - token.content = urlText; - token.level = level; - nodes.push(token); - - token = new state.Token('link_close', 'a', -1); - token.level = --level; - token.markup = 'linkify'; - token.info = 'auto'; - nodes.push(token); - - lastPos = links[ln].lastIndex; - } - if (lastPos < text.length) { - token = new state.Token('text', '', 0); - token.content = text.slice(lastPos); - token.level = level; - nodes.push(token); - } - - // replace current node - blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); - } - } - } -}; - -},{"../common/utils":4}],33:[function(require,module,exports){ -// Normalize input string - -'use strict'; - - -// https://spec.commonmark.org/0.29/#line-ending -var NEWLINES_RE = /\r\n?|\n/g; -var NULL_RE = /\0/g; - - -module.exports = function normalize(state) { - var str; - - // Normalize newlines - str = state.src.replace(NEWLINES_RE, '\n'); - - // Replace NULL characters - str = str.replace(NULL_RE, '\uFFFD'); - - state.src = str; -}; - -},{}],34:[function(require,module,exports){ -// Simple typographic replacements -// -// (c) (C) → © -// (tm) (TM) → ™ -// (r) (R) → ® -// +- → ± -// (p) (P) -> § -// ... → … (also ?.... → ?.., !.... → !..) -// ???????? → ???, !!!!! → !!!, `,,` → `,` -// -- → –, --- → — -// -'use strict'; - -// TODO: -// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ -// - miltiplication 2 x 4 -> 2 × 4 - -var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; - -// Workaround for phantomjs - need regex without /g flag, -// or root check will fail every second time -var SCOPED_ABBR_TEST_RE = /\((c|tm|r|p)\)/i; - -var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig; -var SCOPED_ABBR = { - c: '©', - r: '®', - p: '§', - tm: '™' -}; - -function replaceFn(match, name) { - return SCOPED_ABBR[name.toLowerCase()]; -} - -function replace_scoped(inlineTokens) { - var i, token, inside_autolink = 0; - - for (i = inlineTokens.length - 1; i >= 0; i--) { - token = inlineTokens[i]; - - if (token.type === 'text' && !inside_autolink) { - token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); - } - - if (token.type === 'link_open' && token.info === 'auto') { - inside_autolink--; - } - - if (token.type === 'link_close' && token.info === 'auto') { - inside_autolink++; - } - } -} - -function replace_rare(inlineTokens) { - var i, token, inside_autolink = 0; - - for (i = inlineTokens.length - 1; i >= 0; i--) { - token = inlineTokens[i]; - - if (token.type === 'text' && !inside_autolink) { - if (RARE_RE.test(token.content)) { - token.content = token.content - .replace(/\+-/g, '±') - // .., ..., ....... -> … - // but ?..... & !..... -> ?.. & !.. - .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..') - .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',') - // em-dash - .replace(/(^|[^-])---(?=[^-]|$)/mg, '$1\u2014') - // en-dash - .replace(/(^|\s)--(?=\s|$)/mg, '$1\u2013') - .replace(/(^|[^-\s])--(?=[^-\s]|$)/mg, '$1\u2013'); - } - } - - if (token.type === 'link_open' && token.info === 'auto') { - inside_autolink--; - } - - if (token.type === 'link_close' && token.info === 'auto') { - inside_autolink++; - } - } -} - - -module.exports = function replace(state) { - var blkIdx; - - if (!state.md.options.typographer) { return; } - - for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { - - if (state.tokens[blkIdx].type !== 'inline') { continue; } - - if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { - replace_scoped(state.tokens[blkIdx].children); - } - - if (RARE_RE.test(state.tokens[blkIdx].content)) { - replace_rare(state.tokens[blkIdx].children); - } - - } -}; - -},{}],35:[function(require,module,exports){ -// Convert straight quotation marks to typographic ones -// -'use strict'; - - -var isWhiteSpace = require('../common/utils').isWhiteSpace; -var isPunctChar = require('../common/utils').isPunctChar; -var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; - -var QUOTE_TEST_RE = /['"]/; -var QUOTE_RE = /['"]/g; -var APOSTROPHE = '\u2019'; /* ’ */ - - -function replaceAt(str, index, ch) { - return str.substr(0, index) + ch + str.substr(index + 1); -} - -function process_inlines(tokens, state) { - var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, - isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, - canOpen, canClose, j, isSingle, stack, openQuote, closeQuote; - - stack = []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - - thisLevel = tokens[i].level; - - for (j = stack.length - 1; j >= 0; j--) { - if (stack[j].level <= thisLevel) { break; } - } - stack.length = j + 1; - - if (token.type !== 'text') { continue; } - - text = token.content; - pos = 0; - max = text.length; - - /*eslint no-labels:0,block-scoped-var:0*/ - OUTER: - while (pos < max) { - QUOTE_RE.lastIndex = pos; - t = QUOTE_RE.exec(text); - if (!t) { break; } - - canOpen = canClose = true; - pos = t.index + 1; - isSingle = (t[0] === "'"); - - // Find previous character, - // default to space if it's the beginning of the line - // - lastChar = 0x20; - - if (t.index - 1 >= 0) { - lastChar = text.charCodeAt(t.index - 1); - } else { - for (j = i - 1; j >= 0; j--) { - if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // lastChar defaults to 0x20 - if (!tokens[j].content) continue; // should skip all tokens except 'text', 'html_inline' or 'code_inline' - - lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); - break; - } - } - - // Find next character, - // default to space if it's the end of the line - // - nextChar = 0x20; - - if (pos < max) { - nextChar = text.charCodeAt(pos); - } else { - for (j = i + 1; j < tokens.length; j++) { - if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // nextChar defaults to 0x20 - if (!tokens[j].content) continue; // should skip all tokens except 'text', 'html_inline' or 'code_inline' - - nextChar = tokens[j].content.charCodeAt(0); - break; - } - } - - isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); - isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); - - isLastWhiteSpace = isWhiteSpace(lastChar); - isNextWhiteSpace = isWhiteSpace(nextChar); - - if (isNextWhiteSpace) { - canOpen = false; - } else if (isNextPunctChar) { - if (!(isLastWhiteSpace || isLastPunctChar)) { - canOpen = false; - } - } - - if (isLastWhiteSpace) { - canClose = false; - } else if (isLastPunctChar) { - if (!(isNextWhiteSpace || isNextPunctChar)) { - canClose = false; - } - } - - if (nextChar === 0x22 /* " */ && t[0] === '"') { - if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { - // special case: 1"" - count first quote as an inch - canClose = canOpen = false; - } - } - - if (canOpen && canClose) { - // Replace quotes in the middle of punctuation sequence, but not - // in the middle of the words, i.e.: - // - // 1. foo " bar " baz - not replaced - // 2. foo-"-bar-"-baz - replaced - // 3. foo"bar"baz - not replaced - // - canOpen = isLastPunctChar; - canClose = isNextPunctChar; - } - - if (!canOpen && !canClose) { - // middle of word - if (isSingle) { - token.content = replaceAt(token.content, t.index, APOSTROPHE); - } - continue; - } - - if (canClose) { - // this could be a closing quote, rewind the stack to get a match - for (j = stack.length - 1; j >= 0; j--) { - item = stack[j]; - if (stack[j].level < thisLevel) { break; } - if (item.single === isSingle && stack[j].level === thisLevel) { - item = stack[j]; - - if (isSingle) { - openQuote = state.md.options.quotes[2]; - closeQuote = state.md.options.quotes[3]; - } else { - openQuote = state.md.options.quotes[0]; - closeQuote = state.md.options.quotes[1]; - } - - // replace token.content *before* tokens[item.token].content, - // because, if they are pointing at the same token, replaceAt - // could mess up indices when quote length != 1 - token.content = replaceAt(token.content, t.index, closeQuote); - tokens[item.token].content = replaceAt( - tokens[item.token].content, item.pos, openQuote); - - pos += closeQuote.length - 1; - if (item.token === i) { pos += openQuote.length - 1; } - - text = token.content; - max = text.length; - - stack.length = j; - continue OUTER; - } - } - } - - if (canOpen) { - stack.push({ - token: i, - pos: t.index, - single: isSingle, - level: thisLevel - }); - } else if (canClose && isSingle) { - token.content = replaceAt(token.content, t.index, APOSTROPHE); - } - } - } -} - - -module.exports = function smartquotes(state) { - /*eslint max-depth:0*/ - var blkIdx; - - if (!state.md.options.typographer) { return; } - - for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { - - if (state.tokens[blkIdx].type !== 'inline' || - !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { - continue; - } - - process_inlines(state.tokens[blkIdx].children, state); - } -}; - -},{"../common/utils":4}],36:[function(require,module,exports){ -// Core state object -// -'use strict'; - -var Token = require('../token'); - - -function StateCore(src, md, env) { - this.src = src; - this.env = env; - this.tokens = []; - this.inlineMode = false; - this.md = md; // link to parser instance -} - -// re-export Token class to use in core rules -StateCore.prototype.Token = Token; - - -module.exports = StateCore; - -},{"../token":51}],37:[function(require,module,exports){ -// Process autolinks '' - -'use strict'; - - -/*eslint max-len:0*/ -var EMAIL_RE = /^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/; -var AUTOLINK_RE = /^<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)>/; - - -module.exports = function autolink(state, silent) { - var tail, linkMatch, emailMatch, url, fullUrl, token, - pos = state.pos; - - if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } - - tail = state.src.slice(pos); - - if (tail.indexOf('>') < 0) { return false; } - - if (AUTOLINK_RE.test(tail)) { - linkMatch = tail.match(AUTOLINK_RE); - - url = linkMatch[0].slice(1, -1); - fullUrl = state.md.normalizeLink(url); - if (!state.md.validateLink(fullUrl)) { return false; } - - if (!silent) { - token = state.push('link_open', 'a', 1); - token.attrs = [ [ 'href', fullUrl ] ]; - token.markup = 'autolink'; - token.info = 'auto'; - - token = state.push('text', '', 0); - token.content = state.md.normalizeLinkText(url); - - token = state.push('link_close', 'a', -1); - token.markup = 'autolink'; - token.info = 'auto'; - } - - state.pos += linkMatch[0].length; - return true; - } - - if (EMAIL_RE.test(tail)) { - emailMatch = tail.match(EMAIL_RE); - - url = emailMatch[0].slice(1, -1); - fullUrl = state.md.normalizeLink('mailto:' + url); - if (!state.md.validateLink(fullUrl)) { return false; } - - if (!silent) { - token = state.push('link_open', 'a', 1); - token.attrs = [ [ 'href', fullUrl ] ]; - token.markup = 'autolink'; - token.info = 'auto'; - - token = state.push('text', '', 0); - token.content = state.md.normalizeLinkText(url); - - token = state.push('link_close', 'a', -1); - token.markup = 'autolink'; - token.info = 'auto'; - } - - state.pos += emailMatch[0].length; - return true; - } - - return false; -}; - -},{}],38:[function(require,module,exports){ -// Parse backticks - -'use strict'; - -module.exports = function backtick(state, silent) { - var start, max, marker, matchStart, matchEnd, token, - pos = state.pos, - ch = state.src.charCodeAt(pos); - - if (ch !== 0x60/* ` */) { return false; } - - start = pos; - pos++; - max = state.posMax; - - while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } - - marker = state.src.slice(start, pos); - - matchStart = matchEnd = pos; - - while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { - matchEnd = matchStart + 1; - - while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } - - if (matchEnd - matchStart === marker.length) { - if (!silent) { - token = state.push('code_inline', 'code', 0); - token.markup = marker; - token.content = state.src.slice(pos, matchStart) - .replace(/\n/g, ' ') - .replace(/^ (.+) $/, '$1'); - } - state.pos = matchEnd; - return true; - } - } - - if (!silent) { state.pending += marker; } - state.pos += marker.length; - return true; -}; - -},{}],39:[function(require,module,exports){ -// For each opening emphasis-like marker find a matching closing one -// -'use strict'; - - -function processDelimiters(state, delimiters) { - var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx, - isOddMatch, lastJump, - openersBottom = {}, - max = delimiters.length; - - for (closerIdx = 0; closerIdx < max; closerIdx++) { - closer = delimiters[closerIdx]; - - // Length is only used for emphasis-specific "rule of 3", - // if it's not defined (in strikethrough or 3rd party plugins), - // we can default it to 0 to disable those checks. - // - closer.length = closer.length || 0; - - if (!closer.close) continue; - - // Previously calculated lower bounds (previous fails) - // for each marker and each delimiter length modulo 3. - if (!openersBottom.hasOwnProperty(closer.marker)) { - openersBottom[closer.marker] = [ -1, -1, -1 ]; - } - - minOpenerIdx = openersBottom[closer.marker][closer.length % 3]; - newMinOpenerIdx = -1; - - openerIdx = closerIdx - closer.jump - 1; - - for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) { - opener = delimiters[openerIdx]; - - if (opener.marker !== closer.marker) continue; - - if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx; - - if (opener.open && opener.end < 0) { - - isOddMatch = false; - - // from spec: - // - // If one of the delimiters can both open and close emphasis, then the - // sum of the lengths of the delimiter runs containing the opening and - // closing delimiters must not be a multiple of 3 unless both lengths - // are multiples of 3. - // - if (opener.close || closer.open) { - if ((opener.length + closer.length) % 3 === 0) { - if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { - isOddMatch = true; - } - } - } - - if (!isOddMatch) { - // If previous delimiter cannot be an opener, we can safely skip - // the entire sequence in future checks. This is required to make - // sure algorithm has linear complexity (see *_*_*_*_*_... case). - // - lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? - delimiters[openerIdx - 1].jump + 1 : - 0; - - closer.jump = closerIdx - openerIdx + lastJump; - closer.open = false; - opener.end = closerIdx; - opener.jump = lastJump; - opener.close = false; - newMinOpenerIdx = -1; - break; - } - } - } - - if (newMinOpenerIdx !== -1) { - // If match for this delimiter run failed, we want to set lower bound for - // future lookups. This is required to make sure algorithm has linear - // complexity. - // - // See details here: - // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 - // - openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx; - } - } -} - - -module.exports = function link_pairs(state) { - var curr, - tokens_meta = state.tokens_meta, - max = state.tokens_meta.length; - - processDelimiters(state, state.delimiters); - - for (curr = 0; curr < max; curr++) { - if (tokens_meta[curr] && tokens_meta[curr].delimiters) { - processDelimiters(state, tokens_meta[curr].delimiters); - } - } -}; - -},{}],40:[function(require,module,exports){ -// Process *this* and _that_ -// -'use strict'; - - -// Insert each marker as a separate text token, and add it to delimiter list -// -module.exports.tokenize = function emphasis(state, silent) { - var i, scanned, token, - start = state.pos, - marker = state.src.charCodeAt(start); - - if (silent) { return false; } - - if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false; } - - scanned = state.scanDelims(state.pos, marker === 0x2A); - - for (i = 0; i < scanned.length; i++) { - token = state.push('text', '', 0); - token.content = String.fromCharCode(marker); - - state.delimiters.push({ - // Char code of the starting marker (number). - // - marker: marker, - - // Total length of these series of delimiters. - // - length: scanned.length, - - // An amount of characters before this one that's equivalent to - // current one. In plain English: if this delimiter does not open - // an emphasis, neither do previous `jump` characters. - // - // Used to skip sequences like "*****" in one step, for 1st asterisk - // value will be 0, for 2nd it's 1 and so on. - // - jump: i, - - // A position of the token this delimiter corresponds to. - // - token: state.tokens.length - 1, - - // If this delimiter is matched as a valid opener, `end` will be - // equal to its position, otherwise it's `-1`. - // - end: -1, - - // Boolean flags that determine if this delimiter could open or close - // an emphasis. - // - open: scanned.can_open, - close: scanned.can_close - }); - } - - state.pos += scanned.length; - - return true; -}; - - -function postProcess(state, delimiters) { - var i, - startDelim, - endDelim, - token, - ch, - isStrong, - max = delimiters.length; - - for (i = max - 1; i >= 0; i--) { - startDelim = delimiters[i]; - - if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) { - continue; - } - - // Process only opening markers - if (startDelim.end === -1) { - continue; - } - - endDelim = delimiters[startDelim.end]; - - // If the previous delimiter has the same marker and is adjacent to this one, - // merge those into one strong delimiter. - // - // `whatever` -> `whatever` - // - isStrong = i > 0 && - delimiters[i - 1].end === startDelim.end + 1 && - delimiters[i - 1].token === startDelim.token - 1 && - delimiters[startDelim.end + 1].token === endDelim.token + 1 && - delimiters[i - 1].marker === startDelim.marker; - - ch = String.fromCharCode(startDelim.marker); - - token = state.tokens[startDelim.token]; - token.type = isStrong ? 'strong_open' : 'em_open'; - token.tag = isStrong ? 'strong' : 'em'; - token.nesting = 1; - token.markup = isStrong ? ch + ch : ch; - token.content = ''; - - token = state.tokens[endDelim.token]; - token.type = isStrong ? 'strong_close' : 'em_close'; - token.tag = isStrong ? 'strong' : 'em'; - token.nesting = -1; - token.markup = isStrong ? ch + ch : ch; - token.content = ''; - - if (isStrong) { - state.tokens[delimiters[i - 1].token].content = ''; - state.tokens[delimiters[startDelim.end + 1].token].content = ''; - i--; - } - } -} - - -// Walk through delimiter list and replace text tokens with tags -// -module.exports.postProcess = function emphasis(state) { - var curr, - tokens_meta = state.tokens_meta, - max = state.tokens_meta.length; - - postProcess(state, state.delimiters); - - for (curr = 0; curr < max; curr++) { - if (tokens_meta[curr] && tokens_meta[curr].delimiters) { - postProcess(state, tokens_meta[curr].delimiters); - } - } -}; - -},{}],41:[function(require,module,exports){ -// Process html entity - {, ¯, ", ... - -'use strict'; - -var entities = require('../common/entities'); -var has = require('../common/utils').has; -var isValidEntityCode = require('../common/utils').isValidEntityCode; -var fromCodePoint = require('../common/utils').fromCodePoint; - - -var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i; -var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; - - -module.exports = function entity(state, silent) { - var ch, code, match, pos = state.pos, max = state.posMax; - - if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; } - - if (pos + 1 < max) { - ch = state.src.charCodeAt(pos + 1); - - if (ch === 0x23 /* # */) { - match = state.src.slice(pos).match(DIGITAL_RE); - if (match) { - if (!silent) { - code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); - state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD); - } - state.pos += match[0].length; - return true; - } - } else { - match = state.src.slice(pos).match(NAMED_RE); - if (match) { - if (has(entities, match[1])) { - if (!silent) { state.pending += entities[match[1]]; } - state.pos += match[0].length; - return true; - } - } - } - } - - if (!silent) { state.pending += '&'; } - state.pos++; - return true; -}; - -},{"../common/entities":1,"../common/utils":4}],42:[function(require,module,exports){ -// Process escaped chars and hardbreaks - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - -var ESCAPED = []; - -for (var i = 0; i < 256; i++) { ESCAPED.push(0); } - -'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-' - .split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1; }); - - -module.exports = function escape(state, silent) { - var ch, pos = state.pos, max = state.posMax; - - if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } - - pos++; - - if (pos < max) { - ch = state.src.charCodeAt(pos); - - if (ch < 256 && ESCAPED[ch] !== 0) { - if (!silent) { state.pending += state.src[pos]; } - state.pos += 2; - return true; - } - - if (ch === 0x0A) { - if (!silent) { - state.push('hardbreak', 'br', 0); - } - - pos++; - // skip leading whitespaces from next line - while (pos < max) { - ch = state.src.charCodeAt(pos); - if (!isSpace(ch)) { break; } - pos++; - } - - state.pos = pos; - return true; - } - } - - if (!silent) { state.pending += '\\'; } - state.pos++; - return true; -}; - -},{"../common/utils":4}],43:[function(require,module,exports){ -// Process html tags - -'use strict'; - - -var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE; - - -function isLetter(ch) { - /*eslint no-bitwise:0*/ - var lc = ch | 0x20; // to lower case - return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); -} - - -module.exports = function html_inline(state, silent) { - var ch, match, max, token, - pos = state.pos; - - if (!state.md.options.html) { return false; } - - // Check start - max = state.posMax; - if (state.src.charCodeAt(pos) !== 0x3C/* < */ || - pos + 2 >= max) { - return false; - } - - // Quick fail on second char - ch = state.src.charCodeAt(pos + 1); - if (ch !== 0x21/* ! */ && - ch !== 0x3F/* ? */ && - ch !== 0x2F/* / */ && - !isLetter(ch)) { - return false; - } - - match = state.src.slice(pos).match(HTML_TAG_RE); - if (!match) { return false; } - - if (!silent) { - token = state.push('html_inline', '', 0); - token.content = state.src.slice(pos, pos + match[0].length); - } - state.pos += match[0].length; - return true; -}; - -},{"../common/html_re":3}],44:[function(require,module,exports){ -// Process ![image]( "title") - -'use strict'; - -var normalizeReference = require('../common/utils').normalizeReference; -var isSpace = require('../common/utils').isSpace; - - -module.exports = function image(state, silent) { - var attrs, - code, - content, - label, - labelEnd, - labelStart, - pos, - ref, - res, - title, - token, - tokens, - start, - href = '', - oldPos = state.pos, - max = state.posMax; - - if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } - if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } - - labelStart = state.pos + 2; - labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false); - - // parser failed to find ']', so it's not a valid link - if (labelEnd < 0) { return false; } - - pos = labelEnd + 1; - if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { - // - // Inline link - // - - // [link]( "title" ) - // ^^ skipping these spaces - pos++; - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - if (pos >= max) { return false; } - - // [link]( "title" ) - // ^^^^^^ parsing link destination - start = pos; - res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); - if (res.ok) { - href = state.md.normalizeLink(res.str); - if (state.md.validateLink(href)) { - pos = res.pos; - } else { - href = ''; - } - } - - // [link]( "title" ) - // ^^ skipping these spaces - start = pos; - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - - // [link]( "title" ) - // ^^^^^^^ parsing link title - res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); - if (pos < max && start !== pos && res.ok) { - title = res.str; - pos = res.pos; - - // [link]( "title" ) - // ^^ skipping these spaces - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - } else { - title = ''; - } - - if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { - state.pos = oldPos; - return false; - } - pos++; - } else { - // - // Link reference - // - if (typeof state.env.references === 'undefined') { return false; } - - if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { - start = pos + 1; - pos = state.md.helpers.parseLinkLabel(state, pos); - if (pos >= 0) { - label = state.src.slice(start, pos++); - } else { - pos = labelEnd + 1; - } - } else { - pos = labelEnd + 1; - } - - // covers label === '' and label === undefined - // (collapsed reference link and shortcut reference link respectively) - if (!label) { label = state.src.slice(labelStart, labelEnd); } - - ref = state.env.references[normalizeReference(label)]; - if (!ref) { - state.pos = oldPos; - return false; - } - href = ref.href; - title = ref.title; - } - - // - // We found the end of the link, and know for a fact it's a valid link; - // so all that's left to do is to call tokenizer. - // - if (!silent) { - content = state.src.slice(labelStart, labelEnd); - - state.md.inline.parse( - content, - state.md, - state.env, - tokens = [] - ); - - token = state.push('image', 'img', 0); - token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ]; - token.children = tokens; - token.content = content; - - if (title) { - attrs.push([ 'title', title ]); - } - } - - state.pos = pos; - state.posMax = max; - return true; -}; - -},{"../common/utils":4}],45:[function(require,module,exports){ -// Process [link]( "stuff") - -'use strict'; - -var normalizeReference = require('../common/utils').normalizeReference; -var isSpace = require('../common/utils').isSpace; - - -module.exports = function link(state, silent) { - var attrs, - code, - label, - labelEnd, - labelStart, - pos, - res, - ref, - title, - token, - href = '', - oldPos = state.pos, - max = state.posMax, - start = state.pos, - parseReference = true; - - if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; } - - labelStart = state.pos + 1; - labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true); - - // parser failed to find ']', so it's not a valid link - if (labelEnd < 0) { return false; } - - pos = labelEnd + 1; - if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { - // - // Inline link - // - - // might have found a valid shortcut link, disable reference parsing - parseReference = false; - - // [link]( "title" ) - // ^^ skipping these spaces - pos++; - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - if (pos >= max) { return false; } - - // [link]( "title" ) - // ^^^^^^ parsing link destination - start = pos; - res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); - if (res.ok) { - href = state.md.normalizeLink(res.str); - if (state.md.validateLink(href)) { - pos = res.pos; - } else { - href = ''; - } - } - - // [link]( "title" ) - // ^^ skipping these spaces - start = pos; - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - - // [link]( "title" ) - // ^^^^^^^ parsing link title - res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); - if (pos < max && start !== pos && res.ok) { - title = res.str; - pos = res.pos; - - // [link]( "title" ) - // ^^ skipping these spaces - for (; pos < max; pos++) { - code = state.src.charCodeAt(pos); - if (!isSpace(code) && code !== 0x0A) { break; } - } - } else { - title = ''; - } - - if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { - // parsing a valid shortcut link failed, fallback to reference - parseReference = true; - } - pos++; - } - - if (parseReference) { - // - // Link reference - // - if (typeof state.env.references === 'undefined') { return false; } - - if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { - start = pos + 1; - pos = state.md.helpers.parseLinkLabel(state, pos); - if (pos >= 0) { - label = state.src.slice(start, pos++); - } else { - pos = labelEnd + 1; - } - } else { - pos = labelEnd + 1; - } - - // covers label === '' and label === undefined - // (collapsed reference link and shortcut reference link respectively) - if (!label) { label = state.src.slice(labelStart, labelEnd); } - - ref = state.env.references[normalizeReference(label)]; - if (!ref) { - state.pos = oldPos; - return false; - } - href = ref.href; - title = ref.title; - } - - // - // We found the end of the link, and know for a fact it's a valid link; - // so all that's left to do is to call tokenizer. - // - if (!silent) { - state.pos = labelStart; - state.posMax = labelEnd; - - token = state.push('link_open', 'a', 1); - token.attrs = attrs = [ [ 'href', href ] ]; - if (title) { - attrs.push([ 'title', title ]); - } - - state.md.inline.tokenize(state); - - token = state.push('link_close', 'a', -1); - } - - state.pos = pos; - state.posMax = max; - return true; -}; - -},{"../common/utils":4}],46:[function(require,module,exports){ -// Proceess '\n' - -'use strict'; - -var isSpace = require('../common/utils').isSpace; - - -module.exports = function newline(state, silent) { - var pmax, max, pos = state.pos; - - if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; } - - pmax = state.pending.length - 1; - max = state.posMax; - - // ' \n' -> hardbreak - // Lookup in pending chars is bad practice! Don't copy to other rules! - // Pending string is stored in concat mode, indexed lookups will cause - // convertion to flat mode. - if (!silent) { - if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { - if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { - state.pending = state.pending.replace(/ +$/, ''); - state.push('hardbreak', 'br', 0); - } else { - state.pending = state.pending.slice(0, -1); - state.push('softbreak', 'br', 0); - } - - } else { - state.push('softbreak', 'br', 0); - } - } - - pos++; - - // skip heading spaces for next line - while (pos < max && isSpace(state.src.charCodeAt(pos))) { pos++; } - - state.pos = pos; - return true; -}; - -},{"../common/utils":4}],47:[function(require,module,exports){ -// Inline parser state - -'use strict'; - - -var Token = require('../token'); -var isWhiteSpace = require('../common/utils').isWhiteSpace; -var isPunctChar = require('../common/utils').isPunctChar; -var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; - - -function StateInline(src, md, env, outTokens) { - this.src = src; - this.env = env; - this.md = md; - this.tokens = outTokens; - this.tokens_meta = Array(outTokens.length); - - this.pos = 0; - this.posMax = this.src.length; - this.level = 0; - this.pending = ''; - this.pendingLevel = 0; - - // Stores { start: end } pairs. Useful for backtrack - // optimization of pairs parse (emphasis, strikes). - this.cache = {}; - - // List of emphasis-like delimiters for current tag - this.delimiters = []; - - // Stack of delimiter lists for upper level tags - this._prev_delimiters = []; -} - - -// Flush pending text -// -StateInline.prototype.pushPending = function () { - var token = new Token('text', '', 0); - token.content = this.pending; - token.level = this.pendingLevel; - this.tokens.push(token); - this.pending = ''; - return token; -}; - - -// Push new token to "stream". -// If pending text exists - flush it as text token -// -StateInline.prototype.push = function (type, tag, nesting) { - if (this.pending) { - this.pushPending(); - } - - var token = new Token(type, tag, nesting); - var token_meta = null; - - if (nesting < 0) { - // closing tag - this.level--; - this.delimiters = this._prev_delimiters.pop(); - } - - token.level = this.level; - - if (nesting > 0) { - // opening tag - this.level++; - this._prev_delimiters.push(this.delimiters); - this.delimiters = []; - token_meta = { delimiters: this.delimiters }; - } - - this.pendingLevel = this.level; - this.tokens.push(token); - this.tokens_meta.push(token_meta); - return token; -}; - - -// Scan a sequence of emphasis-like markers, and determine whether -// it can start an emphasis sequence or end an emphasis sequence. -// -// - start - position to scan from (it should point at a valid marker); -// - canSplitWord - determine if these markers can be found inside a word -// -StateInline.prototype.scanDelims = function (start, canSplitWord) { - var pos = start, lastChar, nextChar, count, can_open, can_close, - isLastWhiteSpace, isLastPunctChar, - isNextWhiteSpace, isNextPunctChar, - left_flanking = true, - right_flanking = true, - max = this.posMax, - marker = this.src.charCodeAt(start); - - // treat beginning of the line as a whitespace - lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20; - - while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; } - - count = pos - start; - - // treat end of the line as a whitespace - nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20; - - isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); - isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); - - isLastWhiteSpace = isWhiteSpace(lastChar); - isNextWhiteSpace = isWhiteSpace(nextChar); - - if (isNextWhiteSpace) { - left_flanking = false; - } else if (isNextPunctChar) { - if (!(isLastWhiteSpace || isLastPunctChar)) { - left_flanking = false; - } - } - - if (isLastWhiteSpace) { - right_flanking = false; - } else if (isLastPunctChar) { - if (!(isNextWhiteSpace || isNextPunctChar)) { - right_flanking = false; - } - } - - if (!canSplitWord) { - can_open = left_flanking && (!right_flanking || isLastPunctChar); - can_close = right_flanking && (!left_flanking || isNextPunctChar); - } else { - can_open = left_flanking; - can_close = right_flanking; - } - - return { - can_open: can_open, - can_close: can_close, - length: count - }; -}; - - -// re-export Token class to use in block rules -StateInline.prototype.Token = Token; - - -module.exports = StateInline; - -},{"../common/utils":4,"../token":51}],48:[function(require,module,exports){ -// ~~strike through~~ -// -'use strict'; - - -// Insert each marker as a separate text token, and add it to delimiter list -// -module.exports.tokenize = function strikethrough(state, silent) { - var i, scanned, token, len, ch, - start = state.pos, - marker = state.src.charCodeAt(start); - - if (silent) { return false; } - - if (marker !== 0x7E/* ~ */) { return false; } - - scanned = state.scanDelims(state.pos, true); - len = scanned.length; - ch = String.fromCharCode(marker); - - if (len < 2) { return false; } - - if (len % 2) { - token = state.push('text', '', 0); - token.content = ch; - len--; - } - - for (i = 0; i < len; i += 2) { - token = state.push('text', '', 0); - token.content = ch + ch; - - state.delimiters.push({ - marker: marker, - length: 0, // disable "rule of 3" length checks meant for emphasis - jump: i, - token: state.tokens.length - 1, - end: -1, - open: scanned.can_open, - close: scanned.can_close - }); - } - - state.pos += scanned.length; - - return true; -}; - - -function postProcess(state, delimiters) { - var i, j, - startDelim, - endDelim, - token, - loneMarkers = [], - max = delimiters.length; - - for (i = 0; i < max; i++) { - startDelim = delimiters[i]; - - if (startDelim.marker !== 0x7E/* ~ */) { - continue; - } - - if (startDelim.end === -1) { - continue; - } - - endDelim = delimiters[startDelim.end]; - - token = state.tokens[startDelim.token]; - token.type = 's_open'; - token.tag = 's'; - token.nesting = 1; - token.markup = '~~'; - token.content = ''; - - token = state.tokens[endDelim.token]; - token.type = 's_close'; - token.tag = 's'; - token.nesting = -1; - token.markup = '~~'; - token.content = ''; - - if (state.tokens[endDelim.token - 1].type === 'text' && - state.tokens[endDelim.token - 1].content === '~') { - - loneMarkers.push(endDelim.token - 1); - } - } - - // If a marker sequence has an odd number of characters, it's splitted - // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the - // start of the sequence. - // - // So, we have to move all those markers after subsequent s_close tags. - // - while (loneMarkers.length) { - i = loneMarkers.pop(); - j = i + 1; - - while (j < state.tokens.length && state.tokens[j].type === 's_close') { - j++; - } - - j--; - - if (i !== j) { - token = state.tokens[j]; - state.tokens[j] = state.tokens[i]; - state.tokens[i] = token; - } - } -} - - -// Walk through delimiter list and replace text tokens with tags -// -module.exports.postProcess = function strikethrough(state) { - var curr, - tokens_meta = state.tokens_meta, - max = state.tokens_meta.length; - - postProcess(state, state.delimiters); - - for (curr = 0; curr < max; curr++) { - if (tokens_meta[curr] && tokens_meta[curr].delimiters) { - postProcess(state, tokens_meta[curr].delimiters); - } - } -}; - -},{}],49:[function(require,module,exports){ -// Skip text characters for text token, place those to pending buffer -// and increment current pos - -'use strict'; - - -// Rule to skip pure text -// '{}$%@~+=:' reserved for extentions - -// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ - -// !!!! Don't confuse with "Markdown ASCII Punctuation" chars -// http://spec.commonmark.org/0.15/#ascii-punctuation-character -function isTerminatorChar(ch) { - switch (ch) { - case 0x0A/* \n */: - case 0x21/* ! */: - case 0x23/* # */: - case 0x24/* $ */: - case 0x25/* % */: - case 0x26/* & */: - case 0x2A/* * */: - case 0x2B/* + */: - case 0x2D/* - */: - case 0x3A/* : */: - case 0x3C/* < */: - case 0x3D/* = */: - case 0x3E/* > */: - case 0x40/* @ */: - case 0x5B/* [ */: - case 0x5C/* \ */: - case 0x5D/* ] */: - case 0x5E/* ^ */: - case 0x5F/* _ */: - case 0x60/* ` */: - case 0x7B/* { */: - case 0x7D/* } */: - case 0x7E/* ~ */: - return true; - default: - return false; - } -} - -module.exports = function text(state, silent) { - var pos = state.pos; - - while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { - pos++; - } - - if (pos === state.pos) { return false; } - - if (!silent) { state.pending += state.src.slice(state.pos, pos); } - - state.pos = pos; - - return true; -}; - -// Alternative implementation, for memory. -// -// It costs 10% of performance, but allows extend terminators list, if place it -// to `ParcerInline` property. Probably, will switch to it sometime, such -// flexibility required. - -/* -var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/; - -module.exports = function text(state, silent) { - var pos = state.pos, - idx = state.src.slice(pos).search(TERMINATOR_RE); - - // first char is terminator -> empty text - if (idx === 0) { return false; } - - // no terminator -> text till end of string - if (idx < 0) { - if (!silent) { state.pending += state.src.slice(pos); } - state.pos = state.src.length; - return true; - } - - if (!silent) { state.pending += state.src.slice(pos, pos + idx); } - - state.pos += idx; - - return true; -};*/ - -},{}],50:[function(require,module,exports){ -// Clean up tokens after emphasis and strikethrough postprocessing: -// merge adjacent text nodes into one and re-calculate all token levels -// -// This is necessary because initially emphasis delimiter markers (*, _, ~) -// are treated as their own separate text tokens. Then emphasis rule either -// leaves them as text (needed to merge with adjacent text) or turns them -// into opening/closing tags (which messes up levels inside). -// -'use strict'; - - -module.exports = function text_collapse(state) { - var curr, last, - level = 0, - tokens = state.tokens, - max = state.tokens.length; - - for (curr = last = 0; curr < max; curr++) { - // re-calculate levels after emphasis/strikethrough turns some text nodes - // into opening/closing tags - if (tokens[curr].nesting < 0) level--; // closing tag - tokens[curr].level = level; - if (tokens[curr].nesting > 0) level++; // opening tag - - if (tokens[curr].type === 'text' && - curr + 1 < max && - tokens[curr + 1].type === 'text') { - - // collapse two adjacent text nodes - tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; - } else { - if (curr !== last) { tokens[last] = tokens[curr]; } - - last++; - } - } - - if (curr !== last) { - tokens.length = last; - } -}; - -},{}],51:[function(require,module,exports){ -// Token class - -'use strict'; - - -/** - * class Token - **/ - -/** - * new Token(type, tag, nesting) - * - * Create new token and fill passed properties. - **/ -function Token(type, tag, nesting) { - /** - * Token#type -> String - * - * Type of the token (string, e.g. "paragraph_open") - **/ - this.type = type; - - /** - * Token#tag -> String - * - * html tag name, e.g. "p" - **/ - this.tag = tag; - - /** - * Token#attrs -> Array - * - * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` - **/ - this.attrs = null; - - /** - * Token#map -> Array - * - * Source map info. Format: `[ line_begin, line_end ]` - **/ - this.map = null; - - /** - * Token#nesting -> Number - * - * Level change (number in {-1, 0, 1} set), where: - * - * - `1` means the tag is opening - * - `0` means the tag is self-closing - * - `-1` means the tag is closing - **/ - this.nesting = nesting; - - /** - * Token#level -> Number - * - * nesting level, the same as `state.level` - **/ - this.level = 0; - - /** - * Token#children -> Array - * - * An array of child nodes (inline and img tokens) - **/ - this.children = null; - - /** - * Token#content -> String - * - * In a case of self-closing tag (code, html, fence, etc.), - * it has contents of this tag. - **/ - this.content = ''; - - /** - * Token#markup -> String - * - * '*' or '_' for emphasis, fence string for fence, etc. - **/ - this.markup = ''; - - /** - * Token#info -> String - * - * fence infostring - **/ - this.info = ''; - - /** - * Token#meta -> Object - * - * A place for plugins to store an arbitrary data - **/ - this.meta = null; - - /** - * Token#block -> Boolean - * - * True for block-level tokens, false for inline tokens. - * Used in renderer to calculate line breaks - **/ - this.block = false; - - /** - * Token#hidden -> Boolean - * - * If it's true, ignore this element when rendering. Used for tight lists - * to hide paragraphs. - **/ - this.hidden = false; -} - - -/** - * Token.attrIndex(name) -> Number - * - * Search attribute index by name. - **/ -Token.prototype.attrIndex = function attrIndex(name) { - var attrs, i, len; - - if (!this.attrs) { return -1; } - - attrs = this.attrs; - - for (i = 0, len = attrs.length; i < len; i++) { - if (attrs[i][0] === name) { return i; } - } - return -1; -}; - - -/** - * Token.attrPush(attrData) - * - * Add `[ name, value ]` attribute to list. Init attrs if necessary - **/ -Token.prototype.attrPush = function attrPush(attrData) { - if (this.attrs) { - this.attrs.push(attrData); - } else { - this.attrs = [ attrData ]; - } -}; - - -/** - * Token.attrSet(name, value) - * - * Set `name` attribute to `value`. Override old value if exists. - **/ -Token.prototype.attrSet = function attrSet(name, value) { - var idx = this.attrIndex(name), - attrData = [ name, value ]; - - if (idx < 0) { - this.attrPush(attrData); - } else { - this.attrs[idx] = attrData; - } -}; - - -/** - * Token.attrGet(name) - * - * Get the value of attribute `name`, or null if it does not exist. - **/ -Token.prototype.attrGet = function attrGet(name) { - var idx = this.attrIndex(name), value = null; - if (idx >= 0) { - value = this.attrs[idx][1]; - } - return value; -}; - - -/** - * Token.attrJoin(name, value) - * - * Join value to existing attribute via space. Or create new attribute if not - * exists. Useful to operate with token classes. - **/ -Token.prototype.attrJoin = function attrJoin(name, value) { - var idx = this.attrIndex(name); - - if (idx < 0) { - this.attrPush([ name, value ]); - } else { - this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value; - } -}; - - -module.exports = Token; - -},{}],52:[function(require,module,exports){ -module.exports={ "Aacute": "\u00C1", "aacute": "\u00E1", "Abreve": "\u0102", "abreve": "\u0103", "ac": "\u223E", "acd": "\u223F", "acE": "\u223E\u0333", "Acirc": "\u00C2", "acirc": "\u00E2", "acute": "\u00B4", "Acy": "\u0410", "acy": "\u0430", "AElig": "\u00C6", "aelig": "\u00E6", "af": "\u2061", "Afr": "\uD835\uDD04", "afr": "\uD835\uDD1E", "Agrave": "\u00C0", "agrave": "\u00E0", "alefsym": "\u2135", "aleph": "\u2135", "Alpha": "\u0391", "alpha": "\u03B1", "Amacr": "\u0100", "amacr": "\u0101", "amalg": "\u2A3F", "amp": "&", "AMP": "&", "andand": "\u2A55", "And": "\u2A53", "and": "\u2227", "andd": "\u2A5C", "andslope": "\u2A58", "andv": "\u2A5A", "ang": "\u2220", "ange": "\u29A4", "angle": "\u2220", "angmsdaa": "\u29A8", "angmsdab": "\u29A9", "angmsdac": "\u29AA", "angmsdad": "\u29AB", "angmsdae": "\u29AC", "angmsdaf": "\u29AD", "angmsdag": "\u29AE", "angmsdah": "\u29AF", "angmsd": "\u2221", "angrt": "\u221F", "angrtvb": "\u22BE", "angrtvbd": "\u299D", "angsph": "\u2222", "angst": "\u00C5", "angzarr": "\u237C", "Aogon": "\u0104", "aogon": "\u0105", "Aopf": "\uD835\uDD38", "aopf": "\uD835\uDD52", "apacir": "\u2A6F", "ap": "\u2248", "apE": "\u2A70", "ape": "\u224A", "apid": "\u224B", "apos": "'", "ApplyFunction": "\u2061", "approx": "\u2248", "approxeq": "\u224A", "Aring": "\u00C5", "aring": "\u00E5", "Ascr": "\uD835\uDC9C", "ascr": "\uD835\uDCB6", "Assign": "\u2254", "ast": "*", "asymp": "\u2248", "asympeq": "\u224D", "Atilde": "\u00C3", "atilde": "\u00E3", "Auml": "\u00C4", "auml": "\u00E4", "awconint": "\u2233", "awint": "\u2A11", "backcong": "\u224C", "backepsilon": "\u03F6", "backprime": "\u2035", "backsim": "\u223D", "backsimeq": "\u22CD", "Backslash": "\u2216", "Barv": "\u2AE7", "barvee": "\u22BD", "barwed": "\u2305", "Barwed": "\u2306", "barwedge": "\u2305", "bbrk": "\u23B5", "bbrktbrk": "\u23B6", "bcong": "\u224C", "Bcy": "\u0411", "bcy": "\u0431", "bdquo": "\u201E", "becaus": "\u2235", "because": "\u2235", "Because": "\u2235", "bemptyv": "\u29B0", "bepsi": "\u03F6", "bernou": "\u212C", "Bernoullis": "\u212C", "Beta": "\u0392", "beta": "\u03B2", "beth": "\u2136", "between": "\u226C", "Bfr": "\uD835\uDD05", "bfr": "\uD835\uDD1F", "bigcap": "\u22C2", "bigcirc": "\u25EF", "bigcup": "\u22C3", "bigodot": "\u2A00", "bigoplus": "\u2A01", "bigotimes": "\u2A02", "bigsqcup": "\u2A06", "bigstar": "\u2605", "bigtriangledown": "\u25BD", "bigtriangleup": "\u25B3", "biguplus": "\u2A04", "bigvee": "\u22C1", "bigwedge": "\u22C0", "bkarow": "\u290D", "blacklozenge": "\u29EB", "blacksquare": "\u25AA", "blacktriangle": "\u25B4", "blacktriangledown": "\u25BE", "blacktriangleleft": "\u25C2", "blacktriangleright": "\u25B8", "blank": "\u2423", "blk12": "\u2592", "blk14": "\u2591", "blk34": "\u2593", "block": "\u2588", "bne": "=\u20E5", "bnequiv": "\u2261\u20E5", "bNot": "\u2AED", "bnot": "\u2310", "Bopf": "\uD835\uDD39", "bopf": "\uD835\uDD53", "bot": "\u22A5", "bottom": "\u22A5", "bowtie": "\u22C8", "boxbox": "\u29C9", "boxdl": "\u2510", "boxdL": "\u2555", "boxDl": "\u2556", "boxDL": "\u2557", "boxdr": "\u250C", "boxdR": "\u2552", "boxDr": "\u2553", "boxDR": "\u2554", "boxh": "\u2500", "boxH": "\u2550", "boxhd": "\u252C", "boxHd": "\u2564", "boxhD": "\u2565", "boxHD": "\u2566", "boxhu": "\u2534", "boxHu": "\u2567", "boxhU": "\u2568", "boxHU": "\u2569", "boxminus": "\u229F", "boxplus": "\u229E", "boxtimes": "\u22A0", "boxul": "\u2518", "boxuL": "\u255B", "boxUl": "\u255C", "boxUL": "\u255D", "boxur": "\u2514", "boxuR": "\u2558", "boxUr": "\u2559", "boxUR": "\u255A", "boxv": "\u2502", "boxV": "\u2551", "boxvh": "\u253C", "boxvH": "\u256A", "boxVh": "\u256B", "boxVH": "\u256C", "boxvl": "\u2524", "boxvL": "\u2561", "boxVl": "\u2562", "boxVL": "\u2563", "boxvr": "\u251C", "boxvR": "\u255E", "boxVr": "\u255F", "boxVR": "\u2560", "bprime": "\u2035", "breve": "\u02D8", "Breve": "\u02D8", "brvbar": "\u00A6", "bscr": "\uD835\uDCB7", "Bscr": "\u212C", "bsemi": "\u204F", "bsim": "\u223D", "bsime": "\u22CD", "bsolb": "\u29C5", "bsol": "\\", "bsolhsub": "\u27C8", "bull": "\u2022", "bullet": "\u2022", "bump": "\u224E", "bumpE": "\u2AAE", "bumpe": "\u224F", "Bumpeq": "\u224E", "bumpeq": "\u224F", "Cacute": "\u0106", "cacute": "\u0107", "capand": "\u2A44", "capbrcup": "\u2A49", "capcap": "\u2A4B", "cap": "\u2229", "Cap": "\u22D2", "capcup": "\u2A47", "capdot": "\u2A40", "CapitalDifferentialD": "\u2145", "caps": "\u2229\uFE00", "caret": "\u2041", "caron": "\u02C7", "Cayleys": "\u212D", "ccaps": "\u2A4D", "Ccaron": "\u010C", "ccaron": "\u010D", "Ccedil": "\u00C7", "ccedil": "\u00E7", "Ccirc": "\u0108", "ccirc": "\u0109", "Cconint": "\u2230", "ccups": "\u2A4C", "ccupssm": "\u2A50", "Cdot": "\u010A", "cdot": "\u010B", "cedil": "\u00B8", "Cedilla": "\u00B8", "cemptyv": "\u29B2", "cent": "\u00A2", "centerdot": "\u00B7", "CenterDot": "\u00B7", "cfr": "\uD835\uDD20", "Cfr": "\u212D", "CHcy": "\u0427", "chcy": "\u0447", "check": "\u2713", "checkmark": "\u2713", "Chi": "\u03A7", "chi": "\u03C7", "circ": "\u02C6", "circeq": "\u2257", "circlearrowleft": "\u21BA", "circlearrowright": "\u21BB", "circledast": "\u229B", "circledcirc": "\u229A", "circleddash": "\u229D", "CircleDot": "\u2299", "circledR": "\u00AE", "circledS": "\u24C8", "CircleMinus": "\u2296", "CirclePlus": "\u2295", "CircleTimes": "\u2297", "cir": "\u25CB", "cirE": "\u29C3", "cire": "\u2257", "cirfnint": "\u2A10", "cirmid": "\u2AEF", "cirscir": "\u29C2", "ClockwiseContourIntegral": "\u2232", "CloseCurlyDoubleQuote": "\u201D", "CloseCurlyQuote": "\u2019", "clubs": "\u2663", "clubsuit": "\u2663", "colon": ":", "Colon": "\u2237", "Colone": "\u2A74", "colone": "\u2254", "coloneq": "\u2254", "comma": ",", "commat": "@", "comp": "\u2201", "compfn": "\u2218", "complement": "\u2201", "complexes": "\u2102", "cong": "\u2245", "congdot": "\u2A6D", "Congruent": "\u2261", "conint": "\u222E", "Conint": "\u222F", "ContourIntegral": "\u222E", "copf": "\uD835\uDD54", "Copf": "\u2102", "coprod": "\u2210", "Coproduct": "\u2210", "copy": "\u00A9", "COPY": "\u00A9", "copysr": "\u2117", "CounterClockwiseContourIntegral": "\u2233", "crarr": "\u21B5", "cross": "\u2717", "Cross": "\u2A2F", "Cscr": "\uD835\uDC9E", "cscr": "\uD835\uDCB8", "csub": "\u2ACF", "csube": "\u2AD1", "csup": "\u2AD0", "csupe": "\u2AD2", "ctdot": "\u22EF", "cudarrl": "\u2938", "cudarrr": "\u2935", "cuepr": "\u22DE", "cuesc": "\u22DF", "cularr": "\u21B6", "cularrp": "\u293D", "cupbrcap": "\u2A48", "cupcap": "\u2A46", "CupCap": "\u224D", "cup": "\u222A", "Cup": "\u22D3", "cupcup": "\u2A4A", "cupdot": "\u228D", "cupor": "\u2A45", "cups": "\u222A\uFE00", "curarr": "\u21B7", "curarrm": "\u293C", "curlyeqprec": "\u22DE", "curlyeqsucc": "\u22DF", "curlyvee": "\u22CE", "curlywedge": "\u22CF", "curren": "\u00A4", "curvearrowleft": "\u21B6", "curvearrowright": "\u21B7", "cuvee": "\u22CE", "cuwed": "\u22CF", "cwconint": "\u2232", "cwint": "\u2231", "cylcty": "\u232D", "dagger": "\u2020", "Dagger": "\u2021", "daleth": "\u2138", "darr": "\u2193", "Darr": "\u21A1", "dArr": "\u21D3", "dash": "\u2010", "Dashv": "\u2AE4", "dashv": "\u22A3", "dbkarow": "\u290F", "dblac": "\u02DD", "Dcaron": "\u010E", "dcaron": "\u010F", "Dcy": "\u0414", "dcy": "\u0434", "ddagger": "\u2021", "ddarr": "\u21CA", "DD": "\u2145", "dd": "\u2146", "DDotrahd": "\u2911", "ddotseq": "\u2A77", "deg": "\u00B0", "Del": "\u2207", "Delta": "\u0394", "delta": "\u03B4", "demptyv": "\u29B1", "dfisht": "\u297F", "Dfr": "\uD835\uDD07", "dfr": "\uD835\uDD21", "dHar": "\u2965", "dharl": "\u21C3", "dharr": "\u21C2", "DiacriticalAcute": "\u00B4", "DiacriticalDot": "\u02D9", "DiacriticalDoubleAcute": "\u02DD", "DiacriticalGrave": "`", "DiacriticalTilde": "\u02DC", "diam": "\u22C4", "diamond": "\u22C4", "Diamond": "\u22C4", "diamondsuit": "\u2666", "diams": "\u2666", "die": "\u00A8", "DifferentialD": "\u2146", "digamma": "\u03DD", "disin": "\u22F2", "div": "\u00F7", "divide": "\u00F7", "divideontimes": "\u22C7", "divonx": "\u22C7", "DJcy": "\u0402", "djcy": "\u0452", "dlcorn": "\u231E", "dlcrop": "\u230D", "dollar": "$", "Dopf": "\uD835\uDD3B", "dopf": "\uD835\uDD55", "Dot": "\u00A8", "dot": "\u02D9", "DotDot": "\u20DC", "doteq": "\u2250", "doteqdot": "\u2251", "DotEqual": "\u2250", "dotminus": "\u2238", "dotplus": "\u2214", "dotsquare": "\u22A1", "doublebarwedge": "\u2306", "DoubleContourIntegral": "\u222F", "DoubleDot": "\u00A8", "DoubleDownArrow": "\u21D3", "DoubleLeftArrow": "\u21D0", "DoubleLeftRightArrow": "\u21D4", "DoubleLeftTee": "\u2AE4", "DoubleLongLeftArrow": "\u27F8", "DoubleLongLeftRightArrow": "\u27FA", "DoubleLongRightArrow": "\u27F9", "DoubleRightArrow": "\u21D2", "DoubleRightTee": "\u22A8", "DoubleUpArrow": "\u21D1", "DoubleUpDownArrow": "\u21D5", "DoubleVerticalBar": "\u2225", "DownArrowBar": "\u2913", "downarrow": "\u2193", "DownArrow": "\u2193", "Downarrow": "\u21D3", "DownArrowUpArrow": "\u21F5", "DownBreve": "\u0311", "downdownarrows": "\u21CA", "downharpoonleft": "\u21C3", "downharpoonright": "\u21C2", "DownLeftRightVector": "\u2950", "DownLeftTeeVector": "\u295E", "DownLeftVectorBar": "\u2956", "DownLeftVector": "\u21BD", "DownRightTeeVector": "\u295F", "DownRightVectorBar": "\u2957", "DownRightVector": "\u21C1", "DownTeeArrow": "\u21A7", "DownTee": "\u22A4", "drbkarow": "\u2910", "drcorn": "\u231F", "drcrop": "\u230C", "Dscr": "\uD835\uDC9F", "dscr": "\uD835\uDCB9", "DScy": "\u0405", "dscy": "\u0455", "dsol": "\u29F6", "Dstrok": "\u0110", "dstrok": "\u0111", "dtdot": "\u22F1", "dtri": "\u25BF", "dtrif": "\u25BE", "duarr": "\u21F5", "duhar": "\u296F", "dwangle": "\u29A6", "DZcy": "\u040F", "dzcy": "\u045F", "dzigrarr": "\u27FF", "Eacute": "\u00C9", "eacute": "\u00E9", "easter": "\u2A6E", "Ecaron": "\u011A", "ecaron": "\u011B", "Ecirc": "\u00CA", "ecirc": "\u00EA", "ecir": "\u2256", "ecolon": "\u2255", "Ecy": "\u042D", "ecy": "\u044D", "eDDot": "\u2A77", "Edot": "\u0116", "edot": "\u0117", "eDot": "\u2251", "ee": "\u2147", "efDot": "\u2252", "Efr": "\uD835\uDD08", "efr": "\uD835\uDD22", "eg": "\u2A9A", "Egrave": "\u00C8", "egrave": "\u00E8", "egs": "\u2A96", "egsdot": "\u2A98", "el": "\u2A99", "Element": "\u2208", "elinters": "\u23E7", "ell": "\u2113", "els": "\u2A95", "elsdot": "\u2A97", "Emacr": "\u0112", "emacr": "\u0113", "empty": "\u2205", "emptyset": "\u2205", "EmptySmallSquare": "\u25FB", "emptyv": "\u2205", "EmptyVerySmallSquare": "\u25AB", "emsp13": "\u2004", "emsp14": "\u2005", "emsp": "\u2003", "ENG": "\u014A", "eng": "\u014B", "ensp": "\u2002", "Eogon": "\u0118", "eogon": "\u0119", "Eopf": "\uD835\uDD3C", "eopf": "\uD835\uDD56", "epar": "\u22D5", "eparsl": "\u29E3", "eplus": "\u2A71", "epsi": "\u03B5", "Epsilon": "\u0395", "epsilon": "\u03B5", "epsiv": "\u03F5", "eqcirc": "\u2256", "eqcolon": "\u2255", "eqsim": "\u2242", "eqslantgtr": "\u2A96", "eqslantless": "\u2A95", "Equal": "\u2A75", "equals": "=", "EqualTilde": "\u2242", "equest": "\u225F", "Equilibrium": "\u21CC", "equiv": "\u2261", "equivDD": "\u2A78", "eqvparsl": "\u29E5", "erarr": "\u2971", "erDot": "\u2253", "escr": "\u212F", "Escr": "\u2130", "esdot": "\u2250", "Esim": "\u2A73", "esim": "\u2242", "Eta": "\u0397", "eta": "\u03B7", "ETH": "\u00D0", "eth": "\u00F0", "Euml": "\u00CB", "euml": "\u00EB", "euro": "\u20AC", "excl": "!", "exist": "\u2203", "Exists": "\u2203", "expectation": "\u2130", "exponentiale": "\u2147", "ExponentialE": "\u2147", "fallingdotseq": "\u2252", "Fcy": "\u0424", "fcy": "\u0444", "female": "\u2640", "ffilig": "\uFB03", "fflig": "\uFB00", "ffllig": "\uFB04", "Ffr": "\uD835\uDD09", "ffr": "\uD835\uDD23", "filig": "\uFB01", "FilledSmallSquare": "\u25FC", "FilledVerySmallSquare": "\u25AA", "fjlig": "fj", "flat": "\u266D", "fllig": "\uFB02", "fltns": "\u25B1", "fnof": "\u0192", "Fopf": "\uD835\uDD3D", "fopf": "\uD835\uDD57", "forall": "\u2200", "ForAll": "\u2200", "fork": "\u22D4", "forkv": "\u2AD9", "Fouriertrf": "\u2131", "fpartint": "\u2A0D", "frac12": "\u00BD", "frac13": "\u2153", "frac14": "\u00BC", "frac15": "\u2155", "frac16": "\u2159", "frac18": "\u215B", "frac23": "\u2154", "frac25": "\u2156", "frac34": "\u00BE", "frac35": "\u2157", "frac38": "\u215C", "frac45": "\u2158", "frac56": "\u215A", "frac58": "\u215D", "frac78": "\u215E", "frasl": "\u2044", "frown": "\u2322", "fscr": "\uD835\uDCBB", "Fscr": "\u2131", "gacute": "\u01F5", "Gamma": "\u0393", "gamma": "\u03B3", "Gammad": "\u03DC", "gammad": "\u03DD", "gap": "\u2A86", "Gbreve": "\u011E", "gbreve": "\u011F", "Gcedil": "\u0122", "Gcirc": "\u011C", "gcirc": "\u011D", "Gcy": "\u0413", "gcy": "\u0433", "Gdot": "\u0120", "gdot": "\u0121", "ge": "\u2265", "gE": "\u2267", "gEl": "\u2A8C", "gel": "\u22DB", "geq": "\u2265", "geqq": "\u2267", "geqslant": "\u2A7E", "gescc": "\u2AA9", "ges": "\u2A7E", "gesdot": "\u2A80", "gesdoto": "\u2A82", "gesdotol": "\u2A84", "gesl": "\u22DB\uFE00", "gesles": "\u2A94", "Gfr": "\uD835\uDD0A", "gfr": "\uD835\uDD24", "gg": "\u226B", "Gg": "\u22D9", "ggg": "\u22D9", "gimel": "\u2137", "GJcy": "\u0403", "gjcy": "\u0453", "gla": "\u2AA5", "gl": "\u2277", "glE": "\u2A92", "glj": "\u2AA4", "gnap": "\u2A8A", "gnapprox": "\u2A8A", "gne": "\u2A88", "gnE": "\u2269", "gneq": "\u2A88", "gneqq": "\u2269", "gnsim": "\u22E7", "Gopf": "\uD835\uDD3E", "gopf": "\uD835\uDD58", "grave": "`", "GreaterEqual": "\u2265", "GreaterEqualLess": "\u22DB", "GreaterFullEqual": "\u2267", "GreaterGreater": "\u2AA2", "GreaterLess": "\u2277", "GreaterSlantEqual": "\u2A7E", "GreaterTilde": "\u2273", "Gscr": "\uD835\uDCA2", "gscr": "\u210A", "gsim": "\u2273", "gsime": "\u2A8E", "gsiml": "\u2A90", "gtcc": "\u2AA7", "gtcir": "\u2A7A", "gt": ">", "GT": ">", "Gt": "\u226B", "gtdot": "\u22D7", "gtlPar": "\u2995", "gtquest": "\u2A7C", "gtrapprox": "\u2A86", "gtrarr": "\u2978", "gtrdot": "\u22D7", "gtreqless": "\u22DB", "gtreqqless": "\u2A8C", "gtrless": "\u2277", "gtrsim": "\u2273", "gvertneqq": "\u2269\uFE00", "gvnE": "\u2269\uFE00", "Hacek": "\u02C7", "hairsp": "\u200A", "half": "\u00BD", "hamilt": "\u210B", "HARDcy": "\u042A", "hardcy": "\u044A", "harrcir": "\u2948", "harr": "\u2194", "hArr": "\u21D4", "harrw": "\u21AD", "Hat": "^", "hbar": "\u210F", "Hcirc": "\u0124", "hcirc": "\u0125", "hearts": "\u2665", "heartsuit": "\u2665", "hellip": "\u2026", "hercon": "\u22B9", "hfr": "\uD835\uDD25", "Hfr": "\u210C", "HilbertSpace": "\u210B", "hksearow": "\u2925", "hkswarow": "\u2926", "hoarr": "\u21FF", "homtht": "\u223B", "hookleftarrow": "\u21A9", "hookrightarrow": "\u21AA", "hopf": "\uD835\uDD59", "Hopf": "\u210D", "horbar": "\u2015", "HorizontalLine": "\u2500", "hscr": "\uD835\uDCBD", "Hscr": "\u210B", "hslash": "\u210F", "Hstrok": "\u0126", "hstrok": "\u0127", "HumpDownHump": "\u224E", "HumpEqual": "\u224F", "hybull": "\u2043", "hyphen": "\u2010", "Iacute": "\u00CD", "iacute": "\u00ED", "ic": "\u2063", "Icirc": "\u00CE", "icirc": "\u00EE", "Icy": "\u0418", "icy": "\u0438", "Idot": "\u0130", "IEcy": "\u0415", "iecy": "\u0435", "iexcl": "\u00A1", "iff": "\u21D4", "ifr": "\uD835\uDD26", "Ifr": "\u2111", "Igrave": "\u00CC", "igrave": "\u00EC", "ii": "\u2148", "iiiint": "\u2A0C", "iiint": "\u222D", "iinfin": "\u29DC", "iiota": "\u2129", "IJlig": "\u0132", "ijlig": "\u0133", "Imacr": "\u012A", "imacr": "\u012B", "image": "\u2111", "ImaginaryI": "\u2148", "imagline": "\u2110", "imagpart": "\u2111", "imath": "\u0131", "Im": "\u2111", "imof": "\u22B7", "imped": "\u01B5", "Implies": "\u21D2", "incare": "\u2105", "in": "\u2208", "infin": "\u221E", "infintie": "\u29DD", "inodot": "\u0131", "intcal": "\u22BA", "int": "\u222B", "Int": "\u222C", "integers": "\u2124", "Integral": "\u222B", "intercal": "\u22BA", "Intersection": "\u22C2", "intlarhk": "\u2A17", "intprod": "\u2A3C", "InvisibleComma": "\u2063", "InvisibleTimes": "\u2062", "IOcy": "\u0401", "iocy": "\u0451", "Iogon": "\u012E", "iogon": "\u012F", "Iopf": "\uD835\uDD40", "iopf": "\uD835\uDD5A", "Iota": "\u0399", "iota": "\u03B9", "iprod": "\u2A3C", "iquest": "\u00BF", "iscr": "\uD835\uDCBE", "Iscr": "\u2110", "isin": "\u2208", "isindot": "\u22F5", "isinE": "\u22F9", "isins": "\u22F4", "isinsv": "\u22F3", "isinv": "\u2208", "it": "\u2062", "Itilde": "\u0128", "itilde": "\u0129", "Iukcy": "\u0406", "iukcy": "\u0456", "Iuml": "\u00CF", "iuml": "\u00EF", "Jcirc": "\u0134", "jcirc": "\u0135", "Jcy": "\u0419", "jcy": "\u0439", "Jfr": "\uD835\uDD0D", "jfr": "\uD835\uDD27", "jmath": "\u0237", "Jopf": "\uD835\uDD41", "jopf": "\uD835\uDD5B", "Jscr": "\uD835\uDCA5", "jscr": "\uD835\uDCBF", "Jsercy": "\u0408", "jsercy": "\u0458", "Jukcy": "\u0404", "jukcy": "\u0454", "Kappa": "\u039A", "kappa": "\u03BA", "kappav": "\u03F0", "Kcedil": "\u0136", "kcedil": "\u0137", "Kcy": "\u041A", "kcy": "\u043A", "Kfr": "\uD835\uDD0E", "kfr": "\uD835\uDD28", "kgreen": "\u0138", "KHcy": "\u0425", "khcy": "\u0445", "KJcy": "\u040C", "kjcy": "\u045C", "Kopf": "\uD835\uDD42", "kopf": "\uD835\uDD5C", "Kscr": "\uD835\uDCA6", "kscr": "\uD835\uDCC0", "lAarr": "\u21DA", "Lacute": "\u0139", "lacute": "\u013A", "laemptyv": "\u29B4", "lagran": "\u2112", "Lambda": "\u039B", "lambda": "\u03BB", "lang": "\u27E8", "Lang": "\u27EA", "langd": "\u2991", "langle": "\u27E8", "lap": "\u2A85", "Laplacetrf": "\u2112", "laquo": "\u00AB", "larrb": "\u21E4", "larrbfs": "\u291F", "larr": "\u2190", "Larr": "\u219E", "lArr": "\u21D0", "larrfs": "\u291D", "larrhk": "\u21A9", "larrlp": "\u21AB", "larrpl": "\u2939", "larrsim": "\u2973", "larrtl": "\u21A2", "latail": "\u2919", "lAtail": "\u291B", "lat": "\u2AAB", "late": "\u2AAD", "lates": "\u2AAD\uFE00", "lbarr": "\u290C", "lBarr": "\u290E", "lbbrk": "\u2772", "lbrace": "{", "lbrack": "[", "lbrke": "\u298B", "lbrksld": "\u298F", "lbrkslu": "\u298D", "Lcaron": "\u013D", "lcaron": "\u013E", "Lcedil": "\u013B", "lcedil": "\u013C", "lceil": "\u2308", "lcub": "{", "Lcy": "\u041B", "lcy": "\u043B", "ldca": "\u2936", "ldquo": "\u201C", "ldquor": "\u201E", "ldrdhar": "\u2967", "ldrushar": "\u294B", "ldsh": "\u21B2", "le": "\u2264", "lE": "\u2266", "LeftAngleBracket": "\u27E8", "LeftArrowBar": "\u21E4", "leftarrow": "\u2190", "LeftArrow": "\u2190", "Leftarrow": "\u21D0", "LeftArrowRightArrow": "\u21C6", "leftarrowtail": "\u21A2", "LeftCeiling": "\u2308", "LeftDoubleBracket": "\u27E6", "LeftDownTeeVector": "\u2961", "LeftDownVectorBar": "\u2959", "LeftDownVector": "\u21C3", "LeftFloor": "\u230A", "leftharpoondown": "\u21BD", "leftharpoonup": "\u21BC", "leftleftarrows": "\u21C7", "leftrightarrow": "\u2194", "LeftRightArrow": "\u2194", "Leftrightarrow": "\u21D4", "leftrightarrows": "\u21C6", "leftrightharpoons": "\u21CB", "leftrightsquigarrow": "\u21AD", "LeftRightVector": "\u294E", "LeftTeeArrow": "\u21A4", "LeftTee": "\u22A3", "LeftTeeVector": "\u295A", "leftthreetimes": "\u22CB", "LeftTriangleBar": "\u29CF", "LeftTriangle": "\u22B2", "LeftTriangleEqual": "\u22B4", "LeftUpDownVector": "\u2951", "LeftUpTeeVector": "\u2960", "LeftUpVectorBar": "\u2958", "LeftUpVector": "\u21BF", "LeftVectorBar": "\u2952", "LeftVector": "\u21BC", "lEg": "\u2A8B", "leg": "\u22DA", "leq": "\u2264", "leqq": "\u2266", "leqslant": "\u2A7D", "lescc": "\u2AA8", "les": "\u2A7D", "lesdot": "\u2A7F", "lesdoto": "\u2A81", "lesdotor": "\u2A83", "lesg": "\u22DA\uFE00", "lesges": "\u2A93", "lessapprox": "\u2A85", "lessdot": "\u22D6", "lesseqgtr": "\u22DA", "lesseqqgtr": "\u2A8B", "LessEqualGreater": "\u22DA", "LessFullEqual": "\u2266", "LessGreater": "\u2276", "lessgtr": "\u2276", "LessLess": "\u2AA1", "lesssim": "\u2272", "LessSlantEqual": "\u2A7D", "LessTilde": "\u2272", "lfisht": "\u297C", "lfloor": "\u230A", "Lfr": "\uD835\uDD0F", "lfr": "\uD835\uDD29", "lg": "\u2276", "lgE": "\u2A91", "lHar": "\u2962", "lhard": "\u21BD", "lharu": "\u21BC", "lharul": "\u296A", "lhblk": "\u2584", "LJcy": "\u0409", "ljcy": "\u0459", "llarr": "\u21C7", "ll": "\u226A", "Ll": "\u22D8", "llcorner": "\u231E", "Lleftarrow": "\u21DA", "llhard": "\u296B", "lltri": "\u25FA", "Lmidot": "\u013F", "lmidot": "\u0140", "lmoustache": "\u23B0", "lmoust": "\u23B0", "lnap": "\u2A89", "lnapprox": "\u2A89", "lne": "\u2A87", "lnE": "\u2268", "lneq": "\u2A87", "lneqq": "\u2268", "lnsim": "\u22E6", "loang": "\u27EC", "loarr": "\u21FD", "lobrk": "\u27E6", "longleftarrow": "\u27F5", "LongLeftArrow": "\u27F5", "Longleftarrow": "\u27F8", "longleftrightarrow": "\u27F7", "LongLeftRightArrow": "\u27F7", "Longleftrightarrow": "\u27FA", "longmapsto": "\u27FC", "longrightarrow": "\u27F6", "LongRightArrow": "\u27F6", "Longrightarrow": "\u27F9", "looparrowleft": "\u21AB", "looparrowright": "\u21AC", "lopar": "\u2985", "Lopf": "\uD835\uDD43", "lopf": "\uD835\uDD5D", "loplus": "\u2A2D", "lotimes": "\u2A34", "lowast": "\u2217", "lowbar": "_", "LowerLeftArrow": "\u2199", "LowerRightArrow": "\u2198", "loz": "\u25CA", "lozenge": "\u25CA", "lozf": "\u29EB", "lpar": "(", "lparlt": "\u2993", "lrarr": "\u21C6", "lrcorner": "\u231F", "lrhar": "\u21CB", "lrhard": "\u296D", "lrm": "\u200E", "lrtri": "\u22BF", "lsaquo": "\u2039", "lscr": "\uD835\uDCC1", "Lscr": "\u2112", "lsh": "\u21B0", "Lsh": "\u21B0", "lsim": "\u2272", "lsime": "\u2A8D", "lsimg": "\u2A8F", "lsqb": "[", "lsquo": "\u2018", "lsquor": "\u201A", "Lstrok": "\u0141", "lstrok": "\u0142", "ltcc": "\u2AA6", "ltcir": "\u2A79", "lt": "<", "LT": "<", "Lt": "\u226A", "ltdot": "\u22D6", "lthree": "\u22CB", "ltimes": "\u22C9", "ltlarr": "\u2976", "ltquest": "\u2A7B", "ltri": "\u25C3", "ltrie": "\u22B4", "ltrif": "\u25C2", "ltrPar": "\u2996", "lurdshar": "\u294A", "luruhar": "\u2966", "lvertneqq": "\u2268\uFE00", "lvnE": "\u2268\uFE00", "macr": "\u00AF", "male": "\u2642", "malt": "\u2720", "maltese": "\u2720", "Map": "\u2905", "map": "\u21A6", "mapsto": "\u21A6", "mapstodown": "\u21A7", "mapstoleft": "\u21A4", "mapstoup": "\u21A5", "marker": "\u25AE", "mcomma": "\u2A29", "Mcy": "\u041C", "mcy": "\u043C", "mdash": "\u2014", "mDDot": "\u223A", "measuredangle": "\u2221", "MediumSpace": "\u205F", "Mellintrf": "\u2133", "Mfr": "\uD835\uDD10", "mfr": "\uD835\uDD2A", "mho": "\u2127", "micro": "\u00B5", "midast": "*", "midcir": "\u2AF0", "mid": "\u2223", "middot": "\u00B7", "minusb": "\u229F", "minus": "\u2212", "minusd": "\u2238", "minusdu": "\u2A2A", "MinusPlus": "\u2213", "mlcp": "\u2ADB", "mldr": "\u2026", "mnplus": "\u2213", "models": "\u22A7", "Mopf": "\uD835\uDD44", "mopf": "\uD835\uDD5E", "mp": "\u2213", "mscr": "\uD835\uDCC2", "Mscr": "\u2133", "mstpos": "\u223E", "Mu": "\u039C", "mu": "\u03BC", "multimap": "\u22B8", "mumap": "\u22B8", "nabla": "\u2207", "Nacute": "\u0143", "nacute": "\u0144", "nang": "\u2220\u20D2", "nap": "\u2249", "napE": "\u2A70\u0338", "napid": "\u224B\u0338", "napos": "\u0149", "napprox": "\u2249", "natural": "\u266E", "naturals": "\u2115", "natur": "\u266E", "nbsp": "\u00A0", "nbump": "\u224E\u0338", "nbumpe": "\u224F\u0338", "ncap": "\u2A43", "Ncaron": "\u0147", "ncaron": "\u0148", "Ncedil": "\u0145", "ncedil": "\u0146", "ncong": "\u2247", "ncongdot": "\u2A6D\u0338", "ncup": "\u2A42", "Ncy": "\u041D", "ncy": "\u043D", "ndash": "\u2013", "nearhk": "\u2924", "nearr": "\u2197", "neArr": "\u21D7", "nearrow": "\u2197", "ne": "\u2260", "nedot": "\u2250\u0338", "NegativeMediumSpace": "\u200B", "NegativeThickSpace": "\u200B", "NegativeThinSpace": "\u200B", "NegativeVeryThinSpace": "\u200B", "nequiv": "\u2262", "nesear": "\u2928", "nesim": "\u2242\u0338", "NestedGreaterGreater": "\u226B", "NestedLessLess": "\u226A", "NewLine": "\n", "nexist": "\u2204", "nexists": "\u2204", "Nfr": "\uD835\uDD11", "nfr": "\uD835\uDD2B", "ngE": "\u2267\u0338", "nge": "\u2271", "ngeq": "\u2271", "ngeqq": "\u2267\u0338", "ngeqslant": "\u2A7E\u0338", "nges": "\u2A7E\u0338", "nGg": "\u22D9\u0338", "ngsim": "\u2275", "nGt": "\u226B\u20D2", "ngt": "\u226F", "ngtr": "\u226F", "nGtv": "\u226B\u0338", "nharr": "\u21AE", "nhArr": "\u21CE", "nhpar": "\u2AF2", "ni": "\u220B", "nis": "\u22FC", "nisd": "\u22FA", "niv": "\u220B", "NJcy": "\u040A", "njcy": "\u045A", "nlarr": "\u219A", "nlArr": "\u21CD", "nldr": "\u2025", "nlE": "\u2266\u0338", "nle": "\u2270", "nleftarrow": "\u219A", "nLeftarrow": "\u21CD", "nleftrightarrow": "\u21AE", "nLeftrightarrow": "\u21CE", "nleq": "\u2270", "nleqq": "\u2266\u0338", "nleqslant": "\u2A7D\u0338", "nles": "\u2A7D\u0338", "nless": "\u226E", "nLl": "\u22D8\u0338", "nlsim": "\u2274", "nLt": "\u226A\u20D2", "nlt": "\u226E", "nltri": "\u22EA", "nltrie": "\u22EC", "nLtv": "\u226A\u0338", "nmid": "\u2224", "NoBreak": "\u2060", "NonBreakingSpace": "\u00A0", "nopf": "\uD835\uDD5F", "Nopf": "\u2115", "Not": "\u2AEC", "not": "\u00AC", "NotCongruent": "\u2262", "NotCupCap": "\u226D", "NotDoubleVerticalBar": "\u2226", "NotElement": "\u2209", "NotEqual": "\u2260", "NotEqualTilde": "\u2242\u0338", "NotExists": "\u2204", "NotGreater": "\u226F", "NotGreaterEqual": "\u2271", "NotGreaterFullEqual": "\u2267\u0338", "NotGreaterGreater": "\u226B\u0338", "NotGreaterLess": "\u2279", "NotGreaterSlantEqual": "\u2A7E\u0338", "NotGreaterTilde": "\u2275", "NotHumpDownHump": "\u224E\u0338", "NotHumpEqual": "\u224F\u0338", "notin": "\u2209", "notindot": "\u22F5\u0338", "notinE": "\u22F9\u0338", "notinva": "\u2209", "notinvb": "\u22F7", "notinvc": "\u22F6", "NotLeftTriangleBar": "\u29CF\u0338", "NotLeftTriangle": "\u22EA", "NotLeftTriangleEqual": "\u22EC", "NotLess": "\u226E", "NotLessEqual": "\u2270", "NotLessGreater": "\u2278", "NotLessLess": "\u226A\u0338", "NotLessSlantEqual": "\u2A7D\u0338", "NotLessTilde": "\u2274", "NotNestedGreaterGreater": "\u2AA2\u0338", "NotNestedLessLess": "\u2AA1\u0338", "notni": "\u220C", "notniva": "\u220C", "notnivb": "\u22FE", "notnivc": "\u22FD", "NotPrecedes": "\u2280", "NotPrecedesEqual": "\u2AAF\u0338", "NotPrecedesSlantEqual": "\u22E0", "NotReverseElement": "\u220C", "NotRightTriangleBar": "\u29D0\u0338", "NotRightTriangle": "\u22EB", "NotRightTriangleEqual": "\u22ED", "NotSquareSubset": "\u228F\u0338", "NotSquareSubsetEqual": "\u22E2", "NotSquareSuperset": "\u2290\u0338", "NotSquareSupersetEqual": "\u22E3", "NotSubset": "\u2282\u20D2", "NotSubsetEqual": "\u2288", "NotSucceeds": "\u2281", "NotSucceedsEqual": "\u2AB0\u0338", "NotSucceedsSlantEqual": "\u22E1", "NotSucceedsTilde": "\u227F\u0338", "NotSuperset": "\u2283\u20D2", "NotSupersetEqual": "\u2289", "NotTilde": "\u2241", "NotTildeEqual": "\u2244", "NotTildeFullEqual": "\u2247", "NotTildeTilde": "\u2249", "NotVerticalBar": "\u2224", "nparallel": "\u2226", "npar": "\u2226", "nparsl": "\u2AFD\u20E5", "npart": "\u2202\u0338", "npolint": "\u2A14", "npr": "\u2280", "nprcue": "\u22E0", "nprec": "\u2280", "npreceq": "\u2AAF\u0338", "npre": "\u2AAF\u0338", "nrarrc": "\u2933\u0338", "nrarr": "\u219B", "nrArr": "\u21CF", "nrarrw": "\u219D\u0338", "nrightarrow": "\u219B", "nRightarrow": "\u21CF", "nrtri": "\u22EB", "nrtrie": "\u22ED", "nsc": "\u2281", "nsccue": "\u22E1", "nsce": "\u2AB0\u0338", "Nscr": "\uD835\uDCA9", "nscr": "\uD835\uDCC3", "nshortmid": "\u2224", "nshortparallel": "\u2226", "nsim": "\u2241", "nsime": "\u2244", "nsimeq": "\u2244", "nsmid": "\u2224", "nspar": "\u2226", "nsqsube": "\u22E2", "nsqsupe": "\u22E3", "nsub": "\u2284", "nsubE": "\u2AC5\u0338", "nsube": "\u2288", "nsubset": "\u2282\u20D2", "nsubseteq": "\u2288", "nsubseteqq": "\u2AC5\u0338", "nsucc": "\u2281", "nsucceq": "\u2AB0\u0338", "nsup": "\u2285", "nsupE": "\u2AC6\u0338", "nsupe": "\u2289", "nsupset": "\u2283\u20D2", "nsupseteq": "\u2289", "nsupseteqq": "\u2AC6\u0338", "ntgl": "\u2279", "Ntilde": "\u00D1", "ntilde": "\u00F1", "ntlg": "\u2278", "ntriangleleft": "\u22EA", "ntrianglelefteq": "\u22EC", "ntriangleright": "\u22EB", "ntrianglerighteq": "\u22ED", "Nu": "\u039D", "nu": "\u03BD", "num": "#", "numero": "\u2116", "numsp": "\u2007", "nvap": "\u224D\u20D2", "nvdash": "\u22AC", "nvDash": "\u22AD", "nVdash": "\u22AE", "nVDash": "\u22AF", "nvge": "\u2265\u20D2", "nvgt": ">\u20D2", "nvHarr": "\u2904", "nvinfin": "\u29DE", "nvlArr": "\u2902", "nvle": "\u2264\u20D2", "nvlt": "<\u20D2", "nvltrie": "\u22B4\u20D2", "nvrArr": "\u2903", "nvrtrie": "\u22B5\u20D2", "nvsim": "\u223C\u20D2", "nwarhk": "\u2923", "nwarr": "\u2196", "nwArr": "\u21D6", "nwarrow": "\u2196", "nwnear": "\u2927", "Oacute": "\u00D3", "oacute": "\u00F3", "oast": "\u229B", "Ocirc": "\u00D4", "ocirc": "\u00F4", "ocir": "\u229A", "Ocy": "\u041E", "ocy": "\u043E", "odash": "\u229D", "Odblac": "\u0150", "odblac": "\u0151", "odiv": "\u2A38", "odot": "\u2299", "odsold": "\u29BC", "OElig": "\u0152", "oelig": "\u0153", "ofcir": "\u29BF", "Ofr": "\uD835\uDD12", "ofr": "\uD835\uDD2C", "ogon": "\u02DB", "Ograve": "\u00D2", "ograve": "\u00F2", "ogt": "\u29C1", "ohbar": "\u29B5", "ohm": "\u03A9", "oint": "\u222E", "olarr": "\u21BA", "olcir": "\u29BE", "olcross": "\u29BB", "oline": "\u203E", "olt": "\u29C0", "Omacr": "\u014C", "omacr": "\u014D", "Omega": "\u03A9", "omega": "\u03C9", "Omicron": "\u039F", "omicron": "\u03BF", "omid": "\u29B6", "ominus": "\u2296", "Oopf": "\uD835\uDD46", "oopf": "\uD835\uDD60", "opar": "\u29B7", "OpenCurlyDoubleQuote": "\u201C", "OpenCurlyQuote": "\u2018", "operp": "\u29B9", "oplus": "\u2295", "orarr": "\u21BB", "Or": "\u2A54", "or": "\u2228", "ord": "\u2A5D", "order": "\u2134", "orderof": "\u2134", "ordf": "\u00AA", "ordm": "\u00BA", "origof": "\u22B6", "oror": "\u2A56", "orslope": "\u2A57", "orv": "\u2A5B", "oS": "\u24C8", "Oscr": "\uD835\uDCAA", "oscr": "\u2134", "Oslash": "\u00D8", "oslash": "\u00F8", "osol": "\u2298", "Otilde": "\u00D5", "otilde": "\u00F5", "otimesas": "\u2A36", "Otimes": "\u2A37", "otimes": "\u2297", "Ouml": "\u00D6", "ouml": "\u00F6", "ovbar": "\u233D", "OverBar": "\u203E", "OverBrace": "\u23DE", "OverBracket": "\u23B4", "OverParenthesis": "\u23DC", "para": "\u00B6", "parallel": "\u2225", "par": "\u2225", "parsim": "\u2AF3", "parsl": "\u2AFD", "part": "\u2202", "PartialD": "\u2202", "Pcy": "\u041F", "pcy": "\u043F", "percnt": "%", "period": ".", "permil": "\u2030", "perp": "\u22A5", "pertenk": "\u2031", "Pfr": "\uD835\uDD13", "pfr": "\uD835\uDD2D", "Phi": "\u03A6", "phi": "\u03C6", "phiv": "\u03D5", "phmmat": "\u2133", "phone": "\u260E", "Pi": "\u03A0", "pi": "\u03C0", "pitchfork": "\u22D4", "piv": "\u03D6", "planck": "\u210F", "planckh": "\u210E", "plankv": "\u210F", "plusacir": "\u2A23", "plusb": "\u229E", "pluscir": "\u2A22", "plus": "+", "plusdo": "\u2214", "plusdu": "\u2A25", "pluse": "\u2A72", "PlusMinus": "\u00B1", "plusmn": "\u00B1", "plussim": "\u2A26", "plustwo": "\u2A27", "pm": "\u00B1", "Poincareplane": "\u210C", "pointint": "\u2A15", "popf": "\uD835\uDD61", "Popf": "\u2119", "pound": "\u00A3", "prap": "\u2AB7", "Pr": "\u2ABB", "pr": "\u227A", "prcue": "\u227C", "precapprox": "\u2AB7", "prec": "\u227A", "preccurlyeq": "\u227C", "Precedes": "\u227A", "PrecedesEqual": "\u2AAF", "PrecedesSlantEqual": "\u227C", "PrecedesTilde": "\u227E", "preceq": "\u2AAF", "precnapprox": "\u2AB9", "precneqq": "\u2AB5", "precnsim": "\u22E8", "pre": "\u2AAF", "prE": "\u2AB3", "precsim": "\u227E", "prime": "\u2032", "Prime": "\u2033", "primes": "\u2119", "prnap": "\u2AB9", "prnE": "\u2AB5", "prnsim": "\u22E8", "prod": "\u220F", "Product": "\u220F", "profalar": "\u232E", "profline": "\u2312", "profsurf": "\u2313", "prop": "\u221D", "Proportional": "\u221D", "Proportion": "\u2237", "propto": "\u221D", "prsim": "\u227E", "prurel": "\u22B0", "Pscr": "\uD835\uDCAB", "pscr": "\uD835\uDCC5", "Psi": "\u03A8", "psi": "\u03C8", "puncsp": "\u2008", "Qfr": "\uD835\uDD14", "qfr": "\uD835\uDD2E", "qint": "\u2A0C", "qopf": "\uD835\uDD62", "Qopf": "\u211A", "qprime": "\u2057", "Qscr": "\uD835\uDCAC", "qscr": "\uD835\uDCC6", "quaternions": "\u210D", "quatint": "\u2A16", "quest": "?", "questeq": "\u225F", "quot": "\"", "QUOT": "\"", "rAarr": "\u21DB", "race": "\u223D\u0331", "Racute": "\u0154", "racute": "\u0155", "radic": "\u221A", "raemptyv": "\u29B3", "rang": "\u27E9", "Rang": "\u27EB", "rangd": "\u2992", "range": "\u29A5", "rangle": "\u27E9", "raquo": "\u00BB", "rarrap": "\u2975", "rarrb": "\u21E5", "rarrbfs": "\u2920", "rarrc": "\u2933", "rarr": "\u2192", "Rarr": "\u21A0", "rArr": "\u21D2", "rarrfs": "\u291E", "rarrhk": "\u21AA", "rarrlp": "\u21AC", "rarrpl": "\u2945", "rarrsim": "\u2974", "Rarrtl": "\u2916", "rarrtl": "\u21A3", "rarrw": "\u219D", "ratail": "\u291A", "rAtail": "\u291C", "ratio": "\u2236", "rationals": "\u211A", "rbarr": "\u290D", "rBarr": "\u290F", "RBarr": "\u2910", "rbbrk": "\u2773", "rbrace": "}", "rbrack": "]", "rbrke": "\u298C", "rbrksld": "\u298E", "rbrkslu": "\u2990", "Rcaron": "\u0158", "rcaron": "\u0159", "Rcedil": "\u0156", "rcedil": "\u0157", "rceil": "\u2309", "rcub": "}", "Rcy": "\u0420", "rcy": "\u0440", "rdca": "\u2937", "rdldhar": "\u2969", "rdquo": "\u201D", "rdquor": "\u201D", "rdsh": "\u21B3", "real": "\u211C", "realine": "\u211B", "realpart": "\u211C", "reals": "\u211D", "Re": "\u211C", "rect": "\u25AD", "reg": "\u00AE", "REG": "\u00AE", "ReverseElement": "\u220B", "ReverseEquilibrium": "\u21CB", "ReverseUpEquilibrium": "\u296F", "rfisht": "\u297D", "rfloor": "\u230B", "rfr": "\uD835\uDD2F", "Rfr": "\u211C", "rHar": "\u2964", "rhard": "\u21C1", "rharu": "\u21C0", "rharul": "\u296C", "Rho": "\u03A1", "rho": "\u03C1", "rhov": "\u03F1", "RightAngleBracket": "\u27E9", "RightArrowBar": "\u21E5", "rightarrow": "\u2192", "RightArrow": "\u2192", "Rightarrow": "\u21D2", "RightArrowLeftArrow": "\u21C4", "rightarrowtail": "\u21A3", "RightCeiling": "\u2309", "RightDoubleBracket": "\u27E7", "RightDownTeeVector": "\u295D", "RightDownVectorBar": "\u2955", "RightDownVector": "\u21C2", "RightFloor": "\u230B", "rightharpoondown": "\u21C1", "rightharpoonup": "\u21C0", "rightleftarrows": "\u21C4", "rightleftharpoons": "\u21CC", "rightrightarrows": "\u21C9", "rightsquigarrow": "\u219D", "RightTeeArrow": "\u21A6", "RightTee": "\u22A2", "RightTeeVector": "\u295B", "rightthreetimes": "\u22CC", "RightTriangleBar": "\u29D0", "RightTriangle": "\u22B3", "RightTriangleEqual": "\u22B5", "RightUpDownVector": "\u294F", "RightUpTeeVector": "\u295C", "RightUpVectorBar": "\u2954", "RightUpVector": "\u21BE", "RightVectorBar": "\u2953", "RightVector": "\u21C0", "ring": "\u02DA", "risingdotseq": "\u2253", "rlarr": "\u21C4", "rlhar": "\u21CC", "rlm": "\u200F", "rmoustache": "\u23B1", "rmoust": "\u23B1", "rnmid": "\u2AEE", "roang": "\u27ED", "roarr": "\u21FE", "robrk": "\u27E7", "ropar": "\u2986", "ropf": "\uD835\uDD63", "Ropf": "\u211D", "roplus": "\u2A2E", "rotimes": "\u2A35", "RoundImplies": "\u2970", "rpar": ")", "rpargt": "\u2994", "rppolint": "\u2A12", "rrarr": "\u21C9", "Rrightarrow": "\u21DB", "rsaquo": "\u203A", "rscr": "\uD835\uDCC7", "Rscr": "\u211B", "rsh": "\u21B1", "Rsh": "\u21B1", "rsqb": "]", "rsquo": "\u2019", "rsquor": "\u2019", "rthree": "\u22CC", "rtimes": "\u22CA", "rtri": "\u25B9", "rtrie": "\u22B5", "rtrif": "\u25B8", "rtriltri": "\u29CE", "RuleDelayed": "\u29F4", "ruluhar": "\u2968", "rx": "\u211E", "Sacute": "\u015A", "sacute": "\u015B", "sbquo": "\u201A", "scap": "\u2AB8", "Scaron": "\u0160", "scaron": "\u0161", "Sc": "\u2ABC", "sc": "\u227B", "sccue": "\u227D", "sce": "\u2AB0", "scE": "\u2AB4", "Scedil": "\u015E", "scedil": "\u015F", "Scirc": "\u015C", "scirc": "\u015D", "scnap": "\u2ABA", "scnE": "\u2AB6", "scnsim": "\u22E9", "scpolint": "\u2A13", "scsim": "\u227F", "Scy": "\u0421", "scy": "\u0441", "sdotb": "\u22A1", "sdot": "\u22C5", "sdote": "\u2A66", "searhk": "\u2925", "searr": "\u2198", "seArr": "\u21D8", "searrow": "\u2198", "sect": "\u00A7", "semi": ";", "seswar": "\u2929", "setminus": "\u2216", "setmn": "\u2216", "sext": "\u2736", "Sfr": "\uD835\uDD16", "sfr": "\uD835\uDD30", "sfrown": "\u2322", "sharp": "\u266F", "SHCHcy": "\u0429", "shchcy": "\u0449", "SHcy": "\u0428", "shcy": "\u0448", "ShortDownArrow": "\u2193", "ShortLeftArrow": "\u2190", "shortmid": "\u2223", "shortparallel": "\u2225", "ShortRightArrow": "\u2192", "ShortUpArrow": "\u2191", "shy": "\u00AD", "Sigma": "\u03A3", "sigma": "\u03C3", "sigmaf": "\u03C2", "sigmav": "\u03C2", "sim": "\u223C", "simdot": "\u2A6A", "sime": "\u2243", "simeq": "\u2243", "simg": "\u2A9E", "simgE": "\u2AA0", "siml": "\u2A9D", "simlE": "\u2A9F", "simne": "\u2246", "simplus": "\u2A24", "simrarr": "\u2972", "slarr": "\u2190", "SmallCircle": "\u2218", "smallsetminus": "\u2216", "smashp": "\u2A33", "smeparsl": "\u29E4", "smid": "\u2223", "smile": "\u2323", "smt": "\u2AAA", "smte": "\u2AAC", "smtes": "\u2AAC\uFE00", "SOFTcy": "\u042C", "softcy": "\u044C", "solbar": "\u233F", "solb": "\u29C4", "sol": "/", "Sopf": "\uD835\uDD4A", "sopf": "\uD835\uDD64", "spades": "\u2660", "spadesuit": "\u2660", "spar": "\u2225", "sqcap": "\u2293", "sqcaps": "\u2293\uFE00", "sqcup": "\u2294", "sqcups": "\u2294\uFE00", "Sqrt": "\u221A", "sqsub": "\u228F", "sqsube": "\u2291", "sqsubset": "\u228F", "sqsubseteq": "\u2291", "sqsup": "\u2290", "sqsupe": "\u2292", "sqsupset": "\u2290", "sqsupseteq": "\u2292", "square": "\u25A1", "Square": "\u25A1", "SquareIntersection": "\u2293", "SquareSubset": "\u228F", "SquareSubsetEqual": "\u2291", "SquareSuperset": "\u2290", "SquareSupersetEqual": "\u2292", "SquareUnion": "\u2294", "squarf": "\u25AA", "squ": "\u25A1", "squf": "\u25AA", "srarr": "\u2192", "Sscr": "\uD835\uDCAE", "sscr": "\uD835\uDCC8", "ssetmn": "\u2216", "ssmile": "\u2323", "sstarf": "\u22C6", "Star": "\u22C6", "star": "\u2606", "starf": "\u2605", "straightepsilon": "\u03F5", "straightphi": "\u03D5", "strns": "\u00AF", "sub": "\u2282", "Sub": "\u22D0", "subdot": "\u2ABD", "subE": "\u2AC5", "sube": "\u2286", "subedot": "\u2AC3", "submult": "\u2AC1", "subnE": "\u2ACB", "subne": "\u228A", "subplus": "\u2ABF", "subrarr": "\u2979", "subset": "\u2282", "Subset": "\u22D0", "subseteq": "\u2286", "subseteqq": "\u2AC5", "SubsetEqual": "\u2286", "subsetneq": "\u228A", "subsetneqq": "\u2ACB", "subsim": "\u2AC7", "subsub": "\u2AD5", "subsup": "\u2AD3", "succapprox": "\u2AB8", "succ": "\u227B", "succcurlyeq": "\u227D", "Succeeds": "\u227B", "SucceedsEqual": "\u2AB0", "SucceedsSlantEqual": "\u227D", "SucceedsTilde": "\u227F", "succeq": "\u2AB0", "succnapprox": "\u2ABA", "succneqq": "\u2AB6", "succnsim": "\u22E9", "succsim": "\u227F", "SuchThat": "\u220B", "sum": "\u2211", "Sum": "\u2211", "sung": "\u266A", "sup1": "\u00B9", "sup2": "\u00B2", "sup3": "\u00B3", "sup": "\u2283", "Sup": "\u22D1", "supdot": "\u2ABE", "supdsub": "\u2AD8", "supE": "\u2AC6", "supe": "\u2287", "supedot": "\u2AC4", "Superset": "\u2283", "SupersetEqual": "\u2287", "suphsol": "\u27C9", "suphsub": "\u2AD7", "suplarr": "\u297B", "supmult": "\u2AC2", "supnE": "\u2ACC", "supne": "\u228B", "supplus": "\u2AC0", "supset": "\u2283", "Supset": "\u22D1", "supseteq": "\u2287", "supseteqq": "\u2AC6", "supsetneq": "\u228B", "supsetneqq": "\u2ACC", "supsim": "\u2AC8", "supsub": "\u2AD4", "supsup": "\u2AD6", "swarhk": "\u2926", "swarr": "\u2199", "swArr": "\u21D9", "swarrow": "\u2199", "swnwar": "\u292A", "szlig": "\u00DF", "Tab": "\t", "target": "\u2316", "Tau": "\u03A4", "tau": "\u03C4", "tbrk": "\u23B4", "Tcaron": "\u0164", "tcaron": "\u0165", "Tcedil": "\u0162", "tcedil": "\u0163", "Tcy": "\u0422", "tcy": "\u0442", "tdot": "\u20DB", "telrec": "\u2315", "Tfr": "\uD835\uDD17", "tfr": "\uD835\uDD31", "there4": "\u2234", "therefore": "\u2234", "Therefore": "\u2234", "Theta": "\u0398", "theta": "\u03B8", "thetasym": "\u03D1", "thetav": "\u03D1", "thickapprox": "\u2248", "thicksim": "\u223C", "ThickSpace": "\u205F\u200A", "ThinSpace": "\u2009", "thinsp": "\u2009", "thkap": "\u2248", "thksim": "\u223C", "THORN": "\u00DE", "thorn": "\u00FE", "tilde": "\u02DC", "Tilde": "\u223C", "TildeEqual": "\u2243", "TildeFullEqual": "\u2245", "TildeTilde": "\u2248", "timesbar": "\u2A31", "timesb": "\u22A0", "times": "\u00D7", "timesd": "\u2A30", "tint": "\u222D", "toea": "\u2928", "topbot": "\u2336", "topcir": "\u2AF1", "top": "\u22A4", "Topf": "\uD835\uDD4B", "topf": "\uD835\uDD65", "topfork": "\u2ADA", "tosa": "\u2929", "tprime": "\u2034", "trade": "\u2122", "TRADE": "\u2122", "triangle": "\u25B5", "triangledown": "\u25BF", "triangleleft": "\u25C3", "trianglelefteq": "\u22B4", "triangleq": "\u225C", "triangleright": "\u25B9", "trianglerighteq": "\u22B5", "tridot": "\u25EC", "trie": "\u225C", "triminus": "\u2A3A", "TripleDot": "\u20DB", "triplus": "\u2A39", "trisb": "\u29CD", "tritime": "\u2A3B", "trpezium": "\u23E2", "Tscr": "\uD835\uDCAF", "tscr": "\uD835\uDCC9", "TScy": "\u0426", "tscy": "\u0446", "TSHcy": "\u040B", "tshcy": "\u045B", "Tstrok": "\u0166", "tstrok": "\u0167", "twixt": "\u226C", "twoheadleftarrow": "\u219E", "twoheadrightarrow": "\u21A0", "Uacute": "\u00DA", "uacute": "\u00FA", "uarr": "\u2191", "Uarr": "\u219F", "uArr": "\u21D1", "Uarrocir": "\u2949", "Ubrcy": "\u040E", "ubrcy": "\u045E", "Ubreve": "\u016C", "ubreve": "\u016D", "Ucirc": "\u00DB", "ucirc": "\u00FB", "Ucy": "\u0423", "ucy": "\u0443", "udarr": "\u21C5", "Udblac": "\u0170", "udblac": "\u0171", "udhar": "\u296E", "ufisht": "\u297E", "Ufr": "\uD835\uDD18", "ufr": "\uD835\uDD32", "Ugrave": "\u00D9", "ugrave": "\u00F9", "uHar": "\u2963", "uharl": "\u21BF", "uharr": "\u21BE", "uhblk": "\u2580", "ulcorn": "\u231C", "ulcorner": "\u231C", "ulcrop": "\u230F", "ultri": "\u25F8", "Umacr": "\u016A", "umacr": "\u016B", "uml": "\u00A8", "UnderBar": "_", "UnderBrace": "\u23DF", "UnderBracket": "\u23B5", "UnderParenthesis": "\u23DD", "Union": "\u22C3", "UnionPlus": "\u228E", "Uogon": "\u0172", "uogon": "\u0173", "Uopf": "\uD835\uDD4C", "uopf": "\uD835\uDD66", "UpArrowBar": "\u2912", "uparrow": "\u2191", "UpArrow": "\u2191", "Uparrow": "\u21D1", "UpArrowDownArrow": "\u21C5", "updownarrow": "\u2195", "UpDownArrow": "\u2195", "Updownarrow": "\u21D5", "UpEquilibrium": "\u296E", "upharpoonleft": "\u21BF", "upharpoonright": "\u21BE", "uplus": "\u228E", "UpperLeftArrow": "\u2196", "UpperRightArrow": "\u2197", "upsi": "\u03C5", "Upsi": "\u03D2", "upsih": "\u03D2", "Upsilon": "\u03A5", "upsilon": "\u03C5", "UpTeeArrow": "\u21A5", "UpTee": "\u22A5", "upuparrows": "\u21C8", "urcorn": "\u231D", "urcorner": "\u231D", "urcrop": "\u230E", "Uring": "\u016E", "uring": "\u016F", "urtri": "\u25F9", "Uscr": "\uD835\uDCB0", "uscr": "\uD835\uDCCA", "utdot": "\u22F0", "Utilde": "\u0168", "utilde": "\u0169", "utri": "\u25B5", "utrif": "\u25B4", "uuarr": "\u21C8", "Uuml": "\u00DC", "uuml": "\u00FC", "uwangle": "\u29A7", "vangrt": "\u299C", "varepsilon": "\u03F5", "varkappa": "\u03F0", "varnothing": "\u2205", "varphi": "\u03D5", "varpi": "\u03D6", "varpropto": "\u221D", "varr": "\u2195", "vArr": "\u21D5", "varrho": "\u03F1", "varsigma": "\u03C2", "varsubsetneq": "\u228A\uFE00", "varsubsetneqq": "\u2ACB\uFE00", "varsupsetneq": "\u228B\uFE00", "varsupsetneqq": "\u2ACC\uFE00", "vartheta": "\u03D1", "vartriangleleft": "\u22B2", "vartriangleright": "\u22B3", "vBar": "\u2AE8", "Vbar": "\u2AEB", "vBarv": "\u2AE9", "Vcy": "\u0412", "vcy": "\u0432", "vdash": "\u22A2", "vDash": "\u22A8", "Vdash": "\u22A9", "VDash": "\u22AB", "Vdashl": "\u2AE6", "veebar": "\u22BB", "vee": "\u2228", "Vee": "\u22C1", "veeeq": "\u225A", "vellip": "\u22EE", "verbar": "|", "Verbar": "\u2016", "vert": "|", "Vert": "\u2016", "VerticalBar": "\u2223", "VerticalLine": "|", "VerticalSeparator": "\u2758", "VerticalTilde": "\u2240", "VeryThinSpace": "\u200A", "Vfr": "\uD835\uDD19", "vfr": "\uD835\uDD33", "vltri": "\u22B2", "vnsub": "\u2282\u20D2", "vnsup": "\u2283\u20D2", "Vopf": "\uD835\uDD4D", "vopf": "\uD835\uDD67", "vprop": "\u221D", "vrtri": "\u22B3", "Vscr": "\uD835\uDCB1", "vscr": "\uD835\uDCCB", "vsubnE": "\u2ACB\uFE00", "vsubne": "\u228A\uFE00", "vsupnE": "\u2ACC\uFE00", "vsupne": "\u228B\uFE00", "Vvdash": "\u22AA", "vzigzag": "\u299A", "Wcirc": "\u0174", "wcirc": "\u0175", "wedbar": "\u2A5F", "wedge": "\u2227", "Wedge": "\u22C0", "wedgeq": "\u2259", "weierp": "\u2118", "Wfr": "\uD835\uDD1A", "wfr": "\uD835\uDD34", "Wopf": "\uD835\uDD4E", "wopf": "\uD835\uDD68", "wp": "\u2118", "wr": "\u2240", "wreath": "\u2240", "Wscr": "\uD835\uDCB2", "wscr": "\uD835\uDCCC", "xcap": "\u22C2", "xcirc": "\u25EF", "xcup": "\u22C3", "xdtri": "\u25BD", "Xfr": "\uD835\uDD1B", "xfr": "\uD835\uDD35", "xharr": "\u27F7", "xhArr": "\u27FA", "Xi": "\u039E", "xi": "\u03BE", "xlarr": "\u27F5", "xlArr": "\u27F8", "xmap": "\u27FC", "xnis": "\u22FB", "xodot": "\u2A00", "Xopf": "\uD835\uDD4F", "xopf": "\uD835\uDD69", "xoplus": "\u2A01", "xotime": "\u2A02", "xrarr": "\u27F6", "xrArr": "\u27F9", "Xscr": "\uD835\uDCB3", "xscr": "\uD835\uDCCD", "xsqcup": "\u2A06", "xuplus": "\u2A04", "xutri": "\u25B3", "xvee": "\u22C1", "xwedge": "\u22C0", "Yacute": "\u00DD", "yacute": "\u00FD", "YAcy": "\u042F", "yacy": "\u044F", "Ycirc": "\u0176", "ycirc": "\u0177", "Ycy": "\u042B", "ycy": "\u044B", "yen": "\u00A5", "Yfr": "\uD835\uDD1C", "yfr": "\uD835\uDD36", "YIcy": "\u0407", "yicy": "\u0457", "Yopf": "\uD835\uDD50", "yopf": "\uD835\uDD6A", "Yscr": "\uD835\uDCB4", "yscr": "\uD835\uDCCE", "YUcy": "\u042E", "yucy": "\u044E", "yuml": "\u00FF", "Yuml": "\u0178", "Zacute": "\u0179", "zacute": "\u017A", "Zcaron": "\u017D", "zcaron": "\u017E", "Zcy": "\u0417", "zcy": "\u0437", "Zdot": "\u017B", "zdot": "\u017C", "zeetrf": "\u2128", "ZeroWidthSpace": "\u200B", "Zeta": "\u0396", "zeta": "\u03B6", "zfr": "\uD835\uDD37", "Zfr": "\u2128", "ZHcy": "\u0416", "zhcy": "\u0436", "zigrarr": "\u21DD", "zopf": "\uD835\uDD6B", "Zopf": "\u2124", "Zscr": "\uD835\uDCB5", "zscr": "\uD835\uDCCF", "zwj": "\u200D", "zwnj": "\u200C" } - -},{}],53:[function(require,module,exports){ -'use strict'; - - -//////////////////////////////////////////////////////////////////////////////// -// Helpers - -// Merge objects -// -function assign(obj /*from1, from2, from3, ...*/) { - var sources = Array.prototype.slice.call(arguments, 1); - - sources.forEach(function (source) { - if (!source) { return; } - - Object.keys(source).forEach(function (key) { - obj[key] = source[key]; - }); - }); - - return obj; -} - -function _class(obj) { return Object.prototype.toString.call(obj); } -function isString(obj) { return _class(obj) === '[object String]'; } -function isObject(obj) { return _class(obj) === '[object Object]'; } -function isRegExp(obj) { return _class(obj) === '[object RegExp]'; } -function isFunction(obj) { return _class(obj) === '[object Function]'; } - - -function escapeRE(str) { return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&'); } - -//////////////////////////////////////////////////////////////////////////////// - - -var defaultOptions = { - fuzzyLink: true, - fuzzyEmail: true, - fuzzyIP: false -}; - - -function isOptionsObj(obj) { - return Object.keys(obj || {}).reduce(function (acc, k) { - return acc || defaultOptions.hasOwnProperty(k); - }, false); -} - - -var defaultSchemas = { - 'http:': { - validate: function (text, pos, self) { - var tail = text.slice(pos); - - if (!self.re.http) { - // compile lazily, because "host"-containing variables can change on tlds update. - self.re.http = new RegExp( - '^\\/\\/' + self.re.src_auth + self.re.src_host_port_strict + self.re.src_path, 'i' - ); - } - if (self.re.http.test(tail)) { - return tail.match(self.re.http)[0].length; - } - return 0; - } - }, - 'https:': 'http:', - 'ftp:': 'http:', - '//': { - validate: function (text, pos, self) { - var tail = text.slice(pos); - - if (!self.re.no_http) { - // compile lazily, because "host"-containing variables can change on tlds update. - self.re.no_http = new RegExp( - '^' + - self.re.src_auth + - // Don't allow single-level domains, because of false positives like '//test' - // with code comments - '(?:localhost|(?:(?:' + self.re.src_domain + ')\\.)+' + self.re.src_domain_root + ')' + - self.re.src_port + - self.re.src_host_terminator + - self.re.src_path, - - 'i' - ); - } - - if (self.re.no_http.test(tail)) { - // should not be `://` & `///`, that protects from errors in protocol name - if (pos >= 3 && text[pos - 3] === ':') { return 0; } - if (pos >= 3 && text[pos - 3] === '/') { return 0; } - return tail.match(self.re.no_http)[0].length; - } - return 0; - } - }, - 'mailto:': { - validate: function (text, pos, self) { - var tail = text.slice(pos); - - if (!self.re.mailto) { - self.re.mailto = new RegExp( - '^' + self.re.src_email_name + '@' + self.re.src_host_strict, 'i' - ); - } - if (self.re.mailto.test(tail)) { - return tail.match(self.re.mailto)[0].length; - } - return 0; - } - } -}; - -/*eslint-disable max-len*/ - -// RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js) -var tlds_2ch_src_re = 'a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]'; - -// DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead -var tlds_default = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|'); - -/*eslint-enable max-len*/ - -//////////////////////////////////////////////////////////////////////////////// - -function resetScanCache(self) { - self.__index__ = -1; - self.__text_cache__ = ''; -} - -function createValidator(re) { - return function (text, pos) { - var tail = text.slice(pos); - - if (re.test(tail)) { - return tail.match(re)[0].length; - } - return 0; - }; -} - -function createNormalizer() { - return function (match, self) { - self.normalize(match); - }; -} - -// Schemas compiler. Build regexps. -// -function compile(self) { - - // Load & clone RE patterns. - var re = self.re = require('./lib/re')(self.__opts__); - - // Define dynamic patterns - var tlds = self.__tlds__.slice(); - - self.onCompile(); - - if (!self.__tlds_replaced__) { - tlds.push(tlds_2ch_src_re); - } - tlds.push(re.src_xn); - - re.src_tlds = tlds.join('|'); - - function untpl(tpl) { return tpl.replace('%TLDS%', re.src_tlds); } - - re.email_fuzzy = RegExp(untpl(re.tpl_email_fuzzy), 'i'); - re.link_fuzzy = RegExp(untpl(re.tpl_link_fuzzy), 'i'); - re.link_no_ip_fuzzy = RegExp(untpl(re.tpl_link_no_ip_fuzzy), 'i'); - re.host_fuzzy_test = RegExp(untpl(re.tpl_host_fuzzy_test), 'i'); - - // - // Compile each schema - // - - var aliases = []; - - self.__compiled__ = {}; // Reset compiled data - - function schemaError(name, val) { - throw new Error('(LinkifyIt) Invalid schema "' + name + '": ' + val); - } - - Object.keys(self.__schemas__).forEach(function (name) { - var val = self.__schemas__[name]; - - // skip disabled methods - if (val === null) { return; } - - var compiled = { validate: null, link: null }; - - self.__compiled__[name] = compiled; - - if (isObject(val)) { - if (isRegExp(val.validate)) { - compiled.validate = createValidator(val.validate); - } else if (isFunction(val.validate)) { - compiled.validate = val.validate; - } else { - schemaError(name, val); - } - - if (isFunction(val.normalize)) { - compiled.normalize = val.normalize; - } else if (!val.normalize) { - compiled.normalize = createNormalizer(); - } else { - schemaError(name, val); - } - - return; - } - - if (isString(val)) { - aliases.push(name); - return; - } - - schemaError(name, val); - }); - - // - // Compile postponed aliases - // - - aliases.forEach(function (alias) { - if (!self.__compiled__[self.__schemas__[alias]]) { - // Silently fail on missed schemas to avoid errons on disable. - // schemaError(alias, self.__schemas__[alias]); - return; - } - - self.__compiled__[alias].validate = - self.__compiled__[self.__schemas__[alias]].validate; - self.__compiled__[alias].normalize = - self.__compiled__[self.__schemas__[alias]].normalize; - }); - - // - // Fake record for guessed links - // - self.__compiled__[''] = { validate: null, normalize: createNormalizer() }; - - // - // Build schema condition - // - var slist = Object.keys(self.__compiled__) - .filter(function (name) { - // Filter disabled & fake schemas - return name.length > 0 && self.__compiled__[name]; - }) - .map(escapeRE) - .join('|'); - // (?!_) cause 1.5x slowdown - self.re.schema_test = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'i'); - self.re.schema_search = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'ig'); - - self.re.pretest = RegExp( - '(' + self.re.schema_test.source + ')|(' + self.re.host_fuzzy_test.source + ')|@', - 'i' - ); - - // - // Cleanup - // - - resetScanCache(self); -} - -/** - * class Match - * - * Match result. Single element of array, returned by [[LinkifyIt#match]] - **/ -function Match(self, shift) { - var start = self.__index__, - end = self.__last_index__, - text = self.__text_cache__.slice(start, end); - - /** - * Match#schema -> String - * - * Prefix (protocol) for matched string. - **/ - this.schema = self.__schema__.toLowerCase(); - /** - * Match#index -> Number - * - * First position of matched string. - **/ - this.index = start + shift; - /** - * Match#lastIndex -> Number - * - * Next position after matched string. - **/ - this.lastIndex = end + shift; - /** - * Match#raw -> String - * - * Matched string. - **/ - this.raw = text; - /** - * Match#text -> String - * - * Notmalized text of matched string. - **/ - this.text = text; - /** - * Match#url -> String - * - * Normalized url of matched string. - **/ - this.url = text; -} - -function createMatch(self, shift) { - var match = new Match(self, shift); - - self.__compiled__[match.schema].normalize(match, self); - - return match; -} - - -/** - * class LinkifyIt - **/ - -/** - * new LinkifyIt(schemas, options) - * - schemas (Object): Optional. Additional schemas to validate (prefix/validator) - * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } - * - * Creates new linkifier instance with optional additional schemas. - * Can be called without `new` keyword for convenience. - * - * By default understands: - * - * - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links - * - "fuzzy" links and emails (example.com, foo@bar.com). - * - * `schemas` is an object, where each key/value describes protocol/rule: - * - * - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:` - * for example). `linkify-it` makes shure that prefix is not preceeded with - * alphanumeric char and symbols. Only whitespaces and punctuation allowed. - * - __value__ - rule to check tail after link prefix - * - _String_ - just alias to existing rule - * - _Object_ - * - _validate_ - validator function (should return matched length on success), - * or `RegExp`. - * - _normalize_ - optional function to normalize text & url of matched result - * (for example, for @twitter mentions). - * - * `options`: - * - * - __fuzzyLink__ - recognige URL-s without `http(s):` prefix. Default `true`. - * - __fuzzyIP__ - allow IPs in fuzzy links above. Can conflict with some texts - * like version numbers. Default `false`. - * - __fuzzyEmail__ - recognize emails without `mailto:` prefix. - * - **/ -function LinkifyIt(schemas, options) { - if (!(this instanceof LinkifyIt)) { - return new LinkifyIt(schemas, options); - } - - if (!options) { - if (isOptionsObj(schemas)) { - options = schemas; - schemas = {}; - } - } - - this.__opts__ = assign({}, defaultOptions, options); - - // Cache last tested result. Used to skip repeating steps on next `match` call. - this.__index__ = -1; - this.__last_index__ = -1; // Next scan position - this.__schema__ = ''; - this.__text_cache__ = ''; - - this.__schemas__ = assign({}, defaultSchemas, schemas); - this.__compiled__ = {}; - - this.__tlds__ = tlds_default; - this.__tlds_replaced__ = false; - - this.re = {}; - - compile(this); -} - - -/** chainable - * LinkifyIt#add(schema, definition) - * - schema (String): rule name (fixed pattern prefix) - * - definition (String|RegExp|Object): schema definition - * - * Add new rule definition. See constructor description for details. - **/ -LinkifyIt.prototype.add = function add(schema, definition) { - this.__schemas__[schema] = definition; - compile(this); - return this; -}; - - -/** chainable - * LinkifyIt#set(options) - * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } - * - * Set recognition options for links without schema. - **/ -LinkifyIt.prototype.set = function set(options) { - this.__opts__ = assign(this.__opts__, options); - return this; -}; - - -/** - * LinkifyIt#test(text) -> Boolean - * - * Searches linkifiable pattern and returns `true` on success or `false` on fail. - **/ -LinkifyIt.prototype.test = function test(text) { - // Reset scan cache - this.__text_cache__ = text; - this.__index__ = -1; - - if (!text.length) { return false; } - - var m, ml, me, len, shift, next, re, tld_pos, at_pos; - - // try to scan for link with schema - that's the most simple rule - if (this.re.schema_test.test(text)) { - re = this.re.schema_search; - re.lastIndex = 0; - while ((m = re.exec(text)) !== null) { - len = this.testSchemaAt(text, m[2], re.lastIndex); - if (len) { - this.__schema__ = m[2]; - this.__index__ = m.index + m[1].length; - this.__last_index__ = m.index + m[0].length + len; - break; - } - } - } - - if (this.__opts__.fuzzyLink && this.__compiled__['http:']) { - // guess schemaless links - tld_pos = text.search(this.re.host_fuzzy_test); - if (tld_pos >= 0) { - // if tld is located after found link - no need to check fuzzy pattern - if (this.__index__ < 0 || tld_pos < this.__index__) { - if ((ml = text.match(this.__opts__.fuzzyIP ? this.re.link_fuzzy : this.re.link_no_ip_fuzzy)) !== null) { + state.pos += idx; - shift = ml.index + ml[1].length; + return true; +};*/ + }, + {}, + ], + 50: [ + function (require, module, exports) { + // Clean up tokens after emphasis and strikethrough postprocessing: + // merge adjacent text nodes into one and re-calculate all token levels + // + // This is necessary because initially emphasis delimiter markers (*, _, ~) + // are treated as their own separate text tokens. Then emphasis rule either + // leaves them as text (needed to merge with adjacent text) or turns them + // into opening/closing tags (which messes up levels inside). + // + 'use strict' + + module.exports = function text_collapse(state) { + var curr, + last, + level = 0, + tokens = state.tokens, + max = state.tokens.length + + for (curr = last = 0; curr < max; curr++) { + // re-calculate levels after emphasis/strikethrough turns some text nodes + // into opening/closing tags + if (tokens[curr].nesting < 0) level-- // closing tag + tokens[curr].level = level + if (tokens[curr].nesting > 0) level++ // opening tag + + if (tokens[curr].type === 'text' && curr + 1 < max && tokens[curr + 1].type === 'text') { + // collapse two adjacent text nodes + tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content + } else { + if (curr !== last) { + tokens[last] = tokens[curr] + } + + last++ + } + } - if (this.__index__ < 0 || shift < this.__index__) { - this.__schema__ = ''; - this.__index__ = shift; - this.__last_index__ = ml.index + ml[0].length; + if (curr !== last) { + tokens.length = last + } + } + }, + {}, + ], + 51: [ + function (require, module, exports) { + // Token class + + 'use strict' + + /** + * class Token + **/ + + /** + * new Token(type, tag, nesting) + * + * Create new token and fill passed properties. + **/ + function Token(type, tag, nesting) { + /** + * Token#type -> String + * + * Type of the token (string, e.g. "paragraph_open") + **/ + this.type = type + + /** + * Token#tag -> String + * + * html tag name, e.g. "p" + **/ + this.tag = tag + + /** + * Token#attrs -> Array + * + * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` + **/ + this.attrs = null + + /** + * Token#map -> Array + * + * Source map info. Format: `[ line_begin, line_end ]` + **/ + this.map = null + + /** + * Token#nesting -> Number + * + * Level change (number in {-1, 0, 1} set), where: + * + * - `1` means the tag is opening + * - `0` means the tag is self-closing + * - `-1` means the tag is closing + **/ + this.nesting = nesting + + /** + * Token#level -> Number + * + * nesting level, the same as `state.level` + **/ + this.level = 0 + + /** + * Token#children -> Array + * + * An array of child nodes (inline and img tokens) + **/ + this.children = null + + /** + * Token#content -> String + * + * In a case of self-closing tag (code, html, fence, etc.), + * it has contents of this tag. + **/ + this.content = '' + + /** + * Token#markup -> String + * + * '*' or '_' for emphasis, fence string for fence, etc. + **/ + this.markup = '' + + /** + * Token#info -> String + * + * fence infostring + **/ + this.info = '' + + /** + * Token#meta -> Object + * + * A place for plugins to store an arbitrary data + **/ + this.meta = null + + /** + * Token#block -> Boolean + * + * True for block-level tokens, false for inline tokens. + * Used in renderer to calculate line breaks + **/ + this.block = false + + /** + * Token#hidden -> Boolean + * + * If it's true, ignore this element when rendering. Used for tight lists + * to hide paragraphs. + **/ + this.hidden = false } - } - } - } - } - - if (this.__opts__.fuzzyEmail && this.__compiled__['mailto:']) { - // guess schemaless emails - at_pos = text.indexOf('@'); - if (at_pos >= 0) { - // We can't skip this check, because this cases are possible: - // 192.168.1.1@gmail.com, my.in@example.com - if ((me = text.match(this.re.email_fuzzy)) !== null) { - - shift = me.index + me[1].length; - next = me.index + me[0].length; - - if (this.__index__ < 0 || shift < this.__index__ || - (shift === this.__index__ && next > this.__last_index__)) { - this.__schema__ = 'mailto:'; - this.__index__ = shift; - this.__last_index__ = next; - } - } - } - } - - return this.__index__ >= 0; -}; - - -/** - * LinkifyIt#pretest(text) -> Boolean - * - * Very quick check, that can give false positives. Returns true if link MAY BE - * can exists. Can be used for speed optimization, when you need to check that - * link NOT exists. - **/ -LinkifyIt.prototype.pretest = function pretest(text) { - return this.re.pretest.test(text); -}; - - -/** - * LinkifyIt#testSchemaAt(text, name, position) -> Number - * - text (String): text to scan - * - name (String): rule (schema) name - * - position (Number): text offset to check from - * - * Similar to [[LinkifyIt#test]] but checks only specific protocol tail exactly - * at given position. Returns length of found pattern (0 on fail). - **/ -LinkifyIt.prototype.testSchemaAt = function testSchemaAt(text, schema, pos) { - // If not supported schema check requested - terminate - if (!this.__compiled__[schema.toLowerCase()]) { - return 0; - } - return this.__compiled__[schema.toLowerCase()].validate(text, pos, this); -}; - - -/** - * LinkifyIt#match(text) -> Array|null - * - * Returns array of found link descriptions or `null` on fail. We strongly - * recommend to use [[LinkifyIt#test]] first, for best speed. - * - * ##### Result match description - * - * - __schema__ - link schema, can be empty for fuzzy links, or `//` for - * protocol-neutral links. - * - __index__ - offset of matched text - * - __lastIndex__ - index of next char after mathch end - * - __raw__ - matched text - * - __text__ - normalized text - * - __url__ - link, generated from matched text - **/ -LinkifyIt.prototype.match = function match(text) { - var shift = 0, result = []; - - // Try to take previous element from cache, if .test() called before - if (this.__index__ >= 0 && this.__text_cache__ === text) { - result.push(createMatch(this, shift)); - shift = this.__last_index__; - } - - // Cut head if cache was used - var tail = shift ? text.slice(shift) : text; - - // Scan string until end reached - while (this.test(tail)) { - result.push(createMatch(this, shift)); - - tail = tail.slice(this.__last_index__); - shift += this.__last_index__; - } - - if (result.length) { - return result; - } - - return null; -}; - - -/** chainable - * LinkifyIt#tlds(list [, keepOld]) -> this - * - list (Array): list of tlds - * - keepOld (Boolean): merge with current list if `true` (`false` by default) - * - * Load (or merge) new tlds list. Those are user for fuzzy links (without prefix) - * to avoid false positives. By default this algorythm used: - * - * - hostname with any 2-letter root zones are ok. - * - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф - * are ok. - * - encoded (`xn--...`) root zones are ok. - * - * If list is replaced, then exact match for 2-chars root zones will be checked. - **/ -LinkifyIt.prototype.tlds = function tlds(list, keepOld) { - list = Array.isArray(list) ? list : [ list ]; - - if (!keepOld) { - this.__tlds__ = list.slice(); - this.__tlds_replaced__ = true; - compile(this); - return this; - } - - this.__tlds__ = this.__tlds__.concat(list) - .sort() - .filter(function (el, idx, arr) { - return el !== arr[idx - 1]; - }) - .reverse(); - - compile(this); - return this; -}; - -/** - * LinkifyIt#normalize(match) - * - * Default normalizer (if schema does not define it's own). - **/ -LinkifyIt.prototype.normalize = function normalize(match) { - - // Do minimal possible changes by default. Need to collect feedback prior - // to move forward https://github.com/markdown-it/linkify-it/issues/1 - - if (!match.schema) { match.url = 'http://' + match.url; } - - if (match.schema === 'mailto:' && !/^mailto:/i.test(match.url)) { - match.url = 'mailto:' + match.url; - } -}; - - -/** - * LinkifyIt#onCompile() - * - * Override to modify basic RegExp-s. - **/ -LinkifyIt.prototype.onCompile = function onCompile() { -}; - - -module.exports = LinkifyIt; - -},{"./lib/re":54}],54:[function(require,module,exports){ -'use strict'; - - -module.exports = function (opts) { - var re = {}; - - // Use direct extract instead of `regenerate` to reduse browserified size - re.src_Any = require('uc.micro/properties/Any/regex').source; - re.src_Cc = require('uc.micro/categories/Cc/regex').source; - re.src_Z = require('uc.micro/categories/Z/regex').source; - re.src_P = require('uc.micro/categories/P/regex').source; - // \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation) - re.src_ZPCc = [ re.src_Z, re.src_P, re.src_Cc ].join('|'); + /** + * Token.attrIndex(name) -> Number + * + * Search attribute index by name. + **/ + Token.prototype.attrIndex = function attrIndex(name) { + var attrs, i, len - // \p{\Z\Cc} (white spaces + control) - re.src_ZCc = [ re.src_Z, re.src_Cc ].join('|'); + if (!this.attrs) { + return -1 + } - // Experimental. List of chars, completely prohibited in links - // because can separate it from other part of text - var text_separators = '[><\uff5c]'; + attrs = this.attrs - // All possible word characters (everything without punctuation, spaces & controls) - // Defined via punctuation & spaces to save space - // Should be something like \p{\L\N\S\M} (\w but without `_`) - re.src_pseudo_letter = '(?:(?!' + text_separators + '|' + re.src_ZPCc + ')' + re.src_Any + ')'; - // The same as abothe but without [0-9] - // var src_pseudo_letter_non_d = '(?:(?![0-9]|' + src_ZPCc + ')' + src_Any + ')'; + for (i = 0, len = attrs.length; i < len; i++) { + if (attrs[i][0] === name) { + return i + } + } + return -1 + } - //////////////////////////////////////////////////////////////////////////////// + /** + * Token.attrPush(attrData) + * + * Add `[ name, value ]` attribute to list. Init attrs if necessary + **/ + Token.prototype.attrPush = function attrPush(attrData) { + if (this.attrs) { + this.attrs.push(attrData) + } else { + this.attrs = [attrData] + } + } - re.src_ip4 = + /** + * Token.attrSet(name, value) + * + * Set `name` attribute to `value`. Override old value if exists. + **/ + Token.prototype.attrSet = function attrSet(name, value) { + var idx = this.attrIndex(name), + attrData = [name, value] + + if (idx < 0) { + this.attrPush(attrData) + } else { + this.attrs[idx] = attrData + } + } - '(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'; + /** + * Token.attrGet(name) + * + * Get the value of attribute `name`, or null if it does not exist. + **/ + Token.prototype.attrGet = function attrGet(name) { + var idx = this.attrIndex(name), + value = null + if (idx >= 0) { + value = this.attrs[idx][1] + } + return value + } - // Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch. - re.src_auth = '(?:(?:(?!' + re.src_ZCc + '|[@/\\[\\]()]).)+@)?'; + /** + * Token.attrJoin(name, value) + * + * Join value to existing attribute via space. Or create new attribute if not + * exists. Useful to operate with token classes. + **/ + Token.prototype.attrJoin = function attrJoin(name, value) { + var idx = this.attrIndex(name) + + if (idx < 0) { + this.attrPush([name, value]) + } else { + this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value + } + } - re.src_port = + module.exports = Token + }, + {}, + ], + 52: [ + function (require, module, exports) { + module.exports = { + Aacute: '\u00C1', + aacute: '\u00E1', + Abreve: '\u0102', + abreve: '\u0103', + ac: '\u223E', + acd: '\u223F', + acE: '\u223E\u0333', + Acirc: '\u00C2', + acirc: '\u00E2', + acute: '\u00B4', + Acy: '\u0410', + acy: '\u0430', + AElig: '\u00C6', + aelig: '\u00E6', + af: '\u2061', + Afr: '\uD835\uDD04', + afr: '\uD835\uDD1E', + Agrave: '\u00C0', + agrave: '\u00E0', + alefsym: '\u2135', + aleph: '\u2135', + Alpha: '\u0391', + alpha: '\u03B1', + Amacr: '\u0100', + amacr: '\u0101', + amalg: '\u2A3F', + amp: '&', + AMP: '&', + andand: '\u2A55', + And: '\u2A53', + and: '\u2227', + andd: '\u2A5C', + andslope: '\u2A58', + andv: '\u2A5A', + ang: '\u2220', + ange: '\u29A4', + angle: '\u2220', + angmsdaa: '\u29A8', + angmsdab: '\u29A9', + angmsdac: '\u29AA', + angmsdad: '\u29AB', + angmsdae: '\u29AC', + angmsdaf: '\u29AD', + angmsdag: '\u29AE', + angmsdah: '\u29AF', + angmsd: '\u2221', + angrt: '\u221F', + angrtvb: '\u22BE', + angrtvbd: '\u299D', + angsph: '\u2222', + angst: '\u00C5', + angzarr: '\u237C', + Aogon: '\u0104', + aogon: '\u0105', + Aopf: '\uD835\uDD38', + aopf: '\uD835\uDD52', + apacir: '\u2A6F', + ap: '\u2248', + apE: '\u2A70', + ape: '\u224A', + apid: '\u224B', + apos: "'", + ApplyFunction: '\u2061', + approx: '\u2248', + approxeq: '\u224A', + Aring: '\u00C5', + aring: '\u00E5', + Ascr: '\uD835\uDC9C', + ascr: '\uD835\uDCB6', + Assign: '\u2254', + ast: '*', + asymp: '\u2248', + asympeq: '\u224D', + Atilde: '\u00C3', + atilde: '\u00E3', + Auml: '\u00C4', + auml: '\u00E4', + awconint: '\u2233', + awint: '\u2A11', + backcong: '\u224C', + backepsilon: '\u03F6', + backprime: '\u2035', + backsim: '\u223D', + backsimeq: '\u22CD', + Backslash: '\u2216', + Barv: '\u2AE7', + barvee: '\u22BD', + barwed: '\u2305', + Barwed: '\u2306', + barwedge: '\u2305', + bbrk: '\u23B5', + bbrktbrk: '\u23B6', + bcong: '\u224C', + Bcy: '\u0411', + bcy: '\u0431', + bdquo: '\u201E', + becaus: '\u2235', + because: '\u2235', + Because: '\u2235', + bemptyv: '\u29B0', + bepsi: '\u03F6', + bernou: '\u212C', + Bernoullis: '\u212C', + Beta: '\u0392', + beta: '\u03B2', + beth: '\u2136', + between: '\u226C', + Bfr: '\uD835\uDD05', + bfr: '\uD835\uDD1F', + bigcap: '\u22C2', + bigcirc: '\u25EF', + bigcup: '\u22C3', + bigodot: '\u2A00', + bigoplus: '\u2A01', + bigotimes: '\u2A02', + bigsqcup: '\u2A06', + bigstar: '\u2605', + bigtriangledown: '\u25BD', + bigtriangleup: '\u25B3', + biguplus: '\u2A04', + bigvee: '\u22C1', + bigwedge: '\u22C0', + bkarow: '\u290D', + blacklozenge: '\u29EB', + blacksquare: '\u25AA', + blacktriangle: '\u25B4', + blacktriangledown: '\u25BE', + blacktriangleleft: '\u25C2', + blacktriangleright: '\u25B8', + blank: '\u2423', + blk12: '\u2592', + blk14: '\u2591', + blk34: '\u2593', + block: '\u2588', + bne: '=\u20E5', + bnequiv: '\u2261\u20E5', + bNot: '\u2AED', + bnot: '\u2310', + Bopf: '\uD835\uDD39', + bopf: '\uD835\uDD53', + bot: '\u22A5', + bottom: '\u22A5', + bowtie: '\u22C8', + boxbox: '\u29C9', + boxdl: '\u2510', + boxdL: '\u2555', + boxDl: '\u2556', + boxDL: '\u2557', + boxdr: '\u250C', + boxdR: '\u2552', + boxDr: '\u2553', + boxDR: '\u2554', + boxh: '\u2500', + boxH: '\u2550', + boxhd: '\u252C', + boxHd: '\u2564', + boxhD: '\u2565', + boxHD: '\u2566', + boxhu: '\u2534', + boxHu: '\u2567', + boxhU: '\u2568', + boxHU: '\u2569', + boxminus: '\u229F', + boxplus: '\u229E', + boxtimes: '\u22A0', + boxul: '\u2518', + boxuL: '\u255B', + boxUl: '\u255C', + boxUL: '\u255D', + boxur: '\u2514', + boxuR: '\u2558', + boxUr: '\u2559', + boxUR: '\u255A', + boxv: '\u2502', + boxV: '\u2551', + boxvh: '\u253C', + boxvH: '\u256A', + boxVh: '\u256B', + boxVH: '\u256C', + boxvl: '\u2524', + boxvL: '\u2561', + boxVl: '\u2562', + boxVL: '\u2563', + boxvr: '\u251C', + boxvR: '\u255E', + boxVr: '\u255F', + boxVR: '\u2560', + bprime: '\u2035', + breve: '\u02D8', + Breve: '\u02D8', + brvbar: '\u00A6', + bscr: '\uD835\uDCB7', + Bscr: '\u212C', + bsemi: '\u204F', + bsim: '\u223D', + bsime: '\u22CD', + bsolb: '\u29C5', + bsol: '\\', + bsolhsub: '\u27C8', + bull: '\u2022', + bullet: '\u2022', + bump: '\u224E', + bumpE: '\u2AAE', + bumpe: '\u224F', + Bumpeq: '\u224E', + bumpeq: '\u224F', + Cacute: '\u0106', + cacute: '\u0107', + capand: '\u2A44', + capbrcup: '\u2A49', + capcap: '\u2A4B', + cap: '\u2229', + Cap: '\u22D2', + capcup: '\u2A47', + capdot: '\u2A40', + CapitalDifferentialD: '\u2145', + caps: '\u2229\uFE00', + caret: '\u2041', + caron: '\u02C7', + Cayleys: '\u212D', + ccaps: '\u2A4D', + Ccaron: '\u010C', + ccaron: '\u010D', + Ccedil: '\u00C7', + ccedil: '\u00E7', + Ccirc: '\u0108', + ccirc: '\u0109', + Cconint: '\u2230', + ccups: '\u2A4C', + ccupssm: '\u2A50', + Cdot: '\u010A', + cdot: '\u010B', + cedil: '\u00B8', + Cedilla: '\u00B8', + cemptyv: '\u29B2', + cent: '\u00A2', + centerdot: '\u00B7', + CenterDot: '\u00B7', + cfr: '\uD835\uDD20', + Cfr: '\u212D', + CHcy: '\u0427', + chcy: '\u0447', + check: '\u2713', + checkmark: '\u2713', + Chi: '\u03A7', + chi: '\u03C7', + circ: '\u02C6', + circeq: '\u2257', + circlearrowleft: '\u21BA', + circlearrowright: '\u21BB', + circledast: '\u229B', + circledcirc: '\u229A', + circleddash: '\u229D', + CircleDot: '\u2299', + circledR: '\u00AE', + circledS: '\u24C8', + CircleMinus: '\u2296', + CirclePlus: '\u2295', + CircleTimes: '\u2297', + cir: '\u25CB', + cirE: '\u29C3', + cire: '\u2257', + cirfnint: '\u2A10', + cirmid: '\u2AEF', + cirscir: '\u29C2', + ClockwiseContourIntegral: '\u2232', + CloseCurlyDoubleQuote: '\u201D', + CloseCurlyQuote: '\u2019', + clubs: '\u2663', + clubsuit: '\u2663', + colon: ':', + Colon: '\u2237', + Colone: '\u2A74', + colone: '\u2254', + coloneq: '\u2254', + comma: ',', + commat: '@', + comp: '\u2201', + compfn: '\u2218', + complement: '\u2201', + complexes: '\u2102', + cong: '\u2245', + congdot: '\u2A6D', + Congruent: '\u2261', + conint: '\u222E', + Conint: '\u222F', + ContourIntegral: '\u222E', + copf: '\uD835\uDD54', + Copf: '\u2102', + coprod: '\u2210', + Coproduct: '\u2210', + copy: '\u00A9', + COPY: '\u00A9', + copysr: '\u2117', + CounterClockwiseContourIntegral: '\u2233', + crarr: '\u21B5', + cross: '\u2717', + Cross: '\u2A2F', + Cscr: '\uD835\uDC9E', + cscr: '\uD835\uDCB8', + csub: '\u2ACF', + csube: '\u2AD1', + csup: '\u2AD0', + csupe: '\u2AD2', + ctdot: '\u22EF', + cudarrl: '\u2938', + cudarrr: '\u2935', + cuepr: '\u22DE', + cuesc: '\u22DF', + cularr: '\u21B6', + cularrp: '\u293D', + cupbrcap: '\u2A48', + cupcap: '\u2A46', + CupCap: '\u224D', + cup: '\u222A', + Cup: '\u22D3', + cupcup: '\u2A4A', + cupdot: '\u228D', + cupor: '\u2A45', + cups: '\u222A\uFE00', + curarr: '\u21B7', + curarrm: '\u293C', + curlyeqprec: '\u22DE', + curlyeqsucc: '\u22DF', + curlyvee: '\u22CE', + curlywedge: '\u22CF', + curren: '\u00A4', + curvearrowleft: '\u21B6', + curvearrowright: '\u21B7', + cuvee: '\u22CE', + cuwed: '\u22CF', + cwconint: '\u2232', + cwint: '\u2231', + cylcty: '\u232D', + dagger: '\u2020', + Dagger: '\u2021', + daleth: '\u2138', + darr: '\u2193', + Darr: '\u21A1', + dArr: '\u21D3', + dash: '\u2010', + Dashv: '\u2AE4', + dashv: '\u22A3', + dbkarow: '\u290F', + dblac: '\u02DD', + Dcaron: '\u010E', + dcaron: '\u010F', + Dcy: '\u0414', + dcy: '\u0434', + ddagger: '\u2021', + ddarr: '\u21CA', + DD: '\u2145', + dd: '\u2146', + DDotrahd: '\u2911', + ddotseq: '\u2A77', + deg: '\u00B0', + Del: '\u2207', + Delta: '\u0394', + delta: '\u03B4', + demptyv: '\u29B1', + dfisht: '\u297F', + Dfr: '\uD835\uDD07', + dfr: '\uD835\uDD21', + dHar: '\u2965', + dharl: '\u21C3', + dharr: '\u21C2', + DiacriticalAcute: '\u00B4', + DiacriticalDot: '\u02D9', + DiacriticalDoubleAcute: '\u02DD', + DiacriticalGrave: '`', + DiacriticalTilde: '\u02DC', + diam: '\u22C4', + diamond: '\u22C4', + Diamond: '\u22C4', + diamondsuit: '\u2666', + diams: '\u2666', + die: '\u00A8', + DifferentialD: '\u2146', + digamma: '\u03DD', + disin: '\u22F2', + div: '\u00F7', + divide: '\u00F7', + divideontimes: '\u22C7', + divonx: '\u22C7', + DJcy: '\u0402', + djcy: '\u0452', + dlcorn: '\u231E', + dlcrop: '\u230D', + dollar: '$', + Dopf: '\uD835\uDD3B', + dopf: '\uD835\uDD55', + Dot: '\u00A8', + dot: '\u02D9', + DotDot: '\u20DC', + doteq: '\u2250', + doteqdot: '\u2251', + DotEqual: '\u2250', + dotminus: '\u2238', + dotplus: '\u2214', + dotsquare: '\u22A1', + doublebarwedge: '\u2306', + DoubleContourIntegral: '\u222F', + DoubleDot: '\u00A8', + DoubleDownArrow: '\u21D3', + DoubleLeftArrow: '\u21D0', + DoubleLeftRightArrow: '\u21D4', + DoubleLeftTee: '\u2AE4', + DoubleLongLeftArrow: '\u27F8', + DoubleLongLeftRightArrow: '\u27FA', + DoubleLongRightArrow: '\u27F9', + DoubleRightArrow: '\u21D2', + DoubleRightTee: '\u22A8', + DoubleUpArrow: '\u21D1', + DoubleUpDownArrow: '\u21D5', + DoubleVerticalBar: '\u2225', + DownArrowBar: '\u2913', + downarrow: '\u2193', + DownArrow: '\u2193', + Downarrow: '\u21D3', + DownArrowUpArrow: '\u21F5', + DownBreve: '\u0311', + downdownarrows: '\u21CA', + downharpoonleft: '\u21C3', + downharpoonright: '\u21C2', + DownLeftRightVector: '\u2950', + DownLeftTeeVector: '\u295E', + DownLeftVectorBar: '\u2956', + DownLeftVector: '\u21BD', + DownRightTeeVector: '\u295F', + DownRightVectorBar: '\u2957', + DownRightVector: '\u21C1', + DownTeeArrow: '\u21A7', + DownTee: '\u22A4', + drbkarow: '\u2910', + drcorn: '\u231F', + drcrop: '\u230C', + Dscr: '\uD835\uDC9F', + dscr: '\uD835\uDCB9', + DScy: '\u0405', + dscy: '\u0455', + dsol: '\u29F6', + Dstrok: '\u0110', + dstrok: '\u0111', + dtdot: '\u22F1', + dtri: '\u25BF', + dtrif: '\u25BE', + duarr: '\u21F5', + duhar: '\u296F', + dwangle: '\u29A6', + DZcy: '\u040F', + dzcy: '\u045F', + dzigrarr: '\u27FF', + Eacute: '\u00C9', + eacute: '\u00E9', + easter: '\u2A6E', + Ecaron: '\u011A', + ecaron: '\u011B', + Ecirc: '\u00CA', + ecirc: '\u00EA', + ecir: '\u2256', + ecolon: '\u2255', + Ecy: '\u042D', + ecy: '\u044D', + eDDot: '\u2A77', + Edot: '\u0116', + edot: '\u0117', + eDot: '\u2251', + ee: '\u2147', + efDot: '\u2252', + Efr: '\uD835\uDD08', + efr: '\uD835\uDD22', + eg: '\u2A9A', + Egrave: '\u00C8', + egrave: '\u00E8', + egs: '\u2A96', + egsdot: '\u2A98', + el: '\u2A99', + Element: '\u2208', + elinters: '\u23E7', + ell: '\u2113', + els: '\u2A95', + elsdot: '\u2A97', + Emacr: '\u0112', + emacr: '\u0113', + empty: '\u2205', + emptyset: '\u2205', + EmptySmallSquare: '\u25FB', + emptyv: '\u2205', + EmptyVerySmallSquare: '\u25AB', + emsp13: '\u2004', + emsp14: '\u2005', + emsp: '\u2003', + ENG: '\u014A', + eng: '\u014B', + ensp: '\u2002', + Eogon: '\u0118', + eogon: '\u0119', + Eopf: '\uD835\uDD3C', + eopf: '\uD835\uDD56', + epar: '\u22D5', + eparsl: '\u29E3', + eplus: '\u2A71', + epsi: '\u03B5', + Epsilon: '\u0395', + epsilon: '\u03B5', + epsiv: '\u03F5', + eqcirc: '\u2256', + eqcolon: '\u2255', + eqsim: '\u2242', + eqslantgtr: '\u2A96', + eqslantless: '\u2A95', + Equal: '\u2A75', + equals: '=', + EqualTilde: '\u2242', + equest: '\u225F', + Equilibrium: '\u21CC', + equiv: '\u2261', + equivDD: '\u2A78', + eqvparsl: '\u29E5', + erarr: '\u2971', + erDot: '\u2253', + escr: '\u212F', + Escr: '\u2130', + esdot: '\u2250', + Esim: '\u2A73', + esim: '\u2242', + Eta: '\u0397', + eta: '\u03B7', + ETH: '\u00D0', + eth: '\u00F0', + Euml: '\u00CB', + euml: '\u00EB', + euro: '\u20AC', + excl: '!', + exist: '\u2203', + Exists: '\u2203', + expectation: '\u2130', + exponentiale: '\u2147', + ExponentialE: '\u2147', + fallingdotseq: '\u2252', + Fcy: '\u0424', + fcy: '\u0444', + female: '\u2640', + ffilig: '\uFB03', + fflig: '\uFB00', + ffllig: '\uFB04', + Ffr: '\uD835\uDD09', + ffr: '\uD835\uDD23', + filig: '\uFB01', + FilledSmallSquare: '\u25FC', + FilledVerySmallSquare: '\u25AA', + fjlig: 'fj', + flat: '\u266D', + fllig: '\uFB02', + fltns: '\u25B1', + fnof: '\u0192', + Fopf: '\uD835\uDD3D', + fopf: '\uD835\uDD57', + forall: '\u2200', + ForAll: '\u2200', + fork: '\u22D4', + forkv: '\u2AD9', + Fouriertrf: '\u2131', + fpartint: '\u2A0D', + frac12: '\u00BD', + frac13: '\u2153', + frac14: '\u00BC', + frac15: '\u2155', + frac16: '\u2159', + frac18: '\u215B', + frac23: '\u2154', + frac25: '\u2156', + frac34: '\u00BE', + frac35: '\u2157', + frac38: '\u215C', + frac45: '\u2158', + frac56: '\u215A', + frac58: '\u215D', + frac78: '\u215E', + frasl: '\u2044', + frown: '\u2322', + fscr: '\uD835\uDCBB', + Fscr: '\u2131', + gacute: '\u01F5', + Gamma: '\u0393', + gamma: '\u03B3', + Gammad: '\u03DC', + gammad: '\u03DD', + gap: '\u2A86', + Gbreve: '\u011E', + gbreve: '\u011F', + Gcedil: '\u0122', + Gcirc: '\u011C', + gcirc: '\u011D', + Gcy: '\u0413', + gcy: '\u0433', + Gdot: '\u0120', + gdot: '\u0121', + ge: '\u2265', + gE: '\u2267', + gEl: '\u2A8C', + gel: '\u22DB', + geq: '\u2265', + geqq: '\u2267', + geqslant: '\u2A7E', + gescc: '\u2AA9', + ges: '\u2A7E', + gesdot: '\u2A80', + gesdoto: '\u2A82', + gesdotol: '\u2A84', + gesl: '\u22DB\uFE00', + gesles: '\u2A94', + Gfr: '\uD835\uDD0A', + gfr: '\uD835\uDD24', + gg: '\u226B', + Gg: '\u22D9', + ggg: '\u22D9', + gimel: '\u2137', + GJcy: '\u0403', + gjcy: '\u0453', + gla: '\u2AA5', + gl: '\u2277', + glE: '\u2A92', + glj: '\u2AA4', + gnap: '\u2A8A', + gnapprox: '\u2A8A', + gne: '\u2A88', + gnE: '\u2269', + gneq: '\u2A88', + gneqq: '\u2269', + gnsim: '\u22E7', + Gopf: '\uD835\uDD3E', + gopf: '\uD835\uDD58', + grave: '`', + GreaterEqual: '\u2265', + GreaterEqualLess: '\u22DB', + GreaterFullEqual: '\u2267', + GreaterGreater: '\u2AA2', + GreaterLess: '\u2277', + GreaterSlantEqual: '\u2A7E', + GreaterTilde: '\u2273', + Gscr: '\uD835\uDCA2', + gscr: '\u210A', + gsim: '\u2273', + gsime: '\u2A8E', + gsiml: '\u2A90', + gtcc: '\u2AA7', + gtcir: '\u2A7A', + gt: '>', + GT: '>', + Gt: '\u226B', + gtdot: '\u22D7', + gtlPar: '\u2995', + gtquest: '\u2A7C', + gtrapprox: '\u2A86', + gtrarr: '\u2978', + gtrdot: '\u22D7', + gtreqless: '\u22DB', + gtreqqless: '\u2A8C', + gtrless: '\u2277', + gtrsim: '\u2273', + gvertneqq: '\u2269\uFE00', + gvnE: '\u2269\uFE00', + Hacek: '\u02C7', + hairsp: '\u200A', + half: '\u00BD', + hamilt: '\u210B', + HARDcy: '\u042A', + hardcy: '\u044A', + harrcir: '\u2948', + harr: '\u2194', + hArr: '\u21D4', + harrw: '\u21AD', + Hat: '^', + hbar: '\u210F', + Hcirc: '\u0124', + hcirc: '\u0125', + hearts: '\u2665', + heartsuit: '\u2665', + hellip: '\u2026', + hercon: '\u22B9', + hfr: '\uD835\uDD25', + Hfr: '\u210C', + HilbertSpace: '\u210B', + hksearow: '\u2925', + hkswarow: '\u2926', + hoarr: '\u21FF', + homtht: '\u223B', + hookleftarrow: '\u21A9', + hookrightarrow: '\u21AA', + hopf: '\uD835\uDD59', + Hopf: '\u210D', + horbar: '\u2015', + HorizontalLine: '\u2500', + hscr: '\uD835\uDCBD', + Hscr: '\u210B', + hslash: '\u210F', + Hstrok: '\u0126', + hstrok: '\u0127', + HumpDownHump: '\u224E', + HumpEqual: '\u224F', + hybull: '\u2043', + hyphen: '\u2010', + Iacute: '\u00CD', + iacute: '\u00ED', + ic: '\u2063', + Icirc: '\u00CE', + icirc: '\u00EE', + Icy: '\u0418', + icy: '\u0438', + Idot: '\u0130', + IEcy: '\u0415', + iecy: '\u0435', + iexcl: '\u00A1', + iff: '\u21D4', + ifr: '\uD835\uDD26', + Ifr: '\u2111', + Igrave: '\u00CC', + igrave: '\u00EC', + ii: '\u2148', + iiiint: '\u2A0C', + iiint: '\u222D', + iinfin: '\u29DC', + iiota: '\u2129', + IJlig: '\u0132', + ijlig: '\u0133', + Imacr: '\u012A', + imacr: '\u012B', + image: '\u2111', + ImaginaryI: '\u2148', + imagline: '\u2110', + imagpart: '\u2111', + imath: '\u0131', + Im: '\u2111', + imof: '\u22B7', + imped: '\u01B5', + Implies: '\u21D2', + incare: '\u2105', + in: '\u2208', + infin: '\u221E', + infintie: '\u29DD', + inodot: '\u0131', + intcal: '\u22BA', + int: '\u222B', + Int: '\u222C', + integers: '\u2124', + Integral: '\u222B', + intercal: '\u22BA', + Intersection: '\u22C2', + intlarhk: '\u2A17', + intprod: '\u2A3C', + InvisibleComma: '\u2063', + InvisibleTimes: '\u2062', + IOcy: '\u0401', + iocy: '\u0451', + Iogon: '\u012E', + iogon: '\u012F', + Iopf: '\uD835\uDD40', + iopf: '\uD835\uDD5A', + Iota: '\u0399', + iota: '\u03B9', + iprod: '\u2A3C', + iquest: '\u00BF', + iscr: '\uD835\uDCBE', + Iscr: '\u2110', + isin: '\u2208', + isindot: '\u22F5', + isinE: '\u22F9', + isins: '\u22F4', + isinsv: '\u22F3', + isinv: '\u2208', + it: '\u2062', + Itilde: '\u0128', + itilde: '\u0129', + Iukcy: '\u0406', + iukcy: '\u0456', + Iuml: '\u00CF', + iuml: '\u00EF', + Jcirc: '\u0134', + jcirc: '\u0135', + Jcy: '\u0419', + jcy: '\u0439', + Jfr: '\uD835\uDD0D', + jfr: '\uD835\uDD27', + jmath: '\u0237', + Jopf: '\uD835\uDD41', + jopf: '\uD835\uDD5B', + Jscr: '\uD835\uDCA5', + jscr: '\uD835\uDCBF', + Jsercy: '\u0408', + jsercy: '\u0458', + Jukcy: '\u0404', + jukcy: '\u0454', + Kappa: '\u039A', + kappa: '\u03BA', + kappav: '\u03F0', + Kcedil: '\u0136', + kcedil: '\u0137', + Kcy: '\u041A', + kcy: '\u043A', + Kfr: '\uD835\uDD0E', + kfr: '\uD835\uDD28', + kgreen: '\u0138', + KHcy: '\u0425', + khcy: '\u0445', + KJcy: '\u040C', + kjcy: '\u045C', + Kopf: '\uD835\uDD42', + kopf: '\uD835\uDD5C', + Kscr: '\uD835\uDCA6', + kscr: '\uD835\uDCC0', + lAarr: '\u21DA', + Lacute: '\u0139', + lacute: '\u013A', + laemptyv: '\u29B4', + lagran: '\u2112', + Lambda: '\u039B', + lambda: '\u03BB', + lang: '\u27E8', + Lang: '\u27EA', + langd: '\u2991', + langle: '\u27E8', + lap: '\u2A85', + Laplacetrf: '\u2112', + laquo: '\u00AB', + larrb: '\u21E4', + larrbfs: '\u291F', + larr: '\u2190', + Larr: '\u219E', + lArr: '\u21D0', + larrfs: '\u291D', + larrhk: '\u21A9', + larrlp: '\u21AB', + larrpl: '\u2939', + larrsim: '\u2973', + larrtl: '\u21A2', + latail: '\u2919', + lAtail: '\u291B', + lat: '\u2AAB', + late: '\u2AAD', + lates: '\u2AAD\uFE00', + lbarr: '\u290C', + lBarr: '\u290E', + lbbrk: '\u2772', + lbrace: '{', + lbrack: '[', + lbrke: '\u298B', + lbrksld: '\u298F', + lbrkslu: '\u298D', + Lcaron: '\u013D', + lcaron: '\u013E', + Lcedil: '\u013B', + lcedil: '\u013C', + lceil: '\u2308', + lcub: '{', + Lcy: '\u041B', + lcy: '\u043B', + ldca: '\u2936', + ldquo: '\u201C', + ldquor: '\u201E', + ldrdhar: '\u2967', + ldrushar: '\u294B', + ldsh: '\u21B2', + le: '\u2264', + lE: '\u2266', + LeftAngleBracket: '\u27E8', + LeftArrowBar: '\u21E4', + leftarrow: '\u2190', + LeftArrow: '\u2190', + Leftarrow: '\u21D0', + LeftArrowRightArrow: '\u21C6', + leftarrowtail: '\u21A2', + LeftCeiling: '\u2308', + LeftDoubleBracket: '\u27E6', + LeftDownTeeVector: '\u2961', + LeftDownVectorBar: '\u2959', + LeftDownVector: '\u21C3', + LeftFloor: '\u230A', + leftharpoondown: '\u21BD', + leftharpoonup: '\u21BC', + leftleftarrows: '\u21C7', + leftrightarrow: '\u2194', + LeftRightArrow: '\u2194', + Leftrightarrow: '\u21D4', + leftrightarrows: '\u21C6', + leftrightharpoons: '\u21CB', + leftrightsquigarrow: '\u21AD', + LeftRightVector: '\u294E', + LeftTeeArrow: '\u21A4', + LeftTee: '\u22A3', + LeftTeeVector: '\u295A', + leftthreetimes: '\u22CB', + LeftTriangleBar: '\u29CF', + LeftTriangle: '\u22B2', + LeftTriangleEqual: '\u22B4', + LeftUpDownVector: '\u2951', + LeftUpTeeVector: '\u2960', + LeftUpVectorBar: '\u2958', + LeftUpVector: '\u21BF', + LeftVectorBar: '\u2952', + LeftVector: '\u21BC', + lEg: '\u2A8B', + leg: '\u22DA', + leq: '\u2264', + leqq: '\u2266', + leqslant: '\u2A7D', + lescc: '\u2AA8', + les: '\u2A7D', + lesdot: '\u2A7F', + lesdoto: '\u2A81', + lesdotor: '\u2A83', + lesg: '\u22DA\uFE00', + lesges: '\u2A93', + lessapprox: '\u2A85', + lessdot: '\u22D6', + lesseqgtr: '\u22DA', + lesseqqgtr: '\u2A8B', + LessEqualGreater: '\u22DA', + LessFullEqual: '\u2266', + LessGreater: '\u2276', + lessgtr: '\u2276', + LessLess: '\u2AA1', + lesssim: '\u2272', + LessSlantEqual: '\u2A7D', + LessTilde: '\u2272', + lfisht: '\u297C', + lfloor: '\u230A', + Lfr: '\uD835\uDD0F', + lfr: '\uD835\uDD29', + lg: '\u2276', + lgE: '\u2A91', + lHar: '\u2962', + lhard: '\u21BD', + lharu: '\u21BC', + lharul: '\u296A', + lhblk: '\u2584', + LJcy: '\u0409', + ljcy: '\u0459', + llarr: '\u21C7', + ll: '\u226A', + Ll: '\u22D8', + llcorner: '\u231E', + Lleftarrow: '\u21DA', + llhard: '\u296B', + lltri: '\u25FA', + Lmidot: '\u013F', + lmidot: '\u0140', + lmoustache: '\u23B0', + lmoust: '\u23B0', + lnap: '\u2A89', + lnapprox: '\u2A89', + lne: '\u2A87', + lnE: '\u2268', + lneq: '\u2A87', + lneqq: '\u2268', + lnsim: '\u22E6', + loang: '\u27EC', + loarr: '\u21FD', + lobrk: '\u27E6', + longleftarrow: '\u27F5', + LongLeftArrow: '\u27F5', + Longleftarrow: '\u27F8', + longleftrightarrow: '\u27F7', + LongLeftRightArrow: '\u27F7', + Longleftrightarrow: '\u27FA', + longmapsto: '\u27FC', + longrightarrow: '\u27F6', + LongRightArrow: '\u27F6', + Longrightarrow: '\u27F9', + looparrowleft: '\u21AB', + looparrowright: '\u21AC', + lopar: '\u2985', + Lopf: '\uD835\uDD43', + lopf: '\uD835\uDD5D', + loplus: '\u2A2D', + lotimes: '\u2A34', + lowast: '\u2217', + lowbar: '_', + LowerLeftArrow: '\u2199', + LowerRightArrow: '\u2198', + loz: '\u25CA', + lozenge: '\u25CA', + lozf: '\u29EB', + lpar: '(', + lparlt: '\u2993', + lrarr: '\u21C6', + lrcorner: '\u231F', + lrhar: '\u21CB', + lrhard: '\u296D', + lrm: '\u200E', + lrtri: '\u22BF', + lsaquo: '\u2039', + lscr: '\uD835\uDCC1', + Lscr: '\u2112', + lsh: '\u21B0', + Lsh: '\u21B0', + lsim: '\u2272', + lsime: '\u2A8D', + lsimg: '\u2A8F', + lsqb: '[', + lsquo: '\u2018', + lsquor: '\u201A', + Lstrok: '\u0141', + lstrok: '\u0142', + ltcc: '\u2AA6', + ltcir: '\u2A79', + lt: '<', + LT: '<', + Lt: '\u226A', + ltdot: '\u22D6', + lthree: '\u22CB', + ltimes: '\u22C9', + ltlarr: '\u2976', + ltquest: '\u2A7B', + ltri: '\u25C3', + ltrie: '\u22B4', + ltrif: '\u25C2', + ltrPar: '\u2996', + lurdshar: '\u294A', + luruhar: '\u2966', + lvertneqq: '\u2268\uFE00', + lvnE: '\u2268\uFE00', + macr: '\u00AF', + male: '\u2642', + malt: '\u2720', + maltese: '\u2720', + Map: '\u2905', + map: '\u21A6', + mapsto: '\u21A6', + mapstodown: '\u21A7', + mapstoleft: '\u21A4', + mapstoup: '\u21A5', + marker: '\u25AE', + mcomma: '\u2A29', + Mcy: '\u041C', + mcy: '\u043C', + mdash: '\u2014', + mDDot: '\u223A', + measuredangle: '\u2221', + MediumSpace: '\u205F', + Mellintrf: '\u2133', + Mfr: '\uD835\uDD10', + mfr: '\uD835\uDD2A', + mho: '\u2127', + micro: '\u00B5', + midast: '*', + midcir: '\u2AF0', + mid: '\u2223', + middot: '\u00B7', + minusb: '\u229F', + minus: '\u2212', + minusd: '\u2238', + minusdu: '\u2A2A', + MinusPlus: '\u2213', + mlcp: '\u2ADB', + mldr: '\u2026', + mnplus: '\u2213', + models: '\u22A7', + Mopf: '\uD835\uDD44', + mopf: '\uD835\uDD5E', + mp: '\u2213', + mscr: '\uD835\uDCC2', + Mscr: '\u2133', + mstpos: '\u223E', + Mu: '\u039C', + mu: '\u03BC', + multimap: '\u22B8', + mumap: '\u22B8', + nabla: '\u2207', + Nacute: '\u0143', + nacute: '\u0144', + nang: '\u2220\u20D2', + nap: '\u2249', + napE: '\u2A70\u0338', + napid: '\u224B\u0338', + napos: '\u0149', + napprox: '\u2249', + natural: '\u266E', + naturals: '\u2115', + natur: '\u266E', + nbsp: '\u00A0', + nbump: '\u224E\u0338', + nbumpe: '\u224F\u0338', + ncap: '\u2A43', + Ncaron: '\u0147', + ncaron: '\u0148', + Ncedil: '\u0145', + ncedil: '\u0146', + ncong: '\u2247', + ncongdot: '\u2A6D\u0338', + ncup: '\u2A42', + Ncy: '\u041D', + ncy: '\u043D', + ndash: '\u2013', + nearhk: '\u2924', + nearr: '\u2197', + neArr: '\u21D7', + nearrow: '\u2197', + ne: '\u2260', + nedot: '\u2250\u0338', + NegativeMediumSpace: '\u200B', + NegativeThickSpace: '\u200B', + NegativeThinSpace: '\u200B', + NegativeVeryThinSpace: '\u200B', + nequiv: '\u2262', + nesear: '\u2928', + nesim: '\u2242\u0338', + NestedGreaterGreater: '\u226B', + NestedLessLess: '\u226A', + NewLine: '\n', + nexist: '\u2204', + nexists: '\u2204', + Nfr: '\uD835\uDD11', + nfr: '\uD835\uDD2B', + ngE: '\u2267\u0338', + nge: '\u2271', + ngeq: '\u2271', + ngeqq: '\u2267\u0338', + ngeqslant: '\u2A7E\u0338', + nges: '\u2A7E\u0338', + nGg: '\u22D9\u0338', + ngsim: '\u2275', + nGt: '\u226B\u20D2', + ngt: '\u226F', + ngtr: '\u226F', + nGtv: '\u226B\u0338', + nharr: '\u21AE', + nhArr: '\u21CE', + nhpar: '\u2AF2', + ni: '\u220B', + nis: '\u22FC', + nisd: '\u22FA', + niv: '\u220B', + NJcy: '\u040A', + njcy: '\u045A', + nlarr: '\u219A', + nlArr: '\u21CD', + nldr: '\u2025', + nlE: '\u2266\u0338', + nle: '\u2270', + nleftarrow: '\u219A', + nLeftarrow: '\u21CD', + nleftrightarrow: '\u21AE', + nLeftrightarrow: '\u21CE', + nleq: '\u2270', + nleqq: '\u2266\u0338', + nleqslant: '\u2A7D\u0338', + nles: '\u2A7D\u0338', + nless: '\u226E', + nLl: '\u22D8\u0338', + nlsim: '\u2274', + nLt: '\u226A\u20D2', + nlt: '\u226E', + nltri: '\u22EA', + nltrie: '\u22EC', + nLtv: '\u226A\u0338', + nmid: '\u2224', + NoBreak: '\u2060', + NonBreakingSpace: '\u00A0', + nopf: '\uD835\uDD5F', + Nopf: '\u2115', + Not: '\u2AEC', + not: '\u00AC', + NotCongruent: '\u2262', + NotCupCap: '\u226D', + NotDoubleVerticalBar: '\u2226', + NotElement: '\u2209', + NotEqual: '\u2260', + NotEqualTilde: '\u2242\u0338', + NotExists: '\u2204', + NotGreater: '\u226F', + NotGreaterEqual: '\u2271', + NotGreaterFullEqual: '\u2267\u0338', + NotGreaterGreater: '\u226B\u0338', + NotGreaterLess: '\u2279', + NotGreaterSlantEqual: '\u2A7E\u0338', + NotGreaterTilde: '\u2275', + NotHumpDownHump: '\u224E\u0338', + NotHumpEqual: '\u224F\u0338', + notin: '\u2209', + notindot: '\u22F5\u0338', + notinE: '\u22F9\u0338', + notinva: '\u2209', + notinvb: '\u22F7', + notinvc: '\u22F6', + NotLeftTriangleBar: '\u29CF\u0338', + NotLeftTriangle: '\u22EA', + NotLeftTriangleEqual: '\u22EC', + NotLess: '\u226E', + NotLessEqual: '\u2270', + NotLessGreater: '\u2278', + NotLessLess: '\u226A\u0338', + NotLessSlantEqual: '\u2A7D\u0338', + NotLessTilde: '\u2274', + NotNestedGreaterGreater: '\u2AA2\u0338', + NotNestedLessLess: '\u2AA1\u0338', + notni: '\u220C', + notniva: '\u220C', + notnivb: '\u22FE', + notnivc: '\u22FD', + NotPrecedes: '\u2280', + NotPrecedesEqual: '\u2AAF\u0338', + NotPrecedesSlantEqual: '\u22E0', + NotReverseElement: '\u220C', + NotRightTriangleBar: '\u29D0\u0338', + NotRightTriangle: '\u22EB', + NotRightTriangleEqual: '\u22ED', + NotSquareSubset: '\u228F\u0338', + NotSquareSubsetEqual: '\u22E2', + NotSquareSuperset: '\u2290\u0338', + NotSquareSupersetEqual: '\u22E3', + NotSubset: '\u2282\u20D2', + NotSubsetEqual: '\u2288', + NotSucceeds: '\u2281', + NotSucceedsEqual: '\u2AB0\u0338', + NotSucceedsSlantEqual: '\u22E1', + NotSucceedsTilde: '\u227F\u0338', + NotSuperset: '\u2283\u20D2', + NotSupersetEqual: '\u2289', + NotTilde: '\u2241', + NotTildeEqual: '\u2244', + NotTildeFullEqual: '\u2247', + NotTildeTilde: '\u2249', + NotVerticalBar: '\u2224', + nparallel: '\u2226', + npar: '\u2226', + nparsl: '\u2AFD\u20E5', + npart: '\u2202\u0338', + npolint: '\u2A14', + npr: '\u2280', + nprcue: '\u22E0', + nprec: '\u2280', + npreceq: '\u2AAF\u0338', + npre: '\u2AAF\u0338', + nrarrc: '\u2933\u0338', + nrarr: '\u219B', + nrArr: '\u21CF', + nrarrw: '\u219D\u0338', + nrightarrow: '\u219B', + nRightarrow: '\u21CF', + nrtri: '\u22EB', + nrtrie: '\u22ED', + nsc: '\u2281', + nsccue: '\u22E1', + nsce: '\u2AB0\u0338', + Nscr: '\uD835\uDCA9', + nscr: '\uD835\uDCC3', + nshortmid: '\u2224', + nshortparallel: '\u2226', + nsim: '\u2241', + nsime: '\u2244', + nsimeq: '\u2244', + nsmid: '\u2224', + nspar: '\u2226', + nsqsube: '\u22E2', + nsqsupe: '\u22E3', + nsub: '\u2284', + nsubE: '\u2AC5\u0338', + nsube: '\u2288', + nsubset: '\u2282\u20D2', + nsubseteq: '\u2288', + nsubseteqq: '\u2AC5\u0338', + nsucc: '\u2281', + nsucceq: '\u2AB0\u0338', + nsup: '\u2285', + nsupE: '\u2AC6\u0338', + nsupe: '\u2289', + nsupset: '\u2283\u20D2', + nsupseteq: '\u2289', + nsupseteqq: '\u2AC6\u0338', + ntgl: '\u2279', + Ntilde: '\u00D1', + ntilde: '\u00F1', + ntlg: '\u2278', + ntriangleleft: '\u22EA', + ntrianglelefteq: '\u22EC', + ntriangleright: '\u22EB', + ntrianglerighteq: '\u22ED', + Nu: '\u039D', + nu: '\u03BD', + num: '#', + numero: '\u2116', + numsp: '\u2007', + nvap: '\u224D\u20D2', + nvdash: '\u22AC', + nvDash: '\u22AD', + nVdash: '\u22AE', + nVDash: '\u22AF', + nvge: '\u2265\u20D2', + nvgt: '>\u20D2', + nvHarr: '\u2904', + nvinfin: '\u29DE', + nvlArr: '\u2902', + nvle: '\u2264\u20D2', + nvlt: '<\u20D2', + nvltrie: '\u22B4\u20D2', + nvrArr: '\u2903', + nvrtrie: '\u22B5\u20D2', + nvsim: '\u223C\u20D2', + nwarhk: '\u2923', + nwarr: '\u2196', + nwArr: '\u21D6', + nwarrow: '\u2196', + nwnear: '\u2927', + Oacute: '\u00D3', + oacute: '\u00F3', + oast: '\u229B', + Ocirc: '\u00D4', + ocirc: '\u00F4', + ocir: '\u229A', + Ocy: '\u041E', + ocy: '\u043E', + odash: '\u229D', + Odblac: '\u0150', + odblac: '\u0151', + odiv: '\u2A38', + odot: '\u2299', + odsold: '\u29BC', + OElig: '\u0152', + oelig: '\u0153', + ofcir: '\u29BF', + Ofr: '\uD835\uDD12', + ofr: '\uD835\uDD2C', + ogon: '\u02DB', + Ograve: '\u00D2', + ograve: '\u00F2', + ogt: '\u29C1', + ohbar: '\u29B5', + ohm: '\u03A9', + oint: '\u222E', + olarr: '\u21BA', + olcir: '\u29BE', + olcross: '\u29BB', + oline: '\u203E', + olt: '\u29C0', + Omacr: '\u014C', + omacr: '\u014D', + Omega: '\u03A9', + omega: '\u03C9', + Omicron: '\u039F', + omicron: '\u03BF', + omid: '\u29B6', + ominus: '\u2296', + Oopf: '\uD835\uDD46', + oopf: '\uD835\uDD60', + opar: '\u29B7', + OpenCurlyDoubleQuote: '\u201C', + OpenCurlyQuote: '\u2018', + operp: '\u29B9', + oplus: '\u2295', + orarr: '\u21BB', + Or: '\u2A54', + or: '\u2228', + ord: '\u2A5D', + order: '\u2134', + orderof: '\u2134', + ordf: '\u00AA', + ordm: '\u00BA', + origof: '\u22B6', + oror: '\u2A56', + orslope: '\u2A57', + orv: '\u2A5B', + oS: '\u24C8', + Oscr: '\uD835\uDCAA', + oscr: '\u2134', + Oslash: '\u00D8', + oslash: '\u00F8', + osol: '\u2298', + Otilde: '\u00D5', + otilde: '\u00F5', + otimesas: '\u2A36', + Otimes: '\u2A37', + otimes: '\u2297', + Ouml: '\u00D6', + ouml: '\u00F6', + ovbar: '\u233D', + OverBar: '\u203E', + OverBrace: '\u23DE', + OverBracket: '\u23B4', + OverParenthesis: '\u23DC', + para: '\u00B6', + parallel: '\u2225', + par: '\u2225', + parsim: '\u2AF3', + parsl: '\u2AFD', + part: '\u2202', + PartialD: '\u2202', + Pcy: '\u041F', + pcy: '\u043F', + percnt: '%', + period: '.', + permil: '\u2030', + perp: '\u22A5', + pertenk: '\u2031', + Pfr: '\uD835\uDD13', + pfr: '\uD835\uDD2D', + Phi: '\u03A6', + phi: '\u03C6', + phiv: '\u03D5', + phmmat: '\u2133', + phone: '\u260E', + Pi: '\u03A0', + pi: '\u03C0', + pitchfork: '\u22D4', + piv: '\u03D6', + planck: '\u210F', + planckh: '\u210E', + plankv: '\u210F', + plusacir: '\u2A23', + plusb: '\u229E', + pluscir: '\u2A22', + plus: '+', + plusdo: '\u2214', + plusdu: '\u2A25', + pluse: '\u2A72', + PlusMinus: '\u00B1', + plusmn: '\u00B1', + plussim: '\u2A26', + plustwo: '\u2A27', + pm: '\u00B1', + Poincareplane: '\u210C', + pointint: '\u2A15', + popf: '\uD835\uDD61', + Popf: '\u2119', + pound: '\u00A3', + prap: '\u2AB7', + Pr: '\u2ABB', + pr: '\u227A', + prcue: '\u227C', + precapprox: '\u2AB7', + prec: '\u227A', + preccurlyeq: '\u227C', + Precedes: '\u227A', + PrecedesEqual: '\u2AAF', + PrecedesSlantEqual: '\u227C', + PrecedesTilde: '\u227E', + preceq: '\u2AAF', + precnapprox: '\u2AB9', + precneqq: '\u2AB5', + precnsim: '\u22E8', + pre: '\u2AAF', + prE: '\u2AB3', + precsim: '\u227E', + prime: '\u2032', + Prime: '\u2033', + primes: '\u2119', + prnap: '\u2AB9', + prnE: '\u2AB5', + prnsim: '\u22E8', + prod: '\u220F', + Product: '\u220F', + profalar: '\u232E', + profline: '\u2312', + profsurf: '\u2313', + prop: '\u221D', + Proportional: '\u221D', + Proportion: '\u2237', + propto: '\u221D', + prsim: '\u227E', + prurel: '\u22B0', + Pscr: '\uD835\uDCAB', + pscr: '\uD835\uDCC5', + Psi: '\u03A8', + psi: '\u03C8', + puncsp: '\u2008', + Qfr: '\uD835\uDD14', + qfr: '\uD835\uDD2E', + qint: '\u2A0C', + qopf: '\uD835\uDD62', + Qopf: '\u211A', + qprime: '\u2057', + Qscr: '\uD835\uDCAC', + qscr: '\uD835\uDCC6', + quaternions: '\u210D', + quatint: '\u2A16', + quest: '?', + questeq: '\u225F', + quot: '"', + QUOT: '"', + rAarr: '\u21DB', + race: '\u223D\u0331', + Racute: '\u0154', + racute: '\u0155', + radic: '\u221A', + raemptyv: '\u29B3', + rang: '\u27E9', + Rang: '\u27EB', + rangd: '\u2992', + range: '\u29A5', + rangle: '\u27E9', + raquo: '\u00BB', + rarrap: '\u2975', + rarrb: '\u21E5', + rarrbfs: '\u2920', + rarrc: '\u2933', + rarr: '\u2192', + Rarr: '\u21A0', + rArr: '\u21D2', + rarrfs: '\u291E', + rarrhk: '\u21AA', + rarrlp: '\u21AC', + rarrpl: '\u2945', + rarrsim: '\u2974', + Rarrtl: '\u2916', + rarrtl: '\u21A3', + rarrw: '\u219D', + ratail: '\u291A', + rAtail: '\u291C', + ratio: '\u2236', + rationals: '\u211A', + rbarr: '\u290D', + rBarr: '\u290F', + RBarr: '\u2910', + rbbrk: '\u2773', + rbrace: '}', + rbrack: ']', + rbrke: '\u298C', + rbrksld: '\u298E', + rbrkslu: '\u2990', + Rcaron: '\u0158', + rcaron: '\u0159', + Rcedil: '\u0156', + rcedil: '\u0157', + rceil: '\u2309', + rcub: '}', + Rcy: '\u0420', + rcy: '\u0440', + rdca: '\u2937', + rdldhar: '\u2969', + rdquo: '\u201D', + rdquor: '\u201D', + rdsh: '\u21B3', + real: '\u211C', + realine: '\u211B', + realpart: '\u211C', + reals: '\u211D', + Re: '\u211C', + rect: '\u25AD', + reg: '\u00AE', + REG: '\u00AE', + ReverseElement: '\u220B', + ReverseEquilibrium: '\u21CB', + ReverseUpEquilibrium: '\u296F', + rfisht: '\u297D', + rfloor: '\u230B', + rfr: '\uD835\uDD2F', + Rfr: '\u211C', + rHar: '\u2964', + rhard: '\u21C1', + rharu: '\u21C0', + rharul: '\u296C', + Rho: '\u03A1', + rho: '\u03C1', + rhov: '\u03F1', + RightAngleBracket: '\u27E9', + RightArrowBar: '\u21E5', + rightarrow: '\u2192', + RightArrow: '\u2192', + Rightarrow: '\u21D2', + RightArrowLeftArrow: '\u21C4', + rightarrowtail: '\u21A3', + RightCeiling: '\u2309', + RightDoubleBracket: '\u27E7', + RightDownTeeVector: '\u295D', + RightDownVectorBar: '\u2955', + RightDownVector: '\u21C2', + RightFloor: '\u230B', + rightharpoondown: '\u21C1', + rightharpoonup: '\u21C0', + rightleftarrows: '\u21C4', + rightleftharpoons: '\u21CC', + rightrightarrows: '\u21C9', + rightsquigarrow: '\u219D', + RightTeeArrow: '\u21A6', + RightTee: '\u22A2', + RightTeeVector: '\u295B', + rightthreetimes: '\u22CC', + RightTriangleBar: '\u29D0', + RightTriangle: '\u22B3', + RightTriangleEqual: '\u22B5', + RightUpDownVector: '\u294F', + RightUpTeeVector: '\u295C', + RightUpVectorBar: '\u2954', + RightUpVector: '\u21BE', + RightVectorBar: '\u2953', + RightVector: '\u21C0', + ring: '\u02DA', + risingdotseq: '\u2253', + rlarr: '\u21C4', + rlhar: '\u21CC', + rlm: '\u200F', + rmoustache: '\u23B1', + rmoust: '\u23B1', + rnmid: '\u2AEE', + roang: '\u27ED', + roarr: '\u21FE', + robrk: '\u27E7', + ropar: '\u2986', + ropf: '\uD835\uDD63', + Ropf: '\u211D', + roplus: '\u2A2E', + rotimes: '\u2A35', + RoundImplies: '\u2970', + rpar: ')', + rpargt: '\u2994', + rppolint: '\u2A12', + rrarr: '\u21C9', + Rrightarrow: '\u21DB', + rsaquo: '\u203A', + rscr: '\uD835\uDCC7', + Rscr: '\u211B', + rsh: '\u21B1', + Rsh: '\u21B1', + rsqb: ']', + rsquo: '\u2019', + rsquor: '\u2019', + rthree: '\u22CC', + rtimes: '\u22CA', + rtri: '\u25B9', + rtrie: '\u22B5', + rtrif: '\u25B8', + rtriltri: '\u29CE', + RuleDelayed: '\u29F4', + ruluhar: '\u2968', + rx: '\u211E', + Sacute: '\u015A', + sacute: '\u015B', + sbquo: '\u201A', + scap: '\u2AB8', + Scaron: '\u0160', + scaron: '\u0161', + Sc: '\u2ABC', + sc: '\u227B', + sccue: '\u227D', + sce: '\u2AB0', + scE: '\u2AB4', + Scedil: '\u015E', + scedil: '\u015F', + Scirc: '\u015C', + scirc: '\u015D', + scnap: '\u2ABA', + scnE: '\u2AB6', + scnsim: '\u22E9', + scpolint: '\u2A13', + scsim: '\u227F', + Scy: '\u0421', + scy: '\u0441', + sdotb: '\u22A1', + sdot: '\u22C5', + sdote: '\u2A66', + searhk: '\u2925', + searr: '\u2198', + seArr: '\u21D8', + searrow: '\u2198', + sect: '\u00A7', + semi: ';', + seswar: '\u2929', + setminus: '\u2216', + setmn: '\u2216', + sext: '\u2736', + Sfr: '\uD835\uDD16', + sfr: '\uD835\uDD30', + sfrown: '\u2322', + sharp: '\u266F', + SHCHcy: '\u0429', + shchcy: '\u0449', + SHcy: '\u0428', + shcy: '\u0448', + ShortDownArrow: '\u2193', + ShortLeftArrow: '\u2190', + shortmid: '\u2223', + shortparallel: '\u2225', + ShortRightArrow: '\u2192', + ShortUpArrow: '\u2191', + shy: '\u00AD', + Sigma: '\u03A3', + sigma: '\u03C3', + sigmaf: '\u03C2', + sigmav: '\u03C2', + sim: '\u223C', + simdot: '\u2A6A', + sime: '\u2243', + simeq: '\u2243', + simg: '\u2A9E', + simgE: '\u2AA0', + siml: '\u2A9D', + simlE: '\u2A9F', + simne: '\u2246', + simplus: '\u2A24', + simrarr: '\u2972', + slarr: '\u2190', + SmallCircle: '\u2218', + smallsetminus: '\u2216', + smashp: '\u2A33', + smeparsl: '\u29E4', + smid: '\u2223', + smile: '\u2323', + smt: '\u2AAA', + smte: '\u2AAC', + smtes: '\u2AAC\uFE00', + SOFTcy: '\u042C', + softcy: '\u044C', + solbar: '\u233F', + solb: '\u29C4', + sol: '/', + Sopf: '\uD835\uDD4A', + sopf: '\uD835\uDD64', + spades: '\u2660', + spadesuit: '\u2660', + spar: '\u2225', + sqcap: '\u2293', + sqcaps: '\u2293\uFE00', + sqcup: '\u2294', + sqcups: '\u2294\uFE00', + Sqrt: '\u221A', + sqsub: '\u228F', + sqsube: '\u2291', + sqsubset: '\u228F', + sqsubseteq: '\u2291', + sqsup: '\u2290', + sqsupe: '\u2292', + sqsupset: '\u2290', + sqsupseteq: '\u2292', + square: '\u25A1', + Square: '\u25A1', + SquareIntersection: '\u2293', + SquareSubset: '\u228F', + SquareSubsetEqual: '\u2291', + SquareSuperset: '\u2290', + SquareSupersetEqual: '\u2292', + SquareUnion: '\u2294', + squarf: '\u25AA', + squ: '\u25A1', + squf: '\u25AA', + srarr: '\u2192', + Sscr: '\uD835\uDCAE', + sscr: '\uD835\uDCC8', + ssetmn: '\u2216', + ssmile: '\u2323', + sstarf: '\u22C6', + Star: '\u22C6', + star: '\u2606', + starf: '\u2605', + straightepsilon: '\u03F5', + straightphi: '\u03D5', + strns: '\u00AF', + sub: '\u2282', + Sub: '\u22D0', + subdot: '\u2ABD', + subE: '\u2AC5', + sube: '\u2286', + subedot: '\u2AC3', + submult: '\u2AC1', + subnE: '\u2ACB', + subne: '\u228A', + subplus: '\u2ABF', + subrarr: '\u2979', + subset: '\u2282', + Subset: '\u22D0', + subseteq: '\u2286', + subseteqq: '\u2AC5', + SubsetEqual: '\u2286', + subsetneq: '\u228A', + subsetneqq: '\u2ACB', + subsim: '\u2AC7', + subsub: '\u2AD5', + subsup: '\u2AD3', + succapprox: '\u2AB8', + succ: '\u227B', + succcurlyeq: '\u227D', + Succeeds: '\u227B', + SucceedsEqual: '\u2AB0', + SucceedsSlantEqual: '\u227D', + SucceedsTilde: '\u227F', + succeq: '\u2AB0', + succnapprox: '\u2ABA', + succneqq: '\u2AB6', + succnsim: '\u22E9', + succsim: '\u227F', + SuchThat: '\u220B', + sum: '\u2211', + Sum: '\u2211', + sung: '\u266A', + sup1: '\u00B9', + sup2: '\u00B2', + sup3: '\u00B3', + sup: '\u2283', + Sup: '\u22D1', + supdot: '\u2ABE', + supdsub: '\u2AD8', + supE: '\u2AC6', + supe: '\u2287', + supedot: '\u2AC4', + Superset: '\u2283', + SupersetEqual: '\u2287', + suphsol: '\u27C9', + suphsub: '\u2AD7', + suplarr: '\u297B', + supmult: '\u2AC2', + supnE: '\u2ACC', + supne: '\u228B', + supplus: '\u2AC0', + supset: '\u2283', + Supset: '\u22D1', + supseteq: '\u2287', + supseteqq: '\u2AC6', + supsetneq: '\u228B', + supsetneqq: '\u2ACC', + supsim: '\u2AC8', + supsub: '\u2AD4', + supsup: '\u2AD6', + swarhk: '\u2926', + swarr: '\u2199', + swArr: '\u21D9', + swarrow: '\u2199', + swnwar: '\u292A', + szlig: '\u00DF', + Tab: '\t', + target: '\u2316', + Tau: '\u03A4', + tau: '\u03C4', + tbrk: '\u23B4', + Tcaron: '\u0164', + tcaron: '\u0165', + Tcedil: '\u0162', + tcedil: '\u0163', + Tcy: '\u0422', + tcy: '\u0442', + tdot: '\u20DB', + telrec: '\u2315', + Tfr: '\uD835\uDD17', + tfr: '\uD835\uDD31', + there4: '\u2234', + therefore: '\u2234', + Therefore: '\u2234', + Theta: '\u0398', + theta: '\u03B8', + thetasym: '\u03D1', + thetav: '\u03D1', + thickapprox: '\u2248', + thicksim: '\u223C', + ThickSpace: '\u205F\u200A', + ThinSpace: '\u2009', + thinsp: '\u2009', + thkap: '\u2248', + thksim: '\u223C', + THORN: '\u00DE', + thorn: '\u00FE', + tilde: '\u02DC', + Tilde: '\u223C', + TildeEqual: '\u2243', + TildeFullEqual: '\u2245', + TildeTilde: '\u2248', + timesbar: '\u2A31', + timesb: '\u22A0', + times: '\u00D7', + timesd: '\u2A30', + tint: '\u222D', + toea: '\u2928', + topbot: '\u2336', + topcir: '\u2AF1', + top: '\u22A4', + Topf: '\uD835\uDD4B', + topf: '\uD835\uDD65', + topfork: '\u2ADA', + tosa: '\u2929', + tprime: '\u2034', + trade: '\u2122', + TRADE: '\u2122', + triangle: '\u25B5', + triangledown: '\u25BF', + triangleleft: '\u25C3', + trianglelefteq: '\u22B4', + triangleq: '\u225C', + triangleright: '\u25B9', + trianglerighteq: '\u22B5', + tridot: '\u25EC', + trie: '\u225C', + triminus: '\u2A3A', + TripleDot: '\u20DB', + triplus: '\u2A39', + trisb: '\u29CD', + tritime: '\u2A3B', + trpezium: '\u23E2', + Tscr: '\uD835\uDCAF', + tscr: '\uD835\uDCC9', + TScy: '\u0426', + tscy: '\u0446', + TSHcy: '\u040B', + tshcy: '\u045B', + Tstrok: '\u0166', + tstrok: '\u0167', + twixt: '\u226C', + twoheadleftarrow: '\u219E', + twoheadrightarrow: '\u21A0', + Uacute: '\u00DA', + uacute: '\u00FA', + uarr: '\u2191', + Uarr: '\u219F', + uArr: '\u21D1', + Uarrocir: '\u2949', + Ubrcy: '\u040E', + ubrcy: '\u045E', + Ubreve: '\u016C', + ubreve: '\u016D', + Ucirc: '\u00DB', + ucirc: '\u00FB', + Ucy: '\u0423', + ucy: '\u0443', + udarr: '\u21C5', + Udblac: '\u0170', + udblac: '\u0171', + udhar: '\u296E', + ufisht: '\u297E', + Ufr: '\uD835\uDD18', + ufr: '\uD835\uDD32', + Ugrave: '\u00D9', + ugrave: '\u00F9', + uHar: '\u2963', + uharl: '\u21BF', + uharr: '\u21BE', + uhblk: '\u2580', + ulcorn: '\u231C', + ulcorner: '\u231C', + ulcrop: '\u230F', + ultri: '\u25F8', + Umacr: '\u016A', + umacr: '\u016B', + uml: '\u00A8', + UnderBar: '_', + UnderBrace: '\u23DF', + UnderBracket: '\u23B5', + UnderParenthesis: '\u23DD', + Union: '\u22C3', + UnionPlus: '\u228E', + Uogon: '\u0172', + uogon: '\u0173', + Uopf: '\uD835\uDD4C', + uopf: '\uD835\uDD66', + UpArrowBar: '\u2912', + uparrow: '\u2191', + UpArrow: '\u2191', + Uparrow: '\u21D1', + UpArrowDownArrow: '\u21C5', + updownarrow: '\u2195', + UpDownArrow: '\u2195', + Updownarrow: '\u21D5', + UpEquilibrium: '\u296E', + upharpoonleft: '\u21BF', + upharpoonright: '\u21BE', + uplus: '\u228E', + UpperLeftArrow: '\u2196', + UpperRightArrow: '\u2197', + upsi: '\u03C5', + Upsi: '\u03D2', + upsih: '\u03D2', + Upsilon: '\u03A5', + upsilon: '\u03C5', + UpTeeArrow: '\u21A5', + UpTee: '\u22A5', + upuparrows: '\u21C8', + urcorn: '\u231D', + urcorner: '\u231D', + urcrop: '\u230E', + Uring: '\u016E', + uring: '\u016F', + urtri: '\u25F9', + Uscr: '\uD835\uDCB0', + uscr: '\uD835\uDCCA', + utdot: '\u22F0', + Utilde: '\u0168', + utilde: '\u0169', + utri: '\u25B5', + utrif: '\u25B4', + uuarr: '\u21C8', + Uuml: '\u00DC', + uuml: '\u00FC', + uwangle: '\u29A7', + vangrt: '\u299C', + varepsilon: '\u03F5', + varkappa: '\u03F0', + varnothing: '\u2205', + varphi: '\u03D5', + varpi: '\u03D6', + varpropto: '\u221D', + varr: '\u2195', + vArr: '\u21D5', + varrho: '\u03F1', + varsigma: '\u03C2', + varsubsetneq: '\u228A\uFE00', + varsubsetneqq: '\u2ACB\uFE00', + varsupsetneq: '\u228B\uFE00', + varsupsetneqq: '\u2ACC\uFE00', + vartheta: '\u03D1', + vartriangleleft: '\u22B2', + vartriangleright: '\u22B3', + vBar: '\u2AE8', + Vbar: '\u2AEB', + vBarv: '\u2AE9', + Vcy: '\u0412', + vcy: '\u0432', + vdash: '\u22A2', + vDash: '\u22A8', + Vdash: '\u22A9', + VDash: '\u22AB', + Vdashl: '\u2AE6', + veebar: '\u22BB', + vee: '\u2228', + Vee: '\u22C1', + veeeq: '\u225A', + vellip: '\u22EE', + verbar: '|', + Verbar: '\u2016', + vert: '|', + Vert: '\u2016', + VerticalBar: '\u2223', + VerticalLine: '|', + VerticalSeparator: '\u2758', + VerticalTilde: '\u2240', + VeryThinSpace: '\u200A', + Vfr: '\uD835\uDD19', + vfr: '\uD835\uDD33', + vltri: '\u22B2', + vnsub: '\u2282\u20D2', + vnsup: '\u2283\u20D2', + Vopf: '\uD835\uDD4D', + vopf: '\uD835\uDD67', + vprop: '\u221D', + vrtri: '\u22B3', + Vscr: '\uD835\uDCB1', + vscr: '\uD835\uDCCB', + vsubnE: '\u2ACB\uFE00', + vsubne: '\u228A\uFE00', + vsupnE: '\u2ACC\uFE00', + vsupne: '\u228B\uFE00', + Vvdash: '\u22AA', + vzigzag: '\u299A', + Wcirc: '\u0174', + wcirc: '\u0175', + wedbar: '\u2A5F', + wedge: '\u2227', + Wedge: '\u22C0', + wedgeq: '\u2259', + weierp: '\u2118', + Wfr: '\uD835\uDD1A', + wfr: '\uD835\uDD34', + Wopf: '\uD835\uDD4E', + wopf: '\uD835\uDD68', + wp: '\u2118', + wr: '\u2240', + wreath: '\u2240', + Wscr: '\uD835\uDCB2', + wscr: '\uD835\uDCCC', + xcap: '\u22C2', + xcirc: '\u25EF', + xcup: '\u22C3', + xdtri: '\u25BD', + Xfr: '\uD835\uDD1B', + xfr: '\uD835\uDD35', + xharr: '\u27F7', + xhArr: '\u27FA', + Xi: '\u039E', + xi: '\u03BE', + xlarr: '\u27F5', + xlArr: '\u27F8', + xmap: '\u27FC', + xnis: '\u22FB', + xodot: '\u2A00', + Xopf: '\uD835\uDD4F', + xopf: '\uD835\uDD69', + xoplus: '\u2A01', + xotime: '\u2A02', + xrarr: '\u27F6', + xrArr: '\u27F9', + Xscr: '\uD835\uDCB3', + xscr: '\uD835\uDCCD', + xsqcup: '\u2A06', + xuplus: '\u2A04', + xutri: '\u25B3', + xvee: '\u22C1', + xwedge: '\u22C0', + Yacute: '\u00DD', + yacute: '\u00FD', + YAcy: '\u042F', + yacy: '\u044F', + Ycirc: '\u0176', + ycirc: '\u0177', + Ycy: '\u042B', + ycy: '\u044B', + yen: '\u00A5', + Yfr: '\uD835\uDD1C', + yfr: '\uD835\uDD36', + YIcy: '\u0407', + yicy: '\u0457', + Yopf: '\uD835\uDD50', + yopf: '\uD835\uDD6A', + Yscr: '\uD835\uDCB4', + yscr: '\uD835\uDCCE', + YUcy: '\u042E', + yucy: '\u044E', + yuml: '\u00FF', + Yuml: '\u0178', + Zacute: '\u0179', + zacute: '\u017A', + Zcaron: '\u017D', + zcaron: '\u017E', + Zcy: '\u0417', + zcy: '\u0437', + Zdot: '\u017B', + zdot: '\u017C', + zeetrf: '\u2128', + ZeroWidthSpace: '\u200B', + Zeta: '\u0396', + zeta: '\u03B6', + zfr: '\uD835\uDD37', + Zfr: '\u2128', + ZHcy: '\u0416', + zhcy: '\u0436', + zigrarr: '\u21DD', + zopf: '\uD835\uDD6B', + Zopf: '\u2124', + Zscr: '\uD835\uDCB5', + zscr: '\uD835\uDCCF', + zwj: '\u200D', + zwnj: '\u200C', + } + }, + {}, + ], + 53: [ + function (require, module, exports) { + 'use strict' - '(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?'; + //////////////////////////////////////////////////////////////////////////////// + // Helpers - re.src_host_terminator = + // Merge objects + // + function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1) - '(?=$|' + text_separators + '|' + re.src_ZPCc + ')(?!-|_|:\\d|\\.-|\\.(?!$|' + re.src_ZPCc + '))'; + sources.forEach(function (source) { + if (!source) { + return + } - re.src_path = + Object.keys(source).forEach(function (key) { + obj[key] = source[key] + }) + }) - '(?:' + - '[/?#]' + - '(?:' + - '(?!' + re.src_ZCc + '|' + text_separators + '|[()[\\]{}.,"\'?!\\-]).|' + - '\\[(?:(?!' + re.src_ZCc + '|\\]).)*\\]|' + - '\\((?:(?!' + re.src_ZCc + '|[)]).)*\\)|' + - '\\{(?:(?!' + re.src_ZCc + '|[}]).)*\\}|' + - '\\"(?:(?!' + re.src_ZCc + '|["]).)+\\"|' + - "\\'(?:(?!" + re.src_ZCc + "|[']).)+\\'|" + - "\\'(?=" + re.src_pseudo_letter + '|[-]).|' + // allow `I'm_king` if no pair found - '\\.{2,}[a-zA-Z0-9%/&]|' + // google has many dots in "google search" links (#66, #81). - // github has ... in commit range links, - // Restrict to - // - english - // - percent-encoded - // - parts of file path - // - params separator - // until more examples found. - '\\.(?!' + re.src_ZCc + '|[.]).|' + - (opts && opts['---'] ? - '\\-(?!--(?:[^-]|$))(?:-*)|' // `---` => long dash, terminate - : - '\\-+|' - ) + - '\\,(?!' + re.src_ZCc + ').|' + // allow `,,,` in paths - '\\!+(?!' + re.src_ZCc + '|[!]).|' + // allow `!!!` in paths, but not at the end - '\\?(?!' + re.src_ZCc + '|[?]).' + - ')+' + - '|\\/' + - ')?'; + return obj + } - // Allow anything in markdown spec, forbid quote (") at the first position - // because emails enclosed in quotes are far more common - re.src_email_name = + function _class(obj) { + return Object.prototype.toString.call(obj) + } + function isString(obj) { + return _class(obj) === '[object String]' + } + function isObject(obj) { + return _class(obj) === '[object Object]' + } + function isRegExp(obj) { + return _class(obj) === '[object RegExp]' + } + function isFunction(obj) { + return _class(obj) === '[object Function]' + } - '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*'; + function escapeRE(str) { + return str.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&') + } - re.src_xn = + //////////////////////////////////////////////////////////////////////////////// - 'xn--[a-z0-9\\-]{1,59}'; + var defaultOptions = { + fuzzyLink: true, + fuzzyEmail: true, + fuzzyIP: false, + } - // More to read about domain names - // http://serverfault.com/questions/638260/ + function isOptionsObj(obj) { + return Object.keys(obj || {}).reduce(function (acc, k) { + return acc || defaultOptions.hasOwnProperty(k) + }, false) + } - re.src_domain_root = + var defaultSchemas = { + 'http:': { + validate: function (text, pos, self) { + var tail = text.slice(pos) + + if (!self.re.http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.http = new RegExp( + '^\\/\\/' + self.re.src_auth + self.re.src_host_port_strict + self.re.src_path, + 'i' + ) + } + if (self.re.http.test(tail)) { + return tail.match(self.re.http)[0].length + } + return 0 + }, + }, + 'https:': 'http:', + 'ftp:': 'http:', + '//': { + validate: function (text, pos, self) { + var tail = text.slice(pos) + + if (!self.re.no_http) { + // compile lazily, because "host"-containing variables can change on tlds update. + self.re.no_http = new RegExp( + '^' + + self.re.src_auth + + // Don't allow single-level domains, because of false positives like '//test' + // with code comments + '(?:localhost|(?:(?:' + + self.re.src_domain + + ')\\.)+' + + self.re.src_domain_root + + ')' + + self.re.src_port + + self.re.src_host_terminator + + self.re.src_path, + + 'i' + ) + } + + if (self.re.no_http.test(tail)) { + // should not be `://` & `///`, that protects from errors in protocol name + if (pos >= 3 && text[pos - 3] === ':') { + return 0 + } + if (pos >= 3 && text[pos - 3] === '/') { + return 0 + } + return tail.match(self.re.no_http)[0].length + } + return 0 + }, + }, + 'mailto:': { + validate: function (text, pos, self) { + var tail = text.slice(pos) + + if (!self.re.mailto) { + self.re.mailto = new RegExp('^' + self.re.src_email_name + '@' + self.re.src_host_strict, 'i') + } + if (self.re.mailto.test(tail)) { + return tail.match(self.re.mailto)[0].length + } + return 0 + }, + }, + } - // Allow letters & digits (http://test1) - '(?:' + - re.src_xn + - '|' + - re.src_pseudo_letter + '{1,63}' + - ')'; + /*eslint-disable max-len*/ - re.src_domain = + // RE pattern for 2-character tlds (autogenerated by ./support/tlds_2char_gen.js) + var tlds_2ch_src_re = + 'a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]' - '(?:' + - re.src_xn + - '|' + - '(?:' + re.src_pseudo_letter + ')' + - '|' + - '(?:' + re.src_pseudo_letter + '(?:-|' + re.src_pseudo_letter + '){0,61}' + re.src_pseudo_letter + ')' + - ')'; + // DON'T try to make PRs with changes. Extend TLDs with LinkifyIt.tlds() instead + var tlds_default = 'biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф'.split('|') - re.src_host = + /*eslint-enable max-len*/ - '(?:' + - // Don't need IP check, because digits are already allowed in normal domain names - // src_ip4 + - // '|' + - '(?:(?:(?:' + re.src_domain + ')\\.)*' + re.src_domain/*_root*/ + ')' + - ')'; + //////////////////////////////////////////////////////////////////////////////// - re.tpl_host_fuzzy = + function resetScanCache(self) { + self.__index__ = -1 + self.__text_cache__ = '' + } - '(?:' + - re.src_ip4 + - '|' + - '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))' + - ')'; + function createValidator(re) { + return function (text, pos) { + var tail = text.slice(pos) - re.tpl_host_no_ip_fuzzy = + if (re.test(tail)) { + return tail.match(re)[0].length + } + return 0 + } + } - '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))'; + function createNormalizer() { + return function (match, self) { + self.normalize(match) + } + } - re.src_host_strict = + // Schemas compiler. Build regexps. + // + function compile(self) { + // Load & clone RE patterns. + var re = (self.re = require('./lib/re')(self.__opts__)) - re.src_host + re.src_host_terminator; + // Define dynamic patterns + var tlds = self.__tlds__.slice() - re.tpl_host_fuzzy_strict = + self.onCompile() - re.tpl_host_fuzzy + re.src_host_terminator; + if (!self.__tlds_replaced__) { + tlds.push(tlds_2ch_src_re) + } + tlds.push(re.src_xn) - re.src_host_port_strict = + re.src_tlds = tlds.join('|') - re.src_host + re.src_port + re.src_host_terminator; + function untpl(tpl) { + return tpl.replace('%TLDS%', re.src_tlds) + } - re.tpl_host_port_fuzzy_strict = + re.email_fuzzy = RegExp(untpl(re.tpl_email_fuzzy), 'i') + re.link_fuzzy = RegExp(untpl(re.tpl_link_fuzzy), 'i') + re.link_no_ip_fuzzy = RegExp(untpl(re.tpl_link_no_ip_fuzzy), 'i') + re.host_fuzzy_test = RegExp(untpl(re.tpl_host_fuzzy_test), 'i') - re.tpl_host_fuzzy + re.src_port + re.src_host_terminator; + // + // Compile each schema + // - re.tpl_host_port_no_ip_fuzzy_strict = + var aliases = [] - re.tpl_host_no_ip_fuzzy + re.src_port + re.src_host_terminator; + self.__compiled__ = {} // Reset compiled data + function schemaError(name, val) { + throw new Error('(LinkifyIt) Invalid schema "' + name + '": ' + val) + } - //////////////////////////////////////////////////////////////////////////////// - // Main rules + Object.keys(self.__schemas__).forEach(function (name) { + var val = self.__schemas__[name] + + // skip disabled methods + if (val === null) { + return + } + + var compiled = { validate: null, link: null } + + self.__compiled__[name] = compiled + + if (isObject(val)) { + if (isRegExp(val.validate)) { + compiled.validate = createValidator(val.validate) + } else if (isFunction(val.validate)) { + compiled.validate = val.validate + } else { + schemaError(name, val) + } + + if (isFunction(val.normalize)) { + compiled.normalize = val.normalize + } else if (!val.normalize) { + compiled.normalize = createNormalizer() + } else { + schemaError(name, val) + } + + return + } + + if (isString(val)) { + aliases.push(name) + return + } + + schemaError(name, val) + }) + + // + // Compile postponed aliases + // + + aliases.forEach(function (alias) { + if (!self.__compiled__[self.__schemas__[alias]]) { + // Silently fail on missed schemas to avoid errons on disable. + // schemaError(alias, self.__schemas__[alias]); + return + } + + self.__compiled__[alias].validate = self.__compiled__[self.__schemas__[alias]].validate + self.__compiled__[alias].normalize = self.__compiled__[self.__schemas__[alias]].normalize + }) + + // + // Fake record for guessed links + // + self.__compiled__[''] = { validate: null, normalize: createNormalizer() } + + // + // Build schema condition + // + var slist = Object.keys(self.__compiled__) + .filter(function (name) { + // Filter disabled & fake schemas + return name.length > 0 && self.__compiled__[name] + }) + .map(escapeRE) + .join('|') + // (?!_) cause 1.5x slowdown + self.re.schema_test = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'i') + self.re.schema_search = RegExp('(^|(?!_)(?:[><\uff5c]|' + re.src_ZPCc + '))(' + slist + ')', 'ig') + + self.re.pretest = RegExp( + '(' + self.re.schema_test.source + ')|(' + self.re.host_fuzzy_test.source + ')|@', + 'i' + ) + + // + // Cleanup + // + + resetScanCache(self) + } - // Rude test fuzzy links by host, for quick deny - re.tpl_host_fuzzy_test = + /** + * class Match + * + * Match result. Single element of array, returned by [[LinkifyIt#match]] + **/ + function Match(self, shift) { + var start = self.__index__, + end = self.__last_index__, + text = self.__text_cache__.slice(start, end) + + /** + * Match#schema -> String + * + * Prefix (protocol) for matched string. + **/ + this.schema = self.__schema__.toLowerCase() + /** + * Match#index -> Number + * + * First position of matched string. + **/ + this.index = start + shift + /** + * Match#lastIndex -> Number + * + * Next position after matched string. + **/ + this.lastIndex = end + shift + /** + * Match#raw -> String + * + * Matched string. + **/ + this.raw = text + /** + * Match#text -> String + * + * Notmalized text of matched string. + **/ + this.text = text + /** + * Match#url -> String + * + * Normalized url of matched string. + **/ + this.url = text + } - 'localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:' + re.src_ZPCc + '|>|$))'; + function createMatch(self, shift) { + var match = new Match(self, shift) - re.tpl_email_fuzzy = + self.__compiled__[match.schema].normalize(match, self) - '(^|' + text_separators + '|"|\\(|' + re.src_ZCc + ')' + - '(' + re.src_email_name + '@' + re.tpl_host_fuzzy_strict + ')'; + return match + } - re.tpl_link_fuzzy = - // Fuzzy link can't be prepended with .:/\- and non punctuation. - // but can start with > (markdown blockquote) - '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + - '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_fuzzy_strict + re.src_path + ')'; + /** + * class LinkifyIt + **/ + + /** + * new LinkifyIt(schemas, options) + * - schemas (Object): Optional. Additional schemas to validate (prefix/validator) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Creates new linkifier instance with optional additional schemas. + * Can be called without `new` keyword for convenience. + * + * By default understands: + * + * - `http(s)://...` , `ftp://...`, `mailto:...` & `//...` links + * - "fuzzy" links and emails (example.com, foo@bar.com). + * + * `schemas` is an object, where each key/value describes protocol/rule: + * + * - __key__ - link prefix (usually, protocol name with `:` at the end, `skype:` + * for example). `linkify-it` makes shure that prefix is not preceeded with + * alphanumeric char and symbols. Only whitespaces and punctuation allowed. + * - __value__ - rule to check tail after link prefix + * - _String_ - just alias to existing rule + * - _Object_ + * - _validate_ - validator function (should return matched length on success), + * or `RegExp`. + * - _normalize_ - optional function to normalize text & url of matched result + * (for example, for @twitter mentions). + * + * `options`: + * + * - __fuzzyLink__ - recognige URL-s without `http(s):` prefix. Default `true`. + * - __fuzzyIP__ - allow IPs in fuzzy links above. Can conflict with some texts + * like version numbers. Default `false`. + * - __fuzzyEmail__ - recognize emails without `mailto:` prefix. + * + **/ + function LinkifyIt(schemas, options) { + if (!(this instanceof LinkifyIt)) { + return new LinkifyIt(schemas, options) + } - re.tpl_link_no_ip_fuzzy = - // Fuzzy link can't be prepended with .:/\- and non punctuation. - // but can start with > (markdown blockquote) - '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + re.src_ZPCc + '))' + - '((?![$+<=>^`|\uff5c])' + re.tpl_host_port_no_ip_fuzzy_strict + re.src_path + ')'; + if (!options) { + if (isOptionsObj(schemas)) { + options = schemas + schemas = {} + } + } - return re; -}; + this.__opts__ = assign({}, defaultOptions, options) -},{"uc.micro/categories/Cc/regex":61,"uc.micro/categories/P/regex":63,"uc.micro/categories/Z/regex":64,"uc.micro/properties/Any/regex":66}],55:[function(require,module,exports){ + // Cache last tested result. Used to skip repeating steps on next `match` call. + this.__index__ = -1 + this.__last_index__ = -1 // Next scan position + this.__schema__ = '' + this.__text_cache__ = '' -'use strict'; + this.__schemas__ = assign({}, defaultSchemas, schemas) + this.__compiled__ = {} + this.__tlds__ = tlds_default + this.__tlds_replaced__ = false -/* eslint-disable no-bitwise */ + this.re = {} -var decodeCache = {}; + compile(this) + } -function getDecodeCache(exclude) { - var i, ch, cache = decodeCache[exclude]; - if (cache) { return cache; } + /** chainable + * LinkifyIt#add(schema, definition) + * - schema (String): rule name (fixed pattern prefix) + * - definition (String|RegExp|Object): schema definition + * + * Add new rule definition. See constructor description for details. + **/ + LinkifyIt.prototype.add = function add(schema, definition) { + this.__schemas__[schema] = definition + compile(this) + return this + } - cache = decodeCache[exclude] = []; + /** chainable + * LinkifyIt#set(options) + * - options (Object): { fuzzyLink|fuzzyEmail|fuzzyIP: true|false } + * + * Set recognition options for links without schema. + **/ + LinkifyIt.prototype.set = function set(options) { + this.__opts__ = assign(this.__opts__, options) + return this + } - for (i = 0; i < 128; i++) { - ch = String.fromCharCode(i); - cache.push(ch); - } + /** + * LinkifyIt#test(text) -> Boolean + * + * Searches linkifiable pattern and returns `true` on success or `false` on fail. + **/ + LinkifyIt.prototype.test = function test(text) { + // Reset scan cache + this.__text_cache__ = text + this.__index__ = -1 + + if (!text.length) { + return false + } - for (i = 0; i < exclude.length; i++) { - ch = exclude.charCodeAt(i); - cache[ch] = '%' + ('0' + ch.toString(16).toUpperCase()).slice(-2); - } + var m, ml, me, len, shift, next, re, tld_pos, at_pos + + // try to scan for link with schema - that's the most simple rule + if (this.re.schema_test.test(text)) { + re = this.re.schema_search + re.lastIndex = 0 + while ((m = re.exec(text)) !== null) { + len = this.testSchemaAt(text, m[2], re.lastIndex) + if (len) { + this.__schema__ = m[2] + this.__index__ = m.index + m[1].length + this.__last_index__ = m.index + m[0].length + len + break + } + } + } - return cache; -} + if (this.__opts__.fuzzyLink && this.__compiled__['http:']) { + // guess schemaless links + tld_pos = text.search(this.re.host_fuzzy_test) + if (tld_pos >= 0) { + // if tld is located after found link - no need to check fuzzy pattern + if (this.__index__ < 0 || tld_pos < this.__index__) { + if ( + (ml = text.match(this.__opts__.fuzzyIP ? this.re.link_fuzzy : this.re.link_no_ip_fuzzy)) !== null + ) { + shift = ml.index + ml[1].length + + if (this.__index__ < 0 || shift < this.__index__) { + this.__schema__ = '' + this.__index__ = shift + this.__last_index__ = ml.index + ml[0].length + } + } + } + } + } + if (this.__opts__.fuzzyEmail && this.__compiled__['mailto:']) { + // guess schemaless emails + at_pos = text.indexOf('@') + if (at_pos >= 0) { + // We can't skip this check, because this cases are possible: + // 192.168.1.1@gmail.com, my.in@example.com + if ((me = text.match(this.re.email_fuzzy)) !== null) { + shift = me.index + me[1].length + next = me.index + me[0].length + + if ( + this.__index__ < 0 || + shift < this.__index__ || + (shift === this.__index__ && next > this.__last_index__) + ) { + this.__schema__ = 'mailto:' + this.__index__ = shift + this.__last_index__ = next + } + } + } + } -// Decode percent-encoded string. -// -function decode(string, exclude) { - var cache; + return this.__index__ >= 0 + } - if (typeof exclude !== 'string') { - exclude = decode.defaultChars; - } + /** + * LinkifyIt#pretest(text) -> Boolean + * + * Very quick check, that can give false positives. Returns true if link MAY BE + * can exists. Can be used for speed optimization, when you need to check that + * link NOT exists. + **/ + LinkifyIt.prototype.pretest = function pretest(text) { + return this.re.pretest.test(text) + } - cache = getDecodeCache(exclude); + /** + * LinkifyIt#testSchemaAt(text, name, position) -> Number + * - text (String): text to scan + * - name (String): rule (schema) name + * - position (Number): text offset to check from + * + * Similar to [[LinkifyIt#test]] but checks only specific protocol tail exactly + * at given position. Returns length of found pattern (0 on fail). + **/ + LinkifyIt.prototype.testSchemaAt = function testSchemaAt(text, schema, pos) { + // If not supported schema check requested - terminate + if (!this.__compiled__[schema.toLowerCase()]) { + return 0 + } + return this.__compiled__[schema.toLowerCase()].validate(text, pos, this) + } - return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) { - var i, l, b1, b2, b3, b4, chr, - result = ''; + /** + * LinkifyIt#match(text) -> Array|null + * + * Returns array of found link descriptions or `null` on fail. We strongly + * recommend to use [[LinkifyIt#test]] first, for best speed. + * + * ##### Result match description + * + * - __schema__ - link schema, can be empty for fuzzy links, or `//` for + * protocol-neutral links. + * - __index__ - offset of matched text + * - __lastIndex__ - index of next char after mathch end + * - __raw__ - matched text + * - __text__ - normalized text + * - __url__ - link, generated from matched text + **/ + LinkifyIt.prototype.match = function match(text) { + var shift = 0, + result = [] + + // Try to take previous element from cache, if .test() called before + if (this.__index__ >= 0 && this.__text_cache__ === text) { + result.push(createMatch(this, shift)) + shift = this.__last_index__ + } - for (i = 0, l = seq.length; i < l; i += 3) { - b1 = parseInt(seq.slice(i + 1, i + 3), 16); + // Cut head if cache was used + var tail = shift ? text.slice(shift) : text - if (b1 < 0x80) { - result += cache[b1]; - continue; - } + // Scan string until end reached + while (this.test(tail)) { + result.push(createMatch(this, shift)) - if ((b1 & 0xE0) === 0xC0 && (i + 3 < l)) { - // 110xxxxx 10xxxxxx - b2 = parseInt(seq.slice(i + 4, i + 6), 16); + tail = tail.slice(this.__last_index__) + shift += this.__last_index__ + } - if ((b2 & 0xC0) === 0x80) { - chr = ((b1 << 6) & 0x7C0) | (b2 & 0x3F); + if (result.length) { + return result + } - if (chr < 0x80) { - result += '\ufffd\ufffd'; - } else { - result += String.fromCharCode(chr); + return null } - i += 3; - continue; - } - } - - if ((b1 & 0xF0) === 0xE0 && (i + 6 < l)) { - // 1110xxxx 10xxxxxx 10xxxxxx - b2 = parseInt(seq.slice(i + 4, i + 6), 16); - b3 = parseInt(seq.slice(i + 7, i + 9), 16); + /** chainable + * LinkifyIt#tlds(list [, keepOld]) -> this + * - list (Array): list of tlds + * - keepOld (Boolean): merge with current list if `true` (`false` by default) + * + * Load (or merge) new tlds list. Those are user for fuzzy links (without prefix) + * to avoid false positives. By default this algorythm used: + * + * - hostname with any 2-letter root zones are ok. + * - biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф + * are ok. + * - encoded (`xn--...`) root zones are ok. + * + * If list is replaced, then exact match for 2-chars root zones will be checked. + **/ + LinkifyIt.prototype.tlds = function tlds(list, keepOld) { + list = Array.isArray(list) ? list : [list] + + if (!keepOld) { + this.__tlds__ = list.slice() + this.__tlds_replaced__ = true + compile(this) + return this + } - if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { - chr = ((b1 << 12) & 0xF000) | ((b2 << 6) & 0xFC0) | (b3 & 0x3F); + this.__tlds__ = this.__tlds__ + .concat(list) + .sort() + .filter(function (el, idx, arr) { + return el !== arr[idx - 1] + }) + .reverse() - if (chr < 0x800 || (chr >= 0xD800 && chr <= 0xDFFF)) { - result += '\ufffd\ufffd\ufffd'; - } else { - result += String.fromCharCode(chr); + compile(this) + return this } - i += 6; - continue; - } - } - - if ((b1 & 0xF8) === 0xF0 && (i + 9 < l)) { - // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx - b2 = parseInt(seq.slice(i + 4, i + 6), 16); - b3 = parseInt(seq.slice(i + 7, i + 9), 16); - b4 = parseInt(seq.slice(i + 10, i + 12), 16); - - if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80 && (b4 & 0xC0) === 0x80) { - chr = ((b1 << 18) & 0x1C0000) | ((b2 << 12) & 0x3F000) | ((b3 << 6) & 0xFC0) | (b4 & 0x3F); + /** + * LinkifyIt#normalize(match) + * + * Default normalizer (if schema does not define it's own). + **/ + LinkifyIt.prototype.normalize = function normalize(match) { + // Do minimal possible changes by default. Need to collect feedback prior + // to move forward https://github.com/markdown-it/linkify-it/issues/1 + + if (!match.schema) { + match.url = 'http://' + match.url + } - if (chr < 0x10000 || chr > 0x10FFFF) { - result += '\ufffd\ufffd\ufffd\ufffd'; - } else { - chr -= 0x10000; - result += String.fromCharCode(0xD800 + (chr >> 10), 0xDC00 + (chr & 0x3FF)); + if (match.schema === 'mailto:' && !/^mailto:/i.test(match.url)) { + match.url = 'mailto:' + match.url + } } - i += 9; - continue; - } - } + /** + * LinkifyIt#onCompile() + * + * Override to modify basic RegExp-s. + **/ + LinkifyIt.prototype.onCompile = function onCompile() {} - result += '\ufffd'; - } + module.exports = LinkifyIt + }, + { './lib/re': 54 }, + ], + 54: [ + function (require, module, exports) { + 'use strict' + + module.exports = function (opts) { + var re = {} + + // Use direct extract instead of `regenerate` to reduse browserified size + re.src_Any = require('uc.micro/properties/Any/regex').source + re.src_Cc = require('uc.micro/categories/Cc/regex').source + re.src_Z = require('uc.micro/categories/Z/regex').source + re.src_P = require('uc.micro/categories/P/regex').source + + // \p{\Z\P\Cc\CF} (white spaces + control + format + punctuation) + re.src_ZPCc = [re.src_Z, re.src_P, re.src_Cc].join('|') + + // \p{\Z\Cc} (white spaces + control) + re.src_ZCc = [re.src_Z, re.src_Cc].join('|') + + // Experimental. List of chars, completely prohibited in links + // because can separate it from other part of text + var text_separators = '[><\uff5c]' + + // All possible word characters (everything without punctuation, spaces & controls) + // Defined via punctuation & spaces to save space + // Should be something like \p{\L\N\S\M} (\w but without `_`) + re.src_pseudo_letter = '(?:(?!' + text_separators + '|' + re.src_ZPCc + ')' + re.src_Any + ')' + // The same as abothe but without [0-9] + // var src_pseudo_letter_non_d = '(?:(?![0-9]|' + src_ZPCc + ')' + src_Any + ')'; + + //////////////////////////////////////////////////////////////////////////////// + + re.src_ip4 = '(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' + + // Prohibit any of "@/[]()" in user/pass to avoid wrong domain fetch. + re.src_auth = '(?:(?:(?!' + re.src_ZCc + '|[@/\\[\\]()]).)+@)?' + + re.src_port = '(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?' + + re.src_host_terminator = + '(?=$|' + text_separators + '|' + re.src_ZPCc + ')(?!-|_|:\\d|\\.-|\\.(?!$|' + re.src_ZPCc + '))' + + re.src_path = + '(?:' + + '[/?#]' + + '(?:' + + '(?!' + + re.src_ZCc + + '|' + + text_separators + + '|[()[\\]{}.,"\'?!\\-]).|' + + '\\[(?:(?!' + + re.src_ZCc + + '|\\]).)*\\]|' + + '\\((?:(?!' + + re.src_ZCc + + '|[)]).)*\\)|' + + '\\{(?:(?!' + + re.src_ZCc + + '|[}]).)*\\}|' + + '\\"(?:(?!' + + re.src_ZCc + + '|["]).)+\\"|' + + "\\'(?:(?!" + + re.src_ZCc + + "|[']).)+\\'|" + + "\\'(?=" + + re.src_pseudo_letter + + '|[-]).|' + // allow `I'm_king` if no pair found + '\\.{2,}[a-zA-Z0-9%/&]|' + // google has many dots in "google search" links (#66, #81). + // github has ... in commit range links, + // Restrict to + // - english + // - percent-encoded + // - parts of file path + // - params separator + // until more examples found. + '\\.(?!' + + re.src_ZCc + + '|[.]).|' + + (opts && opts['---'] + ? '\\-(?!--(?:[^-]|$))(?:-*)|' // `---` => long dash, terminate + : '\\-+|') + + '\\,(?!' + + re.src_ZCc + + ').|' + // allow `,,,` in paths + '\\!+(?!' + + re.src_ZCc + + '|[!]).|' + // allow `!!!` in paths, but not at the end + '\\?(?!' + + re.src_ZCc + + '|[?]).' + + ')+' + + '|\\/' + + ')?' + + // Allow anything in markdown spec, forbid quote (") at the first position + // because emails enclosed in quotes are far more common + re.src_email_name = '[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*' + + re.src_xn = 'xn--[a-z0-9\\-]{1,59}' + + // More to read about domain names + // http://serverfault.com/questions/638260/ + + re.src_domain_root = + // Allow letters & digits (http://test1) + '(?:' + re.src_xn + '|' + re.src_pseudo_letter + '{1,63}' + ')' + + re.src_domain = + '(?:' + + re.src_xn + + '|' + + '(?:' + + re.src_pseudo_letter + + ')' + + '|' + + '(?:' + + re.src_pseudo_letter + + '(?:-|' + + re.src_pseudo_letter + + '){0,61}' + + re.src_pseudo_letter + + ')' + + ')' + + re.src_host = + '(?:' + + // Don't need IP check, because digits are already allowed in normal domain names + // src_ip4 + + // '|' + + '(?:(?:(?:' + + re.src_domain + + ')\\.)*' + + re.src_domain /*_root*/ + + ')' + + ')' + + re.tpl_host_fuzzy = '(?:' + re.src_ip4 + '|' + '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))' + ')' + + re.tpl_host_no_ip_fuzzy = '(?:(?:(?:' + re.src_domain + ')\\.)+(?:%TLDS%))' + + re.src_host_strict = re.src_host + re.src_host_terminator + + re.tpl_host_fuzzy_strict = re.tpl_host_fuzzy + re.src_host_terminator + + re.src_host_port_strict = re.src_host + re.src_port + re.src_host_terminator + + re.tpl_host_port_fuzzy_strict = re.tpl_host_fuzzy + re.src_port + re.src_host_terminator + + re.tpl_host_port_no_ip_fuzzy_strict = re.tpl_host_no_ip_fuzzy + re.src_port + re.src_host_terminator + + //////////////////////////////////////////////////////////////////////////////// + // Main rules + + // Rude test fuzzy links by host, for quick deny + re.tpl_host_fuzzy_test = 'localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:' + re.src_ZPCc + '|>|$))' + + re.tpl_email_fuzzy = + '(^|' + + text_separators + + '|"|\\(|' + + re.src_ZCc + + ')' + + '(' + + re.src_email_name + + '@' + + re.tpl_host_fuzzy_strict + + ')' + + re.tpl_link_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + + re.src_ZPCc + + '))' + + '((?![$+<=>^`|\uff5c])' + + re.tpl_host_port_fuzzy_strict + + re.src_path + + ')' + + re.tpl_link_no_ip_fuzzy = + // Fuzzy link can't be prepended with .:/\- and non punctuation. + // but can start with > (markdown blockquote) + '(^|(?![.:/\\-_@])(?:[$+<=>^`|\uff5c]|' + + re.src_ZPCc + + '))' + + '((?![$+<=>^`|\uff5c])' + + re.tpl_host_port_no_ip_fuzzy_strict + + re.src_path + + ')' + + return re + } + }, + { + 'uc.micro/categories/Cc/regex': 61, + 'uc.micro/categories/P/regex': 63, + 'uc.micro/categories/Z/regex': 64, + 'uc.micro/properties/Any/regex': 66, + }, + ], + 55: [ + function (require, module, exports) { + 'use strict' - return result; - }); -} + /* eslint-disable no-bitwise */ + var decodeCache = {} -decode.defaultChars = ';/?:@&=+$,#'; -decode.componentChars = ''; + function getDecodeCache(exclude) { + var i, + ch, + cache = decodeCache[exclude] + if (cache) { + return cache + } + cache = decodeCache[exclude] = [] -module.exports = decode; + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i) + cache.push(ch) + } -},{}],56:[function(require,module,exports){ + for (i = 0; i < exclude.length; i++) { + ch = exclude.charCodeAt(i) + cache[ch] = '%' + ('0' + ch.toString(16).toUpperCase()).slice(-2) + } -'use strict'; + return cache + } + // Decode percent-encoded string. + // + function decode(string, exclude) { + var cache -var encodeCache = {}; + if (typeof exclude !== 'string') { + exclude = decode.defaultChars + } + cache = getDecodeCache(exclude) + + return string.replace(/(%[a-f0-9]{2})+/gi, function (seq) { + var i, + l, + b1, + b2, + b3, + b4, + chr, + result = '' + + for (i = 0, l = seq.length; i < l; i += 3) { + b1 = parseInt(seq.slice(i + 1, i + 3), 16) + + if (b1 < 0x80) { + result += cache[b1] + continue + } + + if ((b1 & 0xe0) === 0xc0 && i + 3 < l) { + // 110xxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16) + + if ((b2 & 0xc0) === 0x80) { + chr = ((b1 << 6) & 0x7c0) | (b2 & 0x3f) + + if (chr < 0x80) { + result += '\ufffd\ufffd' + } else { + result += String.fromCharCode(chr) + } + + i += 3 + continue + } + } + + if ((b1 & 0xf0) === 0xe0 && i + 6 < l) { + // 1110xxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16) + b3 = parseInt(seq.slice(i + 7, i + 9), 16) + + if ((b2 & 0xc0) === 0x80 && (b3 & 0xc0) === 0x80) { + chr = ((b1 << 12) & 0xf000) | ((b2 << 6) & 0xfc0) | (b3 & 0x3f) + + if (chr < 0x800 || (chr >= 0xd800 && chr <= 0xdfff)) { + result += '\ufffd\ufffd\ufffd' + } else { + result += String.fromCharCode(chr) + } + + i += 6 + continue + } + } + + if ((b1 & 0xf8) === 0xf0 && i + 9 < l) { + // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx + b2 = parseInt(seq.slice(i + 4, i + 6), 16) + b3 = parseInt(seq.slice(i + 7, i + 9), 16) + b4 = parseInt(seq.slice(i + 10, i + 12), 16) + + if ((b2 & 0xc0) === 0x80 && (b3 & 0xc0) === 0x80 && (b4 & 0xc0) === 0x80) { + chr = ((b1 << 18) & 0x1c0000) | ((b2 << 12) & 0x3f000) | ((b3 << 6) & 0xfc0) | (b4 & 0x3f) + + if (chr < 0x10000 || chr > 0x10ffff) { + result += '\ufffd\ufffd\ufffd\ufffd' + } else { + chr -= 0x10000 + result += String.fromCharCode(0xd800 + (chr >> 10), 0xdc00 + (chr & 0x3ff)) + } + + i += 9 + continue + } + } + + result += '\ufffd' + } + + return result + }) + } -// Create a lookup array where anything but characters in `chars` string -// and alphanumeric chars is percent-encoded. -// -function getEncodeCache(exclude) { - var i, ch, cache = encodeCache[exclude]; - if (cache) { return cache; } + decode.defaultChars = ';/?:@&=+$,#' + decode.componentChars = '' - cache = encodeCache[exclude] = []; + module.exports = decode + }, + {}, + ], + 56: [ + function (require, module, exports) { + 'use strict' - for (i = 0; i < 128; i++) { - ch = String.fromCharCode(i); + var encodeCache = {} - if (/^[0-9a-z]$/i.test(ch)) { - // always allow unencoded alphanumeric characters - cache.push(ch); - } else { - cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2)); - } - } + // Create a lookup array where anything but characters in `chars` string + // and alphanumeric chars is percent-encoded. + // + function getEncodeCache(exclude) { + var i, + ch, + cache = encodeCache[exclude] + if (cache) { + return cache + } - for (i = 0; i < exclude.length; i++) { - cache[exclude.charCodeAt(i)] = exclude[i]; - } + cache = encodeCache[exclude] = [] - return cache; -} - - -// Encode unsafe characters with percent-encoding, skipping already -// encoded sequences. -// -// - string - string to encode -// - exclude - list of characters to ignore (in addition to a-zA-Z0-9) -// - keepEscaped - don't encode '%' in a correct escape sequence (default: true) -// -function encode(string, exclude, keepEscaped) { - var i, l, code, nextCode, cache, - result = ''; - - if (typeof exclude !== 'string') { - // encode(string, keepEscaped) - keepEscaped = exclude; - exclude = encode.defaultChars; - } + for (i = 0; i < 128; i++) { + ch = String.fromCharCode(i) - if (typeof keepEscaped === 'undefined') { - keepEscaped = true; - } + if (/^[0-9a-z]$/i.test(ch)) { + // always allow unencoded alphanumeric characters + cache.push(ch) + } else { + cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2)) + } + } - cache = getEncodeCache(exclude); + for (i = 0; i < exclude.length; i++) { + cache[exclude.charCodeAt(i)] = exclude[i] + } - for (i = 0, l = string.length; i < l; i++) { - code = string.charCodeAt(i); + return cache + } - if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) { - if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) { - result += string.slice(i, i + 3); - i += 2; - continue; - } - } + // Encode unsafe characters with percent-encoding, skipping already + // encoded sequences. + // + // - string - string to encode + // - exclude - list of characters to ignore (in addition to a-zA-Z0-9) + // - keepEscaped - don't encode '%' in a correct escape sequence (default: true) + // + function encode(string, exclude, keepEscaped) { + var i, + l, + code, + nextCode, + cache, + result = '' + + if (typeof exclude !== 'string') { + // encode(string, keepEscaped) + keepEscaped = exclude + exclude = encode.defaultChars + } - if (code < 128) { - result += cache[code]; - continue; - } + if (typeof keepEscaped === 'undefined') { + keepEscaped = true + } - if (code >= 0xD800 && code <= 0xDFFF) { - if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l) { - nextCode = string.charCodeAt(i + 1); - if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) { - result += encodeURIComponent(string[i] + string[i + 1]); - i++; - continue; - } - } - result += '%EF%BF%BD'; - continue; - } + cache = getEncodeCache(exclude) + + for (i = 0, l = string.length; i < l; i++) { + code = string.charCodeAt(i) + + if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) { + if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) { + result += string.slice(i, i + 3) + i += 2 + continue + } + } + + if (code < 128) { + result += cache[code] + continue + } + + if (code >= 0xd800 && code <= 0xdfff) { + if (code >= 0xd800 && code <= 0xdbff && i + 1 < l) { + nextCode = string.charCodeAt(i + 1) + if (nextCode >= 0xdc00 && nextCode <= 0xdfff) { + result += encodeURIComponent(string[i] + string[i + 1]) + i++ + continue + } + } + result += '%EF%BF%BD' + continue + } + + result += encodeURIComponent(string[i]) + } - result += encodeURIComponent(string[i]); - } + return result + } - return result; -} + encode.defaultChars = ";/?:@&=+$,-_.!~*'()#" + encode.componentChars = "-_.!~*'()" -encode.defaultChars = ";/?:@&=+$,-_.!~*'()#"; -encode.componentChars = "-_.!~*'()"; + module.exports = encode + }, + {}, + ], + 57: [ + function (require, module, exports) { + 'use strict' + module.exports = function format(url) { + var result = '' -module.exports = encode; + result += url.protocol || '' + result += url.slashes ? '//' : '' + result += url.auth ? url.auth + '@' : '' -},{}],57:[function(require,module,exports){ + if (url.hostname && url.hostname.indexOf(':') !== -1) { + // ipv6 address + result += '[' + url.hostname + ']' + } else { + result += url.hostname || '' + } -'use strict'; + result += url.port ? ':' + url.port : '' + result += url.pathname || '' + result += url.search || '' + result += url.hash || '' + return result + } + }, + {}, + ], + 58: [ + function (require, module, exports) { + 'use strict' + + module.exports.encode = require('./encode') + module.exports.decode = require('./decode') + module.exports.format = require('./format') + module.exports.parse = require('./parse') + }, + { './decode': 55, './encode': 56, './format': 57, './parse': 59 }, + ], + 59: [ + function (require, module, exports) { + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. -module.exports = function format(url) { - var result = ''; + 'use strict' - result += url.protocol || ''; - result += url.slashes ? '//' : ''; - result += url.auth ? url.auth + '@' : ''; + // + // Changes from joyent/node: + // + // 1. No leading slash in paths, + // e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` + // + // 2. Backslashes are not replaced with slashes, + // so `http:\\example.org\` is treated like a relative path + // + // 3. Trailing colon is treated like a part of the path, + // i.e. in `http://example.org:foo` pathname is `:foo` + // + // 4. Nothing is URL-encoded in the resulting object, + // (in joyent/node some chars in auth and paths are encoded) + // + // 5. `url.parse()` does not have `parseQueryString` argument + // + // 6. Removed extraneous result properties: `host`, `path`, `query`, etc., + // which can be constructed using other parts of the url. + // - if (url.hostname && url.hostname.indexOf(':') !== -1) { - // ipv6 address - result += '[' + url.hostname + ']'; - } else { - result += url.hostname || ''; - } + function Url() { + this.protocol = null + this.slashes = null + this.auth = null + this.port = null + this.hostname = null + this.hash = null + this.search = null + this.pathname = null + } - result += url.port ? ':' + url.port : ''; - result += url.pathname || ''; - result += url.search || ''; - result += url.hash || ''; - - return result; -}; - -},{}],58:[function(require,module,exports){ -'use strict'; - - -module.exports.encode = require('./encode'); -module.exports.decode = require('./decode'); -module.exports.format = require('./format'); -module.exports.parse = require('./parse'); - -},{"./decode":55,"./encode":56,"./format":57,"./parse":59}],59:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -'use strict'; - -// -// Changes from joyent/node: -// -// 1. No leading slash in paths, -// e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` -// -// 2. Backslashes are not replaced with slashes, -// so `http:\\example.org\` is treated like a relative path -// -// 3. Trailing colon is treated like a part of the path, -// i.e. in `http://example.org:foo` pathname is `:foo` -// -// 4. Nothing is URL-encoded in the resulting object, -// (in joyent/node some chars in auth and paths are encoded) -// -// 5. `url.parse()` does not have `parseQueryString` argument -// -// 6. Removed extraneous result properties: `host`, `path`, `query`, etc., -// which can be constructed using other parts of the url. -// - - -function Url() { - this.protocol = null; - this.slashes = null; - this.auth = null; - this.port = null; - this.hostname = null; - this.hash = null; - this.search = null; - this.pathname = null; -} - -// Reference: RFC 3986, RFC 1808, RFC 2396 - -// define these here so at least they only have to be -// compiled once on the first module load. -var protocolPattern = /^([a-z0-9.+-]+:)/i, - portPattern = /:[0-9]*$/, - - // Special case for a simple path URL - simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, - - // RFC 2396: characters reserved for delimiting URLs. - // We actually just auto-escape these. - delims = [ '<', '>', '"', '`', ' ', '\r', '\n', '\t' ], - - // RFC 2396: characters not allowed for various reasons. - unwise = [ '{', '}', '|', '\\', '^', '`' ].concat(delims), - - // Allowed by RFCs, but cause of XSS attacks. Always escape these. - autoEscape = [ '\'' ].concat(unwise), - // Characters that are never ever allowed in a hostname. - // Note that any invalid chars are also handled, but these - // are the ones that are *expected* to be seen, so we fast-path - // them. - nonHostChars = [ '%', '/', '?', ';', '#' ].concat(autoEscape), - hostEndingChars = [ '/', '?', '#' ], - hostnameMaxLen = 255, - hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, - hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, - // protocols that can allow "unsafe" and "unwise" chars. - /* eslint-disable no-script-url */ - // protocols that never have a hostname. - hostlessProtocol = { - 'javascript': true, - 'javascript:': true - }, - // protocols that always contain a // bit. - slashedProtocol = { - 'http': true, - 'https': true, - 'ftp': true, - 'gopher': true, - 'file': true, - 'http:': true, - 'https:': true, - 'ftp:': true, - 'gopher:': true, - 'file:': true - }; - /* eslint-enable no-script-url */ - -function urlParse(url, slashesDenoteHost) { - if (url && url instanceof Url) { return url; } - - var u = new Url(); - u.parse(url, slashesDenoteHost); - return u; -} - -Url.prototype.parse = function(url, slashesDenoteHost) { - var i, l, lowerProto, hec, slashes, - rest = url; - - // trim before proceeding. - // This is to support parse stuff like " http://foo.com \n" - rest = rest.trim(); - - if (!slashesDenoteHost && url.split('#').length === 1) { - // Try fast path regexp - var simplePath = simplePathPattern.exec(rest); - if (simplePath) { - this.pathname = simplePath[1]; - if (simplePath[2]) { - this.search = simplePath[2]; - } - return this; - } - } + // Reference: RFC 3986, RFC 1808, RFC 2396 + + // define these here so at least they only have to be + // compiled once on the first module load. + var protocolPattern = /^([a-z0-9.+-]+:)/i, + portPattern = /:[0-9]*$/, + // Special case for a simple path URL + simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, + // RFC 2396: characters reserved for delimiting URLs. + // We actually just auto-escape these. + delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], + // RFC 2396: characters not allowed for various reasons. + unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims), + // Allowed by RFCs, but cause of XSS attacks. Always escape these. + autoEscape = ["'"].concat(unwise), + // Characters that are never ever allowed in a hostname. + // Note that any invalid chars are also handled, but these + // are the ones that are *expected* to be seen, so we fast-path + // them. + nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape), + hostEndingChars = ['/', '?', '#'], + hostnameMaxLen = 255, + hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, + hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, + // protocols that can allow "unsafe" and "unwise" chars. + /* eslint-disable no-script-url */ + // protocols that never have a hostname. + hostlessProtocol = { + javascript: true, + 'javascript:': true, + }, + // protocols that always contain a // bit. + slashedProtocol = { + http: true, + https: true, + ftp: true, + gopher: true, + file: true, + 'http:': true, + 'https:': true, + 'ftp:': true, + 'gopher:': true, + 'file:': true, + } + /* eslint-enable no-script-url */ - var proto = protocolPattern.exec(rest); - if (proto) { - proto = proto[0]; - lowerProto = proto.toLowerCase(); - this.protocol = proto; - rest = rest.substr(proto.length); - } + function urlParse(url, slashesDenoteHost) { + if (url && url instanceof Url) { + return url + } - // figure out if it's got a host - // user@server is *always* interpreted as a hostname, and url - // resolution will treat //foo/bar as host=foo,path=bar because that's - // how the browser resolves relative URLs. - if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { - slashes = rest.substr(0, 2) === '//'; - if (slashes && !(proto && hostlessProtocol[proto])) { - rest = rest.substr(2); - this.slashes = true; - } - } + var u = new Url() + u.parse(url, slashesDenoteHost) + return u + } - if (!hostlessProtocol[proto] && - (slashes || (proto && !slashedProtocol[proto]))) { - - // there's a hostname. - // the first instance of /, ?, ;, or # ends the host. - // - // If there is an @ in the hostname, then non-host chars *are* allowed - // to the left of the last @ sign, unless some host-ending character - // comes *before* the @-sign. - // URLs are obnoxious. - // - // ex: - // http://a@b@c/ => user:a@b host:c - // http://a@b?@c => user:a host:c path:/?@c - - // v0.12 TODO(isaacs): This is not quite how Chrome does things. - // Review our test case against browsers more comprehensively. - - // find the first instance of any hostEndingChars - var hostEnd = -1; - for (i = 0; i < hostEndingChars.length; i++) { - hec = rest.indexOf(hostEndingChars[i]); - if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { - hostEnd = hec; - } - } + Url.prototype.parse = function (url, slashesDenoteHost) { + var i, + l, + lowerProto, + hec, + slashes, + rest = url + + // trim before proceeding. + // This is to support parse stuff like " http://foo.com \n" + rest = rest.trim() + + if (!slashesDenoteHost && url.split('#').length === 1) { + // Try fast path regexp + var simplePath = simplePathPattern.exec(rest) + if (simplePath) { + this.pathname = simplePath[1] + if (simplePath[2]) { + this.search = simplePath[2] + } + return this + } + } - // at this point, either we have an explicit point where the - // auth portion cannot go past, or the last @ char is the decider. - var auth, atSign; - if (hostEnd === -1) { - // atSign can be anywhere. - atSign = rest.lastIndexOf('@'); - } else { - // atSign must be in auth portion. - // http://a@b/c@d => host:b auth:a path:/c@d - atSign = rest.lastIndexOf('@', hostEnd); - } + var proto = protocolPattern.exec(rest) + if (proto) { + proto = proto[0] + lowerProto = proto.toLowerCase() + this.protocol = proto + rest = rest.substr(proto.length) + } - // Now we have a portion which is definitely the auth. - // Pull that off. - if (atSign !== -1) { - auth = rest.slice(0, atSign); - rest = rest.slice(atSign + 1); - this.auth = auth; - } + // figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { + slashes = rest.substr(0, 2) === '//' + if (slashes && !(proto && hostlessProtocol[proto])) { + rest = rest.substr(2) + this.slashes = true + } + } - // the host is the remaining to the left of the first non-host char - hostEnd = -1; - for (i = 0; i < nonHostChars.length; i++) { - hec = rest.indexOf(nonHostChars[i]); - if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { - hostEnd = hec; - } - } - // if we still have not hit it, then the entire thing is a host. - if (hostEnd === -1) { - hostEnd = rest.length; - } + if (!hostlessProtocol[proto] && (slashes || (proto && !slashedProtocol[proto]))) { + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:c path:/?@c + + // v0.12 TODO(isaacs): This is not quite how Chrome does things. + // Review our test case against browsers more comprehensively. + + // find the first instance of any hostEndingChars + var hostEnd = -1 + for (i = 0; i < hostEndingChars.length; i++) { + hec = rest.indexOf(hostEndingChars[i]) + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec + } + } + + // at this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + var auth, atSign + if (hostEnd === -1) { + // atSign can be anywhere. + atSign = rest.lastIndexOf('@') + } else { + // atSign must be in auth portion. + // http://a@b/c@d => host:b auth:a path:/c@d + atSign = rest.lastIndexOf('@', hostEnd) + } + + // Now we have a portion which is definitely the auth. + // Pull that off. + if (atSign !== -1) { + auth = rest.slice(0, atSign) + rest = rest.slice(atSign + 1) + this.auth = auth + } + + // the host is the remaining to the left of the first non-host char + hostEnd = -1 + for (i = 0; i < nonHostChars.length; i++) { + hec = rest.indexOf(nonHostChars[i]) + if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) { + hostEnd = hec + } + } + // if we still have not hit it, then the entire thing is a host. + if (hostEnd === -1) { + hostEnd = rest.length + } + + if (rest[hostEnd - 1] === ':') { + hostEnd-- + } + var host = rest.slice(0, hostEnd) + rest = rest.slice(hostEnd) + + // pull out port. + this.parseHost(host) + + // we've indicated that there is a hostname, + // so even if it's empty, it has to be present. + this.hostname = this.hostname || '' + + // if hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + var ipv6Hostname = this.hostname[0] === '[' && this.hostname[this.hostname.length - 1] === ']' + + // validate a little. + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./) + for (i = 0, l = hostparts.length; i < l; i++) { + var part = hostparts[i] + if (!part) { + continue + } + if (!part.match(hostnamePartPattern)) { + var newpart = '' + for (var j = 0, k = part.length; j < k; j++) { + if (part.charCodeAt(j) > 127) { + // we replace non-ASCII char with a temporary placeholder + // we need this to make sure size of hostname is not + // broken by replacing non-ASCII by nothing + newpart += 'x' + } else { + newpart += part[j] + } + } + // we test again with ASCII char only + if (!newpart.match(hostnamePartPattern)) { + var validParts = hostparts.slice(0, i) + var notHost = hostparts.slice(i + 1) + var bit = part.match(hostnamePartStart) + if (bit) { + validParts.push(bit[1]) + notHost.unshift(bit[2]) + } + if (notHost.length) { + rest = notHost.join('.') + rest + } + this.hostname = validParts.join('.') + break + } + } + } + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = '' + } + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.substr(1, this.hostname.length - 2) + } + } - if (rest[hostEnd - 1] === ':') { hostEnd--; } - var host = rest.slice(0, hostEnd); - rest = rest.slice(hostEnd); - - // pull out port. - this.parseHost(host); - - // we've indicated that there is a hostname, - // so even if it's empty, it has to be present. - this.hostname = this.hostname || ''; - - // if hostname begins with [ and ends with ] - // assume that it's an IPv6 address. - var ipv6Hostname = this.hostname[0] === '[' && - this.hostname[this.hostname.length - 1] === ']'; - - // validate a little. - if (!ipv6Hostname) { - var hostparts = this.hostname.split(/\./); - for (i = 0, l = hostparts.length; i < l; i++) { - var part = hostparts[i]; - if (!part) { continue; } - if (!part.match(hostnamePartPattern)) { - var newpart = ''; - for (var j = 0, k = part.length; j < k; j++) { - if (part.charCodeAt(j) > 127) { - // we replace non-ASCII char with a temporary placeholder - // we need this to make sure size of hostname is not - // broken by replacing non-ASCII by nothing - newpart += 'x'; - } else { - newpart += part[j]; + // chop off from the tail first. + var hash = rest.indexOf('#') + if (hash !== -1) { + // got a fragment string. + this.hash = rest.substr(hash) + rest = rest.slice(0, hash) } - } - // we test again with ASCII char only - if (!newpart.match(hostnamePartPattern)) { - var validParts = hostparts.slice(0, i); - var notHost = hostparts.slice(i + 1); - var bit = part.match(hostnamePartStart); - if (bit) { - validParts.push(bit[1]); - notHost.unshift(bit[2]); + var qm = rest.indexOf('?') + if (qm !== -1) { + this.search = rest.substr(qm) + rest = rest.slice(0, qm) } - if (notHost.length) { - rest = notHost.join('.') + rest; + if (rest) { + this.pathname = rest + } + if (slashedProtocol[lowerProto] && this.hostname && !this.pathname) { + this.pathname = '' } - this.hostname = validParts.join('.'); - break; - } - } - } - } - - if (this.hostname.length > hostnameMaxLen) { - this.hostname = ''; - } - // strip [ and ] from the hostname - // the host field still retains them, though - if (ipv6Hostname) { - this.hostname = this.hostname.substr(1, this.hostname.length - 2); - } - } + return this + } - // chop off from the tail first. - var hash = rest.indexOf('#'); - if (hash !== -1) { - // got a fragment string. - this.hash = rest.substr(hash); - rest = rest.slice(0, hash); - } - var qm = rest.indexOf('?'); - if (qm !== -1) { - this.search = rest.substr(qm); - rest = rest.slice(0, qm); - } - if (rest) { this.pathname = rest; } - if (slashedProtocol[lowerProto] && - this.hostname && !this.pathname) { - this.pathname = ''; - } + Url.prototype.parseHost = function (host) { + var port = portPattern.exec(host) + if (port) { + port = port[0] + if (port !== ':') { + this.port = port.substr(1) + } + host = host.substr(0, host.length - port.length) + } + if (host) { + this.hostname = host + } + } - return this; -}; + module.exports = urlParse + }, + {}, + ], + 60: [ + function (require, module, exports) { + ;(function (global) { + /*! https://mths.be/punycode v1.4.1 by @mathias */ + ;(function (root) { + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports + var freeModule = typeof module == 'object' && module && !module.nodeType && module + var freeGlobal = typeof global == 'object' && global + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + /** Error messages */ + errors = { + overflow: 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input', + }, + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + /** Temporary variable */ + key + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]) + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length + var result = [] + while (length--) { + result[length] = fn(array[length]) + } + return result + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@') + var result = '' + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@' + string = parts[1] + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E') + var labels = string.split('.') + var encoded = map(labels, fn).join('.') + return result + encoded + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra + while (counter < length) { + value = string.charCodeAt(counter++) + if (value >= 0xd800 && value <= 0xdbff && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++) + if ((extra & 0xfc00) == 0xdc00) { + // low surrogate + output.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000) + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value) + counter-- + } + } else { + output.push(value) + } + } + return output + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function (value) { + var output = '' + if (value > 0xffff) { + value -= 0x10000 + output += stringFromCharCode(((value >>> 10) & 0x3ff) | 0xd800) + value = 0xdc00 | (value & 0x3ff) + } + output += stringFromCharCode(value) + return output + }).join('') + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22 + } + if (codePoint - 65 < 26) { + return codePoint - 65 + } + if (codePoint - 97 < 26) { + return codePoint - 97 + } + return base + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5) + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0 + delta = firstTime ? floor(delta / damp) : delta >> 1 + delta += floor(delta / numPoints) + for (; /* no initialization */ delta > (baseMinusTMin * tMax) >> 1; k += base) { + delta = floor(delta / baseMinusTMin) + } + return floor(k + ((baseMinusTMin + 1) * delta) / (delta + skew)) + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter) + if (basic < 0) { + basic = 0 + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic') + } + output.push(input.charCodeAt(j)) + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength /* no final expression */; ) { + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base /* no condition */; ; k += base) { + if (index >= inputLength) { + error('invalid-input') + } + + digit = basicToDigit(input.charCodeAt(index++)) + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow') + } + + i += digit * w + t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias + + if (digit < t) { + break + } + + baseMinusT = base - t + if (w > floor(maxInt / baseMinusT)) { + error('overflow') + } + + w *= baseMinusT + } + + out = output.length + 1 + bias = adapt(i - oldi, out, oldi == 0) + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow') + } + + n += floor(i / out) + i %= out + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n) + } + + return ucs2encode(output) + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input) + + // Cache the length + inputLength = input.length + + // Initialize the state + n = initialN + delta = 0 + bias = initialBias + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j] + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)) + } + } + + handledCPCount = basicLength = output.length + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter) + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j] + if (currentValue >= n && currentValue < m) { + m = currentValue + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1 + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow') + } + + delta += (m - n) * handledCPCountPlusOne + n = m + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j] + + if (currentValue < n && ++delta > maxInt) { + error('overflow') + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base /* no condition */; ; k += base) { + t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias + if (q < t) { + break + } + qMinusT = q - t + baseMinusT = base - t + output.push(stringFromCharCode(digitToBasic(t + (qMinusT % baseMinusT), 0))) + q = floor(qMinusT / baseMinusT) + } + + output.push(stringFromCharCode(digitToBasic(q, 0))) + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength) + delta = 0 + ++handledCPCount + } + } + + ++delta + ++n + } + return output.join('') + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function (string) { + return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string + }) + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function (string) { + return regexNonASCII.test(string) ? 'xn--' + encode(string) : string + }) + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + version: '1.4.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + ucs2: { + decode: ucs2decode, + encode: ucs2encode, + }, + decode: decode, + encode: encode, + toASCII: toASCII, + toUnicode: toUnicode, + } + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { + define('punycode', function () { + return punycode + }) + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { + // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = punycode + } else { + // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]) + } + } + } else { + // in Rhino or a web browser + root.punycode = punycode + } + })(this) + }.call( + this, + typeof global !== 'undefined' + ? global + : typeof self !== 'undefined' + ? self + : typeof window !== 'undefined' + ? window + : {} + )) + }, + {}, + ], + 61: [ + function (require, module, exports) { + module.exports = /[\0-\x1F\x7F-\x9F]/ + }, + {}, + ], + 62: [ + function (require, module, exports) { + module.exports = + /[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/ + }, + {}, + ], + 63: [ + function (require, module, exports) { + module.exports = + /[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/ + }, + {}, + ], + 64: [ + function (require, module, exports) { + module.exports = /[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/ + }, + {}, + ], + 65: [ + function (require, module, exports) { + 'use strict' + + exports.Any = require('./properties/Any/regex') + exports.Cc = require('./categories/Cc/regex') + exports.Cf = require('./categories/Cf/regex') + exports.P = require('./categories/P/regex') + exports.Z = require('./categories/Z/regex') + }, + { + './categories/Cc/regex': 61, + './categories/Cf/regex': 62, + './categories/P/regex': 63, + './categories/Z/regex': 64, + './properties/Any/regex': 66, + }, + ], + 66: [ + function (require, module, exports) { + module.exports = + /[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/ + }, + {}, + ], + 67: [ + function (require, module, exports) { + 'use strict' -Url.prototype.parseHost = function(host) { - var port = portPattern.exec(host); - if (port) { - port = port[0]; - if (port !== ':') { - this.port = port.substr(1); - } - host = host.substr(0, host.length - port.length); - } - if (host) { this.hostname = host; } -}; - -module.exports = urlParse; - -},{}],60:[function(require,module,exports){ -(function (global){ -/*! https://mths.be/punycode v1.4.1 by @mathias */ -;(function(root) { - - /** Detect free variables */ - var freeExports = typeof exports == 'object' && exports && - !exports.nodeType && exports; - var freeModule = typeof module == 'object' && module && - !module.nodeType && module; - var freeGlobal = typeof global == 'object' && global; - if ( - freeGlobal.global === freeGlobal || - freeGlobal.window === freeGlobal || - freeGlobal.self === freeGlobal - ) { - root = freeGlobal; - } - - /** - * The `punycode` object. - * @name punycode - * @type Object - */ - var punycode, - - /** Highest positive signed 32-bit float value */ - maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 - - /** Bootstring parameters */ - base = 36, - tMin = 1, - tMax = 26, - skew = 38, - damp = 700, - initialBias = 72, - initialN = 128, // 0x80 - delimiter = '-', // '\x2D' - - /** Regular expressions */ - regexPunycode = /^xn--/, - regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars - regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators - - /** Error messages */ - errors = { - 'overflow': 'Overflow: input needs wider integers to process', - 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', - 'invalid-input': 'Invalid input' - }, - - /** Convenience shortcuts */ - baseMinusTMin = base - tMin, - floor = Math.floor, - stringFromCharCode = String.fromCharCode, - - /** Temporary variable */ - key; - - /*--------------------------------------------------------------------------*/ - - /** - * A generic error utility function. - * @private - * @param {String} type The error type. - * @returns {Error} Throws a `RangeError` with the applicable error message. - */ - function error(type) { - throw new RangeError(errors[type]); - } - - /** - * A generic `Array#map` utility function. - * @private - * @param {Array} array The array to iterate over. - * @param {Function} callback The function that gets called for every array - * item. - * @returns {Array} A new array of values returned by the callback function. - */ - function map(array, fn) { - var length = array.length; - var result = []; - while (length--) { - result[length] = fn(array[length]); - } - return result; - } - - /** - * A simple `Array#map`-like wrapper to work with domain name strings or email - * addresses. - * @private - * @param {String} domain The domain name or email address. - * @param {Function} callback The function that gets called for every - * character. - * @returns {Array} A new string of characters returned by the callback - * function. - */ - function mapDomain(string, fn) { - var parts = string.split('@'); - var result = ''; - if (parts.length > 1) { - // In email addresses, only the domain name should be punycoded. Leave - // the local part (i.e. everything up to `@`) intact. - result = parts[0] + '@'; - string = parts[1]; - } - // Avoid `split(regex)` for IE8 compatibility. See #17. - string = string.replace(regexSeparators, '\x2E'); - var labels = string.split('.'); - var encoded = map(labels, fn).join('.'); - return result + encoded; - } - - /** - * Creates an array containing the numeric code points of each Unicode - * character in the string. While JavaScript uses UCS-2 internally, - * this function will convert a pair of surrogate halves (each of which - * UCS-2 exposes as separate characters) into a single code point, - * matching UTF-16. - * @see `punycode.ucs2.encode` - * @see - * @memberOf punycode.ucs2 - * @name decode - * @param {String} string The Unicode input string (UCS-2). - * @returns {Array} The new array of code points. - */ - function ucs2decode(string) { - var output = [], - counter = 0, - length = string.length, - value, - extra; - while (counter < length) { - value = string.charCodeAt(counter++); - if (value >= 0xD800 && value <= 0xDBFF && counter < length) { - // high surrogate, and there is a next character - extra = string.charCodeAt(counter++); - if ((extra & 0xFC00) == 0xDC00) { // low surrogate - output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); - } else { - // unmatched surrogate; only append this code unit, in case the next - // code unit is the high surrogate of a surrogate pair - output.push(value); - counter--; - } - } else { - output.push(value); - } - } - return output; - } - - /** - * Creates a string based on an array of numeric code points. - * @see `punycode.ucs2.decode` - * @memberOf punycode.ucs2 - * @name encode - * @param {Array} codePoints The array of numeric code points. - * @returns {String} The new Unicode string (UCS-2). - */ - function ucs2encode(array) { - return map(array, function(value) { - var output = ''; - if (value > 0xFFFF) { - value -= 0x10000; - output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); - value = 0xDC00 | value & 0x3FF; - } - output += stringFromCharCode(value); - return output; - }).join(''); - } - - /** - * Converts a basic code point into a digit/integer. - * @see `digitToBasic()` - * @private - * @param {Number} codePoint The basic numeric code point value. - * @returns {Number} The numeric value of a basic code point (for use in - * representing integers) in the range `0` to `base - 1`, or `base` if - * the code point does not represent a value. - */ - function basicToDigit(codePoint) { - if (codePoint - 48 < 10) { - return codePoint - 22; - } - if (codePoint - 65 < 26) { - return codePoint - 65; - } - if (codePoint - 97 < 26) { - return codePoint - 97; - } - return base; - } - - /** - * Converts a digit/integer into a basic code point. - * @see `basicToDigit()` - * @private - * @param {Number} digit The numeric value of a basic code point. - * @returns {Number} The basic code point whose value (when used for - * representing integers) is `digit`, which needs to be in the range - * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is - * used; else, the lowercase form is used. The behavior is undefined - * if `flag` is non-zero and `digit` has no uppercase form. - */ - function digitToBasic(digit, flag) { - // 0..25 map to ASCII a..z or A..Z - // 26..35 map to ASCII 0..9 - return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); - } - - /** - * Bias adaptation function as per section 3.4 of RFC 3492. - * https://tools.ietf.org/html/rfc3492#section-3.4 - * @private - */ - function adapt(delta, numPoints, firstTime) { - var k = 0; - delta = firstTime ? floor(delta / damp) : delta >> 1; - delta += floor(delta / numPoints); - for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { - delta = floor(delta / baseMinusTMin); - } - return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); - } - - /** - * Converts a Punycode string of ASCII-only symbols to a string of Unicode - * symbols. - * @memberOf punycode - * @param {String} input The Punycode string of ASCII-only symbols. - * @returns {String} The resulting string of Unicode symbols. - */ - function decode(input) { - // Don't use UCS-2 - var output = [], - inputLength = input.length, - out, - i = 0, - n = initialN, - bias = initialBias, - basic, - j, - index, - oldi, - w, - k, - digit, - t, - /** Cached calculation results */ - baseMinusT; - - // Handle the basic code points: let `basic` be the number of input code - // points before the last delimiter, or `0` if there is none, then copy - // the first basic code points to the output. - - basic = input.lastIndexOf(delimiter); - if (basic < 0) { - basic = 0; - } - - for (j = 0; j < basic; ++j) { - // if it's not a basic code point - if (input.charCodeAt(j) >= 0x80) { - error('not-basic'); - } - output.push(input.charCodeAt(j)); - } - - // Main decoding loop: start just after the last delimiter if any basic code - // points were copied; start at the beginning otherwise. - - for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { - - // `index` is the index of the next character to be consumed. - // Decode a generalized variable-length integer into `delta`, - // which gets added to `i`. The overflow checking is easier - // if we increase `i` as we go, then subtract off its starting - // value at the end to obtain `delta`. - for (oldi = i, w = 1, k = base; /* no condition */; k += base) { - - if (index >= inputLength) { - error('invalid-input'); - } - - digit = basicToDigit(input.charCodeAt(index++)); - - if (digit >= base || digit > floor((maxInt - i) / w)) { - error('overflow'); - } - - i += digit * w; - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - - if (digit < t) { - break; - } - - baseMinusT = base - t; - if (w > floor(maxInt / baseMinusT)) { - error('overflow'); - } - - w *= baseMinusT; - - } - - out = output.length + 1; - bias = adapt(i - oldi, out, oldi == 0); - - // `i` was supposed to wrap around from `out` to `0`, - // incrementing `n` each time, so we'll fix that now: - if (floor(i / out) > maxInt - n) { - error('overflow'); - } - - n += floor(i / out); - i %= out; - - // Insert `n` at position `i` of the output - output.splice(i++, 0, n); - - } - - return ucs2encode(output); - } - - /** - * Converts a string of Unicode symbols (e.g. a domain name label) to a - * Punycode string of ASCII-only symbols. - * @memberOf punycode - * @param {String} input The string of Unicode symbols. - * @returns {String} The resulting Punycode string of ASCII-only symbols. - */ - function encode(input) { - var n, - delta, - handledCPCount, - basicLength, - bias, - j, - m, - q, - k, - t, - currentValue, - output = [], - /** `inputLength` will hold the number of code points in `input`. */ - inputLength, - /** Cached calculation results */ - handledCPCountPlusOne, - baseMinusT, - qMinusT; - - // Convert the input in UCS-2 to Unicode - input = ucs2decode(input); - - // Cache the length - inputLength = input.length; - - // Initialize the state - n = initialN; - delta = 0; - bias = initialBias; - - // Handle the basic code points - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue < 0x80) { - output.push(stringFromCharCode(currentValue)); - } - } - - handledCPCount = basicLength = output.length; - - // `handledCPCount` is the number of code points that have been handled; - // `basicLength` is the number of basic code points. - - // Finish the basic string - if it is not empty - with a delimiter - if (basicLength) { - output.push(delimiter); - } - - // Main encoding loop: - while (handledCPCount < inputLength) { - - // All non-basic code points < n have been handled already. Find the next - // larger one: - for (m = maxInt, j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue >= n && currentValue < m) { - m = currentValue; - } - } - - // Increase `delta` enough to advance the decoder's state to , - // but guard against overflow - handledCPCountPlusOne = handledCPCount + 1; - if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { - error('overflow'); - } - - delta += (m - n) * handledCPCountPlusOne; - n = m; - - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - - if (currentValue < n && ++delta > maxInt) { - error('overflow'); - } - - if (currentValue == n) { - // Represent delta as a generalized variable-length integer - for (q = delta, k = base; /* no condition */; k += base) { - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - if (q < t) { - break; - } - qMinusT = q - t; - baseMinusT = base - t; - output.push( - stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) - ); - q = floor(qMinusT / baseMinusT); - } - - output.push(stringFromCharCode(digitToBasic(q, 0))); - bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); - delta = 0; - ++handledCPCount; - } - } - - ++delta; - ++n; - - } - return output.join(''); - } - - /** - * Converts a Punycode string representing a domain name or an email address - * to Unicode. Only the Punycoded parts of the input will be converted, i.e. - * it doesn't matter if you call it on a string that has already been - * converted to Unicode. - * @memberOf punycode - * @param {String} input The Punycoded domain name or email address to - * convert to Unicode. - * @returns {String} The Unicode representation of the given Punycode - * string. - */ - function toUnicode(input) { - return mapDomain(input, function(string) { - return regexPunycode.test(string) - ? decode(string.slice(4).toLowerCase()) - : string; - }); - } - - /** - * Converts a Unicode string representing a domain name or an email address to - * Punycode. Only the non-ASCII parts of the domain name will be converted, - * i.e. it doesn't matter if you call it with a domain that's already in - * ASCII. - * @memberOf punycode - * @param {String} input The domain name or email address to convert, as a - * Unicode string. - * @returns {String} The Punycode representation of the given domain name or - * email address. - */ - function toASCII(input) { - return mapDomain(input, function(string) { - return regexNonASCII.test(string) - ? 'xn--' + encode(string) - : string; - }); - } - - /*--------------------------------------------------------------------------*/ - - /** Define the public API */ - punycode = { - /** - * A string representing the current Punycode.js version number. - * @memberOf punycode - * @type String - */ - 'version': '1.4.1', - /** - * An object of methods to convert from JavaScript's internal character - * representation (UCS-2) to Unicode code points, and back. - * @see - * @memberOf punycode - * @type Object - */ - 'ucs2': { - 'decode': ucs2decode, - 'encode': ucs2encode - }, - 'decode': decode, - 'encode': encode, - 'toASCII': toASCII, - 'toUnicode': toUnicode - }; - - /** Expose `punycode` */ - // Some AMD build optimizers, like r.js, check for specific condition patterns - // like the following: - if ( - typeof define == 'function' && - typeof define.amd == 'object' && - define.amd - ) { - define('punycode', function() { - return punycode; - }); - } else if (freeExports && freeModule) { - if (module.exports == freeExports) { - // in Node.js, io.js, or RingoJS v0.8.0+ - freeModule.exports = punycode; - } else { - // in Narwhal or RingoJS v0.7.0- - for (key in punycode) { - punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); - } - } - } else { - // in Rhino or a web browser - root.punycode = punycode; - } - -}(this)); - -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],61:[function(require,module,exports){ -module.exports=/[\0-\x1F\x7F-\x9F]/ -},{}],62:[function(require,module,exports){ -module.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/ -},{}],63:[function(require,module,exports){ -module.exports=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/ -},{}],64:[function(require,module,exports){ -module.exports=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/ -},{}],65:[function(require,module,exports){ -'use strict'; - -exports.Any = require('./properties/Any/regex'); -exports.Cc = require('./categories/Cc/regex'); -exports.Cf = require('./categories/Cf/regex'); -exports.P = require('./categories/P/regex'); -exports.Z = require('./categories/Z/regex'); - -},{"./categories/Cc/regex":61,"./categories/Cf/regex":62,"./categories/P/regex":63,"./categories/Z/regex":64,"./properties/Any/regex":66}],66:[function(require,module,exports){ -module.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/ -},{}],67:[function(require,module,exports){ -'use strict'; - - -module.exports = require('./lib/'); - -},{"./lib/":9}]},{},[67])(67) -}); + module.exports = require('./lib/') + }, + { './lib/': 9 }, + ], + }, + {}, + [67] + )(67) +}) diff --git a/lib/migration.js b/lib/migration.js index 6aaf055a2..6284c92ab 100644 --- a/lib/migration.js +++ b/lib/migration.js @@ -3,154 +3,154 @@ import * as Settings from './miscellaneous-settings.js' import Maneuvers from '../module/actor/maneuver.js' export class Migration { - static async migrateTo096(quiet = false) { - let v = Settings.VERSION_096?.toString() - if (!quiet) ui.notifications?.info('Please wait, migrating Actors to v' + v) - console.log('Migrating Actors to v' + v) - - for (let actor of game.actors?.contents || []) { - let commit = { '_stats.systemVersion': v } - - let data = actor.system - for (const attr in data.attributes) { - if (data.attributes[attr].import == null) - commit = { - ...commit, - ...{ ['data.attributes.' + attr + '.import']: data.attributes[attr].value || 1 }, - } - } - - recurselist(data.skills, (e, k, d) => { - if (e.import == null) commit = { ...commit, ...{ ['data.skills.' + k + '.import']: e.level || 1 } } - }) - - recurselist(data.spells, (e, k, d) => { - if (e.import == null) commit = { ...commit, ...{ ['data.spells.' + k + '.import']: e.level || 1 } } - }) - - recurselist(data.melee, (e, k, d) => { - if (e.import == null) commit = { ...commit, ...{ ['data.melee.' + k + '.import']: e.level || 1 } } - }) - - recurselist(data.ranged, (e, k, d) => { - if (e.import == null) commit = { ...commit, ...{ ['data.ranged.' + k + '.import']: e.level || 1 } } - }) - - console.log('Updating ' + actor.name) - console.log(GURPS.objToString(commit)) - await actor.update(commit) - } - if (!quiet) ui.notifications?.info('Migration to v' + v + ' complete!') - } - - /** - * @param {boolean | undefined} [quiet] - */ - static async migrateTo097(quiet) { - let v = Settings.VERSION_097?.toString() - if (!quiet) ui.notifications?.info('Please wait, migrating Actors to v' + v) - console.log('Migrating Actors to v' + v) - for (let actor of game.actors?.contents || []) { - let commit = { 'data.migrationversion': v } - let data = actor.system - - recurselist(data.hitlocations, (e, k, d) => { - if (e.import == null) commit = { ...commit, ...{ ['data.hitlocations.' + k + '.import']: e.dr } } - }) - console.log('Updating ' + actor.name) - console.log(GURPS.objToString(commit)) - try { - await actor.update(commit) - } catch (err) { - console.log('Error trying to update TActors: ' + GURPS.objToString(err)) - } - } - if (!quiet) ui.notifications?.info('Migration to v' + v + ' complete!') - } - - /** - * @param {boolean | undefined} [quiet] - */ - static async migrateTo0104(quiet) { - let v = Settings.VERSION_0104?.toString() - if (!quiet) ui.notifications?.info('Please wait, migrating Tokens to v' + v) - console.log('Migrating Tokens to v' + v) - for (const scene of game.scenes?.contents || []) { - // @ts-ignore - for (const tokendoc of scene.tokens.contents) { - let actor = tokendoc.actor - if (!!actor) - // data protect against bad tokens - for (const effect of actor.temporaryEffects) { - let orig = effect.data.icon - - // Do not migrate maneuver icons until/unless we get .webp versions - if (Maneuvers.isManeuverIcon(orig)) { - continue - } - - let icon = orig.replace('.jpg', '.webp').replace('.png', '.webp') - console.log(actor.name + ':' + effect.data.label + ' ' + orig + ' -> ' + icon) - try { - await actor.updateEmbeddedDocuments('ActiveEffect', [{ _id: effect.id, icon: icon }]) - } catch (err) { - console.log('Error trying to update Token effects: ' + GURPS.objToString(err)) - } - } - } - } - if (!quiet) ui.notifications?.info('Migration to v' + v + ' complete!') - } - - /** - * @param {boolean} quiet - */ - static async migrateToManeuvers(quiet) { - if (game.user?.isGM) { - if (!quiet) ui.notifications?.info('Please wait, migrating tokens to use maneuvers.') - - for (const scene of game.scenes?.contents || []) { - // @ts-ignore - for (const tokendoc of scene.tokens.contents) { - if (tokendoc.actor) { - let maneuverText = tokendoc.actor.system.conditions.maneuver - try { - await tokendoc.object.setManeuver(maneuverText) - } catch (err) { - console.log('Error trying to update Token maneuvers: ' + GURPS.objToString(err)) - } - tokendoc.object.drawEffects() - } - } - } - - if (!quiet) ui.notifications?.info('Migration to maneuvers complete.') - } - } - - static async fixDataModelProblems(quiet) { - // let v = Settings.VERSION_097?.toString() - if (!quiet) ui.notifications?.info('Please wait, resolving data model problems') - console.log('resolving data model problems') - for (let actor of game.actors?.contents || []) { - let data = actor.system - let isupdated = false - let keys = Object.getOwnPropertyNames(data.melee) - for (let key of keys) { - if (!key.match(/^\d{5}$/)) { - delete data.melee[key] - isupdated = true - } - } - - if (isupdated) { - console.log('Updating ' + actor.name) - console.log(GURPS.objToString({ 'data.-=melee': null })) - await actor.update({ 'data.-=melee': null }) - console.log(GURPS.objToString({ 'data.melee': data.melee })) - await actor.update({ 'data.melee': data.melee }) - } - } - if (!quiet) ui.notifications?.info('Resolving data model problems complete!') - } + static async migrateTo096(quiet = false) { + let v = Settings.VERSION_096?.toString() + if (!quiet) ui.notifications?.info('Please wait, migrating Actors to v' + v) + console.log('Migrating Actors to v' + v) + + for (let actor of game.actors?.contents || []) { + let commit = { '_stats.systemVersion': v } + + let data = actor.system + for (const attr in data.attributes) { + if (data.attributes[attr].import == null) + commit = { + ...commit, + ...{ ['data.attributes.' + attr + '.import']: data.attributes[attr].value || 1 }, + } + } + + recurselist(data.skills, (e, k, d) => { + if (e.import == null) commit = { ...commit, ...{ ['data.skills.' + k + '.import']: e.level || 1 } } + }) + + recurselist(data.spells, (e, k, d) => { + if (e.import == null) commit = { ...commit, ...{ ['data.spells.' + k + '.import']: e.level || 1 } } + }) + + recurselist(data.melee, (e, k, d) => { + if (e.import == null) commit = { ...commit, ...{ ['data.melee.' + k + '.import']: e.level || 1 } } + }) + + recurselist(data.ranged, (e, k, d) => { + if (e.import == null) commit = { ...commit, ...{ ['data.ranged.' + k + '.import']: e.level || 1 } } + }) + + console.log('Updating ' + actor.name) + console.log(GURPS.objToString(commit)) + await actor.update(commit) + } + if (!quiet) ui.notifications?.info('Migration to v' + v + ' complete!') + } + + /** + * @param {boolean | undefined} [quiet] + */ + static async migrateTo097(quiet) { + let v = Settings.VERSION_097?.toString() + if (!quiet) ui.notifications?.info('Please wait, migrating Actors to v' + v) + console.log('Migrating Actors to v' + v) + for (let actor of game.actors?.contents || []) { + let commit = { 'data.migrationversion': v } + let data = actor.system + + recurselist(data.hitlocations, (e, k, d) => { + if (e.import == null) commit = { ...commit, ...{ ['data.hitlocations.' + k + '.import']: e.dr } } + }) + console.log('Updating ' + actor.name) + console.log(GURPS.objToString(commit)) + try { + await actor.update(commit) + } catch (err) { + console.log('Error trying to update TActors: ' + GURPS.objToString(err)) + } + } + if (!quiet) ui.notifications?.info('Migration to v' + v + ' complete!') + } + + /** + * @param {boolean | undefined} [quiet] + */ + static async migrateTo0104(quiet) { + let v = Settings.VERSION_0104?.toString() + if (!quiet) ui.notifications?.info('Please wait, migrating Tokens to v' + v) + console.log('Migrating Tokens to v' + v) + for (const scene of game.scenes?.contents || []) { + // @ts-ignore + for (const tokendoc of scene.tokens.contents) { + let actor = tokendoc.actor + if (!!actor) + // data protect against bad tokens + for (const effect of actor.temporaryEffects) { + let orig = effect.data.icon + + // Do not migrate maneuver icons until/unless we get .webp versions + if (Maneuvers.isManeuverIcon(orig)) { + continue + } + + let icon = orig.replace('.jpg', '.webp').replace('.png', '.webp') + console.log(actor.name + ':' + effect.data.label + ' ' + orig + ' -> ' + icon) + try { + await actor.updateEmbeddedDocuments('ActiveEffect', [{ _id: effect.id, icon: icon }]) + } catch (err) { + console.log('Error trying to update Token effects: ' + GURPS.objToString(err)) + } + } + } + } + if (!quiet) ui.notifications?.info('Migration to v' + v + ' complete!') + } + + /** + * @param {boolean} quiet + */ + static async migrateToManeuvers(quiet) { + if (game.user?.isGM) { + if (!quiet) ui.notifications?.info('Please wait, migrating tokens to use maneuvers.') + + for (const scene of game.scenes?.contents || []) { + // @ts-ignore + for (const tokendoc of scene.tokens.contents) { + if (tokendoc.actor) { + let maneuverText = tokendoc.actor.system.conditions.maneuver + try { + await tokendoc.object.setManeuver(maneuverText) + } catch (err) { + console.log('Error trying to update Token maneuvers: ' + GURPS.objToString(err)) + } + tokendoc.object.drawEffects() + } + } + } + + if (!quiet) ui.notifications?.info('Migration to maneuvers complete.') + } + } + + static async fixDataModelProblems(quiet) { + // let v = Settings.VERSION_097?.toString() + if (!quiet) ui.notifications?.info('Please wait, resolving data model problems') + console.log('resolving data model problems') + for (let actor of game.actors?.contents || []) { + let data = actor.system + let isupdated = false + let keys = Object.getOwnPropertyNames(data.melee) + for (let key of keys) { + if (!key.match(/^\d{5}$/)) { + delete data.melee[key] + isupdated = true + } + } + + if (isupdated) { + console.log('Updating ' + actor.name) + console.log(GURPS.objToString({ 'data.-=melee': null })) + await actor.update({ 'data.-=melee': null }) + console.log(GURPS.objToString({ 'data.melee': data.melee })) + await actor.update({ 'data.melee': data.melee }) + } + } + if (!quiet) ui.notifications?.info('Resolving data model problems complete!') + } } diff --git a/lib/miscellaneous-settings.js b/lib/miscellaneous-settings.js index 95ba2a734..144d44075 100755 --- a/lib/miscellaneous-settings.js +++ b/lib/miscellaneous-settings.js @@ -70,599 +70,598 @@ export const VERSION_0104 = SemanticVersion.fromString('0.10.4') // TODO encapsulate in an object and provide typed getters and setters export function initializeSettings() { - Hooks.once('init', async function() { - // Game Aid Information Settings ---- - - // Keep track of the last version number - game.settings.register(SYSTEM_NAME, SETTING_CHANGELOG_VERSION, { - name: 'Changelog Version', - scope: 'client', - config: false, - type: String, - default: '0.0.0', - onChange: value => console.log(`Change Log version : ${value}`), - }) - - // Keep track of the last version number - game.settings.register(SYSTEM_NAME, SETTING_MIGRATION_VERSION, { - name: 'Migration Version', - scope: 'world', - config: false, - type: String, - default: '0.0.0', - onChange: value => console.log(`Migration Log version : ${value}`), - }) - - // In case the user wants to see what changed between versions - game.settings.register(SYSTEM_NAME, SETTING_SHOW_CHANGELOG, { - name: i18n('GURPS.settingShowReadMe'), - hint: i18n('GURPS.settingHintShowReadMe'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Show Change Log : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_SHOW_3D6, { - name: i18n('GURPS.settingShowDiceRoller'), - hint: i18n('GURPS.settingHintShowDiceRoller'), - scope: 'client', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Show Dice Roller : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_PHYSICAL_DICE, { - name: i18n('GURPS.settingUsePhysicalDice'), - hint: i18n('GURPS.settingHintUsePhysicalDice'), - scope: 'client', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Use Physical Dice : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_USE_QUINTESSENCE, { - name: i18n('GURPS.settingUseQuintessence'), - hint: i18n('GURPS.settingHintUseQuintessence'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Use Quintessence : ${value}`), - }) - - // PDF Configuration ---- - - // Support for combined or separate Basic Set PDFs - game.settings.register(SYSTEM_NAME, 'basicsetpdf', { - name: i18n('GURPS.settingBasicPDFs'), - hint: i18n('GURPS.settingHintBasicPDFs'), - scope: 'world', - config: true, - type: String, - choices: { - Combined: i18n('GURPS.settingBasicPDFsCombined'), - Separate: i18n('GURPS.settingBasicPDFsSeparate'), - }, - default: 'Combined', - onChange: value => console.log(`Basic Set PDFs : ${value}`), - }) - - // GCS/GCA Import Configuration ---- - - game.settings.register(SYSTEM_NAME, SETTING_IGNORE_IMPORT_NAME, { - name: i18n('GURPS.settingImportIgnoreName'), - hint: i18n('GURPS.settingHintImportIgnoreName'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Ignore import name : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_BLOCK_IMPORT, { - name: i18n('GURPS.settingBlockImport'), - hint: i18n('GURPS.settingHintBlockImport'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Block import : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_AUTOMATICALLY_SET_IGNOREQTY, { - name: i18n('GURPS.settingAutoIgnoreQty'), - hint: i18n('GURPS.settingHintAutoIgnoreQty'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Automatically set ignore QTY : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_IMPORT_HP_FP, { - name: i18n('GURPS.settingImportHPAndFP'), - hint: i18n('GURPS.settingHintImportHPAndFP'), - scope: 'world', - config: true, - default: 2, - type: Number, - choices: { - 0: i18n('GURPS.settingImportHPAndFPUseFile'), - 1: i18n('GURPS.settingImportHPAndFPIgnore'), - 2: i18n('GURPS.settingImportHPAndFPAsk'), - }, - onChange: value => console.log(`Import of Current HP and FP : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_IMPORT_BODYPLAN, { - name: i18n('GURPS.settingImportBodyPlan'), - hint: i18n('GURPS.settingImportHintBodyPlan'), - scope: 'world', - config: true, - default: 2, - type: Number, - choices: { - 0: i18n('GURPS.settingImportBodyPlanUseFile'), - 1: i18n('GURPS.settingImportBodyPlanIgnore'), - 2: i18n('GURPS.settingImportBodyPlanAsk'), - }, - onChange: value => console.log(`Import of Body Plan : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_IMPORT_FILE_ENCODING, { - name: i18n('GURPS.settingImportEncoding'), - hint: i18n('GURPS.settingImportHintEncoding'), - scope: 'world', - config: true, - default: 1, - type: Number, - choices: { - 0: i18n('GURPS.settingImportEncodingISO8859'), - 1: i18n('GURPS.settingImportEncodingUTF8'), - }, - onChange: value => console.log(`Import encoding : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_USE_BROWSER_IMPORTER, { - name: i18n('GURPS.settingImportBrowserImporter'), - hint: i18n('GURPS.settingImportHintBrowserImporter'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Using non-locally hosted import dialog : ${value}`), - }) - - // Actor Sheet Configuration ---- - - game.settings.register(SYSTEM_NAME, SETTING_SHOW_SHEET_NAVIGATION, { - name: i18n('GURPS.settingShowNavigation'), - hint: i18n('GURPS.settingHintShowNavigation'), - scope: 'client', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Show navigation footer : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_ENHANCED_INPUT, { - name: i18n('GURPS.settingEnhancedInput'), - hint: i18n('GURPS.settingHintEnhancedInput'), - scope: 'client', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Use enhanced numeric input : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_AUTOMATIC_ENCUMBRANCE, { - name: i18n('GURPS.settingCalculateEnc'), - hint: i18n('GURPS.settingHintCalculateEnc'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Use automatic encumbrance : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_CHECK_EQUIPPED, { - name: i18n('GURPS.settingUseEquipped'), - hint: i18n('GURPS.settingHintUseEquipped'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Check 'Equipped' items in weight calculation : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_REMOVE_UNEQUIPPED, { - name: i18n('GURPS.settingRemoveUnequipped'), - hint: i18n('GURPS.settingHintRemoveUnequipped'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => { - for (const actor of game.actors.contents) { - if (actor.permission >= CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER) - if (actor.sheet) - // Return true if the current game user has observer or owner rights to an actor - actor.sheet.render() - } - }, - }) - - game.settings.register(SYSTEM_NAME, SETTING_SHOW_USER_CREATED, { - name: i18n('GURPS.settingFlagUserCreated'), - hint: i18n('GURPS.settingHintFlagUserCreated'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Show a 'saved' icon for user created items : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_SHOW_FOUNDRY_CREATED, { - name: i18n('GURPS.settingFlagItems'), - hint: i18n('GURPS.settingHintFlagItems'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Show a 'star' icon for Foundry items : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_ignoreImportQty, { - name: i18n('GURPS.settingQtyItems'), - hint: i18n('GURPS.settingHintQtyItems'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Show a 'star' icon for QTY/Count saved items : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_CONVERT_RANGED, { - name: i18n('GURPS.settingConvertRanged'), - hint: i18n('GURPS.settingHintConvertRanged'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Mulitple ranged columns during import : ${value}`), - }) - - // Modifier Bucket Configuration ---- - - game.settings.register(SYSTEM_NAME, SETTING_MODIFIER_TOOLTIP, { - name: i18n('GURPS.modifierShowOnMouseOver'), - hint: i18n('GURPS.modifierShowOnMouseOverHint'), - scope: 'client', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Modifier Tooltip on hover : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_RANGE_TO_BUCKET, { - name: i18n('GURPS.modifierAddRangeRuler'), - hint: i18n('GURPS.modifierAddRangeRulerHint'), - scope: 'client', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Automatically add range ruler mod to bucket : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_BUCKET_SCALE, { - name: i18n('GURPS.modifierViewScale'), - hint: i18n('GURPS.modifierViewScaleHint'), - scope: 'client', - config: true, - default: 1.0, - type: Number, - choices: { - 0.8: i18n('GURPS.modifierScaleVerySmall'), - 0.9: i18n('GURPS.modifierScaleSmall'), - 1.0: i18n('GURPS.modifierScaleNormal'), - 1.1: i18n('GURPS.modifierScaleLarge'), - 1.2: i18n('GURPS.modifierScaleVeryLarge'), - }, - onChange: value => console.log(`Modifier Bucket Scale: ${value}`), - }) - - game.settings.registerMenu(SYSTEM_NAME, SETTING_BUCKET_SELECT_JOURNALS, { - name: i18n('GURPS.modifierSelectJournals'), - hint: i18n('GURPS.modifierSelectJournalsHint'), - label: i18n('GURPS.modifierSelectJournalButton'), - type: ModifierBucketJournals, - restricted: false, - }) - - game.settings.register(SYSTEM_NAME, SETTING_BUCKET_JOURNALS, { - name: i18n('GURPS.modifierJournals'), - scope: 'client', - config: false, - type: Object, - default: {}, - onChange: value => console.log(`Updated Modifier Bucket Journals: ${JSON.stringify(value)}`), - }) - - // Combat options ---- - - game.settings.register(SYSTEM_NAME, SETTING_RANGE_STRATEGY, { - name: i18n('GURPS.settingRangeStrategy'), - hint: i18n('GURPS.settingHintRangeStrategy'), - scope: 'world', - config: true, - type: String, - choices: { - Standard: i18n('GURPS.settingRangeStrategyStandard'), - Simplified: i18n('GURPS.settingRangeStrategyRangeBands'), - TenPenalties: i18n('GURPS.settingRangeStrategyTenPenalties'), - }, - default: 'Standard', - onChange: value => GURPS.rangeObject.update(), - }) - - game.settings.register(SYSTEM_NAME, SETTING_INITIATIVE_FORMULA, { - name: i18n('GURPS.settingCombatInitiative'), - hint: i18n('GURPS.settingHintCombatInitiative'), - scope: 'world', - config: true, - type: String, - default: Initiative.defaultFormula(), - onChange: value => GURPS.setInitiativeFormula(true), - }) - - // Damage calculation options ---- - - game.settings.register(SYSTEM_NAME, SETTING_ONLY_GMS_OPEN_ADD, { - name: i18n('GURPS.settingDamageRestrictADD'), - hint: i18n('GURPS.settingHintDamageRestrictADD'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Only GMs can open ADD : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_SIMPLE_DAMAGE, { - name: i18n('GURPS.settingDamageSimpleADD'), - hint: i18n('GURPS.settingHintDamageSimpleADD'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Use simple Apply Damage Dialog : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_DEFAULT_LOCATION, { - name: i18n('GURPS.settingDamageLocation'), - hint: i18n('GURPS.settingHintDamageLocation'), - scope: 'world', - config: true, - type: String, - choices: { - Torso: 'Torso', - Random: 'Random', - }, - default: 'Torso', - onChange: value => console.log(`Default hit location: ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_APPLY_DIVISOR, { - name: i18n('GURPS.settingDamageAD'), - hint: i18n('GURPS.settingHintDamageAD'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Apply Armor Divisor : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_BLUNT_TRAUMA, { - name: i18n('GURPS.settingDamageBluntTrauma'), - hint: i18n('GURPS.settingHintDamageBluntTrauma'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Apply Blunt Trauma : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_LOCATION_MODIFIERS, { - name: i18n('GURPS.settingDamageLocationMods'), - hint: i18n('GURPS.settingHintDamageLocationMods'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Apply Location Modifiers : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_SHOW_THE_MATH, { - name: i18n('GURPS.settingDamageMath'), - hint: i18n('GURPS.settingHintDamageMath'), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Always expand SHOW THE MATH : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_USE_CONDITIONAL_INJURY, { - name: i18n('GURPS.settingDamageCondInjury'), - hint: i18n('GURPS.settingHintDamagCondInjury'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => GURPS.ConditionalInjury.update(), - }) - - game.settings.register(SYSTEM_NAME, SETTING_DEFAULT_ADD_ACTION, { - name: i18n('GURPS.settingDefaultADDAction'), - hint: i18n('GURPS.settingHintefaultADDAction'), - scope: 'world', - config: true, - type: String, - choices: { - apply: i18n("GURPS.addApplyInjury"), - quiet: i18n("GURPS.addApplyInjuryQuietly"), - target: i18n("GURPS.settingApplyBasedOnTarget"), - }, - default: 'target', - onChange: value => console.log(`ADD apply option: ${value}`), - }) - - // Status Effects Configuration ---- - - game.settings.register(SYSTEM_NAME, SETTING_WHISPER_STATUS_EFFECTS, { - name: i18n('GURPS.settingStatusWhisper'), - hint: i18n('GURPS.settingHintStatusWhisper'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Whisper Status Effects : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_AUTOMATIC_ONETHIRD, { - name: i18n('GURPS.settingStatusReeling'), - hint: i18n('GURPS.settingHintStatusReeling'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Use automatic reeling/tired : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_SHOW_CHAT_FOR_REELING_TIRED, { - name: i18n('GURPS.settingShowChatReeling'), - hint: i18n('GURPS.settingHintShowChatReeling'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Display Reeling/Tired Status in Chat : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_MANEUVER_VISIBILITY, { - name: i18n('GURPS.settingManeuverVisibility'), - hint: i18n('GURPS.settingHintManeuverVisibility'), - scope: 'world', - config: true, - type: String, - choices: { - NoOne: i18n('GURPS.settingManeuverNoOne'), - GMAndOwner: i18n('GURPS.settingManeuverGMOnly'), - Everyone: i18n('GURPS.settingManeuverEveryone'), - }, - default: 'NoOne', - onChange: value => { - console.log(`${SETTING_MANEUVER_VISIBILITY}: ${value}`) - Migration.migrateToManeuvers(true) - }, - }) - - game.settings.register(SYSTEM_NAME, SETTING_MANEUVER_DETAIL, { - name: i18n('GURPS.settingManeuverDetail'), - hint: i18n('GURPS.settingHintManeuverDetail'), - scope: 'world', - config: true, - type: String, - choices: { - Full: i18n('GURPS.settingManeuverDetailFull'), - NoFeint: i18n('GURPS.settingManeuverDetailNoFeint'), - General: i18n('GURPS.settingManeuverDetailGeneral'), - }, - default: 'General', - onChange: value => { - console.log(`${SETTING_MANEUVER_DETAIL}: ${value}`) - Migration.migrateToManeuvers(true) - }, - }) - - game.settings.register(SYSTEM_NAME, SETTING_MANEUVER_UPDATES_MOVE, { - name: i18n('GURPS.settingManeuverMove', 'Maneuver Updates Move'), - hint: i18n( - 'GURPS.settingHintManeuverMove', - "Setting the maneuver (in combat) updates the actor's move value to the maximum allowed by the maneuver (e.g., none, step, half, etc.)." - ), - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`${SETTING_MANEUVER_UPDATES_MOVE}: ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_SHIFT_CLICK_BLIND, { - name: i18n('GURPS.settingPlayerBlindRoll'), - hint: i18n('GURPS.settingHintPlayerBlindRoll'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`SHIFT Click does a Blind roll for players : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_PLAYER_CHAT_PRIVATE, { - name: i18n('GURPS.settingPlayerChatPrivate'), - hint: i18n('GURPS.settingHintPlayerChatPrivate'), - scope: 'world', - config: true, - type: Boolean, - default: false, - onChange: value => console.log(`Player chat private : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_FRIGHT_CHECK_TABLE, { - name: 'Fright Check table name', - scope: 'world', - config: false, - type: String, - default: 'Fright Check', - onChange: value => console.log(`Fright Check table name : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_PORTRAIT_PATH, { - name: 'Portrait Path', - hint: 'Choose where character portraits are stored (in the global Foundry User Data directory, or the local world directory).', - scope: 'world', - config: true, - type: String, - default: 'global', - choices: { - global: 'Global ([Foundry User Data]/Data/images/portaits/', - local: 'Local ([Foundry User Data]/Data/worlds/[World]/images/portaits/)' - }, - onChange: value => console.log(`Portrait Path : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_OVERWRITE_PORTRAITS, { - name: 'Overwrite Portraits', - hint: 'Choose whether character portraits are overwritten on import', - scope: 'world', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Overwrite Portraits : ${value}`), - }) - - game.settings.register(SYSTEM_NAME, SETTING_CTRL_KEY, { - name: 'Set ROLL MODE based on CTRL Key', - hint: 'Automatically change the ROLL MODE for chat based on whether the CTRL/CMD key is down.', - scope: 'client', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`ROLL MODE on CTRL KEY : ${value}`), - }) - - }) + Hooks.once('init', async function () { + // Game Aid Information Settings ---- + + // Keep track of the last version number + game.settings.register(SYSTEM_NAME, SETTING_CHANGELOG_VERSION, { + name: 'Changelog Version', + scope: 'client', + config: false, + type: String, + default: '0.0.0', + onChange: value => console.log(`Change Log version : ${value}`), + }) + + // Keep track of the last version number + game.settings.register(SYSTEM_NAME, SETTING_MIGRATION_VERSION, { + name: 'Migration Version', + scope: 'world', + config: false, + type: String, + default: '0.0.0', + onChange: value => console.log(`Migration Log version : ${value}`), + }) + + // In case the user wants to see what changed between versions + game.settings.register(SYSTEM_NAME, SETTING_SHOW_CHANGELOG, { + name: i18n('GURPS.settingShowReadMe'), + hint: i18n('GURPS.settingHintShowReadMe'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Show Change Log : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_SHOW_3D6, { + name: i18n('GURPS.settingShowDiceRoller'), + hint: i18n('GURPS.settingHintShowDiceRoller'), + scope: 'client', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Show Dice Roller : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_PHYSICAL_DICE, { + name: i18n('GURPS.settingUsePhysicalDice'), + hint: i18n('GURPS.settingHintUsePhysicalDice'), + scope: 'client', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Use Physical Dice : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_USE_QUINTESSENCE, { + name: i18n('GURPS.settingUseQuintessence'), + hint: i18n('GURPS.settingHintUseQuintessence'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Use Quintessence : ${value}`), + }) + + // PDF Configuration ---- + + // Support for combined or separate Basic Set PDFs + game.settings.register(SYSTEM_NAME, 'basicsetpdf', { + name: i18n('GURPS.settingBasicPDFs'), + hint: i18n('GURPS.settingHintBasicPDFs'), + scope: 'world', + config: true, + type: String, + choices: { + Combined: i18n('GURPS.settingBasicPDFsCombined'), + Separate: i18n('GURPS.settingBasicPDFsSeparate'), + }, + default: 'Combined', + onChange: value => console.log(`Basic Set PDFs : ${value}`), + }) + + // GCS/GCA Import Configuration ---- + + game.settings.register(SYSTEM_NAME, SETTING_IGNORE_IMPORT_NAME, { + name: i18n('GURPS.settingImportIgnoreName'), + hint: i18n('GURPS.settingHintImportIgnoreName'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Ignore import name : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_BLOCK_IMPORT, { + name: i18n('GURPS.settingBlockImport'), + hint: i18n('GURPS.settingHintBlockImport'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Block import : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_AUTOMATICALLY_SET_IGNOREQTY, { + name: i18n('GURPS.settingAutoIgnoreQty'), + hint: i18n('GURPS.settingHintAutoIgnoreQty'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Automatically set ignore QTY : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_IMPORT_HP_FP, { + name: i18n('GURPS.settingImportHPAndFP'), + hint: i18n('GURPS.settingHintImportHPAndFP'), + scope: 'world', + config: true, + default: 2, + type: Number, + choices: { + 0: i18n('GURPS.settingImportHPAndFPUseFile'), + 1: i18n('GURPS.settingImportHPAndFPIgnore'), + 2: i18n('GURPS.settingImportHPAndFPAsk'), + }, + onChange: value => console.log(`Import of Current HP and FP : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_IMPORT_BODYPLAN, { + name: i18n('GURPS.settingImportBodyPlan'), + hint: i18n('GURPS.settingImportHintBodyPlan'), + scope: 'world', + config: true, + default: 2, + type: Number, + choices: { + 0: i18n('GURPS.settingImportBodyPlanUseFile'), + 1: i18n('GURPS.settingImportBodyPlanIgnore'), + 2: i18n('GURPS.settingImportBodyPlanAsk'), + }, + onChange: value => console.log(`Import of Body Plan : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_IMPORT_FILE_ENCODING, { + name: i18n('GURPS.settingImportEncoding'), + hint: i18n('GURPS.settingImportHintEncoding'), + scope: 'world', + config: true, + default: 1, + type: Number, + choices: { + 0: i18n('GURPS.settingImportEncodingISO8859'), + 1: i18n('GURPS.settingImportEncodingUTF8'), + }, + onChange: value => console.log(`Import encoding : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_USE_BROWSER_IMPORTER, { + name: i18n('GURPS.settingImportBrowserImporter'), + hint: i18n('GURPS.settingImportHintBrowserImporter'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Using non-locally hosted import dialog : ${value}`), + }) + + // Actor Sheet Configuration ---- + + game.settings.register(SYSTEM_NAME, SETTING_SHOW_SHEET_NAVIGATION, { + name: i18n('GURPS.settingShowNavigation'), + hint: i18n('GURPS.settingHintShowNavigation'), + scope: 'client', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Show navigation footer : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_ENHANCED_INPUT, { + name: i18n('GURPS.settingEnhancedInput'), + hint: i18n('GURPS.settingHintEnhancedInput'), + scope: 'client', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Use enhanced numeric input : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_AUTOMATIC_ENCUMBRANCE, { + name: i18n('GURPS.settingCalculateEnc'), + hint: i18n('GURPS.settingHintCalculateEnc'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Use automatic encumbrance : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_CHECK_EQUIPPED, { + name: i18n('GURPS.settingUseEquipped'), + hint: i18n('GURPS.settingHintUseEquipped'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Check 'Equipped' items in weight calculation : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_REMOVE_UNEQUIPPED, { + name: i18n('GURPS.settingRemoveUnequipped'), + hint: i18n('GURPS.settingHintRemoveUnequipped'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => { + for (const actor of game.actors.contents) { + if (actor.permission >= CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER) + if (actor.sheet) + // Return true if the current game user has observer or owner rights to an actor + actor.sheet.render() + } + }, + }) + + game.settings.register(SYSTEM_NAME, SETTING_SHOW_USER_CREATED, { + name: i18n('GURPS.settingFlagUserCreated'), + hint: i18n('GURPS.settingHintFlagUserCreated'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Show a 'saved' icon for user created items : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_SHOW_FOUNDRY_CREATED, { + name: i18n('GURPS.settingFlagItems'), + hint: i18n('GURPS.settingHintFlagItems'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Show a 'star' icon for Foundry items : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_ignoreImportQty, { + name: i18n('GURPS.settingQtyItems'), + hint: i18n('GURPS.settingHintQtyItems'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Show a 'star' icon for QTY/Count saved items : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_CONVERT_RANGED, { + name: i18n('GURPS.settingConvertRanged'), + hint: i18n('GURPS.settingHintConvertRanged'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Mulitple ranged columns during import : ${value}`), + }) + + // Modifier Bucket Configuration ---- + + game.settings.register(SYSTEM_NAME, SETTING_MODIFIER_TOOLTIP, { + name: i18n('GURPS.modifierShowOnMouseOver'), + hint: i18n('GURPS.modifierShowOnMouseOverHint'), + scope: 'client', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Modifier Tooltip on hover : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_RANGE_TO_BUCKET, { + name: i18n('GURPS.modifierAddRangeRuler'), + hint: i18n('GURPS.modifierAddRangeRulerHint'), + scope: 'client', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Automatically add range ruler mod to bucket : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_BUCKET_SCALE, { + name: i18n('GURPS.modifierViewScale'), + hint: i18n('GURPS.modifierViewScaleHint'), + scope: 'client', + config: true, + default: 1.0, + type: Number, + choices: { + 0.8: i18n('GURPS.modifierScaleVerySmall'), + 0.9: i18n('GURPS.modifierScaleSmall'), + 1.0: i18n('GURPS.modifierScaleNormal'), + 1.1: i18n('GURPS.modifierScaleLarge'), + 1.2: i18n('GURPS.modifierScaleVeryLarge'), + }, + onChange: value => console.log(`Modifier Bucket Scale: ${value}`), + }) + + game.settings.registerMenu(SYSTEM_NAME, SETTING_BUCKET_SELECT_JOURNALS, { + name: i18n('GURPS.modifierSelectJournals'), + hint: i18n('GURPS.modifierSelectJournalsHint'), + label: i18n('GURPS.modifierSelectJournalButton'), + type: ModifierBucketJournals, + restricted: false, + }) + + game.settings.register(SYSTEM_NAME, SETTING_BUCKET_JOURNALS, { + name: i18n('GURPS.modifierJournals'), + scope: 'client', + config: false, + type: Object, + default: {}, + onChange: value => console.log(`Updated Modifier Bucket Journals: ${JSON.stringify(value)}`), + }) + + // Combat options ---- + + game.settings.register(SYSTEM_NAME, SETTING_RANGE_STRATEGY, { + name: i18n('GURPS.settingRangeStrategy'), + hint: i18n('GURPS.settingHintRangeStrategy'), + scope: 'world', + config: true, + type: String, + choices: { + Standard: i18n('GURPS.settingRangeStrategyStandard'), + Simplified: i18n('GURPS.settingRangeStrategyRangeBands'), + TenPenalties: i18n('GURPS.settingRangeStrategyTenPenalties'), + }, + default: 'Standard', + onChange: value => GURPS.rangeObject.update(), + }) + + game.settings.register(SYSTEM_NAME, SETTING_INITIATIVE_FORMULA, { + name: i18n('GURPS.settingCombatInitiative'), + hint: i18n('GURPS.settingHintCombatInitiative'), + scope: 'world', + config: true, + type: String, + default: Initiative.defaultFormula(), + onChange: value => GURPS.setInitiativeFormula(true), + }) + + // Damage calculation options ---- + + game.settings.register(SYSTEM_NAME, SETTING_ONLY_GMS_OPEN_ADD, { + name: i18n('GURPS.settingDamageRestrictADD'), + hint: i18n('GURPS.settingHintDamageRestrictADD'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Only GMs can open ADD : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_SIMPLE_DAMAGE, { + name: i18n('GURPS.settingDamageSimpleADD'), + hint: i18n('GURPS.settingHintDamageSimpleADD'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Use simple Apply Damage Dialog : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_DEFAULT_LOCATION, { + name: i18n('GURPS.settingDamageLocation'), + hint: i18n('GURPS.settingHintDamageLocation'), + scope: 'world', + config: true, + type: String, + choices: { + Torso: 'Torso', + Random: 'Random', + }, + default: 'Torso', + onChange: value => console.log(`Default hit location: ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_APPLY_DIVISOR, { + name: i18n('GURPS.settingDamageAD'), + hint: i18n('GURPS.settingHintDamageAD'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Apply Armor Divisor : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_BLUNT_TRAUMA, { + name: i18n('GURPS.settingDamageBluntTrauma'), + hint: i18n('GURPS.settingHintDamageBluntTrauma'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Apply Blunt Trauma : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_LOCATION_MODIFIERS, { + name: i18n('GURPS.settingDamageLocationMods'), + hint: i18n('GURPS.settingHintDamageLocationMods'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Apply Location Modifiers : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_SHOW_THE_MATH, { + name: i18n('GURPS.settingDamageMath'), + hint: i18n('GURPS.settingHintDamageMath'), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Always expand SHOW THE MATH : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_USE_CONDITIONAL_INJURY, { + name: i18n('GURPS.settingDamageCondInjury'), + hint: i18n('GURPS.settingHintDamagCondInjury'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => GURPS.ConditionalInjury.update(), + }) + + game.settings.register(SYSTEM_NAME, SETTING_DEFAULT_ADD_ACTION, { + name: i18n('GURPS.settingDefaultADDAction'), + hint: i18n('GURPS.settingHintefaultADDAction'), + scope: 'world', + config: true, + type: String, + choices: { + apply: i18n('GURPS.addApplyInjury'), + quiet: i18n('GURPS.addApplyInjuryQuietly'), + target: i18n('GURPS.settingApplyBasedOnTarget'), + }, + default: 'target', + onChange: value => console.log(`ADD apply option: ${value}`), + }) + + // Status Effects Configuration ---- + + game.settings.register(SYSTEM_NAME, SETTING_WHISPER_STATUS_EFFECTS, { + name: i18n('GURPS.settingStatusWhisper'), + hint: i18n('GURPS.settingHintStatusWhisper'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Whisper Status Effects : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_AUTOMATIC_ONETHIRD, { + name: i18n('GURPS.settingStatusReeling'), + hint: i18n('GURPS.settingHintStatusReeling'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Use automatic reeling/tired : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_SHOW_CHAT_FOR_REELING_TIRED, { + name: i18n('GURPS.settingShowChatReeling'), + hint: i18n('GURPS.settingHintShowChatReeling'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Display Reeling/Tired Status in Chat : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_MANEUVER_VISIBILITY, { + name: i18n('GURPS.settingManeuverVisibility'), + hint: i18n('GURPS.settingHintManeuverVisibility'), + scope: 'world', + config: true, + type: String, + choices: { + NoOne: i18n('GURPS.settingManeuverNoOne'), + GMAndOwner: i18n('GURPS.settingManeuverGMOnly'), + Everyone: i18n('GURPS.settingManeuverEveryone'), + }, + default: 'NoOne', + onChange: value => { + console.log(`${SETTING_MANEUVER_VISIBILITY}: ${value}`) + Migration.migrateToManeuvers(true) + }, + }) + + game.settings.register(SYSTEM_NAME, SETTING_MANEUVER_DETAIL, { + name: i18n('GURPS.settingManeuverDetail'), + hint: i18n('GURPS.settingHintManeuverDetail'), + scope: 'world', + config: true, + type: String, + choices: { + Full: i18n('GURPS.settingManeuverDetailFull'), + NoFeint: i18n('GURPS.settingManeuverDetailNoFeint'), + General: i18n('GURPS.settingManeuverDetailGeneral'), + }, + default: 'General', + onChange: value => { + console.log(`${SETTING_MANEUVER_DETAIL}: ${value}`) + Migration.migrateToManeuvers(true) + }, + }) + + game.settings.register(SYSTEM_NAME, SETTING_MANEUVER_UPDATES_MOVE, { + name: i18n('GURPS.settingManeuverMove', 'Maneuver Updates Move'), + hint: i18n( + 'GURPS.settingHintManeuverMove', + "Setting the maneuver (in combat) updates the actor's move value to the maximum allowed by the maneuver (e.g., none, step, half, etc.)." + ), + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`${SETTING_MANEUVER_UPDATES_MOVE}: ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_SHIFT_CLICK_BLIND, { + name: i18n('GURPS.settingPlayerBlindRoll'), + hint: i18n('GURPS.settingHintPlayerBlindRoll'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`SHIFT Click does a Blind roll for players : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_PLAYER_CHAT_PRIVATE, { + name: i18n('GURPS.settingPlayerChatPrivate'), + hint: i18n('GURPS.settingHintPlayerChatPrivate'), + scope: 'world', + config: true, + type: Boolean, + default: false, + onChange: value => console.log(`Player chat private : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_FRIGHT_CHECK_TABLE, { + name: 'Fright Check table name', + scope: 'world', + config: false, + type: String, + default: 'Fright Check', + onChange: value => console.log(`Fright Check table name : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_PORTRAIT_PATH, { + name: 'Portrait Path', + hint: 'Choose where character portraits are stored (in the global Foundry User Data directory, or the local world directory).', + scope: 'world', + config: true, + type: String, + default: 'global', + choices: { + global: 'Global ([Foundry User Data]/Data/images/portaits/', + local: 'Local ([Foundry User Data]/Data/worlds/[World]/images/portaits/)', + }, + onChange: value => console.log(`Portrait Path : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_OVERWRITE_PORTRAITS, { + name: 'Overwrite Portraits', + hint: 'Choose whether character portraits are overwritten on import', + scope: 'world', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Overwrite Portraits : ${value}`), + }) + + game.settings.register(SYSTEM_NAME, SETTING_CTRL_KEY, { + name: 'Set ROLL MODE based on CTRL Key', + hint: 'Automatically change the ROLL MODE for chat based on whether the CTRL/CMD key is down.', + scope: 'client', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`ROLL MODE on CTRL KEY : ${value}`), + }) + }) } diff --git a/lib/moustachewax.js b/lib/moustachewax.js index 439a4cfe4..99f511701 100755 --- a/lib/moustachewax.js +++ b/lib/moustachewax.js @@ -2,15 +2,15 @@ import * as HitLocations from '../module/hitlocation/hitlocation.js' import { - isArray, - isEmpty, - getComparison, - getOperation, - i18n, - i18n_f, - zeroFill, - recurselist, - quotedAttackName, + isArray, + isEmpty, + getComparison, + getOperation, + i18n, + i18n_f, + zeroFill, + recurselist, + quotedAttackName, } from './utilities.js' import * as settings from './miscellaneous-settings.js' import Maneuvers from '../module/actor/maneuver.js' @@ -22,702 +22,705 @@ import { gurpslink } from '../module/utilities/gurpslink.js' /* Called Moustache Wax because it helps Handlebars. Get it? */ -export default function() { - // If you need to add Handlebars helpers, here are a few useful examples: - Handlebars.registerHelper('concat', function() { - var outStr = '' - for (var arg in arguments) { - if (typeof arguments[arg] != 'object') { - outStr += arguments[arg] - } - } - return outStr - }) - - /** - * move is a single entry of the form { mode: string, value: number } - * returns array of options. - */ - Handlebars.registerHelper('moveOptions', function(move) { - let options = [MoveModes.Ground, MoveModes.Air, MoveModes.Water, MoveModes.Space] - - if (options.includes(move.mode)) return options - options.push(move.mode) - return options - }) - - Handlebars.registerHelper('damageTypeOptions', function(type) { - let options = Object.values(GURPS.DamageTables.translationTable) - if (options.includes(type)) return options - options.push(type) - return options - }) - - // Add "@index to {{times}} function - Handlebars.registerHelper('times', function(n, content) { - let result = '' - for (let i = 0; i < n; i++) { - content.data.index = i + 1 - result += content.fn(i) - } - return result - }) - - Handlebars.registerHelper('pluralize', function(word, quantity) { - if (quantity == 1) return word - - if (word.slice(-1) == 's' || word.slice(-1) == 'x') return `${word}es` - return `${word}s` - }) - - Handlebars.registerHelper('chooseplural', function(quantity, single, plural) { - if (quantity == 1) return single - return plural - }) - - Handlebars.registerHelper('i18n', function(value, fallback) { - if (!!fallback?.hash) - // Allow i18n to work using the old localize syntax - return i18n_f(value, fallback.hash) - else return i18n(value, fallback) - }) - - Handlebars.registerHelper('i18n_f', function(value, data, fallback) { - return i18n_f(value, data, fallback) - }) - - Handlebars.registerHelper('defined', function(a) { - return a != undefined - }) - Handlebars.registerHelper('gt', function(a, b) { - return a > b - }) - Handlebars.registerHelper('lt', function(a, b) { - return a < b - }) - Handlebars.registerHelper('eq', function(a, b) { - return a == b - }) - Handlebars.registerHelper('ne', function(a, b) { - return a != b - }) - Handlebars.registerHelper('abs', function(a) { - return Math.abs(a) - }) - Handlebars.registerHelper('and', function() { - for (let arg in arguments) { - if (arguments[arg] == false) { - return false - } - } - return true - }) - Handlebars.registerHelper('or', function() { - for (let arg in arguments) { - if (arguments[arg] == true) { - return true - } - } - return false - }) - Handlebars.registerHelper('isNum', function(value) { - if (value == null) return false - if (value == '0') return true - if (value == '') return false - return !isNaN(parseInt(value)) // Used to allow "numbers" like '12F' or '11U' for fencing/unwieldy parry - }) - Handlebars.registerHelper('not', function(value) { - return !value - }) - Handlebars.registerHelper('includes', function(array, value) { - return array.includes(value) - }) - - Handlebars.registerHelper('toLowerCase', function(str) { - return str.toLowerCase() - }) - - Handlebars.registerHelper('zeroFill', function(number, width) { - return zeroFill(number, width) - }) - - Handlebars.registerHelper('toNumber', function(value) { - if (typeof value == 'string') return parseDecimalNumber(value) - return value - }) - - Handlebars.registerHelper('debug', function(value) { - if (GURPS.DEBUG == false) return - console.log('Current context:') - console.log('================') - // @ts-ignore - console.log(this) - - if (value) { - console.log('Value:') - console.log('================') - console.log(value) - } - }) - - Handlebars.registerHelper('jsonStringify', function(object) { - return JSON.stringify(object) - }) - - Handlebars.registerHelper('jsonToObject', function(json) { - return JSON.parse(json) - }) - - Handlebars.registerHelper('replace', function() { - let format = arguments[0] - for (let index = 1; index < arguments.length; index++) { - let value = arguments[index] - format = format.replace(`$${index}`, arguments[index]) - } - return format - }) - - /* - * if value is equal to compareTo, return _default; otherwise return the - * format string replacing '*' with value. - */ - Handlebars.registerHelper('printIfNe', function(value, compareTo, format, _default = '') { - if (value === compareTo) return _default - let result = format.replace('*', value) - return result - }) - - Handlebars.registerHelper('objToString', function(str) { - let o = GURPS.objToString(str) - console.log(o) - return o - }) - - Handlebars.registerHelper('simpleRating', function(lvl) { - if (!lvl) return 'UNKNOWN' - let l = parseInt(lvl) - if (l < 10) return 'Poor' - if (l <= 11) return 'Fair' - if (l <= 13) return 'Good' - if (l <= 15) return 'Great' - if (l <= 18) return 'Super' - return 'Epic' - }) - - Handlebars.registerHelper('notEmpty', function(obj) { - return !!obj ? Object.values(obj).length > 0 : false - }) - - Handlebars.registerHelper('empty', function(obj) { - return !obj || Object.values(obj).length === 0 - }) - - Handlebars.registerHelper('hasNoChildren', function(contains, collapsed) { - let c1 = !!contains ? Object.values(contains).length == 0 : true - let c2 = !!collapsed ? Object.values(collapsed).length == 0 : true - return c1 && c2 - }) - - Handlebars.registerHelper('length', function(obj) { - if (getType(obj) === 'Object') return Object.values(obj).length - if (getType(obj) === 'Array') return obj.length - return 0 - }) - - /// NOTE: To use this, you must use {{{gurpslink sometext}}}. The triple {{{}}} keeps it from interpreting the HTML - Handlebars.registerHelper('gurpslink', function(str, root, clrdmods = false) { - // this is a stupid trick to 'unescape' HTML entities, like converting 'ü' to 'ü' - var template = document.createElement('textarea') - template.innerHTML = str - str = template.childNodes[0]?.nodeValue || str // hack may not work, so default back to original string - - let actor = root?.data?.root?.actor - if (!actor) actor = root?.actor - return new Handlebars.SafeString(gurpslink(str, root == true || clrdmods == true)) - }) - - /// NOTE: To use this, you must use {{{gurpslinkbr sometext}}}. The triple {{{}}} keeps it from interpreting the HTML - // Same as gurpslink, but converts \n to
    for large text values (notes) - Handlebars.registerHelper('gurpslinkbr', function(str, root, clrdmods = false) { - let actor = root?.data?.root?.actor - if (!actor) actor = root?.actor - return gurpslink(str, root == true || clrdmods == true).replace(/\n/g, '
    ') - }) - - Handlebars.registerHelper('listeqt', function(context, options) { - var data - if (options.data) data = Handlebars.createFrame(options.data) - - let ans = GURPS.listeqtrecurse(context, options, 0, data) - return ans - }) - - /** - * Convert a hierarchy of contained items into a flat map. - * - * The input is expected to be a map containing data elements; each element may also contain - * another map of the exact same type as either a 'contains' or 'collapsed' property. - * - * What is returned is a single map in which all elements appear, with the appropriate property - * keys. The individual elements will no longer contain the 'contains' or 'collapsed' properties. - * The element will also be enhanced with the 'nesting level' of the data as an 'indent' property. - */ - Handlebars.registerHelper('flatlist', function(context) { - let data = {} - flatlist(context, 0, '', data, false) - return data - }) - - // Provides the same information as flatlist, but may check equipped status (based on system setting) - Handlebars.registerHelper('attackflatlist', function(context) { - let data = {} - flatlist( - context, - 0, - '', - data, - false, - game.settings.get(settings.SYSTEM_NAME, settings.SETTING_REMOVE_UNEQUIPPED) ? this.actor : null - ) - return data - }) - - const flatlist = function(context, level, parentkey, data, isCollapsed, actorToCheckEquipment) { - if (!context) return data - - for (let key in context) { - let item = context[key] - let display = true - if (actorToCheckEquipment) { - // if we have been given an actor, then check to see if the melee or ranged item is equipped in the inventory - let checked = false - recurselist(actorToCheckEquipment.system.equipment.carried, e => { - // check - if (item.name.startsWith(e.name)) { - checked = true - if (!e.equipped) display = false - } - }) - if (!checked) - recurselist(actorToCheckEquipment.system.equipment.other, e => { - if (item.name.startsWith(e.name)) display = false - }) - } - if (display) { - let newKey = parentkey + key - - let newItem = { indent: level } - for (let propertyKey in item) { - if (!['contains', 'collapsed', 'indent'].includes(propertyKey)) { - newItem[propertyKey] = item[propertyKey] - } - } - newItem['hasCollapsed'] = !!item?.collapsed && Object.values(item?.collapsed).length > 0 - newItem['hasContains'] = !!item?.contains && Object.values(item?.contains).length > 0 - newItem['isCollapsed'] = isCollapsed - - data[newKey] = newItem - - if (newItem['hasContains']) flatlist(item.contains, level + 1, newKey + '.contains.', data, isCollapsed) - if (newItem['hasCollapsed']) flatlist(item.collapsed, level + 1, newKey + '.collapsed.', data, true) - } - } - } - - Handlebars.registerHelper('listattack', function(src, key, options) { - var data - if (options.data) data = Handlebars.createFrame(options.data) - - let context = src[key] - let ans = GURPS.listeqtrecurse(context, options, 0, data, '', src) - return ans - }) - - // Only necessary because of the FG import - Handlebars.registerHelper('hitlocationroll', function(loc, roll, data) { - if (!roll) { - // get hitlocation table name - let tableName = data?.additionalresources?.bodyplan - if (!tableName) tableName = 'humanoid' - let table = HitLocations.hitlocationDictionary[tableName] - if (!table) table = HitLocations.hitlocationDictionary['humanoid'] - roll = table[loc]?.roll - } - return roll - }) - - Handlebars.registerHelper('hitlocationpenalty', function(loc, penalty, data) { - if (!penalty) { - // get hitlocation table name - let tableName = data?.additionalresources?.bodyplan - if (!tableName) tableName = 'humanoid' - let table = HitLocations.hitlocationDictionary[tableName] - if (!table) table = HitLocations.hitlocationDictionary['humanoid'] - penalty = table[loc]?.penalty - } - return penalty - }) - - Handlebars.registerHelper('fractionalize', function(value, digits) { - if (typeof value == 'number') { - let wholeNumber = Math.floor(value) - if (wholeNumber === value) { - return value - } - - let fraction = value - wholeNumber - let wholeNumberText = wholeNumber === 0 ? '' : `${wholeNumber}` - if (fraction === 1 / 3) return `${wholeNumberText} 1/3`.trim() - if (fraction === 2 / 3) return `${wholeNumberText} 2/3`.trim() - return parseFloat(value.toFixed(digits)) - } - return value - }) - - /** - * Helper for ADD wounding modifier row of the results table. - */ - Handlebars.registerHelper('woundModifierText', function(calc) { - let add = calc.additionalWoundModifier ? ` + ${calc.additionalWoundModifier} add` : '' - let vul = calc.isVulnerable ? ` × ${calc.vulnerabilityMultiple} (Vuln.)` : '' - - if (vul.length + add.length === 0) return calc.damageType - - if (vul.length === 0) return `${calc.damageType}${add}`.trim() - - if (add.length === 0) return `${calc.damageType}${vul}`.trim() - - return `(${calc.damageType}${add})${vul}`.trim() - }) - - Handlebars.registerHelper('isWoundModAdjForLocation', function(calc) { - if (calc.isWoundModifierAdjustedForLocation) { - let location = calc.effectiveWoundModifiers[calc.damageType] - return !!location && location.changed === 'hitlocation' - } - return false - }) - - Handlebars.registerHelper('isWoundModAdjForInjuryTol', function(calc) { - if (calc.isWoundModifierAdjustedForInjuryTolerance) { - let location = calc.effectiveWoundModifiers[calc.damageType] - return !!location && location.changed === 'injury-tolerance' - } - return false - }) - - Handlebars.registerHelper('filter', function(objects, key) { - // objects - array of object to filter - // key - property to filter on - // @ts-ignore - if (isArray(objects)) return objects.filter(!isEmpty) - - // assume this is an object with numeric keys - if (typeof objects === 'object' && objects !== null) { - let results = [] - let index = 0 - let entry = objects[`${index}`] - - while (entry) { - if (!isEmpty(entry.name)) results.push(entry) - index++ - entry = objects[`${index}`] - } - return results - } - return [] - }) - - Handlebars.registerHelper('listAllBodyPlans', function() { - return HitLocations.getHitLocationTableNames() - }) - - Handlebars.registerHelper('listAllManeuvers', function() { - return Maneuvers.getAllData() - }) - - Handlebars.registerHelper('listAllPostures', function() { - let postures = GURPS.StatusEffect.getAllPostures() - return postures - }) - - Handlebars.registerHelper('showTheMath', function() { - return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_THE_MATH) ? 'checked' : '' - }) - - Handlebars.registerHelper('inCombat', function(data) { - if (data.actor && !!game.combats.active) { - return game.combats.active.combatants.contents.map(it => it.actor?.id).filter(e => !!e).includes(data?.actor?.id) - } - return false - }) - - // Allows handling of multiple page refs, e.g."B101,MA150" - Handlebars.registerHelper('pdflink', function(link) { - let txt = link - if (Array.isArray(link)) txt = link.join(',') - return !!txt - ? txt - .split(',') - .map((/** @type {string} */ l) => gurpslink(`[PDF:${l}]`)) - .join(', ') - : '' - }) - - // Allows handling of multiple page refs, e.g."B101,MA150" and external links - Handlebars.registerHelper('pdflinkext', function(obj) { - if (!obj) return '' - let txt = obj.pageref - if (Array.isArray(txt)) txt = txt.join(',') - if (!txt) return '' - return txt - .split(',') - .map((/** @type {string} */ l) => { - if (!!obj.externallink) return `
    *Link` - else if (l.match(/https?:\/\//i)) { - return `*Link` - } else return gurpslink(`[PDF:${l}]`) - }) - .join(', ') - }) - - Handlebars.registerHelper('round', function(num) { - // remove any commas, the grab any leading signed number - let temp = (num + '') - .trim() - .replace(',', '') - .replace(/^(-?\d+(?:\.\d+)*?) +.*/, '$1') - // @ts-ignore - return +(Math.round(temp + 'e+2') + 'e-2') - }) - - Handlebars.registerHelper('toLocaleString', function(number) { - return !!number ? number.toLocaleString() : '' // Add data protection - }) - - Handlebars.registerHelper('displayNumber', function(num, options) { - let showPlus = options.hash?.showPlus ?? false - if (num != null) { - if (parseInt(num) === 0) return showPlus ? '+0' : '0' - if (parseInt(num) < 0) return num.toString().replace('-', '−') - if (arguments[1] != false && num.toString()[0] !== '+') return `+${num}` - return num.toString() - } else return '' // null or undefined - }) - - Handlebars.registerHelper('invoke', function(object, options) { - let name = options.hash?.method - return object[name]() - }) - - Handlebars.registerHelper('displayDecimal', function(num, options) { - if (num != null) { - num = parseFloat(num.toString()) - - let places = options.hash?.number ?? 1 - num = num.toFixed(places).toString() - if (options.hash?.removeZeros) { - while (num.toString().endsWith('0')) num = num.substr(0, num.length - 1) - if (num.toString().endsWith('.')) num = num.substr(0, num.length - 1) - } - - if (parseFloat(num) < 0) return num.toString().replace('-', '−') - - if (options.hash?.forceSign && num.toString()[0] !== '+') return `+${num}` - return num.toString() - } else return '' // null or undefined - }) - - Handlebars.registerHelper('optionSetStyle', function(boolean) { - return !!boolean ? 'buttonpulsatingred' : 'buttongrey' - }) - - /** - * Find the key of the tracker with the given name. - */ - Handlebars.registerHelper('trackerIndex', function(data, trackerName) { - if (!!data && !!data.additionalresources?.tracker) { - let tracker = data.additionalresources.tracker - // find the tracker with trackerName - let found = Object.keys(tracker).find(it => tracker[it].name === trackerName) - if (!!found) { - return found - } - } - return null - }) - - /** - * Find the tracker with the given name. - */ - Handlebars.registerHelper('tracker', function(data, trackerName) { - if (!!data && !!data.additionalresources?.tracker) { - // find the tracker with name 'Control Points' - let tracker = Object.values(data.additionalresources?.tracker).find(it => it.name === trackerName) - if (!!tracker) { - return tracker - } - } - return null - }) - - /** - * Returns the index of the first threshold that matches the value. - */ - Handlebars.registerHelper('threshold-of', function(thresholds, max, value) { - // return the index of the threshold that the value falls into - let result = null - thresholds.some(function( +export default function () { + // If you need to add Handlebars helpers, here are a few useful examples: + Handlebars.registerHelper('concat', function () { + var outStr = '' + for (var arg in arguments) { + if (typeof arguments[arg] != 'object') { + outStr += arguments[arg] + } + } + return outStr + }) + + /** + * move is a single entry of the form { mode: string, value: number } + * returns array of options. + */ + Handlebars.registerHelper('moveOptions', function (move) { + let options = [MoveModes.Ground, MoveModes.Air, MoveModes.Water, MoveModes.Space] + + if (options.includes(move.mode)) return options + options.push(move.mode) + return options + }) + + Handlebars.registerHelper('damageTypeOptions', function (type) { + let options = Object.values(GURPS.DamageTables.translationTable) + if (options.includes(type)) return options + options.push(type) + return options + }) + + // Add "@index to {{times}} function + Handlebars.registerHelper('times', function (n, content) { + let result = '' + for (let i = 0; i < n; i++) { + content.data.index = i + 1 + result += content.fn(i) + } + return result + }) + + Handlebars.registerHelper('pluralize', function (word, quantity) { + if (quantity == 1) return word + + if (word.slice(-1) == 's' || word.slice(-1) == 'x') return `${word}es` + return `${word}s` + }) + + Handlebars.registerHelper('chooseplural', function (quantity, single, plural) { + if (quantity == 1) return single + return plural + }) + + Handlebars.registerHelper('i18n', function (value, fallback) { + if (!!fallback?.hash) + // Allow i18n to work using the old localize syntax + return i18n_f(value, fallback.hash) + else return i18n(value, fallback) + }) + + Handlebars.registerHelper('i18n_f', function (value, data, fallback) { + return i18n_f(value, data, fallback) + }) + + Handlebars.registerHelper('defined', function (a) { + return a != undefined + }) + Handlebars.registerHelper('gt', function (a, b) { + return a > b + }) + Handlebars.registerHelper('lt', function (a, b) { + return a < b + }) + Handlebars.registerHelper('eq', function (a, b) { + return a == b + }) + Handlebars.registerHelper('ne', function (a, b) { + return a != b + }) + Handlebars.registerHelper('abs', function (a) { + return Math.abs(a) + }) + Handlebars.registerHelper('and', function () { + for (let arg in arguments) { + if (arguments[arg] == false) { + return false + } + } + return true + }) + Handlebars.registerHelper('or', function () { + for (let arg in arguments) { + if (arguments[arg] == true) { + return true + } + } + return false + }) + Handlebars.registerHelper('isNum', function (value) { + if (value == null) return false + if (value == '0') return true + if (value == '') return false + return !isNaN(parseInt(value)) // Used to allow "numbers" like '12F' or '11U' for fencing/unwieldy parry + }) + Handlebars.registerHelper('not', function (value) { + return !value + }) + Handlebars.registerHelper('includes', function (array, value) { + return array.includes(value) + }) + + Handlebars.registerHelper('toLowerCase', function (str) { + return str.toLowerCase() + }) + + Handlebars.registerHelper('zeroFill', function (number, width) { + return zeroFill(number, width) + }) + + Handlebars.registerHelper('toNumber', function (value) { + if (typeof value == 'string') return parseDecimalNumber(value) + return value + }) + + Handlebars.registerHelper('debug', function (value) { + if (GURPS.DEBUG == false) return + console.log('Current context:') + console.log('================') + // @ts-ignore + console.log(this) + + if (value) { + console.log('Value:') + console.log('================') + console.log(value) + } + }) + + Handlebars.registerHelper('jsonStringify', function (object) { + return JSON.stringify(object) + }) + + Handlebars.registerHelper('jsonToObject', function (json) { + return JSON.parse(json) + }) + + Handlebars.registerHelper('replace', function () { + let format = arguments[0] + for (let index = 1; index < arguments.length; index++) { + let value = arguments[index] + format = format.replace(`$${index}`, arguments[index]) + } + return format + }) + + /* + * if value is equal to compareTo, return _default; otherwise return the + * format string replacing '*' with value. + */ + Handlebars.registerHelper('printIfNe', function (value, compareTo, format, _default = '') { + if (value === compareTo) return _default + let result = format.replace('*', value) + return result + }) + + Handlebars.registerHelper('objToString', function (str) { + let o = GURPS.objToString(str) + console.log(o) + return o + }) + + Handlebars.registerHelper('simpleRating', function (lvl) { + if (!lvl) return 'UNKNOWN' + let l = parseInt(lvl) + if (l < 10) return 'Poor' + if (l <= 11) return 'Fair' + if (l <= 13) return 'Good' + if (l <= 15) return 'Great' + if (l <= 18) return 'Super' + return 'Epic' + }) + + Handlebars.registerHelper('notEmpty', function (obj) { + return !!obj ? Object.values(obj).length > 0 : false + }) + + Handlebars.registerHelper('empty', function (obj) { + return !obj || Object.values(obj).length === 0 + }) + + Handlebars.registerHelper('hasNoChildren', function (contains, collapsed) { + let c1 = !!contains ? Object.values(contains).length == 0 : true + let c2 = !!collapsed ? Object.values(collapsed).length == 0 : true + return c1 && c2 + }) + + Handlebars.registerHelper('length', function (obj) { + if (getType(obj) === 'Object') return Object.values(obj).length + if (getType(obj) === 'Array') return obj.length + return 0 + }) + + /// NOTE: To use this, you must use {{{gurpslink sometext}}}. The triple {{{}}} keeps it from interpreting the HTML + Handlebars.registerHelper('gurpslink', function (str, root, clrdmods = false) { + // this is a stupid trick to 'unescape' HTML entities, like converting 'ü' to 'ü' + var template = document.createElement('textarea') + template.innerHTML = str + str = template.childNodes[0]?.nodeValue || str // hack may not work, so default back to original string + + let actor = root?.data?.root?.actor + if (!actor) actor = root?.actor + return new Handlebars.SafeString(gurpslink(str, root == true || clrdmods == true)) + }) + + /// NOTE: To use this, you must use {{{gurpslinkbr sometext}}}. The triple {{{}}} keeps it from interpreting the HTML + // Same as gurpslink, but converts \n to
    for large text values (notes) + Handlebars.registerHelper('gurpslinkbr', function (str, root, clrdmods = false) { + let actor = root?.data?.root?.actor + if (!actor) actor = root?.actor + return gurpslink(str, root == true || clrdmods == true).replace(/\n/g, '
    ') + }) + + Handlebars.registerHelper('listeqt', function (context, options) { + var data + if (options.data) data = Handlebars.createFrame(options.data) + + let ans = GURPS.listeqtrecurse(context, options, 0, data) + return ans + }) + + /** + * Convert a hierarchy of contained items into a flat map. + * + * The input is expected to be a map containing data elements; each element may also contain + * another map of the exact same type as either a 'contains' or 'collapsed' property. + * + * What is returned is a single map in which all elements appear, with the appropriate property + * keys. The individual elements will no longer contain the 'contains' or 'collapsed' properties. + * The element will also be enhanced with the 'nesting level' of the data as an 'indent' property. + */ + Handlebars.registerHelper('flatlist', function (context) { + let data = {} + flatlist(context, 0, '', data, false) + return data + }) + + // Provides the same information as flatlist, but may check equipped status (based on system setting) + Handlebars.registerHelper('attackflatlist', function (context) { + let data = {} + flatlist( + context, + 0, + '', + data, + false, + game.settings.get(settings.SYSTEM_NAME, settings.SETTING_REMOVE_UNEQUIPPED) ? this.actor : null + ) + return data + }) + + const flatlist = function (context, level, parentkey, data, isCollapsed, actorToCheckEquipment) { + if (!context) return data + + for (let key in context) { + let item = context[key] + let display = true + if (actorToCheckEquipment) { + // if we have been given an actor, then check to see if the melee or ranged item is equipped in the inventory + let checked = false + recurselist(actorToCheckEquipment.system.equipment.carried, e => { + // check + if (item.name.startsWith(e.name)) { + checked = true + if (!e.equipped) display = false + } + }) + if (!checked) + recurselist(actorToCheckEquipment.system.equipment.other, e => { + if (item.name.startsWith(e.name)) display = false + }) + } + if (display) { + let newKey = parentkey + key + + let newItem = { indent: level } + for (let propertyKey in item) { + if (!['contains', 'collapsed', 'indent'].includes(propertyKey)) { + newItem[propertyKey] = item[propertyKey] + } + } + newItem['hasCollapsed'] = !!item?.collapsed && Object.values(item?.collapsed).length > 0 + newItem['hasContains'] = !!item?.contains && Object.values(item?.contains).length > 0 + newItem['isCollapsed'] = isCollapsed + + data[newKey] = newItem + + if (newItem['hasContains']) flatlist(item.contains, level + 1, newKey + '.contains.', data, isCollapsed) + if (newItem['hasCollapsed']) flatlist(item.collapsed, level + 1, newKey + '.collapsed.', data, true) + } + } + } + + Handlebars.registerHelper('listattack', function (src, key, options) { + var data + if (options.data) data = Handlebars.createFrame(options.data) + + let context = src[key] + let ans = GURPS.listeqtrecurse(context, options, 0, data, '', src) + return ans + }) + + // Only necessary because of the FG import + Handlebars.registerHelper('hitlocationroll', function (loc, roll, data) { + if (!roll) { + // get hitlocation table name + let tableName = data?.additionalresources?.bodyplan + if (!tableName) tableName = 'humanoid' + let table = HitLocations.hitlocationDictionary[tableName] + if (!table) table = HitLocations.hitlocationDictionary['humanoid'] + roll = table[loc]?.roll + } + return roll + }) + + Handlebars.registerHelper('hitlocationpenalty', function (loc, penalty, data) { + if (!penalty) { + // get hitlocation table name + let tableName = data?.additionalresources?.bodyplan + if (!tableName) tableName = 'humanoid' + let table = HitLocations.hitlocationDictionary[tableName] + if (!table) table = HitLocations.hitlocationDictionary['humanoid'] + penalty = table[loc]?.penalty + } + return penalty + }) + + Handlebars.registerHelper('fractionalize', function (value, digits) { + if (typeof value == 'number') { + let wholeNumber = Math.floor(value) + if (wholeNumber === value) { + return value + } + + let fraction = value - wholeNumber + let wholeNumberText = wholeNumber === 0 ? '' : `${wholeNumber}` + if (fraction === 1 / 3) return `${wholeNumberText} 1/3`.trim() + if (fraction === 2 / 3) return `${wholeNumberText} 2/3`.trim() + return parseFloat(value.toFixed(digits)) + } + return value + }) + + /** + * Helper for ADD wounding modifier row of the results table. + */ + Handlebars.registerHelper('woundModifierText', function (calc) { + let add = calc.additionalWoundModifier ? ` + ${calc.additionalWoundModifier} add` : '' + let vul = calc.isVulnerable ? ` × ${calc.vulnerabilityMultiple} (Vuln.)` : '' + + if (vul.length + add.length === 0) return calc.damageType + + if (vul.length === 0) return `${calc.damageType}${add}`.trim() + + if (add.length === 0) return `${calc.damageType}${vul}`.trim() + + return `(${calc.damageType}${add})${vul}`.trim() + }) + + Handlebars.registerHelper('isWoundModAdjForLocation', function (calc) { + if (calc.isWoundModifierAdjustedForLocation) { + let location = calc.effectiveWoundModifiers[calc.damageType] + return !!location && location.changed === 'hitlocation' + } + return false + }) + + Handlebars.registerHelper('isWoundModAdjForInjuryTol', function (calc) { + if (calc.isWoundModifierAdjustedForInjuryTolerance) { + let location = calc.effectiveWoundModifiers[calc.damageType] + return !!location && location.changed === 'injury-tolerance' + } + return false + }) + + Handlebars.registerHelper('filter', function (objects, key) { + // objects - array of object to filter + // key - property to filter on + // @ts-ignore + if (isArray(objects)) return objects.filter(!isEmpty) + + // assume this is an object with numeric keys + if (typeof objects === 'object' && objects !== null) { + let results = [] + let index = 0 + let entry = objects[`${index}`] + + while (entry) { + if (!isEmpty(entry.name)) results.push(entry) + index++ + entry = objects[`${index}`] + } + return results + } + return [] + }) + + Handlebars.registerHelper('listAllBodyPlans', function () { + return HitLocations.getHitLocationTableNames() + }) + + Handlebars.registerHelper('listAllManeuvers', function () { + return Maneuvers.getAllData() + }) + + Handlebars.registerHelper('listAllPostures', function () { + let postures = GURPS.StatusEffect.getAllPostures() + return postures + }) + + Handlebars.registerHelper('showTheMath', function () { + return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_THE_MATH) ? 'checked' : '' + }) + + Handlebars.registerHelper('inCombat', function (data) { + if (data.actor && !!game.combats.active) { + return game.combats.active.combatants.contents + .map(it => it.actor?.id) + .filter(e => !!e) + .includes(data?.actor?.id) + } + return false + }) + + // Allows handling of multiple page refs, e.g."B101,MA150" + Handlebars.registerHelper('pdflink', function (link) { + let txt = link + if (Array.isArray(link)) txt = link.join(',') + return !!txt + ? txt + .split(',') + .map((/** @type {string} */ l) => gurpslink(`[PDF:${l}]`)) + .join(', ') + : '' + }) + + // Allows handling of multiple page refs, e.g."B101,MA150" and external links + Handlebars.registerHelper('pdflinkext', function (obj) { + if (!obj) return '' + let txt = obj.pageref + if (Array.isArray(txt)) txt = txt.join(',') + if (!txt) return '' + return txt + .split(',') + .map((/** @type {string} */ l) => { + if (!!obj.externallink) return `*Link` + else if (l.match(/https?:\/\//i)) { + return `*Link` + } else return gurpslink(`[PDF:${l}]`) + }) + .join(', ') + }) + + Handlebars.registerHelper('round', function (num) { + // remove any commas, the grab any leading signed number + let temp = (num + '') + .trim() + .replace(',', '') + .replace(/^(-?\d+(?:\.\d+)*?) +.*/, '$1') + // @ts-ignore + return +(Math.round(temp + 'e+2') + 'e-2') + }) + + Handlebars.registerHelper('toLocaleString', function (number) { + return !!number ? number.toLocaleString() : '' // Add data protection + }) + + Handlebars.registerHelper('displayNumber', function (num, options) { + let showPlus = options.hash?.showPlus ?? false + if (num != null) { + if (parseInt(num) === 0) return showPlus ? '+0' : '0' + if (parseInt(num) < 0) return num.toString().replace('-', '−') + if (arguments[1] != false && num.toString()[0] !== '+') return `+${num}` + return num.toString() + } else return '' // null or undefined + }) + + Handlebars.registerHelper('invoke', function (object, options) { + let name = options.hash?.method + return object[name]() + }) + + Handlebars.registerHelper('displayDecimal', function (num, options) { + if (num != null) { + num = parseFloat(num.toString()) + + let places = options.hash?.number ?? 1 + num = num.toFixed(places).toString() + if (options.hash?.removeZeros) { + while (num.toString().endsWith('0')) num = num.substr(0, num.length - 1) + if (num.toString().endsWith('.')) num = num.substr(0, num.length - 1) + } + + if (parseFloat(num) < 0) return num.toString().replace('-', '−') + + if (options.hash?.forceSign && num.toString()[0] !== '+') return `+${num}` + return num.toString() + } else return '' // null or undefined + }) + + Handlebars.registerHelper('optionSetStyle', function (boolean) { + return !!boolean ? 'buttonpulsatingred' : 'buttongrey' + }) + + /** + * Find the key of the tracker with the given name. + */ + Handlebars.registerHelper('trackerIndex', function (data, trackerName) { + if (!!data && !!data.additionalresources?.tracker) { + let tracker = data.additionalresources.tracker + // find the tracker with trackerName + let found = Object.keys(tracker).find(it => tracker[it].name === trackerName) + if (!!found) { + return found + } + } + return null + }) + + /** + * Find the tracker with the given name. + */ + Handlebars.registerHelper('tracker', function (data, trackerName) { + if (!!data && !!data.additionalresources?.tracker) { + // find the tracker with name 'Control Points' + let tracker = Object.values(data.additionalresources?.tracker).find(it => it.name === trackerName) + if (!!tracker) { + return tracker + } + } + return null + }) + + /** + * Returns the index of the first threshold that matches the value. + */ + Handlebars.registerHelper('threshold-of', function (thresholds, max, value) { + // return the index of the threshold that the value falls into + let result = null + thresholds.some(function ( /** @type {{ operator: string; comparison: string; value: number; }} */ threshold, /** @type {number} */ index - ) { - let op = getOperation(threshold.operator) - let comparison = getComparison(threshold.comparison) - let testValue = op(max, threshold.value) - return comparison(value, testValue) ? ((result = index), true) : false - }) - return result - }) - - /** - * Unlike the 'threshold-of' method above, this returns the *last* threshold whose condition matches. - */ - Handlebars.registerHelper('breakpoint-of', function(thresholds, max, value) { - // return the index of the threshold that the value falls into - let matches = thresholds.filter(function(threshold, index) { - let op = getOperation(threshold.operator) - let comparison = getComparison(threshold.comparison) - let testValue = op(max, threshold.value) - return comparison(value, testValue) - }) - - return matches.pop() - }) - - /** - * Unlike the 'threshold-of' method above, this returns the index of the *last* threshold whose condition matches. - */ - Handlebars.registerHelper('breakpointIndex-of', function(thresholds, max, value) { - // return the index of the threshold that the value falls into - let matches = thresholds.filter(function(threshold, index) { - let op = getOperation(threshold.operator) - let comparison = getComparison(threshold.comparison) - let testValue = op(max, threshold.value) - return comparison(value, testValue) - }) - - return thresholds.lastIndexOf(matches.pop()) - }) - - /** - * Return an array of the calculated breakpoint values along with the matching threshold. - */ - Handlebars.registerHelper('thresholdBreakpoints', function(tracker) { - let results = [] - tracker.thresholds.forEach(threshold => { - let op = getOperation(threshold.operator) - let temp = op(tracker.max, threshold.value) - let value = tracker.isDamageTracker ? Math.ceil(temp) : Math.floor(temp) - - results.push({ - breakpoint: value, - threshold: threshold, - }) - }) - return results - }) - - Handlebars.registerHelper('truthy', function(value) { - return !!value - }) - - /** - * TODO Maybe unnecessary -- just use 'thresholBreakpoints', above?? - */ - Handlebars.registerHelper('controlBreakpoints', function(tracker) { - let results = [] - tracker.thresholds.forEach(threshold => { - let op = getOperation(threshold.operator) - let temp = op(tracker.max, threshold.value) - let value = Math.ceil(temp) - - results.push({ - breakpoint: value, - label: threshold.condition, - comparison: threshold.comparison, - color: threshold.color, - abbreviation: threshold.abbreviation, - }) - }) - return results - }) - - Handlebars.registerHelper('include-if', function(condition, iftrue, iffalse) { - if (arguments.length == 3) iffalse = '' - return !!condition ? iftrue : iffalse - }) - - Handlebars.registerHelper('select-if', function(value, expected) { - return value == expected ? 'selected' : '' - }) - - Handlebars.registerHelper('disabled', function(value) { - return !!value ? 'disabled' : '' - }) - - Handlebars.registerHelper('gmod', function(value) { - return !!value ? 'gmod' : '' - }) - - Handlebars.registerHelper('rollable', function(value) { - return !!value ? 'rollable' : '' - }) - - Handlebars.registerHelper('isUserCreated', function(obj) { - return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_USER_CREATED) && !!obj.save - }) - - Handlebars.registerHelper('isFoundryItem', function(obj) { - return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_FOUNDRY_CREATED) && !!obj.itemid - }) - - Handlebars.registerHelper('ignoreImportQty', function(obj) { - return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_ignoreImportQty) && !!obj.ignoreImportQty - }) - - Handlebars.registerHelper('displayItemHover', function(obj) { - return ( - game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_FOUNDRY_CREATED) && - !!obj.img && - obj.img != 'icons/svg/item-bag.svg' - ) - }) - - Handlebars.registerHelper('automaticEncumbrance', function() { - return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ENCUMBRANCE) - }) - - Handlebars.registerHelper('first', function(array) { - return array[0] - }) - - Handlebars.registerHelper('multiplyDice', function(formula, count) { - return multiplyDice(formula, count) - }) - - Handlebars.registerHelper('collapsible-content', function(id, data, group, options) { - let title = data[0] - let type = !!group && group.length > 0 ? `type='radio' name='${group}'` : `type='checkbox'` - let content = data - .slice(1) - .map((/** @type {string} */ it) => - it.startsWith('*') - ? `
    ${it.slice(1)}
    ` - : `
    ${it}
    ` - ) - .join('\n') - - let template = ` + ) { + let op = getOperation(threshold.operator) + let comparison = getComparison(threshold.comparison) + let testValue = op(max, threshold.value) + return comparison(value, testValue) ? ((result = index), true) : false + }) + return result + }) + + /** + * Unlike the 'threshold-of' method above, this returns the *last* threshold whose condition matches. + */ + Handlebars.registerHelper('breakpoint-of', function (thresholds, max, value) { + // return the index of the threshold that the value falls into + let matches = thresholds.filter(function (threshold, index) { + let op = getOperation(threshold.operator) + let comparison = getComparison(threshold.comparison) + let testValue = op(max, threshold.value) + return comparison(value, testValue) + }) + + return matches.pop() + }) + + /** + * Unlike the 'threshold-of' method above, this returns the index of the *last* threshold whose condition matches. + */ + Handlebars.registerHelper('breakpointIndex-of', function (thresholds, max, value) { + // return the index of the threshold that the value falls into + let matches = thresholds.filter(function (threshold, index) { + let op = getOperation(threshold.operator) + let comparison = getComparison(threshold.comparison) + let testValue = op(max, threshold.value) + return comparison(value, testValue) + }) + + return thresholds.lastIndexOf(matches.pop()) + }) + + /** + * Return an array of the calculated breakpoint values along with the matching threshold. + */ + Handlebars.registerHelper('thresholdBreakpoints', function (tracker) { + let results = [] + tracker.thresholds.forEach(threshold => { + let op = getOperation(threshold.operator) + let temp = op(tracker.max, threshold.value) + let value = tracker.isDamageTracker ? Math.ceil(temp) : Math.floor(temp) + + results.push({ + breakpoint: value, + threshold: threshold, + }) + }) + return results + }) + + Handlebars.registerHelper('truthy', function (value) { + return !!value + }) + + /** + * TODO Maybe unnecessary -- just use 'thresholBreakpoints', above?? + */ + Handlebars.registerHelper('controlBreakpoints', function (tracker) { + let results = [] + tracker.thresholds.forEach(threshold => { + let op = getOperation(threshold.operator) + let temp = op(tracker.max, threshold.value) + let value = Math.ceil(temp) + + results.push({ + breakpoint: value, + label: threshold.condition, + comparison: threshold.comparison, + color: threshold.color, + abbreviation: threshold.abbreviation, + }) + }) + return results + }) + + Handlebars.registerHelper('include-if', function (condition, iftrue, iffalse) { + if (arguments.length == 3) iffalse = '' + return !!condition ? iftrue : iffalse + }) + + Handlebars.registerHelper('select-if', function (value, expected) { + return value == expected ? 'selected' : '' + }) + + Handlebars.registerHelper('disabled', function (value) { + return !!value ? 'disabled' : '' + }) + + Handlebars.registerHelper('gmod', function (value) { + return !!value ? 'gmod' : '' + }) + + Handlebars.registerHelper('rollable', function (value) { + return !!value ? 'rollable' : '' + }) + + Handlebars.registerHelper('isUserCreated', function (obj) { + return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_USER_CREATED) && !!obj.save + }) + + Handlebars.registerHelper('isFoundryItem', function (obj) { + return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_FOUNDRY_CREATED) && !!obj.itemid + }) + + Handlebars.registerHelper('ignoreImportQty', function (obj) { + return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_ignoreImportQty) && !!obj.ignoreImportQty + }) + + Handlebars.registerHelper('displayItemHover', function (obj) { + return ( + game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_FOUNDRY_CREATED) && + !!obj.img && + obj.img != 'icons/svg/item-bag.svg' + ) + }) + + Handlebars.registerHelper('automaticEncumbrance', function () { + return game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ENCUMBRANCE) + }) + + Handlebars.registerHelper('first', function (array) { + return array[0] + }) + + Handlebars.registerHelper('multiplyDice', function (formula, count) { + return multiplyDice(formula, count) + }) + + Handlebars.registerHelper('collapsible-content', function (id, data, group, options) { + let title = data[0] + let type = !!group && group.length > 0 ? `type='radio' name='${group}'` : `type='checkbox'` + let content = data + .slice(1) + .map((/** @type {string} */ it) => + it.startsWith('*') + ? `
    ${it.slice(1)}
    ` + : `
    ${it}
    ` + ) + .join('\n') + + let template = `
    @@ -728,98 +731,98 @@ ${content}
    ` - return new Handlebars.SafeString(template) - }) - - Handlebars.registerHelper('damageTerm', function(calc, options) { - let armorDivisor = - calc.useArmorDivisor && calc.armorDivisor // - ? calc.armorDivisor == -1 - ? '(∞)' - : `(${calc.armorDivisor})` // - : '' - - let damageType = - calc.damageType !== 'dmg' && calc.damageType !== 'injury' && calc.damageType !== 'none' ? calc.damageType : '' - let damageModifier = calc.damageModifier || '' - return [armorDivisor, damageType, damageModifier].join(' ').trim() - }) - - /** - * Added to color the rollable parts of the character sheet. - * Made this part eslint compatible... - * ~Stevil - */ - // eslint-disable-next-line no-undef - Handlebars.registerHelper('switch', function(value, options) { - this.switch_value = value - this.switch_break = false - return options.fn(this) - }) - - // eslint-disable-next-line no-undef - Handlebars.registerHelper('case', function(value, options) { - if (value === this.switch_value) { - this.switch_break = true - return options.fn(this) - } - }) - - // eslint-disable-next-line no-undef - Handlebars.registerHelper('default', function(value, options) { - if (this.switch_break == false) { - return value - } - }) - - Handlebars.registerHelper('quotedAttackName', function(prefix, item) { - return prefix + ':' + quotedAttackName(item) - }) - - // === register Handlebars partials === - // Partial name will be the last component of the path name, e.g.: 'systems/gurps/templates/actor/foo.hbs" -- the name is "foo". - // Use it in an HTML/HBS file like this: {{> foo }}. - // See https://handlebarsjs.com/guide/partials.html#partials for more documentation. - const templates = [ - 'systems/gurps/templates/actor/sections/advantages.hbs', - 'systems/gurps/templates/actor/sections/attributes.hbs', - 'systems/gurps/templates/actor/sections/basic-attributes.hbs', - 'systems/gurps/templates/actor/sections/ci-editor.hbs', - 'systems/gurps/templates/actor/sections/conditional-injury.hbs', - 'systems/gurps/templates/actor/sections/conditionalmods.hbs', - 'systems/gurps/templates/actor/sections/conditions.hbs', - 'systems/gurps/templates/actor/sections/description.hbs', - 'systems/gurps/templates/actor/sections/encumbrance.hbs', - 'systems/gurps/templates/actor/sections/footer.hbs', - 'systems/gurps/templates/actor/sections/equipment.hbs', - 'systems/gurps/templates/actor/sections/hpfp-editor.hbs', - 'systems/gurps/templates/actor/sections/hpfp-tracker.hbs', - 'systems/gurps/templates/actor/sections/identity.hbs', - 'systems/gurps/templates/actor/sections/lifting.hbs', - 'systems/gurps/templates/actor/sections/locations.hbs', - 'systems/gurps/templates/actor/sections/melee.hbs', - 'systems/gurps/templates/actor/sections/miscellaneous.hbs', - 'systems/gurps/templates/actor/sections/notes.hbs', - 'systems/gurps/templates/actor/sections/points.hbs', - 'systems/gurps/templates/actor/sections/portrait.hbs', - 'systems/gurps/templates/actor/sections/quicknote.hbs', - 'systems/gurps/templates/actor/sections/ranged.hbs', - 'systems/gurps/templates/actor/sections/reactions.hbs', - 'systems/gurps/templates/actor/sections/resource-controls.hbs', - 'systems/gurps/templates/actor/sections/resource-tracker.hbs', - 'systems/gurps/templates/actor/sections/secondary-attributes.hbs', - 'systems/gurps/templates/actor/sections/skills.hbs', - 'systems/gurps/templates/actor/sections/speed-range-table.hbs', - 'systems/gurps/templates/actor/sections/spells.hbs', - 'systems/gurps/templates/actor/sections/trackers.hbs', - ] - - templates.forEach(filename => { - let name = filename.substr(filename.lastIndexOf('/') + 1).replace(/(.*)\.hbs/, '$1') - fetch(filename) - .then(it => it.text()) - .then(async text => { - Handlebars.registerPartial(name, text) - }) - }) + return new Handlebars.SafeString(template) + }) + + Handlebars.registerHelper('damageTerm', function (calc, options) { + let armorDivisor = + calc.useArmorDivisor && calc.armorDivisor // + ? calc.armorDivisor == -1 + ? '(∞)' + : `(${calc.armorDivisor})` // + : '' + + let damageType = + calc.damageType !== 'dmg' && calc.damageType !== 'injury' && calc.damageType !== 'none' ? calc.damageType : '' + let damageModifier = calc.damageModifier || '' + return [armorDivisor, damageType, damageModifier].join(' ').trim() + }) + + /** + * Added to color the rollable parts of the character sheet. + * Made this part eslint compatible... + * ~Stevil + */ + // eslint-disable-next-line no-undef + Handlebars.registerHelper('switch', function (value, options) { + this.switch_value = value + this.switch_break = false + return options.fn(this) + }) + + // eslint-disable-next-line no-undef + Handlebars.registerHelper('case', function (value, options) { + if (value === this.switch_value) { + this.switch_break = true + return options.fn(this) + } + }) + + // eslint-disable-next-line no-undef + Handlebars.registerHelper('default', function (value, options) { + if (this.switch_break == false) { + return value + } + }) + + Handlebars.registerHelper('quotedAttackName', function (prefix, item) { + return prefix + ':' + quotedAttackName(item) + }) + + // === register Handlebars partials === + // Partial name will be the last component of the path name, e.g.: 'systems/gurps/templates/actor/foo.hbs" -- the name is "foo". + // Use it in an HTML/HBS file like this: {{> foo }}. + // See https://handlebarsjs.com/guide/partials.html#partials for more documentation. + const templates = [ + 'systems/gurps/templates/actor/sections/advantages.hbs', + 'systems/gurps/templates/actor/sections/attributes.hbs', + 'systems/gurps/templates/actor/sections/basic-attributes.hbs', + 'systems/gurps/templates/actor/sections/ci-editor.hbs', + 'systems/gurps/templates/actor/sections/conditional-injury.hbs', + 'systems/gurps/templates/actor/sections/conditionalmods.hbs', + 'systems/gurps/templates/actor/sections/conditions.hbs', + 'systems/gurps/templates/actor/sections/description.hbs', + 'systems/gurps/templates/actor/sections/encumbrance.hbs', + 'systems/gurps/templates/actor/sections/footer.hbs', + 'systems/gurps/templates/actor/sections/equipment.hbs', + 'systems/gurps/templates/actor/sections/hpfp-editor.hbs', + 'systems/gurps/templates/actor/sections/hpfp-tracker.hbs', + 'systems/gurps/templates/actor/sections/identity.hbs', + 'systems/gurps/templates/actor/sections/lifting.hbs', + 'systems/gurps/templates/actor/sections/locations.hbs', + 'systems/gurps/templates/actor/sections/melee.hbs', + 'systems/gurps/templates/actor/sections/miscellaneous.hbs', + 'systems/gurps/templates/actor/sections/notes.hbs', + 'systems/gurps/templates/actor/sections/points.hbs', + 'systems/gurps/templates/actor/sections/portrait.hbs', + 'systems/gurps/templates/actor/sections/quicknote.hbs', + 'systems/gurps/templates/actor/sections/ranged.hbs', + 'systems/gurps/templates/actor/sections/reactions.hbs', + 'systems/gurps/templates/actor/sections/resource-controls.hbs', + 'systems/gurps/templates/actor/sections/resource-tracker.hbs', + 'systems/gurps/templates/actor/sections/secondary-attributes.hbs', + 'systems/gurps/templates/actor/sections/skills.hbs', + 'systems/gurps/templates/actor/sections/speed-range-table.hbs', + 'systems/gurps/templates/actor/sections/spells.hbs', + 'systems/gurps/templates/actor/sections/trackers.hbs', + ] + + templates.forEach(filename => { + let name = filename.substr(filename.lastIndexOf('/') + 1).replace(/(.*)\.hbs/, '$1') + fetch(filename) + .then(it => it.text()) + .then(async text => { + Handlebars.registerPartial(name, text) + }) + }) } diff --git a/lib/npc-input.js b/lib/npc-input.js index a1c6a5693..0cae27b24 100755 --- a/lib/npc-input.js +++ b/lib/npc-input.js @@ -7,27 +7,27 @@ import * as HitLocations from '../module/hitlocation/hitlocation.js' import { i18n, sanitize } from './utilities.js' import * as settings from '../lib/miscellaneous-settings.js' -Hooks.once('init', async function() { - game.settings.registerMenu(settings.SYSTEM_NAME, settings.SETTING_MOOK_DEFAULT_EDITOR, { - name: i18n('GURPS.settingMookGenerator'), - label: i18n('GURPS.settingLabelMookGenerator'), - hint: i18n('GURPS.settingHintMookGenerator'), - type: NpcInputDefaultEditor, - restricted: true, - }) - - game.settings.register(settings.SYSTEM_NAME, settings.SETTING_MOOK_DEFAULT, { - name: 'Mook Default', - scope: 'world', - config: false, - type: Object, - default: new Mook(), - onChange: value => console.log(`Updated Mook Default: ${value}`), - }) +Hooks.once('init', async function () { + game.settings.registerMenu(settings.SYSTEM_NAME, settings.SETTING_MOOK_DEFAULT_EDITOR, { + name: i18n('GURPS.settingMookGenerator'), + label: i18n('GURPS.settingLabelMookGenerator'), + hint: i18n('GURPS.settingHintMookGenerator'), + type: NpcInputDefaultEditor, + restricted: true, + }) + + game.settings.register(settings.SYSTEM_NAME, settings.SETTING_MOOK_DEFAULT, { + name: 'Mook Default', + scope: 'world', + config: false, + type: Object, + default: new Mook(), + onChange: value => console.log(`Updated Mook Default: ${value}`), + }) }) Hooks.on(`renderNpcInput`, (app, html, data) => { - $(html).find('#npc-input-name').focus() + $(html).find('#npc-input-name').focus() }) // Hooks.once("ready", () => { new NpcInput().render(true) }); @@ -37,1209 +37,1209 @@ const ERR = `???:` // Keys we might see during attribute parsing, and whether we should skip them when looking for data const POSSIBLE_ATTRIBUTE_KEYS = { - basic: true, // This is the prefix to "basic speed", "basic move" - speed: false, - move: false, - dodge: false, - sm: false, - parry: false, - block: false, - dr: false, - damage: false, - bl: false, - fright: true, // prefix to "fright check" - check: false, - height: false, - weight: false, - age: false, - dmg: false, - hp: false, - fp: false, - skills: true, - attributes: true, - secondary: true, - characteristics: true, - advantages: true, + basic: true, // This is the prefix to "basic speed", "basic move" + speed: false, + move: false, + dodge: false, + sm: false, + parry: false, + block: false, + dr: false, + damage: false, + bl: false, + fright: true, // prefix to "fright check" + check: false, + height: false, + weight: false, + age: false, + dmg: false, + hp: false, + fp: false, + skills: true, + attributes: true, + secondary: true, + characteristics: true, + advantages: true, } export class NpcInput extends FormApplication { - constructor(actor, options = {}) { - super(options) - let m = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_MOOK_DEFAULT) - this.mook = m || new Mook() - this.testing = true - } - - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['npc-input', 'sheet', 'actor', 'form'], - id: 'npc-input', - template: 'systems/gurps/templates/actor/npc-input.hbs', - resizable: true, - minimizable: false, - width: 800, - height: 700, - title: 'Mook Generator', - }) - } - getData(options) { - let data = super.getData(options) - data.mook = this.mook - data.mode = this.testing ? 'Test Mook' : 'Create Mook' - data.displaybuttons = true - return data - } - - setTesting(t = true) { - this.testing = t - this.createButton.innerText = this.testing ? 'Test Mook' : 'Create Mook' - } - - activateListeners(html) { - super.activateListeners(html) - - html.find('.gcs-input-sm2').inputFilter(value => digitsOnly.test(value)) - html.find('input[type=text]').on('change paste keyup', ev => { - let el = ev.currentTarget - let k = el.dataset.key - if (!!k) { - this.mook[k] = el.value - this.setTesting() - } - }) - - html.find('.npc-input-ta').on('change', ev => { - let el = ev.currentTarget - let k = el.dataset.key - if (!!k) { - this.mook[k] = el.value - this.setTesting() - } - }) - - html.find('#npc-input-create').on('click keydown focusout', ev => { - if (ev.type == 'click' || (ev.type == 'keydown' && ev.which == 13)) this.createMook(ev) - else { - ev.preventDefault() - $(html).find('#npc-input-name').focus() - } - }) - html.find('#npc-input-import').on('click keydown', ev => { - if (ev.type == 'click' || (ev.type == 'keydown' && ev.which == 13)) this.importStatBlock(ev) - }) - this.createButton = html.find('#npc-input-create')[0] - - const frame = html[0]?.querySelector('.npc-input') - if (!!frame) - frame.addEventListener('drop', event => { - event.preventDefault() - if (event.originalEvent) event = event.originalEvent - this.applyDrop(JSON.parse(event.dataTransfer.getData('text/plain'))) - }) - } - - applyDrop(data) { - if (data.actorid) { - const actorData = game.actors.get(data.actorid).data - let element = getProperty(actorData, data.key) - let key = data.key.match(/^[^\.]+\.([^\.]+)/)[1] - let tmp = '' - let tmp2 = '' - let notes = element.notes || '' - switch (key) { - case 'skills': - case 'spells': { - tmp += element.name + '-' + element.level - break - } - case 'melee': - case 'ranged': { - if (element.acc) tmp2 += ' acc ' + element.acc - if (element.range) tmp2 += ' range ' + element.range - if (element.rcl) tmp2 += ' rcl ' + element.rcl - if (element.rof) tmp2 += ' rof ' + element.rof - if (element.shots) tmp2 += ' shots ' + element.shots - if (element.st) tmp2 += ' st ' + element.st - if (element.usage) tmp2 += ' usage' + element.usage - if (element.bulk) tmp2 += ' bulk ' + element.bulk - if (element.halfd) tmp2 += ' halfd ' + element.halfd - if (element.max) tmp2 += ' max ' + element.max - if (element.parry) tmp2 += ' parry ' + element.parry - if (element.reach) tmp2 += ' reach ' + element.reach - if (element.block) tmp2 += ' block ' + element.block - tmp += element.name + ' (' + element.level + ') ' + element.damage - break - } - case 'ads': { - key = 'traits' - tmp += element.name - if (element.pageref) tmp += ' Page Ref: ' + element.pageref - break - } - case 'notes': { - tmp += element.notes - notes = '' - break - } - case 'equipment': { - tmp += `${element.name}; ${element.count}; $${element.cost}; ${element.weight} lb` - break - } - } - let orig = this.mook[key] - tmp = orig + (orig ? '\n' : '') + tmp - tmp += tmp2 - if (notes) tmp += '\n# ' + notes.split('\n').join('\n# ').replaceAll(';', ',') - this.mook[key] = tmp - } else { - if (data.otf) { - this.mook.notes += ' [' + data.otf + ']' - } - } - this.setTesting() - this.render(true) - } - - async importStatBlock(ev) { - ev.preventDefault() - let self = this - let b = this.savedStatBlock || '' - let d = new Dialog( - { - title: `Import Stat Block`, - content: await renderTemplate('systems/gurps/templates/import-stat-block.html', { block: b }), - buttons: { - import: { - icon: '', - label: 'Import', - callback: html => { - let ta = html.find('#npc-input-import-ta')[0] - let t = ta.value - if (t.length < 3) t = EX[parseInt(t)] - if (!!t) this.parseStatBlock(t) - self.render(true) - }, - }, - no: { - icon: '', - label: 'Cancel', - }, - }, - default: false, - }, - { - width: 800, - height: 800, - } - ) - d.render(true) - } - - async createMook(ev) { - ev.preventDefault() - if (this.testing) { - let err = this.check() - if (!!err) ui.notifications.warn('Unable to create Mook: ' + err) - else this.setTesting(false) - } else { - let data = { name: this.mook.name, type: 'character' } - let a = await GurpsActor.create(data, { renderSheet: false }) - await this.populate(a) - await a.setFlag('core', 'sheetClass', 'gurps.GurpsActorNpcSheet') - a.sheet.render(true) - } - this.render(true) - } - - async populate(a) { - let m = this.mook - let data = a.system - let att = data.attributes - - // + is a trick to force strings to ints - att.ST.import = +m.st - att.DX.import = +m.dx - att.IQ.import = +m.iq - att.HT.import = +m.ht - att.WILL.import = +m.will - att.PER.import = +m.per - - data.HP.max = +m.hp - data.HP.value = +m.hp - data.FP.max = +m.fp - data.FP.value = +m.fp - - data.basicmove.value = parseInt(m.move) - data.basicspeed.value = m.speed - data.frightcheck = m.will - if (!!m.check) data.frightcheck = m.check // Imported "fright check" - - data.hearing = m.per - data.tastesmell = m.per - data.touch = m.per - data.vision = m.per - if (!!m.parry) data.parry = parseInt(m.parry) - if (!!m.block) data.block = parseInt(m.block) - - let ns = {} - let nt = new Note(m.notes.trim()) - GURPS.put(ns, nt) - - // Since we removed the default hitlocations from template.json, we need to create at least one entry for the mook - // But to maintain a similar look, we will just recreate the humanoid locations. - let hls = {} - for (const [key, value] of Object.entries(HitLocations.hitlocationDictionary.humanoid)) { - let loc = { ...value, ...{ where: key } } - if (key == HitLocations.HitLocation.TORSO) loc.import = m.dr - GURPS.put(hls, loc) - } - - let es = {} - let e = new Encumbrance() - e.level = 0 - e.current = true - e.key = 'enc0' - e.weight = 0 - e.move = parseInt(m.move) - e.dodge = parseInt(m.dodge) - GURPS.put(es, e) - - let melee = {} - m.a_melee.forEach(me => GURPS.put(melee, me)) - - let ranged = {} - m.a_ranged.forEach(r => GURPS.put(ranged, r)) - - let ts = {} - ts.title = m.title - let dt = new Date().toString().split(' ').splice(1, 3).join(' ') - ts.createdon = dt - if (!!m.sm) ts.sizemod = m.sm[0] == '-' ? m.sm : m.sm[0] == '+' ? '' : '+' + m.sm - ts.appearance = m.desc - ts.height = m.height - ts.weight = m.weight - ts.age = m.age - - let skills = {} - m.a_skills.forEach(s => GURPS.put(skills, s)) - - let spells = {} - m.a_spells.forEach(s => GURPS.put(spells, s)) - - let ads = {} - m.a_traits.forEach(a => GURPS.put(ads, a)) - - let eqt = {} - m.a_equipment.forEach(e => GURPS.put(eqt, e)) - - let dmg = m.damage || m.dmg || '' - let thrst = dmg.split('/')[0] - let swng = dmg.split('/')[1] - if (!swng && !!thrst) { - swng = thrst - thrst = '' - } - - let commit = { - 'data.attributes': att, - 'data.HP': data.HP, - 'data.FP': data.FP, - 'data.basicmove': data.basicmove, - 'data.basicspeed': data.basicspeed, - 'data.currentmove': parseInt(m.move), - 'data.currentdodge': parseInt(m.dodge), - 'data.frightcheck': data.frightcheck, - 'data.hearing': data.hearing, - 'data.tastesmell': data.tastesmell, - 'data.touch': data.touch, - 'data.vision': data.vision, - 'data.equippedparry': data.parry, - 'data.parry': data.parry, - 'data.equippedblock': data.block, - 'data.block': data.block, - 'data.notes': ns, - 'data.hitlocations': hls, - 'data.encumbrance': es, - 'data.melee': melee, - 'data.ranged': ranged, - 'data.traits': ts, - 'data.skills': skills, - 'data.spells': spells, - 'data.ads': ads, - 'data.swing': swng, - 'data.thrust': thrst, - 'data.equipment.carried': eqt, - } - - await a.update(commit) - await a.postImport() - console.log('Created Mook:') - console.log(a) - } - - sanit(key) { - this.mook[key] = sanitize(this.mook[key]) - } - - check() { - let e = '' - if (!this.mook.name) e = ', No Name' - this.sanit('melee') - this.sanit('ranged') - this.sanit('traits') - this.sanit('skills') - this.sanit('spells') - this.sanit('equipment') - - if (this.checkTraits()) e += ', Error in Traits' - if (this.checkSkills()) e += ', Error in Skills' - if (this.checkMelee()) e += ', Error in Melee' - if (this.checkRanged()) e += ', Error in Ranged' - if (this.checkSpells()) e += ', Error in Spells' - if (this.checkEquipment()) e += ', Error in Equipment' - return e.substr(2) - } - - // return an array of string representing each line - prep(text, delim) { - var ans - if (!!delim) ans = this.parseDelimLines(text, delim) - else ans = text.split('\n') - return ans.map(e => this.cleanLine(e)).filter(e => e.length > 0) - } - - // Allow () to include delims without breaking line - parseDelimLines(str, delim) { - let arr = [] - let i = 0 - let line = '' - let d = 0 - while (i < str.length) { - let c = str[i++] - if ((c == delim && d == 0) || c == '\n') { - arr.push(line) - line = '' - } else { - line += c - if (c == '(') d++ - if (c == ')') d-- - } - } - if (!!line) arr.push(line) - return arr - } - - checkSkills() { - const m = this.mook - let txt = '' - let arr = [] - this.prep(m.skills, ';').forEach(e => { - if (e.includes(ERR)) return - txt += '\n' + e - if (e.startsWith(COMMENT_CHAR)) { - if (arr.length > 0) this.addToNotes(arr, e.substr(1), ' ') - return - } - const i = e.lastIndexOf('-') - if (i < 1) return (txt += `\n${ERR} missing '-'`) - const n = e.substring(0, i).trim() - const v = e.substr(i + 1).trim() - if (!v) return (txt += `\n${ERR} missing skill level`) - if (isNaN(v)) return (txt += `\n${ERR} "${v}" is not a number`) - arr.push(new Skill(n, v)) - }) - m.skills = txt.substr(1) - m.a_skills = arr - return txt.includes(ERR) - } - - checkSpells() { - const m = this.mook - let txt = '' - let arr = [] - this.prep(m.spells, ';').forEach(e => { - if (e.includes(ERR)) return - txt += '\n' + e - if (e.startsWith(COMMENT_CHAR)) { - if (arr.length > 0) this.addToNotes(arr, e.substr(1), ' ') - return - } - const i = e.lastIndexOf('-') - if (i < 1) return (txt += `\n${ERR} missing '-'`) - const n = e.substring(0, i).trim() - const v = e.substr(i + 1).trim() - if (!v) return (txt += `\n${ERR} missing spell level`) - if (isNaN(v)) return (txt += `\n${ERR} "${v}" is not a number`) - arr.push(new Skill(n, v)) - }) - m.spell = txt.substr(1) - m.a_spells = arr - return txt.includes(ERR) - } - - checkMelee() { - const pats = [ - { regex: '(^usage|^Usage|^mode|^Mode)\\w+', var: 'mode' }, - { regex: '(^parry|^Parry)\\d+', var: 'parry' }, - { regex: '(^reach|^Reach)[\\w,]+', var: 'reach' }, - { regex: '(^st|^ST|^St)\\d+', var: 'st' }, - { regex: '(^block|^Block)\\d+', var: 'block' }, - ] - const m = this.mook - let txt = '' - let arr = [] - this.prep(m.melee).forEach(e => { - if (e.includes(ERR)) return - txt += '\n' + e - if (e.startsWith(COMMENT_CHAR)) { - if (arr.length > 0) this.addToNotes(arr, e.substr(1), ' ') - return - } - var me, remain - let parse = e.replace( - /(.*) ?\((\d+)\) (\d+)d(\d*)([-+]\d+)?([xX\*]\d+)?(\([.\d]+\))?(!)? ?(\w+\+?\+?)(.*)$/g, - '$1~$2~$3~$4~$5~$6~$7~$8~$9~$10' - ) - if (e != parse) { - parse = parse.split('~') - me = new Melee( - parse[0].trim(), - parse[1], - parse[2] + 'd' + parse[3] + parse[4] + parse[5] + parse[6] + parse[7] + ' ' + parse[8] - ) - remain = parse[9].trim() - } else { - parse = e.replace(/(.*) ?\(([ \w]+)\) "([^"]+)" ?(.*)$/g, '$1~$2~$3~$4') - if (e == parse) return (txt += `\n${ERR} unable to find (level) and damage`) - parse = parse.split('~') - me = new Melee(parse[0].trim(), parse[1], parse[2]) - remain = parse[3].trim() - } - if (!!remain) { - let ext = remain.replace(/ +/g, ' ').split(' ') - if (ext.length % 2 != 0) return (txt += `\n${ERR} unable to parse "${remain}"`) - for (let i = 0; i < ext.length; i += 2) { - let s = ext[i] + ext[i + 1] - let found = false - pats.forEach(p => { - if (s.match(new RegExp(p.regex))) { - me[p.var] = ext[i + 1] - found = true - } - }) - if (!found) return (txt += `\n${ERR} unknown pattern "${ext[i]} ${ext[i + 1]}"`) - } - } - arr.push(me) - }) - m.melee = txt.substr(1) - m.a_melee = arr - return txt.includes(ERR) - } - - checkRanged() { - const pats = [ - { regex: '(^acc|^Acc)\\d+', var: 'acc' }, - { regex: '(^rof|^RoF|^Rof)\\d+', var: 'rof' }, - { regex: '(^rcl|^Rcl)\\d+', var: 'rcl' }, - { regex: '(^usage|^Usage|^mode|^Mode)\\w+', var: 'mode' }, - { regex: '(^range|^Range)\\d+(\\/\\d+)?', var: 'range' }, - { regex: '(^shots|^Shots)[\\w\\)\\(]+', var: 'shots' }, - { regex: '(^bulk|^Bulk)[\\w-]+', var: 'bulk' }, - { regex: '(^st|^ST|^St)\\d+', var: 'st' }, - { regex: '^halfd\\d+', var: 'halfd' }, - { regex: '^max\\d+', var: 'max' }, - ] - const m = this.mook - let txt = '' - let arr = [] - this.prep(m.ranged).forEach(e => { - if (e.includes(ERR)) return - txt += '\n' + e - if (e.startsWith(COMMENT_CHAR)) { - if (arr.length > 0) this.addToNotes(arr, e.substr(1), ' ') - return - } - let parse = e.replace( - /(.*) ?\((\d+)\) (\d+)d(\d*)([-+]\d+)?([xX\*]\d+)?(\([.\d]+\))?(!)?:? ?([\w-+]+)(.*)/g, - '$1~$2~$3~$4~$5~$6~$7~$8~$9~$10' - ) - - var r, remain - if (e != parse) { - parse = parse.split('~') - r = new Ranged( - parse[0].trim(), - parse[1], - parse[2] + 'd' + parse[3] + parse[4] + parse[5] + parse[6] + parse[7] + ' ' + parse[8] - ) - remain = parse[9].trim() - } else { - parse = e.replace(/(.*) ?\(([ \w]+)\):? "([^"]+)" ?(.*)$/g, '$1~$2~$3~$4') - if (e == parse) return (txt += `\n${ERR} unable to find (level) and damage`) - parse = parse.split('~') - r = new Ranged(parse[0].trim(), parse[1], parse[2]) - remain = parse[3].trim() - } - if (!!remain) { - let ext = remain.trim().replace(/ +/g, ' ').split(' ') - if (ext.length % 2 != 0) return (txt += `\n${ERR} unable to parse for `) - for (let i = 0; i < ext.length; i += 2) { - let s = ext[i] + ext[i + 1] - let found = false - pats.forEach(p => { - if (s.match(new RegExp(p.regex))) { - r[p.var] = ext[i + 1] - found = true - } - }) - if (!found) return (txt += `\n${ERR} unknown pattern "${ext[i]} ${ext[i + 1]}"`) - } - } - r.checkRange() - arr.push(r) - }) - m.ranged = txt.substr(1) - m.a_ranged = arr - return txt.includes(ERR) - } - - addToNotes(arr, note, delim) { - if (arr.length == 0) return - let n = arr[arr.length - 1].notes - if (!!n) n += delim + note - else n = note - arr[arr.length - 1].notes = n - } - - checkTraits() { - const m = this.mook - let txt = '' - let arr = [] - this.prep(m.traits, ';').forEach(e => { - txt += '\n' + e - if (e.startsWith(COMMENT_CHAR)) { - if (arr.length > 0) this.addToNotes(arr, e.substr(1), '\n') - return - } - arr.push(new Advantage(e)) - }) - m.traits = txt.substr(1) - m.a_traits = arr - return false - } - - checkEquipment() { - const m = this.mook - let txt = '' - let arr = [] - this.prep(m.equipment).forEach(e => { - if (e.includes(ERR)) return - txt += '\n' + e - if (e.startsWith(COMMENT_CHAR)) { - this.addToNotes(arr, e.substr(1), '\n') - return - } - let a = e.split(';') - if (a.length != 4) { - return (txt += `\n${ERR} Expecting ; ; $; lb\n`) - } else { - let eqt = new Equipment(a[0]) - eqt.count = parseInt(a[1]) - eqt.equipped = true - eqt.carried = true - let n = a[2].match(/ *\$ *([\?\d]+)/) - if (n) eqt.cost = n[1] - else { - return (txt += `\n${ERR} Unable to find '$ '\n`) - } - n = a[3].match(/ *([\?\d.]+) * lbs?/) - if (n) eqt.weight = n[1] - else { - return (txt += `\n${ERR} Unable to find ' lb'\n`) - } - Equipment.calc(eqt) - arr.push(eqt) - } - }) - m.equipment = txt.substr(1) - m.a_equipment = arr - return txt.includes(ERR) - } - - preparePastedText(txt) { - txt = sanitize(txt) - txt = txt.replace(/\t/g, '; ') // replace tabs with '; ' - txt = txt.replace(/ +/g, ' ') // remove multiple spaces in a row - txt = txt.replace(/[^ -~\n]+/g, '') // remove remaining non-ascii - return this.cleanLine(txt) // trim and remove leading and trailing periods. - } - - parseStatBlock(txt) { - this.setTesting() - this.statblk = this.preparePastedText(txt) - this.savedStatBlock = this.statblk - console.log(this.statblk) - try { - this.resetMook() - this.checkForNameNotes() - this.parseAttrs() - this.parseAttacks() - this.parseTraits() - this.parseS('Skills', 'Spells', 'skills') - this.parseS('Spells', 'Equipment', 'spells') - this.parseEquipment() - this.parseFinalNotes() - this.parseAttacks(true) - } catch (e) { - console.log(e) - ui.notifications.warn(e) - } - } - - resetMook() { - this.mook = new Mook() - this.mook.name = '' - this.mook.notes = '' - this.mook.melee = '' - this.mook.ranged = '' - this.mook.traits = '' - this.mook.skills = '' - this.mook.spells = '' - this.mook.equipment = '' - } - - gatherAttackLines(attblk, currentline, oldformat) { - // read lines looking for (\d+) and any lines following that do NOT have (\d+) - if (oldformat) return [attblk, currentline, this.nextToken()] - var nextline - ;[attblk, nextline] = this.nextTokenPrim(attblk, '\n', false, true) - while (!!nextline && (nextline.match(/[Ss]hots.*\(\d+\)/) || !nextline.match(/\(\d+\)/))) { - // If the new line doesn't contain a skill level "(\d+)" that isn't part of [Ss]hots assume it is still from the previous attack - currentline += ' ' + nextline - ;[attblk, nextline] = this.nextTokenPrim(attblk, '\n', false, true) - } - return [attblk, currentline, nextline] - } - - findInLine(line, regex) { - let re = new RegExp(regex) - let m = line.match(re) - if (!!m) line = line.replace(re, '') - return [line, m] - } - - parseEquipment() { - if (!this.peek('Equipment')) return - let line = this.nextline() // Skip equipment line - line = this.nextline() - while (!!line) { - var m - let cost = '?' - let weight = '?' - let qty = 1 - let name = '' - ;[line, m] = this.findInLine(line, /\$([\.\d]+)[ ,\.]*/) - if (!!m) cost = m[1] - ;[line, m] = this.findInLine(line, /([\.\d]+) ?lbs?[ ,\.]*/) - if (!!m) weight = m[1] - ;[line, m] = this.findInLine(line, /^ *(\d+)/) - if (!!m) qty = m[1] - ;[line, m] = this.findInLine(line, /^([^(\.])+\((\d+)\)/) - if (!!m) { - qty = m[2] - name = m[1] - } - line = line.replace(/\[[ \W]+/g, '') //clean up runs of non-word chars in brackets - name += this.cleanLine(line) - name = name.replace(';', ',') - this.mook.equipment += name + '; ' + qty + '; $' + cost + '; ' + weight + ' lb\n' - line = this.nextline() - while (line === '') line = this.nextline() - } - } - - parseS(start, end, path) { - this.trim() - if (this.peek(start)) this.nextToken(start) - let goUntil = this.peek(end) - let line = this.nextline() - if (!line) return - line = this.cleanLine(line) - let s = '' - // while (!!line && (line.match(/[^-]+-\d+/) || (!!goUntil && !line.startsWith(end)))) { - while (!!line && line.match(/[^-]+-\d+/) && !line.startsWith(end)) { - s += '\n' + this.cleanLine(line) - line = this.nextToken('\n', false, true) - } - //if (!s) this.appendToNotes(`?? No ${start} matching pattern '${path}-lvl' found`); - s = this.cleanLine(s) - let delim = ';' - if (s.includes(delim)) s = s.replace(/\n/g, ' ') - // If has delims, then remove newlines - else delim = '\n' // No ";", so assume one per line - var l - while (!!s) { - ;[s, l] = this.nextTokenPrim(s, delim, false, true) // Start it off reading the first line - - // Remove skill type, rsl and points Broadsword (A) DX+2 [8]-16 - l = this.cleanLine(l).replace(/ ?\([a-zA-Z]+\) [a-zA-Z]+([-+][0-9]+)? \[ *-?\d+\ *] */g, '') - l = l.replace(/ [SDIH][TXQ][-+]?[0-9]* ?/, ' ') - l = l.replace(/ ?\[ *-?\d+ *\],?\.? ?/g, ' ') - l = this.cleanLine(l) - let m = l.match(/([^-]+ *- *\d+)(.*)/) - if (m) { - this.mook[path] += '\n' + m[1] - if (!!m[2]) this.mook[path] += '\n' + COMMENT_CHAR + m[2] - } else if (!!l) this.mook[path] += '\n' + COMMENT_CHAR + `Unknown ${start} pattern ${l}` - } - if (!!line) this.pushToken(line) - } - - parseTraits() { - let n = this.peekskipto('Advantages/Disadvantages') - n += this.peekskipto('Advantages') - n += this.peekskipto('Traits') - if (!!n) this.appendToNotes(ERR + ' Skipped before Traits: ' + n, '\n') - this.trim() - let trblk = this.nextToken('Skills', 'Spells', true) - if (!trblk) return this.appendToNotes(ERR + " Looking for Traits block, unable to find 'Skills'") // Basically always need "Skills" or "Spells" - let traits = '' - if (trblk.includes(';')) trblk = trblk.replace(/\n/g, ' ') - // if it contains ";", then remove newlines - else { - // no ";", maybe using commas? - if (trblk.split(',').length > 2) trblk = trblk.replace(/,/g, ';') // Assume comma separated - } - trblk = trblk.replace(/disadvantages/gi, ';') - trblk = trblk.replace(/perks/gi, ';') - trblk = trblk.replace(/quirks/gi, ';') - trblk = trblk.replace(/ ?\[-?\d+\*?\],?\.? ?/g, ' ') - this.prep(trblk, ';').forEach(t => { - t = t.replace(/\( *(\d+) *or less *\)/g, '($1)') // Compress some forms of CR rolls - let m = t.match(/(.*)\((\d+)\)/) - if (!!m) traits += `\n[CR: ${m[2]} ${m[1].trim()}]` - // Convert CR roll into OtF - else { - if (t != 'and') traits += '\n' + t - } - }) - this.mook.traits = traits - } - - parseAttacks(oldformat = false) { - const rpats = [ - { regex: ' [Aa]cc *(\\d+) ?,?', var: 'acc' }, - { regex: ' [Rr]o[Ff] *(\\d+) ?,?', var: 'rof' }, - { regex: ' [Rr]cl *(\\d+) ?,?', var: 'rcl' }, - { regex: ' 1\\/2[Dd] *(\\d+) ?,?', var: 'halfd' }, - { regex: ' [Mm]ax *(\\d+) ?,?', var: 'max' }, - { regex: ' [Ss]hots *([\\w\\)\\(]+) ?,?', var: 'shots' }, - { regex: ' [Bb]ulk *([\\w-]+) ?,?', var: 'bulk' }, - { regex: ' [Ss][Tt] *(\\d+) ?,?', var: 'st' }, - { regex: ' ?[Rr]anged,? *with ?', var: '' }, - { regex: ' ?[Rr]anged,?', var: '' }, - { regex: ' ?[Rr]ange ([0-9/]+) *,?', var: 'range' }, - ] - var attblk - if (oldformat) { - attblk = this.statblk - if (!attblk) return - } else { - attblk = this.nextToken('Traits:', 'Advantages/Disadvantages:') // Look for either as start of ads/disads - if (!attblk) { - if (this.peek('Skills:')) attblk = this.nextToken('Skills:', 'junk') - else if (!this.peek('\nWeapons:')) this.mook.melee = COMMENT_CHAR + 'No attacks found' // If Weapons aren't listedt later, show error. - } - } - if (!attblk) return - attblk = this.cleanLine(attblk) - // assume a line is an attack if it contains '(n)' - let line, nextline - ;[attblk, line] = this.nextTokenPrim(attblk, '\n', false, true) // Start it off reading the first line - ;[attblk, line, nextline] = this.gatherAttackLines(attblk, line, oldformat) // collect up any more lines. - - while (!!line) { - save = line - line = this.cleanLine(line) - var name, lvl, dmg, save - ;[line, name] = this.nextTokenPrim(line, '(', false) - if (!name) { - this.mook.melee += `${COMMENT_CHAR}Unrecognized attack: "${save}" ` - ;[attblk, line, nextline] = this.gatherAttackLines(attblk, nextline, oldformat) // collect up any more lines. - continue - } - name = name.trim() - ;[line, lvl] = this.nextTokenPrim(line, ')', '):') - if (!lvl || isNaN(lvl)) { - this.mook.melee += `${COMMENT_CHAR}No level or level is not a number "${save}"` - ;[attblk, line, nextline] = this.gatherAttackLines(attblk, nextline, oldformat) // collect up any more lines. - continue - } - //[line, dmg] = this.nextTokenPrim(line, ".", ",", true); // find up to . or , or end of string - ;[line, dmg] = this.nextTokenV2Prim(line, ['.', ',', ';'], true) - let savedmg1 = dmg - let note = '' - ;[dmg, note] = this.mapDmg(line, dmg) - if (!!dmg && !!note) note = '\n' + COMMENT_CHAR + this.cleanLine(note) - if (!dmg) { - // If not dmg formula, try one more time. - ;[line, dmg] = this.nextTokenPrim(line, '.', ',', true) // find up to . or , or end of string - let savedmg2 = dmg - ;[dmg, note] = this.mapDmg(line, dmg) - if (!dmg) { - line = savedmg1 + ' ' + savedmg2 + ' ' + note // Nope, couldn't find anything, so reset the line - note = '' - } else if (!!note) note = '\n' + COMMENT_CHAR + this.cleanLine(note) - } - let regex = /.*[Rr]each (?[^\.]+)/g - let result = regex.exec(line) - let extra = '' - let final = '' - if (!!result?.groups?.reach) { - // If it has Reach, it is definitely melee - extra = ' reach ' + result.groups.reach.replace(/ /g, '') - line = this.cleanLine(line.replace(/[Rr]each [^\.]+/, '')) - if (!!line) note += '\n' + COMMENT_CHAR + line - final = '\n' + name + ' (' + lvl + ') ' + dmg + extra - this.mook.melee += final + note - } else { - let ranged = [] - rpats.forEach(p => { - let re = new RegExp(p.regex) - let match = line.match(re) - if (!!match) { - line = line.replace(re, '').trim() - if (!!match[1]) ranged.push(p.var + ' ' + match[1]) - } - }) - if (ranged.length > 0) { - extra = ranged.join(' ') - final = '\n' + name + ' (' + lvl + ') ' + dmg + ' ' + extra - if (!!line) note += '\n' + COMMENT_CHAR + line - this.mook.ranged += final + note - } else { - // but it may not have either, in which case we treat as melee - final = '\n' + name + ' (' + lvl + ') ' + dmg + extra - if (!!line) note += '\n' + COMMENT_CHAR + line - this.mook.melee += final + note - } - } - ;[attblk, line, nextline] = this.gatherAttackLines(attblk, nextline, oldformat) // collect up any more lines. - } - } - - mapDmg(line, dmg) { - if (!dmg) return ['', ''] - dmg = dmg.trim().toLowerCase() - let p = GURPS.DamageTables.parseDmg(dmg) - if (p == dmg) return ['', line] - let a = p.split('~') - let roll = a[0] + 'd' + a[1] + a[2] + a[3] + a[4] - let types = a[5].trim().split(' ') - let m = GURPS.DamageTables.translate(types[0]) - if (!m) return ['', `Unrecognized damage type "${types[0]}" for "${line}"`] - return [roll + ' ' + m, types.slice(1).join(' ')] - } - - appendToNotes(t, suffix = ' ') { - this.mook.notes += t + suffix - } - - // If the first line does not contain a ":" (or "ST "), then it probably is not the start of the stat block - checkForNameNotes() { - this.trim() - let line = this.nextline() - if (!line) return - let curregex = /ST.*\d+.*HP.*\d+/ - let oldregex = /ST.*\d+.*DX.*\d+/ - let forumregex = /^ST:? +\d?\d/ - - let first = true - while (!!line && !line.match(curregex) && !line.match(oldregex) && !line.match(forumregex)) { - // Assume we haven't started the stat block yet - // if the first line has 2 or fewer spaces, assume that it is a name. Just guessing here - if (first && (line.match(/ /g) || []).length < 3) this.mook.name = line - else this.appendToNotes(line) - line = this.nextline() - first = false - } - if (!!this.mook.notes) this.mook.notes += '\n\n' - this.pushToken(line) - } - - parseFinalNotes() { - let postProcessWeapons = this.stealahead('\nWeapons:') - this.trim() - let t = this.nextToken() - if (!!t) { - if (t == 'Class:') { - this.appendToNotes(t + ' ' + this.nextline(), '\n') - return this.parseFinalNotes() - } - if (t == 'Notes:') this.appendToNotes(this.statblk) - else this.appendToNotes(t + ' ' + this.statblk) - } - this.mook.notes = this.mook.notes.trim() - this.statblk = postProcessWeapons - } - - pushToken(t) { - this.statblk = t + '\n' + this.statblk - } - - // This is the exhaustive test to see if we want to parse it as an attribute. - // Note: we may want to parse some things just so we can safely skip over them - isAttribute(a) { - if (!a) return false - if (a == 'Traits:' || a == 'Advantages/Disadvantages:') return false - if (a.match(/\w+:/) || !!GURPS.attributepaths[a]) return true - if (a.match(/\[\d+\],?/)) return true // points costs [20] accept this to parse, to skip over it - return POSSIBLE_ATTRIBUTE_KEYS.hasOwnProperty(a.toLowerCase()) - } - - // We know we accept it, however, it may be 'junk' that we are just trying to skip over. - getAttrib(t) { - if (t.match(/\[\d+\],?/)) return false // points costs [20] // Don't count this as "any" - let a = t.replace(/[^A-Za-z]/g, '') // remove anything but letters - // Special case is attributes are listed as "basic speed" or "fright check" - if (!!POSSIBLE_ATTRIBUTE_KEYS[a.toLowerCase()]) return '' - return a - } - - storeAttrib(attrib, value) { - let val = value.replace(/[,;\.]$/g, '') // try to clean up the value by stripping crap off the end - attrib - .toLowerCase() - .split('/') - .forEach(a => (this.mook[a] = val)) // handles cases like "Parry/Block: 9" - console.log('Storing attribute: ' + attrib + '=' + val) - } - - parseAttrs() { - var attr, val, saved - this.trim() - let unknowns = [] - unknowns.push([]) // Each line will have its own array of unparsed keys - let line = this.nextline() - saved = line - let any = false - do { - ;[line, attr] = this.nextPrim(line) - if (!line && !attr) { - if (!any) break - line = this.nextline() - saved = line - ;[line, attr] = this.nextPrim(line) - any = false - unknowns.push([]) - } - while (this.isAttribute(attr)) { - // Something we recognize - let goodattr = this.getAttrib(attr) // An actual attribute - if (goodattr !== false) any = true // Anything except "false" means "keep looking" - if (!!goodattr) { - ;[line, val] = this.nextPrim(line) - if (!line && !val) { - line = this.nextline() - ;[line, val] = this.nextPrim(line) - } - this.storeAttrib(goodattr, val) - } - ;[line, attr] = this.nextPrim(line) - if (!line && !attr) { - if (!any) break - line = this.nextline() - saved = line - ;[line, attr] = this.nextPrim(line) - any = false - unknowns.push([]) - } - } - if ('Traits:' == attr) break - if (!!attr) unknowns.slice(-1)[0].push(attr) - } while (true) - unknowns.pop() // The last line didn't have any attributes, so not really errors. - this.pushToken(saved) // We didn't use this line, so put it back - let a = unknowns - .map(a => a.join(' ')) - .join(' ') - .trim() // collapse all unknowns into a string - if (!!a) { - // we parsed some things that did not work. - this.appendToNotes(ERR + ' ' + a, '\n') - } - } - - stealahead(str) { - let i = this.statblk.indexOf(str) - if (i < 0) return '' - let s = this.statblk.substr(i + str.length + 1) - this.statblk = this.statblk.substring(0, 1) - return s - } - - peek(str) { - return this.statblk.includes(str) - } - - peekskipto(str) { - return this.peek(str) ? this.nextToken(str, false) : '' - } - - trim() { - this.statblk = this.cleanLine(this.statblk) - } - - nextToken(d1 = ' ', d2 = '\n', all = false) { - let [s, t] = this.nextTokenPrim(this.statblk, d1, d2, all) - this.statblk = s - return t - } - - nextline() { - let [s, t] = this.nextlinePrim(this.statblk) - this.statblk = s - return t - } - - next(delim = ' ') { - let [s, t] = this.nextPrim(this.statblk, delim) - this.statblk = s - return t - } - - nextlinePrim(txt) { - return this.nextPrim(txt, '\n') - } - - nextPrim(txt, delim = ' ') { - return this.nextTokenPrim(txt, delim, false, true) - } - - nextTokenPrim(str, d1 = ' ', d2 = '\n', all = false) { - // d2 must be equal or longer in length than d1 ")" and "):" - if (!str) return [str, undefined] - let i = str.indexOf(d1) - let j = str.indexOf(d2) - if (i >= 0 && j >= 0) { - if (j <= i) { - d1 = d2 // - i = j // Crappy hack to be able to search for 2 delims - } - let t = str.substring(0, i) - return [str.substr(i + d1.length).trim(), t] - } - if (i >= 0) { - let t = str.substring(0, i) - return [str.substr(i + d1.length).trim(), t] - } - if (j >= 0) { - let t = str.substring(0, j) - return [str.substr(j + d2.length).trim(), t] - } - return all ? ['', str] : [str, undefined] - } - - nextTokenV2Prim(str, arr, all = false) { - if (!str) return [str, undefined] - let best = Number.MAX_SAFE_INTEGER - let bestIndex = -1 - for (let index = 0; index < arr.length; index++) { - let d = arr[index] - let cur = str.indexOf(d) - if (cur >= 0 && cur < best) { - best = cur - bestIndex = index - } - } - if (bestIndex >= 0) { - let t = str.substring(0, best) - return [str.substr(best + arr[bestIndex].length).trim(), t] - } else { - return all ? ['', str] : [str, undefined] - } - } - - cleanLine(line) { - let start = line - if (!line) return line - let pat = '*,.:' // things that just clutter up the line - if (pat.includes(line[0])) line = line.substr(1) - if (pat.includes(line[line.length - 1])) line = line.substring(0, line.length - 1) - line = line.trim() - return start == line ? line : this.cleanLine(line) - } + constructor(actor, options = {}) { + super(options) + let m = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_MOOK_DEFAULT) + this.mook = m || new Mook() + this.testing = true + } + + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['npc-input', 'sheet', 'actor', 'form'], + id: 'npc-input', + template: 'systems/gurps/templates/actor/npc-input.hbs', + resizable: true, + minimizable: false, + width: 800, + height: 700, + title: 'Mook Generator', + }) + } + getData(options) { + let data = super.getData(options) + data.mook = this.mook + data.mode = this.testing ? 'Test Mook' : 'Create Mook' + data.displaybuttons = true + return data + } + + setTesting(t = true) { + this.testing = t + this.createButton.innerText = this.testing ? 'Test Mook' : 'Create Mook' + } + + activateListeners(html) { + super.activateListeners(html) + + html.find('.gcs-input-sm2').inputFilter(value => digitsOnly.test(value)) + html.find('input[type=text]').on('change paste keyup', ev => { + let el = ev.currentTarget + let k = el.dataset.key + if (!!k) { + this.mook[k] = el.value + this.setTesting() + } + }) + + html.find('.npc-input-ta').on('change', ev => { + let el = ev.currentTarget + let k = el.dataset.key + if (!!k) { + this.mook[k] = el.value + this.setTesting() + } + }) + + html.find('#npc-input-create').on('click keydown focusout', ev => { + if (ev.type == 'click' || (ev.type == 'keydown' && ev.which == 13)) this.createMook(ev) + else { + ev.preventDefault() + $(html).find('#npc-input-name').focus() + } + }) + html.find('#npc-input-import').on('click keydown', ev => { + if (ev.type == 'click' || (ev.type == 'keydown' && ev.which == 13)) this.importStatBlock(ev) + }) + this.createButton = html.find('#npc-input-create')[0] + + const frame = html[0]?.querySelector('.npc-input') + if (!!frame) + frame.addEventListener('drop', event => { + event.preventDefault() + if (event.originalEvent) event = event.originalEvent + this.applyDrop(JSON.parse(event.dataTransfer.getData('text/plain'))) + }) + } + + applyDrop(data) { + if (data.actorid) { + const actorData = game.actors.get(data.actorid).data + let element = getProperty(actorData, data.key) + let key = data.key.match(/^[^\.]+\.([^\.]+)/)[1] + let tmp = '' + let tmp2 = '' + let notes = element.notes || '' + switch (key) { + case 'skills': + case 'spells': { + tmp += element.name + '-' + element.level + break + } + case 'melee': + case 'ranged': { + if (element.acc) tmp2 += ' acc ' + element.acc + if (element.range) tmp2 += ' range ' + element.range + if (element.rcl) tmp2 += ' rcl ' + element.rcl + if (element.rof) tmp2 += ' rof ' + element.rof + if (element.shots) tmp2 += ' shots ' + element.shots + if (element.st) tmp2 += ' st ' + element.st + if (element.usage) tmp2 += ' usage' + element.usage + if (element.bulk) tmp2 += ' bulk ' + element.bulk + if (element.halfd) tmp2 += ' halfd ' + element.halfd + if (element.max) tmp2 += ' max ' + element.max + if (element.parry) tmp2 += ' parry ' + element.parry + if (element.reach) tmp2 += ' reach ' + element.reach + if (element.block) tmp2 += ' block ' + element.block + tmp += element.name + ' (' + element.level + ') ' + element.damage + break + } + case 'ads': { + key = 'traits' + tmp += element.name + if (element.pageref) tmp += ' Page Ref: ' + element.pageref + break + } + case 'notes': { + tmp += element.notes + notes = '' + break + } + case 'equipment': { + tmp += `${element.name}; ${element.count}; $${element.cost}; ${element.weight} lb` + break + } + } + let orig = this.mook[key] + tmp = orig + (orig ? '\n' : '') + tmp + tmp += tmp2 + if (notes) tmp += '\n# ' + notes.split('\n').join('\n# ').replaceAll(';', ',') + this.mook[key] = tmp + } else { + if (data.otf) { + this.mook.notes += ' [' + data.otf + ']' + } + } + this.setTesting() + this.render(true) + } + + async importStatBlock(ev) { + ev.preventDefault() + let self = this + let b = this.savedStatBlock || '' + let d = new Dialog( + { + title: `Import Stat Block`, + content: await renderTemplate('systems/gurps/templates/import-stat-block.html', { block: b }), + buttons: { + import: { + icon: '', + label: 'Import', + callback: html => { + let ta = html.find('#npc-input-import-ta')[0] + let t = ta.value + if (t.length < 3) t = EX[parseInt(t)] + if (!!t) this.parseStatBlock(t) + self.render(true) + }, + }, + no: { + icon: '', + label: 'Cancel', + }, + }, + default: false, + }, + { + width: 800, + height: 800, + } + ) + d.render(true) + } + + async createMook(ev) { + ev.preventDefault() + if (this.testing) { + let err = this.check() + if (!!err) ui.notifications.warn('Unable to create Mook: ' + err) + else this.setTesting(false) + } else { + let data = { name: this.mook.name, type: 'character' } + let a = await GurpsActor.create(data, { renderSheet: false }) + await this.populate(a) + await a.setFlag('core', 'sheetClass', 'gurps.GurpsActorNpcSheet') + a.sheet.render(true) + } + this.render(true) + } + + async populate(a) { + let m = this.mook + let data = a.system + let att = data.attributes + + // + is a trick to force strings to ints + att.ST.import = +m.st + att.DX.import = +m.dx + att.IQ.import = +m.iq + att.HT.import = +m.ht + att.WILL.import = +m.will + att.PER.import = +m.per + + data.HP.max = +m.hp + data.HP.value = +m.hp + data.FP.max = +m.fp + data.FP.value = +m.fp + + data.basicmove.value = parseInt(m.move) + data.basicspeed.value = m.speed + data.frightcheck = m.will + if (!!m.check) data.frightcheck = m.check // Imported "fright check" + + data.hearing = m.per + data.tastesmell = m.per + data.touch = m.per + data.vision = m.per + if (!!m.parry) data.parry = parseInt(m.parry) + if (!!m.block) data.block = parseInt(m.block) + + let ns = {} + let nt = new Note(m.notes.trim()) + GURPS.put(ns, nt) + + // Since we removed the default hitlocations from template.json, we need to create at least one entry for the mook + // But to maintain a similar look, we will just recreate the humanoid locations. + let hls = {} + for (const [key, value] of Object.entries(HitLocations.hitlocationDictionary.humanoid)) { + let loc = { ...value, ...{ where: key } } + if (key == HitLocations.HitLocation.TORSO) loc.import = m.dr + GURPS.put(hls, loc) + } + + let es = {} + let e = new Encumbrance() + e.level = 0 + e.current = true + e.key = 'enc0' + e.weight = 0 + e.move = parseInt(m.move) + e.dodge = parseInt(m.dodge) + GURPS.put(es, e) + + let melee = {} + m.a_melee.forEach(me => GURPS.put(melee, me)) + + let ranged = {} + m.a_ranged.forEach(r => GURPS.put(ranged, r)) + + let ts = {} + ts.title = m.title + let dt = new Date().toString().split(' ').splice(1, 3).join(' ') + ts.createdon = dt + if (!!m.sm) ts.sizemod = m.sm[0] == '-' ? m.sm : m.sm[0] == '+' ? '' : '+' + m.sm + ts.appearance = m.desc + ts.height = m.height + ts.weight = m.weight + ts.age = m.age + + let skills = {} + m.a_skills.forEach(s => GURPS.put(skills, s)) + + let spells = {} + m.a_spells.forEach(s => GURPS.put(spells, s)) + + let ads = {} + m.a_traits.forEach(a => GURPS.put(ads, a)) + + let eqt = {} + m.a_equipment.forEach(e => GURPS.put(eqt, e)) + + let dmg = m.damage || m.dmg || '' + let thrst = dmg.split('/')[0] + let swng = dmg.split('/')[1] + if (!swng && !!thrst) { + swng = thrst + thrst = '' + } + + let commit = { + 'data.attributes': att, + 'data.HP': data.HP, + 'data.FP': data.FP, + 'data.basicmove': data.basicmove, + 'data.basicspeed': data.basicspeed, + 'data.currentmove': parseInt(m.move), + 'data.currentdodge': parseInt(m.dodge), + 'data.frightcheck': data.frightcheck, + 'data.hearing': data.hearing, + 'data.tastesmell': data.tastesmell, + 'data.touch': data.touch, + 'data.vision': data.vision, + 'data.equippedparry': data.parry, + 'data.parry': data.parry, + 'data.equippedblock': data.block, + 'data.block': data.block, + 'data.notes': ns, + 'data.hitlocations': hls, + 'data.encumbrance': es, + 'data.melee': melee, + 'data.ranged': ranged, + 'data.traits': ts, + 'data.skills': skills, + 'data.spells': spells, + 'data.ads': ads, + 'data.swing': swng, + 'data.thrust': thrst, + 'data.equipment.carried': eqt, + } + + await a.update(commit) + await a.postImport() + console.log('Created Mook:') + console.log(a) + } + + sanit(key) { + this.mook[key] = sanitize(this.mook[key]) + } + + check() { + let e = '' + if (!this.mook.name) e = ', No Name' + this.sanit('melee') + this.sanit('ranged') + this.sanit('traits') + this.sanit('skills') + this.sanit('spells') + this.sanit('equipment') + + if (this.checkTraits()) e += ', Error in Traits' + if (this.checkSkills()) e += ', Error in Skills' + if (this.checkMelee()) e += ', Error in Melee' + if (this.checkRanged()) e += ', Error in Ranged' + if (this.checkSpells()) e += ', Error in Spells' + if (this.checkEquipment()) e += ', Error in Equipment' + return e.substr(2) + } + + // return an array of string representing each line + prep(text, delim) { + var ans + if (!!delim) ans = this.parseDelimLines(text, delim) + else ans = text.split('\n') + return ans.map(e => this.cleanLine(e)).filter(e => e.length > 0) + } + + // Allow () to include delims without breaking line + parseDelimLines(str, delim) { + let arr = [] + let i = 0 + let line = '' + let d = 0 + while (i < str.length) { + let c = str[i++] + if ((c == delim && d == 0) || c == '\n') { + arr.push(line) + line = '' + } else { + line += c + if (c == '(') d++ + if (c == ')') d-- + } + } + if (!!line) arr.push(line) + return arr + } + + checkSkills() { + const m = this.mook + let txt = '' + let arr = [] + this.prep(m.skills, ';').forEach(e => { + if (e.includes(ERR)) return + txt += '\n' + e + if (e.startsWith(COMMENT_CHAR)) { + if (arr.length > 0) this.addToNotes(arr, e.substr(1), ' ') + return + } + const i = e.lastIndexOf('-') + if (i < 1) return (txt += `\n${ERR} missing '-'`) + const n = e.substring(0, i).trim() + const v = e.substr(i + 1).trim() + if (!v) return (txt += `\n${ERR} missing skill level`) + if (isNaN(v)) return (txt += `\n${ERR} "${v}" is not a number`) + arr.push(new Skill(n, v)) + }) + m.skills = txt.substr(1) + m.a_skills = arr + return txt.includes(ERR) + } + + checkSpells() { + const m = this.mook + let txt = '' + let arr = [] + this.prep(m.spells, ';').forEach(e => { + if (e.includes(ERR)) return + txt += '\n' + e + if (e.startsWith(COMMENT_CHAR)) { + if (arr.length > 0) this.addToNotes(arr, e.substr(1), ' ') + return + } + const i = e.lastIndexOf('-') + if (i < 1) return (txt += `\n${ERR} missing '-'`) + const n = e.substring(0, i).trim() + const v = e.substr(i + 1).trim() + if (!v) return (txt += `\n${ERR} missing spell level`) + if (isNaN(v)) return (txt += `\n${ERR} "${v}" is not a number`) + arr.push(new Skill(n, v)) + }) + m.spell = txt.substr(1) + m.a_spells = arr + return txt.includes(ERR) + } + + checkMelee() { + const pats = [ + { regex: '(^usage|^Usage|^mode|^Mode)\\w+', var: 'mode' }, + { regex: '(^parry|^Parry)\\d+', var: 'parry' }, + { regex: '(^reach|^Reach)[\\w,]+', var: 'reach' }, + { regex: '(^st|^ST|^St)\\d+', var: 'st' }, + { regex: '(^block|^Block)\\d+', var: 'block' }, + ] + const m = this.mook + let txt = '' + let arr = [] + this.prep(m.melee).forEach(e => { + if (e.includes(ERR)) return + txt += '\n' + e + if (e.startsWith(COMMENT_CHAR)) { + if (arr.length > 0) this.addToNotes(arr, e.substr(1), ' ') + return + } + var me, remain + let parse = e.replace( + /(.*) ?\((\d+)\) (\d+)d(\d*)([-+]\d+)?([xX\*]\d+)?(\([.\d]+\))?(!)? ?(\w+\+?\+?)(.*)$/g, + '$1~$2~$3~$4~$5~$6~$7~$8~$9~$10' + ) + if (e != parse) { + parse = parse.split('~') + me = new Melee( + parse[0].trim(), + parse[1], + parse[2] + 'd' + parse[3] + parse[4] + parse[5] + parse[6] + parse[7] + ' ' + parse[8] + ) + remain = parse[9].trim() + } else { + parse = e.replace(/(.*) ?\(([ \w]+)\) "([^"]+)" ?(.*)$/g, '$1~$2~$3~$4') + if (e == parse) return (txt += `\n${ERR} unable to find (level) and damage`) + parse = parse.split('~') + me = new Melee(parse[0].trim(), parse[1], parse[2]) + remain = parse[3].trim() + } + if (!!remain) { + let ext = remain.replace(/ +/g, ' ').split(' ') + if (ext.length % 2 != 0) return (txt += `\n${ERR} unable to parse "${remain}"`) + for (let i = 0; i < ext.length; i += 2) { + let s = ext[i] + ext[i + 1] + let found = false + pats.forEach(p => { + if (s.match(new RegExp(p.regex))) { + me[p.var] = ext[i + 1] + found = true + } + }) + if (!found) return (txt += `\n${ERR} unknown pattern "${ext[i]} ${ext[i + 1]}"`) + } + } + arr.push(me) + }) + m.melee = txt.substr(1) + m.a_melee = arr + return txt.includes(ERR) + } + + checkRanged() { + const pats = [ + { regex: '(^acc|^Acc)\\d+', var: 'acc' }, + { regex: '(^rof|^RoF|^Rof)\\d+', var: 'rof' }, + { regex: '(^rcl|^Rcl)\\d+', var: 'rcl' }, + { regex: '(^usage|^Usage|^mode|^Mode)\\w+', var: 'mode' }, + { regex: '(^range|^Range)\\d+(\\/\\d+)?', var: 'range' }, + { regex: '(^shots|^Shots)[\\w\\)\\(]+', var: 'shots' }, + { regex: '(^bulk|^Bulk)[\\w-]+', var: 'bulk' }, + { regex: '(^st|^ST|^St)\\d+', var: 'st' }, + { regex: '^halfd\\d+', var: 'halfd' }, + { regex: '^max\\d+', var: 'max' }, + ] + const m = this.mook + let txt = '' + let arr = [] + this.prep(m.ranged).forEach(e => { + if (e.includes(ERR)) return + txt += '\n' + e + if (e.startsWith(COMMENT_CHAR)) { + if (arr.length > 0) this.addToNotes(arr, e.substr(1), ' ') + return + } + let parse = e.replace( + /(.*) ?\((\d+)\) (\d+)d(\d*)([-+]\d+)?([xX\*]\d+)?(\([.\d]+\))?(!)?:? ?([\w-+]+)(.*)/g, + '$1~$2~$3~$4~$5~$6~$7~$8~$9~$10' + ) + + var r, remain + if (e != parse) { + parse = parse.split('~') + r = new Ranged( + parse[0].trim(), + parse[1], + parse[2] + 'd' + parse[3] + parse[4] + parse[5] + parse[6] + parse[7] + ' ' + parse[8] + ) + remain = parse[9].trim() + } else { + parse = e.replace(/(.*) ?\(([ \w]+)\):? "([^"]+)" ?(.*)$/g, '$1~$2~$3~$4') + if (e == parse) return (txt += `\n${ERR} unable to find (level) and damage`) + parse = parse.split('~') + r = new Ranged(parse[0].trim(), parse[1], parse[2]) + remain = parse[3].trim() + } + if (!!remain) { + let ext = remain.trim().replace(/ +/g, ' ').split(' ') + if (ext.length % 2 != 0) return (txt += `\n${ERR} unable to parse for `) + for (let i = 0; i < ext.length; i += 2) { + let s = ext[i] + ext[i + 1] + let found = false + pats.forEach(p => { + if (s.match(new RegExp(p.regex))) { + r[p.var] = ext[i + 1] + found = true + } + }) + if (!found) return (txt += `\n${ERR} unknown pattern "${ext[i]} ${ext[i + 1]}"`) + } + } + r.checkRange() + arr.push(r) + }) + m.ranged = txt.substr(1) + m.a_ranged = arr + return txt.includes(ERR) + } + + addToNotes(arr, note, delim) { + if (arr.length == 0) return + let n = arr[arr.length - 1].notes + if (!!n) n += delim + note + else n = note + arr[arr.length - 1].notes = n + } + + checkTraits() { + const m = this.mook + let txt = '' + let arr = [] + this.prep(m.traits, ';').forEach(e => { + txt += '\n' + e + if (e.startsWith(COMMENT_CHAR)) { + if (arr.length > 0) this.addToNotes(arr, e.substr(1), '\n') + return + } + arr.push(new Advantage(e)) + }) + m.traits = txt.substr(1) + m.a_traits = arr + return false + } + + checkEquipment() { + const m = this.mook + let txt = '' + let arr = [] + this.prep(m.equipment).forEach(e => { + if (e.includes(ERR)) return + txt += '\n' + e + if (e.startsWith(COMMENT_CHAR)) { + this.addToNotes(arr, e.substr(1), '\n') + return + } + let a = e.split(';') + if (a.length != 4) { + return (txt += `\n${ERR} Expecting ; ; $; lb\n`) + } else { + let eqt = new Equipment(a[0]) + eqt.count = parseInt(a[1]) + eqt.equipped = true + eqt.carried = true + let n = a[2].match(/ *\$ *([\?\d]+)/) + if (n) eqt.cost = n[1] + else { + return (txt += `\n${ERR} Unable to find '$ '\n`) + } + n = a[3].match(/ *([\?\d.]+) * lbs?/) + if (n) eqt.weight = n[1] + else { + return (txt += `\n${ERR} Unable to find ' lb'\n`) + } + Equipment.calc(eqt) + arr.push(eqt) + } + }) + m.equipment = txt.substr(1) + m.a_equipment = arr + return txt.includes(ERR) + } + + preparePastedText(txt) { + txt = sanitize(txt) + txt = txt.replace(/\t/g, '; ') // replace tabs with '; ' + txt = txt.replace(/ +/g, ' ') // remove multiple spaces in a row + txt = txt.replace(/[^ -~\n]+/g, '') // remove remaining non-ascii + return this.cleanLine(txt) // trim and remove leading and trailing periods. + } + + parseStatBlock(txt) { + this.setTesting() + this.statblk = this.preparePastedText(txt) + this.savedStatBlock = this.statblk + console.log(this.statblk) + try { + this.resetMook() + this.checkForNameNotes() + this.parseAttrs() + this.parseAttacks() + this.parseTraits() + this.parseS('Skills', 'Spells', 'skills') + this.parseS('Spells', 'Equipment', 'spells') + this.parseEquipment() + this.parseFinalNotes() + this.parseAttacks(true) + } catch (e) { + console.log(e) + ui.notifications.warn(e) + } + } + + resetMook() { + this.mook = new Mook() + this.mook.name = '' + this.mook.notes = '' + this.mook.melee = '' + this.mook.ranged = '' + this.mook.traits = '' + this.mook.skills = '' + this.mook.spells = '' + this.mook.equipment = '' + } + + gatherAttackLines(attblk, currentline, oldformat) { + // read lines looking for (\d+) and any lines following that do NOT have (\d+) + if (oldformat) return [attblk, currentline, this.nextToken()] + var nextline + ;[attblk, nextline] = this.nextTokenPrim(attblk, '\n', false, true) + while (!!nextline && (nextline.match(/[Ss]hots.*\(\d+\)/) || !nextline.match(/\(\d+\)/))) { + // If the new line doesn't contain a skill level "(\d+)" that isn't part of [Ss]hots assume it is still from the previous attack + currentline += ' ' + nextline + ;[attblk, nextline] = this.nextTokenPrim(attblk, '\n', false, true) + } + return [attblk, currentline, nextline] + } + + findInLine(line, regex) { + let re = new RegExp(regex) + let m = line.match(re) + if (!!m) line = line.replace(re, '') + return [line, m] + } + + parseEquipment() { + if (!this.peek('Equipment')) return + let line = this.nextline() // Skip equipment line + line = this.nextline() + while (!!line) { + var m + let cost = '?' + let weight = '?' + let qty = 1 + let name = '' + ;[line, m] = this.findInLine(line, /\$([\.\d]+)[ ,\.]*/) + if (!!m) cost = m[1] + ;[line, m] = this.findInLine(line, /([\.\d]+) ?lbs?[ ,\.]*/) + if (!!m) weight = m[1] + ;[line, m] = this.findInLine(line, /^ *(\d+)/) + if (!!m) qty = m[1] + ;[line, m] = this.findInLine(line, /^([^(\.])+\((\d+)\)/) + if (!!m) { + qty = m[2] + name = m[1] + } + line = line.replace(/\[[ \W]+/g, '') //clean up runs of non-word chars in brackets + name += this.cleanLine(line) + name = name.replace(';', ',') + this.mook.equipment += name + '; ' + qty + '; $' + cost + '; ' + weight + ' lb\n' + line = this.nextline() + while (line === '') line = this.nextline() + } + } + + parseS(start, end, path) { + this.trim() + if (this.peek(start)) this.nextToken(start) + let goUntil = this.peek(end) + let line = this.nextline() + if (!line) return + line = this.cleanLine(line) + let s = '' + // while (!!line && (line.match(/[^-]+-\d+/) || (!!goUntil && !line.startsWith(end)))) { + while (!!line && line.match(/[^-]+-\d+/) && !line.startsWith(end)) { + s += '\n' + this.cleanLine(line) + line = this.nextToken('\n', false, true) + } + //if (!s) this.appendToNotes(`?? No ${start} matching pattern '${path}-lvl' found`); + s = this.cleanLine(s) + let delim = ';' + if (s.includes(delim)) s = s.replace(/\n/g, ' ') + // If has delims, then remove newlines + else delim = '\n' // No ";", so assume one per line + var l + while (!!s) { + ;[s, l] = this.nextTokenPrim(s, delim, false, true) // Start it off reading the first line + + // Remove skill type, rsl and points Broadsword (A) DX+2 [8]-16 + l = this.cleanLine(l).replace(/ ?\([a-zA-Z]+\) [a-zA-Z]+([-+][0-9]+)? \[ *-?\d+\ *] */g, '') + l = l.replace(/ [SDIH][TXQ][-+]?[0-9]* ?/, ' ') + l = l.replace(/ ?\[ *-?\d+ *\],?\.? ?/g, ' ') + l = this.cleanLine(l) + let m = l.match(/([^-]+ *- *\d+)(.*)/) + if (m) { + this.mook[path] += '\n' + m[1] + if (!!m[2]) this.mook[path] += '\n' + COMMENT_CHAR + m[2] + } else if (!!l) this.mook[path] += '\n' + COMMENT_CHAR + `Unknown ${start} pattern ${l}` + } + if (!!line) this.pushToken(line) + } + + parseTraits() { + let n = this.peekskipto('Advantages/Disadvantages') + n += this.peekskipto('Advantages') + n += this.peekskipto('Traits') + if (!!n) this.appendToNotes(ERR + ' Skipped before Traits: ' + n, '\n') + this.trim() + let trblk = this.nextToken('Skills', 'Spells', true) + if (!trblk) return this.appendToNotes(ERR + " Looking for Traits block, unable to find 'Skills'") // Basically always need "Skills" or "Spells" + let traits = '' + if (trblk.includes(';')) trblk = trblk.replace(/\n/g, ' ') + // if it contains ";", then remove newlines + else { + // no ";", maybe using commas? + if (trblk.split(',').length > 2) trblk = trblk.replace(/,/g, ';') // Assume comma separated + } + trblk = trblk.replace(/disadvantages/gi, ';') + trblk = trblk.replace(/perks/gi, ';') + trblk = trblk.replace(/quirks/gi, ';') + trblk = trblk.replace(/ ?\[-?\d+\*?\],?\.? ?/g, ' ') + this.prep(trblk, ';').forEach(t => { + t = t.replace(/\( *(\d+) *or less *\)/g, '($1)') // Compress some forms of CR rolls + let m = t.match(/(.*)\((\d+)\)/) + if (!!m) traits += `\n[CR: ${m[2]} ${m[1].trim()}]` + // Convert CR roll into OtF + else { + if (t != 'and') traits += '\n' + t + } + }) + this.mook.traits = traits + } + + parseAttacks(oldformat = false) { + const rpats = [ + { regex: ' [Aa]cc *(\\d+) ?,?', var: 'acc' }, + { regex: ' [Rr]o[Ff] *(\\d+) ?,?', var: 'rof' }, + { regex: ' [Rr]cl *(\\d+) ?,?', var: 'rcl' }, + { regex: ' 1\\/2[Dd] *(\\d+) ?,?', var: 'halfd' }, + { regex: ' [Mm]ax *(\\d+) ?,?', var: 'max' }, + { regex: ' [Ss]hots *([\\w\\)\\(]+) ?,?', var: 'shots' }, + { regex: ' [Bb]ulk *([\\w-]+) ?,?', var: 'bulk' }, + { regex: ' [Ss][Tt] *(\\d+) ?,?', var: 'st' }, + { regex: ' ?[Rr]anged,? *with ?', var: '' }, + { regex: ' ?[Rr]anged,?', var: '' }, + { regex: ' ?[Rr]ange ([0-9/]+) *,?', var: 'range' }, + ] + var attblk + if (oldformat) { + attblk = this.statblk + if (!attblk) return + } else { + attblk = this.nextToken('Traits:', 'Advantages/Disadvantages:') // Look for either as start of ads/disads + if (!attblk) { + if (this.peek('Skills:')) attblk = this.nextToken('Skills:', 'junk') + else if (!this.peek('\nWeapons:')) this.mook.melee = COMMENT_CHAR + 'No attacks found' // If Weapons aren't listedt later, show error. + } + } + if (!attblk) return + attblk = this.cleanLine(attblk) + // assume a line is an attack if it contains '(n)' + let line, nextline + ;[attblk, line] = this.nextTokenPrim(attblk, '\n', false, true) // Start it off reading the first line + ;[attblk, line, nextline] = this.gatherAttackLines(attblk, line, oldformat) // collect up any more lines. + + while (!!line) { + save = line + line = this.cleanLine(line) + var name, lvl, dmg, save + ;[line, name] = this.nextTokenPrim(line, '(', false) + if (!name) { + this.mook.melee += `${COMMENT_CHAR}Unrecognized attack: "${save}" ` + ;[attblk, line, nextline] = this.gatherAttackLines(attblk, nextline, oldformat) // collect up any more lines. + continue + } + name = name.trim() + ;[line, lvl] = this.nextTokenPrim(line, ')', '):') + if (!lvl || isNaN(lvl)) { + this.mook.melee += `${COMMENT_CHAR}No level or level is not a number "${save}"` + ;[attblk, line, nextline] = this.gatherAttackLines(attblk, nextline, oldformat) // collect up any more lines. + continue + } + //[line, dmg] = this.nextTokenPrim(line, ".", ",", true); // find up to . or , or end of string + ;[line, dmg] = this.nextTokenV2Prim(line, ['.', ',', ';'], true) + let savedmg1 = dmg + let note = '' + ;[dmg, note] = this.mapDmg(line, dmg) + if (!!dmg && !!note) note = '\n' + COMMENT_CHAR + this.cleanLine(note) + if (!dmg) { + // If not dmg formula, try one more time. + ;[line, dmg] = this.nextTokenPrim(line, '.', ',', true) // find up to . or , or end of string + let savedmg2 = dmg + ;[dmg, note] = this.mapDmg(line, dmg) + if (!dmg) { + line = savedmg1 + ' ' + savedmg2 + ' ' + note // Nope, couldn't find anything, so reset the line + note = '' + } else if (!!note) note = '\n' + COMMENT_CHAR + this.cleanLine(note) + } + let regex = /.*[Rr]each (?[^\.]+)/g + let result = regex.exec(line) + let extra = '' + let final = '' + if (!!result?.groups?.reach) { + // If it has Reach, it is definitely melee + extra = ' reach ' + result.groups.reach.replace(/ /g, '') + line = this.cleanLine(line.replace(/[Rr]each [^\.]+/, '')) + if (!!line) note += '\n' + COMMENT_CHAR + line + final = '\n' + name + ' (' + lvl + ') ' + dmg + extra + this.mook.melee += final + note + } else { + let ranged = [] + rpats.forEach(p => { + let re = new RegExp(p.regex) + let match = line.match(re) + if (!!match) { + line = line.replace(re, '').trim() + if (!!match[1]) ranged.push(p.var + ' ' + match[1]) + } + }) + if (ranged.length > 0) { + extra = ranged.join(' ') + final = '\n' + name + ' (' + lvl + ') ' + dmg + ' ' + extra + if (!!line) note += '\n' + COMMENT_CHAR + line + this.mook.ranged += final + note + } else { + // but it may not have either, in which case we treat as melee + final = '\n' + name + ' (' + lvl + ') ' + dmg + extra + if (!!line) note += '\n' + COMMENT_CHAR + line + this.mook.melee += final + note + } + } + ;[attblk, line, nextline] = this.gatherAttackLines(attblk, nextline, oldformat) // collect up any more lines. + } + } + + mapDmg(line, dmg) { + if (!dmg) return ['', ''] + dmg = dmg.trim().toLowerCase() + let p = GURPS.DamageTables.parseDmg(dmg) + if (p == dmg) return ['', line] + let a = p.split('~') + let roll = a[0] + 'd' + a[1] + a[2] + a[3] + a[4] + let types = a[5].trim().split(' ') + let m = GURPS.DamageTables.translate(types[0]) + if (!m) return ['', `Unrecognized damage type "${types[0]}" for "${line}"`] + return [roll + ' ' + m, types.slice(1).join(' ')] + } + + appendToNotes(t, suffix = ' ') { + this.mook.notes += t + suffix + } + + // If the first line does not contain a ":" (or "ST "), then it probably is not the start of the stat block + checkForNameNotes() { + this.trim() + let line = this.nextline() + if (!line) return + let curregex = /ST.*\d+.*HP.*\d+/ + let oldregex = /ST.*\d+.*DX.*\d+/ + let forumregex = /^ST:? +\d?\d/ + + let first = true + while (!!line && !line.match(curregex) && !line.match(oldregex) && !line.match(forumregex)) { + // Assume we haven't started the stat block yet + // if the first line has 2 or fewer spaces, assume that it is a name. Just guessing here + if (first && (line.match(/ /g) || []).length < 3) this.mook.name = line + else this.appendToNotes(line) + line = this.nextline() + first = false + } + if (!!this.mook.notes) this.mook.notes += '\n\n' + this.pushToken(line) + } + + parseFinalNotes() { + let postProcessWeapons = this.stealahead('\nWeapons:') + this.trim() + let t = this.nextToken() + if (!!t) { + if (t == 'Class:') { + this.appendToNotes(t + ' ' + this.nextline(), '\n') + return this.parseFinalNotes() + } + if (t == 'Notes:') this.appendToNotes(this.statblk) + else this.appendToNotes(t + ' ' + this.statblk) + } + this.mook.notes = this.mook.notes.trim() + this.statblk = postProcessWeapons + } + + pushToken(t) { + this.statblk = t + '\n' + this.statblk + } + + // This is the exhaustive test to see if we want to parse it as an attribute. + // Note: we may want to parse some things just so we can safely skip over them + isAttribute(a) { + if (!a) return false + if (a == 'Traits:' || a == 'Advantages/Disadvantages:') return false + if (a.match(/\w+:/) || !!GURPS.attributepaths[a]) return true + if (a.match(/\[\d+\],?/)) return true // points costs [20] accept this to parse, to skip over it + return POSSIBLE_ATTRIBUTE_KEYS.hasOwnProperty(a.toLowerCase()) + } + + // We know we accept it, however, it may be 'junk' that we are just trying to skip over. + getAttrib(t) { + if (t.match(/\[\d+\],?/)) return false // points costs [20] // Don't count this as "any" + let a = t.replace(/[^A-Za-z]/g, '') // remove anything but letters + // Special case is attributes are listed as "basic speed" or "fright check" + if (!!POSSIBLE_ATTRIBUTE_KEYS[a.toLowerCase()]) return '' + return a + } + + storeAttrib(attrib, value) { + let val = value.replace(/[,;\.]$/g, '') // try to clean up the value by stripping crap off the end + attrib + .toLowerCase() + .split('/') + .forEach(a => (this.mook[a] = val)) // handles cases like "Parry/Block: 9" + console.log('Storing attribute: ' + attrib + '=' + val) + } + + parseAttrs() { + var attr, val, saved + this.trim() + let unknowns = [] + unknowns.push([]) // Each line will have its own array of unparsed keys + let line = this.nextline() + saved = line + let any = false + do { + ;[line, attr] = this.nextPrim(line) + if (!line && !attr) { + if (!any) break + line = this.nextline() + saved = line + ;[line, attr] = this.nextPrim(line) + any = false + unknowns.push([]) + } + while (this.isAttribute(attr)) { + // Something we recognize + let goodattr = this.getAttrib(attr) // An actual attribute + if (goodattr !== false) any = true // Anything except "false" means "keep looking" + if (!!goodattr) { + ;[line, val] = this.nextPrim(line) + if (!line && !val) { + line = this.nextline() + ;[line, val] = this.nextPrim(line) + } + this.storeAttrib(goodattr, val) + } + ;[line, attr] = this.nextPrim(line) + if (!line && !attr) { + if (!any) break + line = this.nextline() + saved = line + ;[line, attr] = this.nextPrim(line) + any = false + unknowns.push([]) + } + } + if ('Traits:' == attr) break + if (!!attr) unknowns.slice(-1)[0].push(attr) + } while (true) + unknowns.pop() // The last line didn't have any attributes, so not really errors. + this.pushToken(saved) // We didn't use this line, so put it back + let a = unknowns + .map(a => a.join(' ')) + .join(' ') + .trim() // collapse all unknowns into a string + if (!!a) { + // we parsed some things that did not work. + this.appendToNotes(ERR + ' ' + a, '\n') + } + } + + stealahead(str) { + let i = this.statblk.indexOf(str) + if (i < 0) return '' + let s = this.statblk.substr(i + str.length + 1) + this.statblk = this.statblk.substring(0, 1) + return s + } + + peek(str) { + return this.statblk.includes(str) + } + + peekskipto(str) { + return this.peek(str) ? this.nextToken(str, false) : '' + } + + trim() { + this.statblk = this.cleanLine(this.statblk) + } + + nextToken(d1 = ' ', d2 = '\n', all = false) { + let [s, t] = this.nextTokenPrim(this.statblk, d1, d2, all) + this.statblk = s + return t + } + + nextline() { + let [s, t] = this.nextlinePrim(this.statblk) + this.statblk = s + return t + } + + next(delim = ' ') { + let [s, t] = this.nextPrim(this.statblk, delim) + this.statblk = s + return t + } + + nextlinePrim(txt) { + return this.nextPrim(txt, '\n') + } + + nextPrim(txt, delim = ' ') { + return this.nextTokenPrim(txt, delim, false, true) + } + + nextTokenPrim(str, d1 = ' ', d2 = '\n', all = false) { + // d2 must be equal or longer in length than d1 ")" and "):" + if (!str) return [str, undefined] + let i = str.indexOf(d1) + let j = str.indexOf(d2) + if (i >= 0 && j >= 0) { + if (j <= i) { + d1 = d2 // + i = j // Crappy hack to be able to search for 2 delims + } + let t = str.substring(0, i) + return [str.substr(i + d1.length).trim(), t] + } + if (i >= 0) { + let t = str.substring(0, i) + return [str.substr(i + d1.length).trim(), t] + } + if (j >= 0) { + let t = str.substring(0, j) + return [str.substr(j + d2.length).trim(), t] + } + return all ? ['', str] : [str, undefined] + } + + nextTokenV2Prim(str, arr, all = false) { + if (!str) return [str, undefined] + let best = Number.MAX_SAFE_INTEGER + let bestIndex = -1 + for (let index = 0; index < arr.length; index++) { + let d = arr[index] + let cur = str.indexOf(d) + if (cur >= 0 && cur < best) { + best = cur + bestIndex = index + } + } + if (bestIndex >= 0) { + let t = str.substring(0, best) + return [str.substr(best + arr[bestIndex].length).trim(), t] + } else { + return all ? ['', str] : [str, undefined] + } + } + + cleanLine(line) { + let start = line + if (!line) return line + let pat = '*,.:' // things that just clutter up the line + if (pat.includes(line[0])) line = line.substr(1) + if (pat.includes(line[line.length - 1])) line = line.substring(0, line.length - 1) + line = line.trim() + return start == line ? line : this.cleanLine(line) + } } export class NpcInputDefaultEditor extends NpcInput { - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['npc-input', 'sheet', 'actor'], - id: 'npc-input', - template: 'systems/gurps/templates/actor/npc-input.hbs', - resizable: true, - minimizable: false, - width: 800, - height: 650, - title: 'Mook Generator Defaults Editor', - closeOnSubmit: true, - }) - } - - getData(options) { - let data = super.getData(options) - data.displaybuttons = false - return data - } - - close(options) { - super.close(options) - game.settings.set(settings.SYSTEM_NAME, settings.SETTING_MOOK_DEFAULT, this.mook) - } - - setTesting(t = true) { - // Do nothing in the Defaults Editor - } + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['npc-input', 'sheet', 'actor'], + id: 'npc-input', + template: 'systems/gurps/templates/actor/npc-input.hbs', + resizable: true, + minimizable: false, + width: 800, + height: 650, + title: 'Mook Generator Defaults Editor', + closeOnSubmit: true, + }) + } + + getData(options) { + let data = super.getData(options) + data.displaybuttons = false + return data + } + + close(options) { + super.close(options) + game.settings.set(settings.SYSTEM_NAME, settings.SETTING_MOOK_DEFAULT, this.mook) + } + + setTesting(t = true) { + // Do nothing in the Defaults Editor + } } class Mook { - constructor() { - this.name = '' - this.title = 'bad guy' - this.desc = 'appearence' - this.st = 10 - this.dx = 10 - this.iq = 10 - this.ht = 10 - this.dodge = 0 - this.parry = 0 - this.hp = 10 - this.will = 10 - this.per = 10 - this.fp = 10 - this.speed = 0 - this.move = 0 - this.sm = '+0' - this.dr = 0 - this.notes = `Notes. May include On-the-Fly formulas + constructor() { + this.name = '' + this.title = 'bad guy' + this.desc = 'appearence' + this.st = 10 + this.dx = 10 + this.iq = 10 + this.ht = 10 + this.dodge = 0 + this.parry = 0 + this.hp = 10 + this.will = 10 + this.per = 10 + this.fp = 10 + this.speed = 0 + this.move = 0 + this.sm = '+0' + this.dr = 0 + this.notes = `Notes. May include On-the-Fly formulas [IQ to remember something] [Dodge] [+2 Blessed]` - this.traits = `Ugly [-4 from everyone] + this.traits = `Ugly [-4 from everyone] High Pain Threshold; [CR: 12 Gambling]` - this.skills = `Barter-14 + this.skills = `Barter-14 Search-13;Lockpicking-11` - this.melee = `Punch (12) 1d-2 cr + this.melee = `Punch (12) 1d-2 cr Kick (11) 1d cr` - this.ranged = `Slingshot (9) 1d-3 imp acc 2` - this.spells = 'Create Water-11' - this.equipment = 'Item; 1; $15; 0.5 lbs' - } + this.ranged = `Slingshot (9) 1d-3 imp acc 2` + this.spells = 'Create Water-11' + this.equipment = 'Item; 1; $15; 0.5 lbs' + } } let EX = [ - // 0 + // 0 - `Goblin + `Goblin Goblins are the smallest of the goblin-kin, and therefore spend their days being bullied by orcs and tossed around by angry hobgoblins. This has led to a cowardly disposition, @@ -1285,8 +1285,8 @@ giving orders. Shamans have IQ 10+ and Power Investiture spells such as Deathtouch! Goblins are easily intimidated, so they�fll negotiate if cornered . . . and backstab as soon as they aren�ft.`, - // 1 - `ST: 13 HP: 13 Speed: 5.00 + // 1 + `ST: 13 HP: 13 Speed: 5.00 DX: 11 Will: 9 Move: 5 IQ: 9 Per: 12 HT: 11 FP: 11 SM: 0 @@ -1315,8 +1315,8 @@ Spellcasters are always shamans with IQ 10+, Power Investiture 1-3, and druidic spells. Wildmen will negotiate with anyone who hasn't violated one of their taboos. `, - // 2 - `STEALTH GOLEM + // 2 + `STEALTH GOLEM ST 21; DX 16*; IQ 11; HT 14. Will 13; Per 16*; Speed 9.00; Dodge 12*; Move 9*. SM 0; 300 lbs. @@ -1335,9 +1335,9 @@ Stealth-18; Tactics-12; Traps-14. * During daylight hours, the stealth golem has DX 10, Per 10, Dodge 8, Move 5, and loses these specific advantages. Modify all skills accordingly.`, - // 3 + // 3 - `ST: 11 HP: 11 Speed: 8.00 + `ST: 11 HP: 11 Speed: 8.00 DX: 13 Will: 8 Move: 8 IQ: 8 Per: 8 HT: 12 FP: N/A SM: 0 @@ -1374,8 +1374,8 @@ maybe even armor fit for a skeleton! Though not truly evil, the magic animating it usually is. No undead servitor will negotiate or reveal useful information. `, - // 4 - `Fire Elemental + // 4 + `Fire Elemental A mobile flame with a roughly humanoid shape. In the wild, these beings lurk in or near volcanoes and lava, but they sometimes come out to play in (or set) wildfires. A fire elemental @@ -1414,8 +1414,8 @@ they vanish instantly if wounded to -1HP, and can also be dismissed by the Banish spell (Spells, pp. 59-60). `, - // 5 - `ST: 23 HP: 23 Speed: 6.50 + // 5 + `ST: 23 HP: 23 Speed: 6.50 DX: 14 Will: 13 Move: 10 (Air Move 13) IQ: 5 Per: 12 HT: 12 FP: 12 SM: +1 @@ -1442,8 +1442,8 @@ Voice; Quadruped; Temperature Tolerance 2 (Heat); Wild Animal. Skills: Brawling-16; Innate Attack (Breath)-16. `, - // 6 - `Zombie + // 6 + `Zombie Rotting corpses reanimated by dark necromancy . not by strange contagion or other gnatural h causes . are by far the most common undead servitors. There isn ft a lich (p. 40) out @@ -1497,8 +1497,8 @@ they receive a major wound from fire. Not truly evil, though the magic animating it usually is. No undead servitor will negotiate or reveal useful information. `, - // 7 - `ST: 18 HP: 18 Speed: 6.00 + // 7 + `ST: 18 HP: 18 Speed: 6.00 DX: 12 Will: 10 Move: 9 IQ: 10 Per: 11 HT: 12 FP: 12 SM: 0 @@ -1527,8 +1527,8 @@ but if they can�ft, they�fll negotiate. Truly evil. `, - // 8 - `ST: 15 HP: 15 Speed: 8.00 + // 8 + `ST: 15 HP: 15 Speed: 8.00 DX: 18 Will: 15 Move: 8 IQ: 12 Per: 15 Weight: 100.150 lbs. HT: 13 FP: 13 SM: 0 @@ -1577,8 +1577,8 @@ out of combat, double it with a running start, quadruple it for both. `, - // 9 - `Guards + // 9 + `Guards ST 10; DX 10; IQ 9; HT 11. Damage 1d-2/1d; BL 20 lbs.; HP 10; Will 9; Per 10; FP 11. Basic Speed 5.25; Basic Move 5; Dodge 8; Parry 9 (Brawling). @@ -1587,8 +1587,8 @@ Advantages/Disadvantages: Cantonese (Native). Skills: Brawling-13; Guns/TL8 (Pistol)-12; Guns/TL8 (SMG)-12; Knife-14. `, - // 10 - `The Mate + // 10 + `The Mate ST 11; DX 12; IQ 10; HT 11. Damage 1d-1/1d+1; BL 24 lbs.; HP 11; Will 10; Per 12; FP 11. Basic Speed 5.75; Basic Move 5; Dodge 9; Parry 10 (Knife). @@ -1598,8 +1598,8 @@ Advantages/Disadvantages: Acute Hearing 2; Cantonese Skills: Brawling-14; Guns/TL8 (Pistol)-14; Guns/TL8 (SMG)-14; Knife-15. `, - // 11 - `Notwithstanding the page name, this guy is not a Nazi party member. He's just a German soldier in 1939. His main motivation is the same as that of many young men like him in his day, patriotism. + // 11 + `Notwithstanding the page name, this guy is not a Nazi party member. He's just a German soldier in 1939. His main motivation is the same as that of many young men like him in his day, patriotism. This is a very generic German infantryman, with no special personal Traits, save the most common ones. His Attributes and Skills reflect the fact that in 1939, the average soldier was pretty thoroughly trained and physically conditioned; also, he belongs to a first-class division, in which most privates would be young, healthy and bright for their age. Height: 5'11", weight: 155 lbs., age: 21. @@ -1619,8 +1619,8 @@ Kick (10): 1d cr, Reach C,1 Traits: Addiction (Tobacco); Duty (Heer; 15 or less; Extremely Hazardous); Fanaticism (Patriotism); Fit. Skills: Armoury/TL6 (Small Arms)-10; Brawling-12; Camouflage-11; Climibing-10; Explosives/TL6 (Demolition)-10; First Aid/TL6-11; Gambling-10; Gunner/TL6 (Machine Gun)-11; Guns/TL6 (Light Machine Gun)-12; Guns/TL6 (Rifle)-13; Hiking-10; Jumping-11; Navigation/TL6 (Land)-10; Savoir-Faire (Military)-11; Scrounging-11; Soldier/TL6-12; Spear-10; Stealth-10; Survival (Woodlands)-10; Swimming-11; Teamster (Equines)-10; Throwing-10; Traps/TL6-10. `, - // 12 - `Crystal Rat-Men + // 12 + `Crystal Rat-Men The process which changes giant rats into rat-men imbues some with strange abilities. Crystal rat-men have long crystalline claws which cut through armor easily, and their skin @@ -1648,8 +1648,8 @@ but are capable of innate ranged attacks. However, crystal rat-men are liable to be overtaken by rage when they fight, so their battles tend to end spectacularly, one way or another.`, - // 13 - `Tsorvano + // 13 + `Tsorvano 273 points Tsorvano is an ex-brigand who met Halmaro years ago at sword�s point, while Halmaro was an aggressive diff --git a/lib/parse-css-file/parse-css-file.js b/lib/parse-css-file/parse-css-file.js index 5704c6907..d8eb85163 100644 --- a/lib/parse-css-file/parse-css-file.js +++ b/lib/parse-css-file/parse-css-file.js @@ -1,422 +1,422 @@ -/** - * Parsing CSS in JavaScript / jQuery - * https://jsfiddle.net/developit/vzkckrw4/ - * Rewrote and made file eslint compatible... - * ~Stevil - */ - -import { objectToArray } from '../../lib/utilities.js' -import { cssColors, cssSettings, url } from '../../module/color-character-sheet/color-character-sheet-settings.js' - -export async function readCSSfile() { - const theSettings = objectToArray(cssSettings.findCSS) - const resp = await fetch(url) - const cssData = await resp.text() - const parsedCSS = await parseCss(cssData) - for (const CSSfind of theSettings) { - cssColors.push(await findSelectorRule(parsedCSS, CSSfind.selector, CSSfind.rule)) - } -} - -export async function parseCss(text) { - const tokenizer = /([\s\S]+?)\{([\s\S]*?)\}/gi - const rules = [] - let rule - let token - let style - text = text.replace(/\/\*[\s\S]*?\*\//g, '') - while ((token = tokenizer.exec(text))) { - style = await parseRule(token[2].trim()) - style.cssText = await stringifyRule(style) - rule = { - // eslint-disable-next-line no-useless-escape - selectorText: token[1].trim().replace(/\s*\,\s*/, ', '), - style: style, - } - rule.cssText = rule.selectorText + ' { ' + rule.style.cssText + ' }' - rules.push(rule) - } - return rules -} - -export async function parseRule(css) { - // eslint-disable-next-line no-useless-escape - const tokenizer = /\s*([a-z\-]+)\s*:\s*((?:[^;]*url\(.*?\)[^;]*|[^;]*)*)\s*(?:;|$)/gi - const obj = {} - let token - while ((token = tokenizer.exec(css))) { - obj[token[1].toLowerCase()] = token[2] - } - return obj -} - -export async function stringifyRule(style) { - let text = '' - const keys = Object.keys(style).sort() - for (let i = 0; i < keys.length; i++) { - text += ' ' + keys[i] + ': ' + style[keys[i]] + ';' - } - return text.substring(1) -} - -export async function findSelectorRule(parsedCSS, selector, rule) { - let selectorText = '' - for (const parsedCSSLine of parsedCSS) { - selectorText = parsedCSSLine.selectorText - const findSelectorText = selectorText.split(', ') - const matchSelector = selector.toLowerCase() - if (jQuery.inArray(matchSelector, findSelectorText) >= 0) { - return processFoundRule(rule, parsedCSSLine.style[rule.toLowerCase()]) - } - } - return false -} - -export async function processFoundRule(rule, foundRule) { - if (rule.toLowerCase().indexOf('color') >= 0) { - // looking for color data - const colornames = await getColorArr('names') - const colorhexs = await getColorArr('hexs') - if (foundRule.toLowerCase().indexOf('rgb(') >= 0) { - // convert rgb to hex - foundRule = foundRule.replace(/;/g, '') - foundRule = foundRule.replace(/!important/g, '') - foundRule = foundRule.trim() - const result = foundRule.match(/\d+/g) - foundRule = '#' + await hex(result[0]) + await hex(result[1]) + await hex(result[2]) - } else if (foundRule.toLowerCase().indexOf('#') >= 0) { - // conver hex to only hex - foundRule = foundRule.replace(/;/g, '') - foundRule = foundRule.replace(/!important/g, '') - foundRule = foundRule.trim() - } else { - foundRule = foundRule.replace(/;/g, '') - foundRule = foundRule.replace(/!important/g, '') - foundRule = foundRule.trim() - let result = null - $.each(colornames, function (index, value) { - if (result == null && value.toLowerCase() === foundRule.toLowerCase()) { - result = index - return false - } - }) - foundRule = '#' + colorhexs[result] - } - } - if (foundRule === undefined) { - foundRule = false - } - return foundRule -} - -export async function hex(x) { - const digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'] - return isNaN(x) ? '00' : digits[(x - (x % 16)) / 16] + digits[x % 16] -} - -export async function getColorArr(x) { - if (x === 'names') { - return [ - 'AliceBlue', - 'AntiqueWhite', - 'Aqua', - 'Aquamarine', - 'Azure', - 'Beige', - 'Bisque', - 'Black', - 'BlanchedAlmond', - 'Blue', - 'BlueViolet', - 'Brown', - 'BurlyWood', - 'CadetBlue', - 'Chartreuse', - 'Chocolate', - 'Coral', - 'CornflowerBlue', - 'Cornsilk', - 'Crimson', - 'Cyan', - 'DarkBlue', - 'DarkCyan', - 'DarkGoldenRod', - 'DarkGray', - 'DarkGrey', - 'DarkGreen', - 'DarkKhaki', - 'DarkMagenta', - 'DarkOliveGreen', - 'DarkOrange', - 'DarkOrchid', - 'DarkRed', - 'DarkSalmon', - 'DarkSeaGreen', - 'DarkSlateBlue', - 'DarkSlateGray', - 'DarkSlateGrey', - 'DarkTurquoise', - 'DarkViolet', - 'DeepPink', - 'DeepSkyBlue', - 'DimGray', - 'DimGrey', - 'DodgerBlue', - 'FireBrick', - 'FloralWhite', - 'ForestGreen', - 'Fuchsia', - 'Gainsboro', - 'GhostWhite', - 'Gold', - 'GoldenRod', - 'Gray', - 'Grey', - 'Green', - 'GreenYellow', - 'HoneyDew', - 'HotPink', - 'IndianRed', - 'Indigo', - 'Ivory', - 'Khaki', - 'Lavender', - 'LavenderBlush', - 'LawnGreen', - 'LemonChiffon', - 'LightBlue', - 'LightCoral', - 'LightCyan', - 'LightGoldenRodYellow', - 'LightGray', - 'LightGrey', - 'LightGreen', - 'LightPink', - 'LightSalmon', - 'LightSeaGreen', - 'LightSkyBlue', - 'LightSlateGray', - 'LightSlateGrey', - 'LightSteelBlue', - 'LightYellow', - 'Lime', - 'LimeGreen', - 'Linen', - 'Magenta', - 'Maroon', - 'MediumAquaMarine', - 'MediumBlue', - 'MediumOrchid', - 'MediumPurple', - 'MediumSeaGreen', - 'MediumSlateBlue', - 'MediumSpringGreen', - 'MediumTurquoise', - 'MediumVioletRed', - 'MidnightBlue', - 'MintCream', - 'MistyRose', - 'Moccasin', - 'NavajoWhite', - 'Navy', - 'OldLace', - 'Olive', - 'OliveDrab', - 'Orange', - 'OrangeRed', - 'Orchid', - 'PaleGoldenRod', - 'PaleGreen', - 'PaleTurquoise', - 'PaleVioletRed', - 'PapayaWhip', - 'PeachPuff', - 'Peru', - 'Pink', - 'Plum', - 'PowderBlue', - 'Purple', - 'RebeccaPurple', - 'Red', - 'RosyBrown', - 'RoyalBlue', - 'SaddleBrown', - 'Salmon', - 'SandyBrown', - 'SeaGreen', - 'SeaShell', - 'Sienna', - 'Silver', - 'SkyBlue', - 'SlateBlue', - 'SlateGray', - 'SlateGrey', - 'Snow', - 'SpringGreen', - 'SteelBlue', - 'Tan', - 'Teal', - 'Thistle', - 'Tomato', - 'Turquoise', - 'Violet', - 'Wheat', - 'White', - 'WhiteSmoke', - 'Yellow', - 'YellowGreen', - ] - } - if (x === 'hexs') { - return [ - 'f0f8ff', - 'faebd7', - '00ffff', - '7fffd4', - 'f0ffff', - 'f5f5dc', - 'ffe4c4', - '000000', - 'ffebcd', - '0000ff', - '8a2be2', - 'a52a2a', - 'deb887', - '5f9ea0', - '7fff00', - 'd2691e', - 'ff7f50', - '6495ed', - 'fff8dc', - 'dc143c', - '00ffff', - '00008b', - '008b8b', - 'b8860b', - 'a9a9a9', - 'a9a9a9', - '006400', - 'bdb76b', - '8b008b', - '556b2f', - 'ff8c00', - '9932cc', - '8b0000', - 'e9967a', - '8fbc8f', - '483d8b', - '2f4f4f', - '2f4f4f', - '00ced1', - '9400d3', - 'ff1493', - '00bfff', - '696969', - '696969', - '1e90ff', - 'b22222', - 'fffaf0', - '228b22', - 'ff00ff', - 'dcdcdc', - 'f8f8ff', - 'ffd700', - 'daa520', - '808080', - '808080', - '008000', - 'adff2f', - 'f0fff0', - 'ff69b4', - 'cd5c5c', - '4b0082', - 'fffff0', - 'f0e68c', - 'e6e6fa', - 'fff0f5', - '7cfc00', - 'fffacd', - 'add8e6', - 'f08080', - 'e0ffff', - 'fafad2', - 'd3d3d3', - 'd3d3d3', - '90ee90', - 'ffb6c1', - 'ffa07a', - '20b2aa', - '87cefa', - '778899', - '778899', - 'b0c4de', - 'ffffe0', - '00ff00', - '32cd32', - 'faf0e6', - 'ff00ff', - '800000', - '66cdaa', - '0000cd', - 'ba55d3', - '9370db', - '3cb371', - '7b68ee', - '00fa9a', - '48d1cc', - 'c71585', - '191970', - 'f5fffa', - 'ffe4e1', - 'ffe4b5', - 'ffdead', - '000080', - 'fdf5e6', - '808000', - '6b8e23', - 'ffa500', - 'ff4500', - 'da70d6', - 'eee8aa', - '98fb98', - 'afeeee', - 'db7093', - 'ffefd5', - 'ffdab9', - 'cd853f', - 'ffc0cb', - 'dda0dd', - 'b0e0e6', - '800080', - '663399', - 'ff0000', - 'bc8f8f', - '4169e1', - '8b4513', - 'fa8072', - 'f4a460', - '2e8b57', - 'fff5ee', - 'a0522d', - 'c0c0c0', - '87ceeb', - '6a5acd', - '708090', - '708090', - 'fffafa', - '00ff7f', - '4682b4', - 'd2b48c', - '008080', - 'd8bfd8', - 'ff6347', - '40e0d0', - 'ee82ee', - 'f5deb3', - 'ffffff', - 'f5f5f5', - 'ffff00', - '9acd32', - ] - } -} +/** + * Parsing CSS in JavaScript / jQuery + * https://jsfiddle.net/developit/vzkckrw4/ + * Rewrote and made file eslint compatible... + * ~Stevil + */ + +import { objectToArray } from '../../lib/utilities.js' +import { cssColors, cssSettings, url } from '../../module/color-character-sheet/color-character-sheet-settings.js' + +export async function readCSSfile() { + const theSettings = objectToArray(cssSettings.findCSS) + const resp = await fetch(url) + const cssData = await resp.text() + const parsedCSS = await parseCss(cssData) + for (const CSSfind of theSettings) { + cssColors.push(await findSelectorRule(parsedCSS, CSSfind.selector, CSSfind.rule)) + } +} + +export async function parseCss(text) { + const tokenizer = /([\s\S]+?)\{([\s\S]*?)\}/gi + const rules = [] + let rule + let token + let style + text = text.replace(/\/\*[\s\S]*?\*\//g, '') + while ((token = tokenizer.exec(text))) { + style = await parseRule(token[2].trim()) + style.cssText = await stringifyRule(style) + rule = { + // eslint-disable-next-line no-useless-escape + selectorText: token[1].trim().replace(/\s*\,\s*/, ', '), + style: style, + } + rule.cssText = rule.selectorText + ' { ' + rule.style.cssText + ' }' + rules.push(rule) + } + return rules +} + +export async function parseRule(css) { + // eslint-disable-next-line no-useless-escape + const tokenizer = /\s*([a-z\-]+)\s*:\s*((?:[^;]*url\(.*?\)[^;]*|[^;]*)*)\s*(?:;|$)/gi + const obj = {} + let token + while ((token = tokenizer.exec(css))) { + obj[token[1].toLowerCase()] = token[2] + } + return obj +} + +export async function stringifyRule(style) { + let text = '' + const keys = Object.keys(style).sort() + for (let i = 0; i < keys.length; i++) { + text += ' ' + keys[i] + ': ' + style[keys[i]] + ';' + } + return text.substring(1) +} + +export async function findSelectorRule(parsedCSS, selector, rule) { + let selectorText = '' + for (const parsedCSSLine of parsedCSS) { + selectorText = parsedCSSLine.selectorText + const findSelectorText = selectorText.split(', ') + const matchSelector = selector.toLowerCase() + if (jQuery.inArray(matchSelector, findSelectorText) >= 0) { + return processFoundRule(rule, parsedCSSLine.style[rule.toLowerCase()]) + } + } + return false +} + +export async function processFoundRule(rule, foundRule) { + if (rule.toLowerCase().indexOf('color') >= 0) { + // looking for color data + const colornames = await getColorArr('names') + const colorhexs = await getColorArr('hexs') + if (foundRule.toLowerCase().indexOf('rgb(') >= 0) { + // convert rgb to hex + foundRule = foundRule.replace(/;/g, '') + foundRule = foundRule.replace(/!important/g, '') + foundRule = foundRule.trim() + const result = foundRule.match(/\d+/g) + foundRule = '#' + (await hex(result[0])) + (await hex(result[1])) + (await hex(result[2])) + } else if (foundRule.toLowerCase().indexOf('#') >= 0) { + // conver hex to only hex + foundRule = foundRule.replace(/;/g, '') + foundRule = foundRule.replace(/!important/g, '') + foundRule = foundRule.trim() + } else { + foundRule = foundRule.replace(/;/g, '') + foundRule = foundRule.replace(/!important/g, '') + foundRule = foundRule.trim() + let result = null + $.each(colornames, function (index, value) { + if (result == null && value.toLowerCase() === foundRule.toLowerCase()) { + result = index + return false + } + }) + foundRule = '#' + colorhexs[result] + } + } + if (foundRule === undefined) { + foundRule = false + } + return foundRule +} + +export async function hex(x) { + const digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'] + return isNaN(x) ? '00' : digits[(x - (x % 16)) / 16] + digits[x % 16] +} + +export async function getColorArr(x) { + if (x === 'names') { + return [ + 'AliceBlue', + 'AntiqueWhite', + 'Aqua', + 'Aquamarine', + 'Azure', + 'Beige', + 'Bisque', + 'Black', + 'BlanchedAlmond', + 'Blue', + 'BlueViolet', + 'Brown', + 'BurlyWood', + 'CadetBlue', + 'Chartreuse', + 'Chocolate', + 'Coral', + 'CornflowerBlue', + 'Cornsilk', + 'Crimson', + 'Cyan', + 'DarkBlue', + 'DarkCyan', + 'DarkGoldenRod', + 'DarkGray', + 'DarkGrey', + 'DarkGreen', + 'DarkKhaki', + 'DarkMagenta', + 'DarkOliveGreen', + 'DarkOrange', + 'DarkOrchid', + 'DarkRed', + 'DarkSalmon', + 'DarkSeaGreen', + 'DarkSlateBlue', + 'DarkSlateGray', + 'DarkSlateGrey', + 'DarkTurquoise', + 'DarkViolet', + 'DeepPink', + 'DeepSkyBlue', + 'DimGray', + 'DimGrey', + 'DodgerBlue', + 'FireBrick', + 'FloralWhite', + 'ForestGreen', + 'Fuchsia', + 'Gainsboro', + 'GhostWhite', + 'Gold', + 'GoldenRod', + 'Gray', + 'Grey', + 'Green', + 'GreenYellow', + 'HoneyDew', + 'HotPink', + 'IndianRed', + 'Indigo', + 'Ivory', + 'Khaki', + 'Lavender', + 'LavenderBlush', + 'LawnGreen', + 'LemonChiffon', + 'LightBlue', + 'LightCoral', + 'LightCyan', + 'LightGoldenRodYellow', + 'LightGray', + 'LightGrey', + 'LightGreen', + 'LightPink', + 'LightSalmon', + 'LightSeaGreen', + 'LightSkyBlue', + 'LightSlateGray', + 'LightSlateGrey', + 'LightSteelBlue', + 'LightYellow', + 'Lime', + 'LimeGreen', + 'Linen', + 'Magenta', + 'Maroon', + 'MediumAquaMarine', + 'MediumBlue', + 'MediumOrchid', + 'MediumPurple', + 'MediumSeaGreen', + 'MediumSlateBlue', + 'MediumSpringGreen', + 'MediumTurquoise', + 'MediumVioletRed', + 'MidnightBlue', + 'MintCream', + 'MistyRose', + 'Moccasin', + 'NavajoWhite', + 'Navy', + 'OldLace', + 'Olive', + 'OliveDrab', + 'Orange', + 'OrangeRed', + 'Orchid', + 'PaleGoldenRod', + 'PaleGreen', + 'PaleTurquoise', + 'PaleVioletRed', + 'PapayaWhip', + 'PeachPuff', + 'Peru', + 'Pink', + 'Plum', + 'PowderBlue', + 'Purple', + 'RebeccaPurple', + 'Red', + 'RosyBrown', + 'RoyalBlue', + 'SaddleBrown', + 'Salmon', + 'SandyBrown', + 'SeaGreen', + 'SeaShell', + 'Sienna', + 'Silver', + 'SkyBlue', + 'SlateBlue', + 'SlateGray', + 'SlateGrey', + 'Snow', + 'SpringGreen', + 'SteelBlue', + 'Tan', + 'Teal', + 'Thistle', + 'Tomato', + 'Turquoise', + 'Violet', + 'Wheat', + 'White', + 'WhiteSmoke', + 'Yellow', + 'YellowGreen', + ] + } + if (x === 'hexs') { + return [ + 'f0f8ff', + 'faebd7', + '00ffff', + '7fffd4', + 'f0ffff', + 'f5f5dc', + 'ffe4c4', + '000000', + 'ffebcd', + '0000ff', + '8a2be2', + 'a52a2a', + 'deb887', + '5f9ea0', + '7fff00', + 'd2691e', + 'ff7f50', + '6495ed', + 'fff8dc', + 'dc143c', + '00ffff', + '00008b', + '008b8b', + 'b8860b', + 'a9a9a9', + 'a9a9a9', + '006400', + 'bdb76b', + '8b008b', + '556b2f', + 'ff8c00', + '9932cc', + '8b0000', + 'e9967a', + '8fbc8f', + '483d8b', + '2f4f4f', + '2f4f4f', + '00ced1', + '9400d3', + 'ff1493', + '00bfff', + '696969', + '696969', + '1e90ff', + 'b22222', + 'fffaf0', + '228b22', + 'ff00ff', + 'dcdcdc', + 'f8f8ff', + 'ffd700', + 'daa520', + '808080', + '808080', + '008000', + 'adff2f', + 'f0fff0', + 'ff69b4', + 'cd5c5c', + '4b0082', + 'fffff0', + 'f0e68c', + 'e6e6fa', + 'fff0f5', + '7cfc00', + 'fffacd', + 'add8e6', + 'f08080', + 'e0ffff', + 'fafad2', + 'd3d3d3', + 'd3d3d3', + '90ee90', + 'ffb6c1', + 'ffa07a', + '20b2aa', + '87cefa', + '778899', + '778899', + 'b0c4de', + 'ffffe0', + '00ff00', + '32cd32', + 'faf0e6', + 'ff00ff', + '800000', + '66cdaa', + '0000cd', + 'ba55d3', + '9370db', + '3cb371', + '7b68ee', + '00fa9a', + '48d1cc', + 'c71585', + '191970', + 'f5fffa', + 'ffe4e1', + 'ffe4b5', + 'ffdead', + '000080', + 'fdf5e6', + '808000', + '6b8e23', + 'ffa500', + 'ff4500', + 'da70d6', + 'eee8aa', + '98fb98', + 'afeeee', + 'db7093', + 'ffefd5', + 'ffdab9', + 'cd853f', + 'ffc0cb', + 'dda0dd', + 'b0e0e6', + '800080', + '663399', + 'ff0000', + 'bc8f8f', + '4169e1', + '8b4513', + 'fa8072', + 'f4a460', + '2e8b57', + 'fff5ee', + 'a0522d', + 'c0c0c0', + '87ceeb', + '6a5acd', + '708090', + '708090', + 'fffafa', + '00ff7f', + '4682b4', + 'd2b48c', + '008080', + 'd8bfd8', + 'ff6347', + '40e0d0', + 'ee82ee', + 'f5deb3', + 'ffffff', + 'f5f5f5', + 'ffff00', + '9acd32', + ] + } +} diff --git a/lib/parselink.js b/lib/parselink.js index 0f5c3b625..85e67efd8 100755 --- a/lib/parselink.js +++ b/lib/parselink.js @@ -48,7 +48,7 @@ export function parseForRollOrDamage(str, overridetxt) { // Straight roll 4d, 2d-1, etc. Is "damage" if it includes a damage type. Allows "!" suffix to indicate minimum of 1. // Supports: 2d+1x3(5), 4dX2(0.5), etc // Straight roll, no damage type. 4d, 2d-1, etc. Allows "!" suffix to indicate minimum of 1. - str = str.toString() // convert possible array to single string + str = str.toString() // convert possible array to single string let a = str.match(DAMAGE_REGEX) if (!!a) { const D = a.groups.D || '' // Can now support non-variable damage '2 cut' or '2x3(1) imp' @@ -660,14 +660,14 @@ export function parselink(str, htmldesc, clrdmods = false) { action: action, } } - + m = str.match(/https?:\/\//i) if (!!m) { let lbl = !!overridetxt ? overridetxt : str let action = { orig: str, label: lbl, - type: 'href', + type: 'href', } return { action: action, text: `${lbl}` } } diff --git a/lib/ranges.js b/lib/ranges.js index b93ced68e..fd6b761e6 100755 --- a/lib/ranges.js +++ b/lib/ranges.js @@ -34,135 +34,123 @@ import RepeatingSequenceConverter from '../module/utilities/repeating-sequence.j modifier text for the modifier bucket. */ export default class GURPSRange { - constructor() { - this.setup() - this.ranges = basicSetRanges - this._buildModifiers() - } - - setup() { - let self = this + constructor() { + this.setup() + this.ranges = basicSetRanges + this._buildModifiers() + } - // Set the range to whatever the setting is upon opening the world. - // Have to do it this way because the order of "init Hooks" is indeterminate. - // This will run after all init hooks have been processed. - Hooks.once('ready', async function() { - self.update() + setup() { + let self = this - // Replace the range ruler with our own. - Ruler.prototype._getSegmentLabel = (segmentDistance, totalDistance, isTotal) => { - const units = canvas.scene.data.gridUnits - let dist = (d, u) => { - return `${Math.round(d * 100) / 100} ${u}` - } - let yards = self.convert_to_yards(totalDistance, units) - let label = dist(segmentDistance, units) - let mod = self.yardsToSpeedRangePenalty(yards) - GURPS.ModifierBucket.setTempRangeMod(mod) - if (isTotal && segmentDistance !== totalDistance) { - label += ` [${dist(totalDistance, units)}]` - } - return label + ` (${mod})` - } + // Set the range to whatever the setting is upon opening the world. + // Have to do it this way because the order of "init Hooks" is indeterminate. + // This will run after all init hooks have been processed. + Hooks.once('ready', async function () { + self.update() - if (!Ruler.prototype._endMeasurementOrig) { - // Only monkey patch if we haven't done so before. - Ruler.prototype._endMeasurementOrig = Ruler.prototype._endMeasurement - Ruler.prototype._endMeasurement = function() { - let addRangeMod = !this.draggedEntity // Will be false is using DragRuler and it was movement - this._endMeasurementOrig() - if (addRangeMod) GURPS.ModifierBucket.addTempRangeMod() - } - } - }) - } + // Replace the range ruler with our own. + Ruler.prototype._getSegmentLabel = (segmentDistance, totalDistance, isTotal) => { + const units = canvas.scene.data.gridUnits + let dist = (d, u) => { + return `${Math.round(d * 100) / 100} ${u}` + } + let yards = self.convert_to_yards(totalDistance, units) + let label = dist(segmentDistance, units) + let mod = self.yardsToSpeedRangePenalty(yards) + GURPS.ModifierBucket.setTempRangeMod(mod) + if (isTotal && segmentDistance !== totalDistance) { + label += ` [${dist(totalDistance, units)}]` + } + return label + ` (${mod})` + } - convert_to_yards(numeric, Unit) { - //console.log("entering convert to yards") - let meter = 0; - let unit = Unit.toLowerCase(); - //console.log("Assigning Unit") - //console.log("Unit in convert_to_yards is " + unit); - if (unit == "meters" || unit == "meter" || unit == "m") - meter = numeric - else if (unit == "millimeters" || unit == "millimeter" || unit == "mm") - meter = numeric / 1000 - else if (unit == "kilometers" || unit == "kilometer" || unit == "km") - meter = numeric * 1000 - else if (unit == "miles" || unit == "mile" || unit == "mi") - meter = numeric / 0.00062137 - else if (unit == "inches" || unit == "inch" || unit == "in") - meter = numeric / 39.370 - else if (unit == "centimeters" || unit == "centimeter" || unit == "cm") - meter = numeric / 100 - else if (unit == "feet" || unit == "foot" || unit == "ft") - meter = numeric / 3.2808 - else if (unit == "yards" || unit == "yard" || unit == "yd" || unit == "y") - meter = numeric / (3.2808 / 3) - else if (unit == "lightyears" || unit == "lightyear" || unit == "ly") - meter = numeric * 9460730472580800 - else if (unit == "astronomical units" || unit == "astronomical unit" || unit == "au") - meter = numeric * 149597870700 - else if (unit == "parsecs" || unit == "parsec" || unit == "pc") - meter = numeric * 30856776376340068 - return meter * 1.0936 - } + if (!Ruler.prototype._endMeasurementOrig) { + // Only monkey patch if we haven't done so before. + Ruler.prototype._endMeasurementOrig = Ruler.prototype._endMeasurement + Ruler.prototype._endMeasurement = function () { + let addRangeMod = !this.draggedEntity // Will be false is using DragRuler and it was movement + this._endMeasurementOrig() + if (addRangeMod) GURPS.ModifierBucket.addTempRangeMod() + } + } + }) + } + convert_to_yards(numeric, Unit) { + //console.log("entering convert to yards") + let meter = 0 + let unit = Unit.toLowerCase() + //console.log("Assigning Unit") + //console.log("Unit in convert_to_yards is " + unit); + if (unit == 'meters' || unit == 'meter' || unit == 'm') meter = numeric + else if (unit == 'millimeters' || unit == 'millimeter' || unit == 'mm') meter = numeric / 1000 + else if (unit == 'kilometers' || unit == 'kilometer' || unit == 'km') meter = numeric * 1000 + else if (unit == 'miles' || unit == 'mile' || unit == 'mi') meter = numeric / 0.00062137 + else if (unit == 'inches' || unit == 'inch' || unit == 'in') meter = numeric / 39.37 + else if (unit == 'centimeters' || unit == 'centimeter' || unit == 'cm') meter = numeric / 100 + else if (unit == 'feet' || unit == 'foot' || unit == 'ft') meter = numeric / 3.2808 + else if (unit == 'yards' || unit == 'yard' || unit == 'yd' || unit == 'y') meter = numeric / (3.2808 / 3) + else if (unit == 'lightyears' || unit == 'lightyear' || unit == 'ly') meter = numeric * 9460730472580800 + else if (unit == 'astronomical units' || unit == 'astronomical unit' || unit == 'au') meter = numeric * 149597870700 + else if (unit == 'parsecs' || unit == 'parsec' || unit == 'pc') meter = numeric * 30856776376340068 + return meter * 1.0936 + } - yardsToSpeedRangePenalty(yards) { - let currentValue = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_RANGE_STRATEGY) - if (currentValue == 'Standard') { - return SSRT.getModifier(yards) - } else { - for (let range of this.ranges) { - if (typeof range.max === 'string') - // Handles last distance being "500+" - return range.penalty - if (yards <= range.max) return range.penalty - } - } - } + yardsToSpeedRangePenalty(yards) { + let currentValue = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_RANGE_STRATEGY) + if (currentValue == 'Standard') { + return SSRT.getModifier(yards) + } else { + for (let range of this.ranges) { + if (typeof range.max === 'string') + // Handles last distance being "500+" + return range.penalty + if (yards <= range.max) return range.penalty + } + } + } - _buildModifiers() { - /** @type {import('../module/modifier-bucket/bucket-app.js').Modifier[]} */ - let m = [] - this.ranges.forEach(band => { - if (band.penalty != 0) GURPS.ModifierBucket.addModifier(band.penalty, band.moddesc, m) - }) - this.modifiers = m.map(e => e.mod + ' ' + e.desc) - } + _buildModifiers() { + /** @type {import('../module/modifier-bucket/bucket-app.js').Modifier[]} */ + let m = [] + this.ranges.forEach(band => { + if (band.penalty != 0) GURPS.ModifierBucket.addModifier(band.penalty, band.moddesc, m) + }) + this.modifiers = m.map(e => e.mod + ' ' + e.desc) + } - async update() { - let currentValue = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_RANGE_STRATEGY) - console.log(currentValue) + async update() { + let currentValue = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_RANGE_STRATEGY) + console.log(currentValue) - switch (currentValue) { - case 'Standard': { - this.ranges = basicSetRanges - break - } - case 'TenPenalties': { - this.ranges = penaltiesPerTenRanges - break - } - default: { - this.ranges = monsterHunter2Ranges - break - } - } + switch (currentValue) { + case 'Standard': { + this.ranges = basicSetRanges + break + } + case 'TenPenalties': { + this.ranges = penaltiesPerTenRanges + break + } + default: { + this.ranges = monsterHunter2Ranges + break + } + } - this._buildModifiers() + this._buildModifiers() - // update modifier bucket - if (!!GURPS.ModifierBucket) GURPS.ModifierBucket.refresh() + // update modifier bucket + if (!!GURPS.ModifierBucket) GURPS.ModifierBucket.refresh() - // FYI update all actors - for (const actor of game.actors.contents) { - if (actor.permission >= CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER) - // Return true if the current game user has observer or owner rights to an actor - await actor.update({ ranges: this.ranges }) - } - } + // FYI update all actors + for (const actor of game.actors.contents) { + if (actor.permission >= CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER) + // Return true if the current game user has observer or owner rights to an actor + await actor.update({ ranges: this.ranges }) + } + } } // Must be kept in order... checking range vs Max. If >Max, go to next entry. @@ -173,36 +161,36 @@ export default class GURPSRange { } */ const monsterHunter2Ranges = [ - { - moddesc: 'Close range (5 yds)', - max: 5, - penalty: 0, - description: 'Can touch or strike foe', - }, - { - moddesc: 'Short range (20 yds)', - max: 20, - penalty: -3, - description: 'Can talk to foe; pistol or muscle-powered missile range', - }, - { - moddesc: 'Medium range (100 yds)', - max: 100, - penalty: -7, - description: 'Can only shout to foe; shotgun or SMG range', - }, - { - moddesc: 'Long range (500 yds)', - max: 500, - penalty: -11, - description: 'Opponent out of earshot; rifle range', - }, - { - moddesc: 'Extreme range (500+ yds)', - max: '500+', // Finaly entry. We will check for "is string" to assume infinite - penalty: -15, - desc: 'Rival difficult to even see; sniper range', - }, + { + moddesc: 'Close range (5 yds)', + max: 5, + penalty: 0, + description: 'Can touch or strike foe', + }, + { + moddesc: 'Short range (20 yds)', + max: 20, + penalty: -3, + description: 'Can talk to foe; pistol or muscle-powered missile range', + }, + { + moddesc: 'Medium range (100 yds)', + max: 100, + penalty: -7, + description: 'Can only shout to foe; shotgun or SMG range', + }, + { + moddesc: 'Long range (500 yds)', + max: 500, + penalty: -11, + description: 'Opponent out of earshot; rifle range', + }, + { + moddesc: 'Extreme range (500+ yds)', + max: '500+', // Finaly entry. We will check for "is string" to assume infinite + penalty: -15, + desc: 'Rival difficult to even see; sniper range', + }, ] // Must be kept in order... checking range vs Max. If >Max, go to next entry. @@ -210,79 +198,79 @@ const basicSetRanges = [] // Yes, I should be able to do this programatically... but my brain hurts right now, so there. const r = [ - 2, - 0, - 3, - -1, - 5, - -2, - 7, - -3, - 10, - -4, - 15, - -5, - 20, - -6, - 30, - -7, - 50, - -8, - 70, - -9, - 100, - -10, - 150, - -11, - 200, - -12, - 300, - -13, - 500, - -14, - '500+', - -15, + 2, + 0, + 3, + -1, + 5, + -2, + 7, + -3, + 10, + -4, + 15, + -5, + 20, + -6, + 30, + -7, + 50, + -8, + 70, + -9, + 100, + -10, + 150, + -11, + 200, + -12, + 300, + -13, + 500, + -14, + '500+', + -15, ] for (let i = 0; i < r.length; i = i + 2) { - let d = { - moddesc: `for range/speed ${r[i]} yds`, - max: r[i], - penalty: r[i + 1], - desc: `${r[i]} yds`, - } - basicSetRanges.push(d) + let d = { + moddesc: `for range/speed ${r[i]} yds`, + max: r[i], + penalty: r[i + 1], + desc: `${r[i]} yds`, + } + basicSetRanges.push(d) } const penaltiesPerTenRanges = [] for (let i = 0; i < 50; i++) { - penaltiesPerTenRanges.push({ - moddesc: `for range/speed ${(i + 1) * 10} meters`, - max: (i + 1) * 10, - penalty: -i, - desc: `${(i + 1) * 10} m`, - }) + penaltiesPerTenRanges.push({ + moddesc: `for range/speed ${(i + 1) * 10} meters`, + max: (i + 1) * 10, + penalty: -i, + desc: `${(i + 1) * 10} m`, + }) } class SizeAndSpeedRangeTable { - constructor() { - this._table = new RepeatingSequenceConverter([2, 3, 5, 7, 10, 15]) - } + constructor() { + this._table = new RepeatingSequenceConverter([2, 3, 5, 7, 10, 15]) + } - // pass in distance in yards, get back modifier - getModifier(yards) { - return -this._table.valueToIndex(yards) - } + // pass in distance in yards, get back modifier + getModifier(yards) { + return -this._table.valueToIndex(yards) + } - // pass in modifier, get distance in yards - getDistance(modifier) { - return this._table.indexToValue(modifier) - } + // pass in modifier, get distance in yards + getDistance(modifier) { + return this._table.indexToValue(modifier) + } - // pass in distance in yards, get back the furthest distance that has the same modifier - ceil(yards) { - return this._table.ceil(measure) - } + // pass in distance in yards, get back the furthest distance that has the same modifier + ceil(yards) { + return this._table.ceil(measure) + } } const SSRT = new SizeAndSpeedRangeTable() diff --git a/lib/semver.js b/lib/semver.js index bd3a8549e..2ce034399 100755 --- a/lib/semver.js +++ b/lib/semver.js @@ -1,46 +1,45 @@ export class SemanticVersion { - static re = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + static re = + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ constructor() { - this.major = 0; - this.minor = 0; - this.patch = 0; - this.preRelease = ""; - this.buildMetaData = ""; + this.major = 0 + this.minor = 0 + this.patch = 0 + this.preRelease = '' + this.buildMetaData = '' } static fromString(str) { if (str.match(this.re)) { - let result = new this(); - result.major = parseInt(RegExp.$1); - result.minor = parseInt(RegExp.$2); - result.patch = parseInt(RegExp.$3); - result.preRelease = RegExp.$4 || ""; - result.buildMetaData = RegExp.$5 || ""; - return result; + let result = new this() + result.major = parseInt(RegExp.$1) + result.minor = parseInt(RegExp.$2) + result.patch = parseInt(RegExp.$3) + result.preRelease = RegExp.$4 || '' + result.buildMetaData = RegExp.$5 || '' + return result } - return null; + return null } toString() { - return `${this.major}.${this.minor}.${this.patch}`; + return `${this.major}.${this.minor}.${this.patch}` } isHigherThan(otherVersion) { - if (this.major > otherVersion.major) return true; - if (this.major === otherVersion.major && this.minor > otherVersion.minor) return true; - if (this.major === otherVersion.major - && this.minor === otherVersion.minor - && this.patch > otherVersion.patch) return true; - return false; + if (this.major > otherVersion.major) return true + if (this.major === otherVersion.major && this.minor > otherVersion.minor) return true + if (this.major === otherVersion.major && this.minor === otherVersion.minor && this.patch > otherVersion.patch) + return true + return false } isLowerThan(otherVersion) { - if (this.major < otherVersion.major) return true; - if (this.major === otherVersion.major && this.minor < otherVersion.minor) return true; - if (this.major === otherVersion.major - && this.minor === otherVersion.minor - && this.patch < otherVersion.patch) return true; - return false; + if (this.major < otherVersion.major) return true + if (this.major === otherVersion.major && this.minor < otherVersion.minor) return true + if (this.major === otherVersion.major && this.minor === otherVersion.minor && this.patch < otherVersion.patch) + return true + return false } } diff --git a/lib/utilities.js b/lib/utilities.js index 81d7c1a03..fa9e1b3bb 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -566,7 +566,7 @@ export function makeElementDraggable(element, type, cssClass, payload, dragImage } export function arrayBuffertoBase64(buffer) { -/* var binary = '' + /* var binary = '' var bytes = new Uint8Array(buffer) var len = bytes.byteLength for (var i = 0; i < len; i++) { @@ -581,9 +581,9 @@ export function arrayBuffertoBase64(buffer) { export function quotedAttackName(item) { let q = '"' let n = item.name - if (n.includes(q)) q ="'" + if (n.includes(q)) q = "'" if (n.includes(' ')) n = q + n + q - if (!!item.mode) n = n + ' (' + item.mode + ')' + if (!!item.mode) n = n + ' (' + item.mode + ')' return n } @@ -592,16 +592,19 @@ export function requestFpHp(resp) { let a = game.canvas.tokens.get(tuple[1]).actor let o = !!tuple[0] ? game.users.get(tuple[0]) : game.user if (o.isSelf && a.isOwner) { - setTimeout(() => - Dialog.confirm({ - title: `${resp.actorname}`, - content: i18n_f('GURPS.chatWantsToExecute', { command: resp.command, name: a.name }), - yes: y => { - let old = GURPS.LastActor - GURPS.SetLastActor(a) - GURPS.executeOTF(resp.command).then(p => GURPS.SetLastActor(old)) - }, - }), 50) + setTimeout( + () => + Dialog.confirm({ + title: `${resp.actorname}`, + content: i18n_f('GURPS.chatWantsToExecute', { command: resp.command, name: a.name }), + yes: y => { + let old = GURPS.LastActor + GURPS.SetLastActor(a) + GURPS.executeOTF(resp.command).then(p => GURPS.SetLastActor(old)) + }, + }), + 50 + ) } }) } diff --git a/lib/xregexp-all.js b/lib/xregexp-all.js index 5fa45dbb0..8a4d3de29 100644 --- a/lib/xregexp-all.js +++ b/lib/xregexp-all.js @@ -1,7236 +1,8916 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.XRegExp = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i - * Steven Levithan (c) 2012-present MIT License - */ -var _default = function _default(XRegExp) { - var REGEX_DATA = 'xregexp'; - var subParts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*\]/g; - var parts = XRegExp.union([/\({{([\w$]+)}}\)|{{([\w$]+)}}/, subParts], 'g', { - conjunction: 'or' - }); - /** - * Strips a leading `^` and trailing unescaped `$`, if both are present. - * - * @private - * @param {String} pattern Pattern to process. - * @returns {String} Pattern with edge anchors removed. - */ - - function deanchor(pattern) { - // Allow any number of empty noncapturing groups before/after anchors, because regexes - // built/generated by XRegExp sometimes include them - var leadingAnchor = /^(?:\(\?:\))*\^/; - var trailingAnchor = /\$(?:\(\?:\))*$/; - - if (leadingAnchor.test(pattern) && trailingAnchor.test(pattern) && // Ensure that the trailing `$` isn't escaped - trailingAnchor.test(pattern.replace(/\\[\s\S]/g, ''))) { - return pattern.replace(leadingAnchor, '').replace(trailingAnchor, ''); +;(function (f) { + if (typeof exports === 'object' && typeof module !== 'undefined') { + module.exports = f() + } else if (typeof define === 'function' && define.amd) { + define([], f) + } else { + var g + if (typeof window !== 'undefined') { + g = window + } else if (typeof global !== 'undefined') { + g = global + } else if (typeof self !== 'undefined') { + g = self + } else { + g = this } - - return pattern; - } - /** - * Converts the provided value to an XRegExp. Native RegExp flags are not preserved. - * - * @private - * @param {String|RegExp} value Value to convert. - * @param {Boolean} [addFlagX] Whether to apply the `x` flag in cases when `value` is not - * already a regex generated by XRegExp - * @returns {RegExp} XRegExp object with XRegExp syntax applied. - */ - - - function asXRegExp(value, addFlagX) { - var flags = addFlagX ? 'x' : ''; - return XRegExp.isRegExp(value) ? value[REGEX_DATA] && value[REGEX_DATA].captureNames ? // Don't recompile, to preserve capture names - value : // Recompile as XRegExp - XRegExp(value.source, flags) : // Compile string as XRegExp - XRegExp(value, flags); - } - - function interpolate(substitution) { - return substitution instanceof RegExp ? substitution : XRegExp.escape(substitution); - } - - function reduceToSubpatternsObject(subpatterns, interpolated, subpatternIndex) { - subpatterns["subpattern".concat(subpatternIndex)] = interpolated; - return subpatterns; + g.XRegExp = f() } - - function embedSubpatternAfter(raw, subpatternIndex, rawLiterals) { - var hasSubpattern = subpatternIndex < rawLiterals.length - 1; - return raw + (hasSubpattern ? "{{subpattern".concat(subpatternIndex, "}}") : ''); - } - /** - * Provides tagged template literals that create regexes with XRegExp syntax and flags. The - * provided pattern is handled as a raw string, so backslashes don't need to be escaped. - * - * Interpolation of strings and regexes shares the features of `XRegExp.build`. Interpolated - * patterns are treated as atomic units when quantified, interpolated strings have their special - * characters escaped, a leading `^` and trailing unescaped `$` are stripped from interpolated - * regexes if both are present, and any backreferences within an interpolated regex are - * rewritten to work within the overall pattern. - * - * @memberOf XRegExp - * @param {String} [flags] Any combination of XRegExp flags. - * @returns {Function} Handler for template literals that construct regexes with XRegExp syntax. - * @example - * - * XRegExp.tag()`\b\w+\b`.test('word'); // -> true - * - * const hours = /1[0-2]|0?[1-9]/; - * const minutes = /(?[0-5][0-9])/; - * const time = XRegExp.tag('x')`\b ${hours} : ${minutes} \b`; - * time.test('10:59'); // -> true - * XRegExp.exec('10:59', time).groups.minutes; // -> '59' - * - * const backref1 = /(a)\1/; - * const backref2 = /(b)\1/; - * XRegExp.tag()`${backref1}${backref2}`.test('aabb'); // -> true - */ - - - XRegExp.tag = function (flags) { - return function (literals) { - var _context, _context2; - - for (var _len = arguments.length, substitutions = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - substitutions[_key - 1] = arguments[_key]; - } - - var subpatterns = (0, _reduce["default"])(_context = (0, _map["default"])(substitutions).call(substitutions, interpolate)).call(_context, reduceToSubpatternsObject, {}); - var pattern = (0, _map["default"])(_context2 = literals.raw).call(_context2, embedSubpatternAfter).join(''); - return XRegExp.build(pattern, subpatterns, flags); - }; - }; - /** - * Builds regexes using named subpatterns, for readability and pattern reuse. Backreferences in - * the outer pattern and provided subpatterns are automatically renumbered to work correctly. - * Native flags used by provided subpatterns are ignored in favor of the `flags` argument. - * - * @memberOf XRegExp - * @param {String} pattern XRegExp pattern using `{{name}}` for embedded subpatterns. Allows - * `({{name}})` as shorthand for `(?{{name}})`. Patterns cannot be embedded within - * character classes. - * @param {Object} subs Lookup object for named subpatterns. Values can be strings or regexes. A - * leading `^` and trailing unescaped `$` are stripped from subpatterns, if both are present. - * @param {String} [flags] Any combination of XRegExp flags. - * @returns {RegExp} Regex with interpolated subpatterns. - * @example - * - * const time = XRegExp.build('(?x)^ {{hours}} ({{minutes}}) $', { - * hours: XRegExp.build('{{h12}} : | {{h24}}', { - * h12: /1[0-2]|0?[1-9]/, - * h24: /2[0-3]|[01][0-9]/ - * }, 'x'), - * minutes: /^[0-5][0-9]$/ - * }); - * time.test('10:59'); // -> true - * XRegExp.exec('10:59', time).groups.minutes; // -> '59' - */ - - - XRegExp.build = function (pattern, subs, flags) { - flags = flags || ''; // Used with `asXRegExp` calls for `pattern` and subpatterns in `subs`, to work around how - // some browsers convert `RegExp('\n')` to a regex that contains the literal characters `\` - // and `n`. See more details at . - - var addFlagX = (0, _indexOf["default"])(flags).call(flags, 'x') !== -1; - var inlineFlags = /^\(\?([\w$]+)\)/.exec(pattern); // Add flags within a leading mode modifier to the overall pattern's flags - - if (inlineFlags) { - flags = XRegExp._clipDuplicates(flags + inlineFlags[1]); - } - - var data = {}; - - for (var p in subs) { - if (subs.hasOwnProperty(p)) { - // Passing to XRegExp enables extended syntax and ensures independent validity, - // lest an unescaped `(`, `)`, `[`, or trailing `\` breaks the `(?:)` wrapper. For - // subpatterns provided as native regexes, it dies on octals and adds the property - // used to hold extended regex instance data, for simplicity. - var sub = asXRegExp(subs[p], addFlagX); - data[p] = { - // Deanchoring allows embedding independently useful anchored regexes. If you - // really need to keep your anchors, double them (i.e., `^^...$$`). - pattern: deanchor(sub.source), - names: sub[REGEX_DATA].captureNames || [] - }; - } - } // Passing to XRegExp dies on octals and ensures the outer pattern is independently valid; - // helps keep this simple. Named captures will be put back. - - - var patternAsRegex = asXRegExp(pattern, addFlagX); // 'Caps' is short for 'captures' - - var numCaps = 0; - var numPriorCaps; - var numOuterCaps = 0; - var outerCapsMap = [0]; - var outerCapNames = patternAsRegex[REGEX_DATA].captureNames || []; - var output = patternAsRegex.source.replace(parts, function ($0, $1, $2, $3, $4) { - var subName = $1 || $2; - var capName; - var intro; - var localCapIndex; // Named subpattern - - if (subName) { - var _context3; - - if (!data.hasOwnProperty(subName)) { - throw new ReferenceError("Undefined property ".concat($0)); - } // Named subpattern was wrapped in a capturing group - - - if ($1) { - capName = outerCapNames[numOuterCaps]; - outerCapsMap[++numOuterCaps] = ++numCaps; // If it's a named group, preserve the name. Otherwise, use the subpattern name - // as the capture name - - intro = "(?<".concat(capName || subName, ">"); - } else { - intro = '(?:'; - } - - numPriorCaps = numCaps; - var rewrittenSubpattern = data[subName].pattern.replace(subParts, function (match, paren, backref) { - // Capturing group - if (paren) { - capName = data[subName].names[numCaps - numPriorCaps]; - ++numCaps; // If the current capture has a name, preserve the name - - if (capName) { - return "(?<".concat(capName, ">"); - } // Backreference - - } else if (backref) { - localCapIndex = +backref - 1; // Rewrite the backreference - - return data[subName].names[localCapIndex] ? // Need to preserve the backreference name in case using flag `n` - "\\k<".concat(data[subName].names[localCapIndex], ">") : "\\".concat(+backref + numPriorCaps); +})(function () { + var define, module, exports + return (function () { + function r(e, n, t) { + function o(i, f) { + if (!n[i]) { + if (!e[i]) { + var c = 'function' == typeof require && require + if (!f && c) return c(i, !0) + if (u) return u(i, !0) + var a = new Error("Cannot find module '" + i + "'") + throw ((a.code = 'MODULE_NOT_FOUND'), a) } - - return match; - }); - return (0, _concat["default"])(_context3 = "".concat(intro)).call(_context3, rewrittenSubpattern, ")"); - } // Capturing group - - - if ($3) { - capName = outerCapNames[numOuterCaps]; - outerCapsMap[++numOuterCaps] = ++numCaps; // If the current capture has a name, preserve the name - - if (capName) { - return "(?<".concat(capName, ">"); - } // Backreference - - } else if ($4) { - localCapIndex = +$4 - 1; // Rewrite the backreference - - return outerCapNames[localCapIndex] ? // Need to preserve the backreference name in case using flag `n` - "\\k<".concat(outerCapNames[localCapIndex], ">") : "\\".concat(outerCapsMap[+$4]); - } - - return $0; - }); - return XRegExp(output, flags); - }; -}; - -exports["default"] = _default; -module.exports = exports.default; -},{"@babel/runtime-corejs3/core-js-stable/instance/concat":11,"@babel/runtime-corejs3/core-js-stable/instance/index-of":14,"@babel/runtime-corejs3/core-js-stable/instance/map":15,"@babel/runtime-corejs3/core-js-stable/instance/reduce":16,"@babel/runtime-corejs3/core-js-stable/object/define-property":20,"@babel/runtime-corejs3/helpers/interopRequireDefault":30}],2:[function(require,module,exports){ -"use strict"; - -var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); - -var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); - -_Object$defineProperty(exports, "__esModule", { - value: true -}); - -exports["default"] = void 0; - -var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of")); - -var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); - -var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); - -/*! - * XRegExp.matchRecursive 5.1.0 - * - * Steven Levithan (c) 2009-present MIT License - */ -var _default = function _default(XRegExp) { - /** - * Returns a match detail object composed of the provided values. - * - * @private - */ - function row(name, value, start, end) { - return { - name: name, - value: value, - start: start, - end: end - }; - } - /** - * Returns an array of match strings between outermost left and right delimiters, or an array of - * objects with detailed match parts and position data. By default, an error is thrown if - * delimiters are unbalanced within the subject string. - * - * @memberOf XRegExp - * @param {String} str String to search. - * @param {String} left Left delimiter as an XRegExp pattern. - * @param {String} right Right delimiter as an XRegExp pattern. - * @param {String} [flags] Any combination of XRegExp flags, used for the left and right delimiters. - * @param {Object} [options] Options object with optional properties: - * - `valueNames` {Array} Providing `valueNames` changes the return value from an array of - * matched strings to an array of objects that provide the value and start/end positions - * for the matched strings as well as the matched delimiters and unmatched string segments. - * To use this extended information mode, provide an array of 4 strings that name the parts - * to be returned: - * 1. String segments outside of (before, between, and after) matches. - * 2. Matched outermost left delimiters. - * 3. Matched text between the outermost left and right delimiters. - * 4. Matched outermost right delimiters. - * Taken together, these parts include the entire subject string if used with flag g. - * Use `null` for any of these values to omit unneeded parts from the returned results. - * - `escapeChar` {String} Single char used to escape delimiters within the subject string. - * - `unbalanced` {String} Handling mode for unbalanced delimiters. Options are: - * - 'error' - throw (default) - * - 'skip' - unbalanced delimiters are treated as part of the text between delimiters, and - * searches continue at the end of the unbalanced delimiter. - * - 'skip-lazy' - unbalanced delimiters are treated as part of the text between delimiters, - * and searches continue one character after the start of the unbalanced delimiter. - * @returns {Array} Array of matches, or an empty array. - * @example - * - * // Basic usage - * const str1 = '(t((e))s)t()(ing)'; - * XRegExp.matchRecursive(str1, '\\(', '\\)', 'g'); - * // -> ['t((e))s', '', 'ing'] - * - * // Extended information mode with valueNames - * const str2 = 'Here is
    an
    example'; - * XRegExp.matchRecursive(str2, '', '', 'gi', { - * valueNames: ['between', 'left', 'match', 'right'] - * }); - * // -> [ - * // {name: 'between', value: 'Here is ', start: 0, end: 8}, - * // {name: 'left', value: '
    ', start: 8, end: 13}, - * // {name: 'match', value: '
    an
    ', start: 13, end: 27}, - * // {name: 'right', value: '
    ', start: 27, end: 33}, - * // {name: 'between', value: ' example', start: 33, end: 41} - * // ] - * - * // Omitting unneeded parts with null valueNames, and using escapeChar - * const str3 = '...{1}.\\{{function(x,y){return {y:x}}}'; - * XRegExp.matchRecursive(str3, '{', '}', 'g', { - * valueNames: ['literal', null, 'value', null], - * escapeChar: '\\' - * }); - * // -> [ - * // {name: 'literal', value: '...', start: 0, end: 3}, - * // {name: 'value', value: '1', start: 4, end: 5}, - * // {name: 'literal', value: '.\\{', start: 6, end: 9}, - * // {name: 'value', value: 'function(x,y){return {y:x}}', start: 10, end: 37} - * // ] - * - * // Sticky mode via flag y - * const str4 = '<1><<<2>>><3>4<5>'; - * XRegExp.matchRecursive(str4, '<', '>', 'gy'); - * // -> ['1', '<<2>>', '3'] - * - * // Skipping unbalanced delimiters instead of erroring - * const str5 = 'Here is
    an
    unbalanced example'; - * XRegExp.matchRecursive(str5, '', '
    ', 'gi', { - * unbalanced: 'skip' - * }); - * // -> ['an'] - */ - - - XRegExp.matchRecursive = function (str, left, right, flags, options) { - flags = flags || ''; - options = options || {}; - var global = (0, _indexOf["default"])(flags).call(flags, 'g') !== -1; - var sticky = (0, _indexOf["default"])(flags).call(flags, 'y') !== -1; // Flag `y` is handled manually - - var basicFlags = flags.replace(/y/g, ''); - left = XRegExp(left, basicFlags); - right = XRegExp(right, basicFlags); - var esc; - var _options = options, - escapeChar = _options.escapeChar; - - if (escapeChar) { - var _context, _context2; - - if (escapeChar.length > 1) { - throw new Error('Cannot use more than one escape character'); - } - - escapeChar = XRegExp.escape(escapeChar); // Example of concatenated `esc` regex: - // `escapeChar`: '%' - // `left`: '<' - // `right`: '>' - // Regex is: /(?:%[\S\s]|(?:(?!<|>)[^%])+)+/ - - esc = new RegExp((0, _concat["default"])(_context = (0, _concat["default"])(_context2 = "(?:".concat(escapeChar, "[\\S\\s]|(?:(?!")).call(_context2, // Using `XRegExp.union` safely rewrites backreferences in `left` and `right`. - // Intentionally not passing `basicFlags` to `XRegExp.union` since any syntax - // transformation resulting from those flags was already applied to `left` and - // `right` when they were passed through the XRegExp constructor above. - XRegExp.union([left, right], '', { - conjunction: 'or' - }).source, ")[^")).call(_context, escapeChar, "])+)+"), // Flags `gy` not needed here - flags.replace(XRegExp._hasNativeFlag('s') ? /[^imsu]/g : /[^imu]/g, '')); - } - - var openTokens = 0; - var delimStart = 0; - var delimEnd = 0; - var lastOuterEnd = 0; - var outerStart; - var innerStart; - var leftMatch; - var rightMatch; - var vN = options.valueNames; - var output = []; - - while (true) { - // If using an escape character, advance to the delimiter's next starting position, - // skipping any escaped characters in between - if (escapeChar) { - delimEnd += (XRegExp.exec(str, esc, delimEnd, 'sticky') || [''])[0].length; - } - - leftMatch = XRegExp.exec(str, left, delimEnd); - rightMatch = XRegExp.exec(str, right, delimEnd); // Keep the leftmost match only - - if (leftMatch && rightMatch) { - if (leftMatch.index <= rightMatch.index) { - rightMatch = null; - } else { - leftMatch = null; + var p = (n[i] = { exports: {} }) + e[i][0].call( + p.exports, + function (r) { + var n = e[i][1][r] + return o(n || r) + }, + p, + p.exports, + r, + e, + n, + t + ) } - } // Paths (LM: leftMatch, RM: rightMatch, OT: openTokens): - // LM | RM | OT | Result - // 1 | 0 | 1 | loop - // 1 | 0 | 0 | loop - // 0 | 1 | 1 | loop - // 0 | 1 | 0 | throw - // 0 | 0 | 1 | throw - // 0 | 0 | 0 | break - // The paths above don't include the sticky mode special case. The loop ends after the - // first completed match if not `global`. - - - if (leftMatch || rightMatch) { - delimStart = (leftMatch || rightMatch).index; - delimEnd = delimStart + (leftMatch || rightMatch)[0].length; - } else if (!openTokens) { - break; + return n[i].exports } - - if (sticky && !openTokens && delimStart > lastOuterEnd) { - break; - } - - if (leftMatch) { - if (!openTokens) { - outerStart = delimStart; - innerStart = delimEnd; - } - - openTokens += 1; - } else if (rightMatch && openTokens) { - openTokens -= 1; - - if (!openTokens) { - if (vN) { - if (vN[0] && outerStart > lastOuterEnd) { - output.push(row(vN[0], (0, _slice["default"])(str).call(str, lastOuterEnd, outerStart), lastOuterEnd, outerStart)); + for (var u = 'function' == typeof require && require, i = 0; i < t.length; i++) o(t[i]) + return o + } + return r + })()( + { + 1: [ + function (require, module, exports) { + 'use strict' + + var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property') + + var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault') + + _Object$defineProperty(exports, '__esModule', { + value: true, + }) + + exports['default'] = void 0 + + var _reduce = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/reduce')) + + var _map = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/map')) + + var _indexOf = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/index-of')) + + var _concat = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/concat')) + + /*! + * XRegExp.build 5.1.0 + * + * Steven Levithan (c) 2012-present MIT License + */ + var _default = function _default(XRegExp) { + var REGEX_DATA = 'xregexp' + var subParts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*\]/g + var parts = XRegExp.union([/\({{([\w$]+)}}\)|{{([\w$]+)}}/, subParts], 'g', { + conjunction: 'or', + }) + /** + * Strips a leading `^` and trailing unescaped `$`, if both are present. + * + * @private + * @param {String} pattern Pattern to process. + * @returns {String} Pattern with edge anchors removed. + */ + + function deanchor(pattern) { + // Allow any number of empty noncapturing groups before/after anchors, because regexes + // built/generated by XRegExp sometimes include them + var leadingAnchor = /^(?:\(\?:\))*\^/ + var trailingAnchor = /\$(?:\(\?:\))*$/ + + if ( + leadingAnchor.test(pattern) && + trailingAnchor.test(pattern) && // Ensure that the trailing `$` isn't escaped + trailingAnchor.test(pattern.replace(/\\[\s\S]/g, '')) + ) { + return pattern.replace(leadingAnchor, '').replace(trailingAnchor, '') + } + + return pattern + } + /** + * Converts the provided value to an XRegExp. Native RegExp flags are not preserved. + * + * @private + * @param {String|RegExp} value Value to convert. + * @param {Boolean} [addFlagX] Whether to apply the `x` flag in cases when `value` is not + * already a regex generated by XRegExp + * @returns {RegExp} XRegExp object with XRegExp syntax applied. + */ + + function asXRegExp(value, addFlagX) { + var flags = addFlagX ? 'x' : '' + return XRegExp.isRegExp(value) + ? value[REGEX_DATA] && value[REGEX_DATA].captureNames // Don't recompile, to preserve capture names + ? value // Recompile as XRegExp + : XRegExp(value.source, flags) // Compile string as XRegExp + : XRegExp(value, flags) } - if (vN[1]) { - output.push(row(vN[1], (0, _slice["default"])(str).call(str, outerStart, innerStart), outerStart, innerStart)); + function interpolate(substitution) { + return substitution instanceof RegExp ? substitution : XRegExp.escape(substitution) } - if (vN[2]) { - output.push(row(vN[2], (0, _slice["default"])(str).call(str, innerStart, delimStart), innerStart, delimStart)); + function reduceToSubpatternsObject(subpatterns, interpolated, subpatternIndex) { + subpatterns['subpattern'.concat(subpatternIndex)] = interpolated + return subpatterns } - if (vN[3]) { - output.push(row(vN[3], (0, _slice["default"])(str).call(str, delimStart, delimEnd), delimStart, delimEnd)); + function embedSubpatternAfter(raw, subpatternIndex, rawLiterals) { + var hasSubpattern = subpatternIndex < rawLiterals.length - 1 + return raw + (hasSubpattern ? '{{subpattern'.concat(subpatternIndex, '}}') : '') + } + /** + * Provides tagged template literals that create regexes with XRegExp syntax and flags. The + * provided pattern is handled as a raw string, so backslashes don't need to be escaped. + * + * Interpolation of strings and regexes shares the features of `XRegExp.build`. Interpolated + * patterns are treated as atomic units when quantified, interpolated strings have their special + * characters escaped, a leading `^` and trailing unescaped `$` are stripped from interpolated + * regexes if both are present, and any backreferences within an interpolated regex are + * rewritten to work within the overall pattern. + * + * @memberOf XRegExp + * @param {String} [flags] Any combination of XRegExp flags. + * @returns {Function} Handler for template literals that construct regexes with XRegExp syntax. + * @example + * + * XRegExp.tag()`\b\w+\b`.test('word'); // -> true + * + * const hours = /1[0-2]|0?[1-9]/; + * const minutes = /(?[0-5][0-9])/; + * const time = XRegExp.tag('x')`\b ${hours} : ${minutes} \b`; + * time.test('10:59'); // -> true + * XRegExp.exec('10:59', time).groups.minutes; // -> '59' + * + * const backref1 = /(a)\1/; + * const backref2 = /(b)\1/; + * XRegExp.tag()`${backref1}${backref2}`.test('aabb'); // -> true + */ + + XRegExp.tag = function (flags) { + return function (literals) { + var _context, _context2 + + for ( + var _len = arguments.length, substitutions = new Array(_len > 1 ? _len - 1 : 0), _key = 1; + _key < _len; + _key++ + ) { + substitutions[_key - 1] = arguments[_key] + } + + var subpatterns = (0, _reduce['default'])( + (_context = (0, _map['default'])(substitutions).call(substitutions, interpolate)) + ).call(_context, reduceToSubpatternsObject, {}) + var pattern = (0, _map['default'])((_context2 = literals.raw)) + .call(_context2, embedSubpatternAfter) + .join('') + return XRegExp.build(pattern, subpatterns, flags) + } + } + /** + * Builds regexes using named subpatterns, for readability and pattern reuse. Backreferences in + * the outer pattern and provided subpatterns are automatically renumbered to work correctly. + * Native flags used by provided subpatterns are ignored in favor of the `flags` argument. + * + * @memberOf XRegExp + * @param {String} pattern XRegExp pattern using `{{name}}` for embedded subpatterns. Allows + * `({{name}})` as shorthand for `(?{{name}})`. Patterns cannot be embedded within + * character classes. + * @param {Object} subs Lookup object for named subpatterns. Values can be strings or regexes. A + * leading `^` and trailing unescaped `$` are stripped from subpatterns, if both are present. + * @param {String} [flags] Any combination of XRegExp flags. + * @returns {RegExp} Regex with interpolated subpatterns. + * @example + * + * const time = XRegExp.build('(?x)^ {{hours}} ({{minutes}}) $', { + * hours: XRegExp.build('{{h12}} : | {{h24}}', { + * h12: /1[0-2]|0?[1-9]/, + * h24: /2[0-3]|[01][0-9]/ + * }, 'x'), + * minutes: /^[0-5][0-9]$/ + * }); + * time.test('10:59'); // -> true + * XRegExp.exec('10:59', time).groups.minutes; // -> '59' + */ + + XRegExp.build = function (pattern, subs, flags) { + flags = flags || '' // Used with `asXRegExp` calls for `pattern` and subpatterns in `subs`, to work around how + // some browsers convert `RegExp('\n')` to a regex that contains the literal characters `\` + // and `n`. See more details at . + + var addFlagX = (0, _indexOf['default'])(flags).call(flags, 'x') !== -1 + var inlineFlags = /^\(\?([\w$]+)\)/.exec(pattern) // Add flags within a leading mode modifier to the overall pattern's flags + + if (inlineFlags) { + flags = XRegExp._clipDuplicates(flags + inlineFlags[1]) + } + + var data = {} + + for (var p in subs) { + if (subs.hasOwnProperty(p)) { + // Passing to XRegExp enables extended syntax and ensures independent validity, + // lest an unescaped `(`, `)`, `[`, or trailing `\` breaks the `(?:)` wrapper. For + // subpatterns provided as native regexes, it dies on octals and adds the property + // used to hold extended regex instance data, for simplicity. + var sub = asXRegExp(subs[p], addFlagX) + data[p] = { + // Deanchoring allows embedding independently useful anchored regexes. If you + // really need to keep your anchors, double them (i.e., `^^...$$`). + pattern: deanchor(sub.source), + names: sub[REGEX_DATA].captureNames || [], + } + } + } // Passing to XRegExp dies on octals and ensures the outer pattern is independently valid; + // helps keep this simple. Named captures will be put back. + + var patternAsRegex = asXRegExp(pattern, addFlagX) // 'Caps' is short for 'captures' + + var numCaps = 0 + var numPriorCaps + var numOuterCaps = 0 + var outerCapsMap = [0] + var outerCapNames = patternAsRegex[REGEX_DATA].captureNames || [] + var output = patternAsRegex.source.replace(parts, function ($0, $1, $2, $3, $4) { + var subName = $1 || $2 + var capName + var intro + var localCapIndex // Named subpattern + + if (subName) { + var _context3 + + if (!data.hasOwnProperty(subName)) { + throw new ReferenceError('Undefined property '.concat($0)) + } // Named subpattern was wrapped in a capturing group + + if ($1) { + capName = outerCapNames[numOuterCaps] + outerCapsMap[++numOuterCaps] = ++numCaps // If it's a named group, preserve the name. Otherwise, use the subpattern name + // as the capture name + + intro = '(?<'.concat(capName || subName, '>') + } else { + intro = '(?:' + } + + numPriorCaps = numCaps + var rewrittenSubpattern = data[subName].pattern.replace(subParts, function (match, paren, backref) { + // Capturing group + if (paren) { + capName = data[subName].names[numCaps - numPriorCaps] + ++numCaps // If the current capture has a name, preserve the name + + if (capName) { + return '(?<'.concat(capName, '>') + } // Backreference + } else if (backref) { + localCapIndex = +backref - 1 // Rewrite the backreference + + return data[subName].names[localCapIndex] // Need to preserve the backreference name in case using flag `n` + ? '\\k<'.concat(data[subName].names[localCapIndex], '>') + : '\\'.concat(+backref + numPriorCaps) + } + + return match + }) + return (0, _concat['default'])((_context3 = ''.concat(intro))).call( + _context3, + rewrittenSubpattern, + ')' + ) + } // Capturing group + + if ($3) { + capName = outerCapNames[numOuterCaps] + outerCapsMap[++numOuterCaps] = ++numCaps // If the current capture has a name, preserve the name + + if (capName) { + return '(?<'.concat(capName, '>') + } // Backreference + } else if ($4) { + localCapIndex = +$4 - 1 // Rewrite the backreference + + return outerCapNames[localCapIndex] // Need to preserve the backreference name in case using flag `n` + ? '\\k<'.concat(outerCapNames[localCapIndex], '>') + : '\\'.concat(outerCapsMap[+$4]) + } + + return $0 + }) + return XRegExp(output, flags) } - } else { - output.push((0, _slice["default"])(str).call(str, innerStart, delimStart)); } - lastOuterEnd = delimEnd; - - if (!global) { - break; + exports['default'] = _default + module.exports = exports.default + }, + { + '@babel/runtime-corejs3/core-js-stable/instance/concat': 11, + '@babel/runtime-corejs3/core-js-stable/instance/index-of': 14, + '@babel/runtime-corejs3/core-js-stable/instance/map': 15, + '@babel/runtime-corejs3/core-js-stable/instance/reduce': 16, + '@babel/runtime-corejs3/core-js-stable/object/define-property': 20, + '@babel/runtime-corejs3/helpers/interopRequireDefault': 30, + }, + ], + 2: [ + function (require, module, exports) { + 'use strict' + + var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property') + + var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault') + + _Object$defineProperty(exports, '__esModule', { + value: true, + }) + + exports['default'] = void 0 + + var _indexOf = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/index-of')) + + var _concat = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/concat')) + + var _slice = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/slice')) + + /*! + * XRegExp.matchRecursive 5.1.0 + * + * Steven Levithan (c) 2009-present MIT License + */ + var _default = function _default(XRegExp) { + /** + * Returns a match detail object composed of the provided values. + * + * @private + */ + function row(name, value, start, end) { + return { + name: name, + value: value, + start: start, + end: end, + } + } + /** + * Returns an array of match strings between outermost left and right delimiters, or an array of + * objects with detailed match parts and position data. By default, an error is thrown if + * delimiters are unbalanced within the subject string. + * + * @memberOf XRegExp + * @param {String} str String to search. + * @param {String} left Left delimiter as an XRegExp pattern. + * @param {String} right Right delimiter as an XRegExp pattern. + * @param {String} [flags] Any combination of XRegExp flags, used for the left and right delimiters. + * @param {Object} [options] Options object with optional properties: + * - `valueNames` {Array} Providing `valueNames` changes the return value from an array of + * matched strings to an array of objects that provide the value and start/end positions + * for the matched strings as well as the matched delimiters and unmatched string segments. + * To use this extended information mode, provide an array of 4 strings that name the parts + * to be returned: + * 1. String segments outside of (before, between, and after) matches. + * 2. Matched outermost left delimiters. + * 3. Matched text between the outermost left and right delimiters. + * 4. Matched outermost right delimiters. + * Taken together, these parts include the entire subject string if used with flag g. + * Use `null` for any of these values to omit unneeded parts from the returned results. + * - `escapeChar` {String} Single char used to escape delimiters within the subject string. + * - `unbalanced` {String} Handling mode for unbalanced delimiters. Options are: + * - 'error' - throw (default) + * - 'skip' - unbalanced delimiters are treated as part of the text between delimiters, and + * searches continue at the end of the unbalanced delimiter. + * - 'skip-lazy' - unbalanced delimiters are treated as part of the text between delimiters, + * and searches continue one character after the start of the unbalanced delimiter. + * @returns {Array} Array of matches, or an empty array. + * @example + * + * // Basic usage + * const str1 = '(t((e))s)t()(ing)'; + * XRegExp.matchRecursive(str1, '\\(', '\\)', 'g'); + * // -> ['t((e))s', '', 'ing'] + * + * // Extended information mode with valueNames + * const str2 = 'Here is
    an
    example'; + * XRegExp.matchRecursive(str2, '', '', 'gi', { + * valueNames: ['between', 'left', 'match', 'right'] + * }); + * // -> [ + * // {name: 'between', value: 'Here is ', start: 0, end: 8}, + * // {name: 'left', value: '
    ', start: 8, end: 13}, + * // {name: 'match', value: '
    an
    ', start: 13, end: 27}, + * // {name: 'right', value: '
    ', start: 27, end: 33}, + * // {name: 'between', value: ' example', start: 33, end: 41} + * // ] + * + * // Omitting unneeded parts with null valueNames, and using escapeChar + * const str3 = '...{1}.\\{{function(x,y){return {y:x}}}'; + * XRegExp.matchRecursive(str3, '{', '}', 'g', { + * valueNames: ['literal', null, 'value', null], + * escapeChar: '\\' + * }); + * // -> [ + * // {name: 'literal', value: '...', start: 0, end: 3}, + * // {name: 'value', value: '1', start: 4, end: 5}, + * // {name: 'literal', value: '.\\{', start: 6, end: 9}, + * // {name: 'value', value: 'function(x,y){return {y:x}}', start: 10, end: 37} + * // ] + * + * // Sticky mode via flag y + * const str4 = '<1><<<2>>><3>4<5>'; + * XRegExp.matchRecursive(str4, '<', '>', 'gy'); + * // -> ['1', '<<2>>', '3'] + * + * // Skipping unbalanced delimiters instead of erroring + * const str5 = 'Here is
    an
    unbalanced example'; + * XRegExp.matchRecursive(str5, '', '
    ', 'gi', { + * unbalanced: 'skip' + * }); + * // -> ['an'] + */ + + XRegExp.matchRecursive = function (str, left, right, flags, options) { + flags = flags || '' + options = options || {} + var global = (0, _indexOf['default'])(flags).call(flags, 'g') !== -1 + var sticky = (0, _indexOf['default'])(flags).call(flags, 'y') !== -1 // Flag `y` is handled manually + + var basicFlags = flags.replace(/y/g, '') + left = XRegExp(left, basicFlags) + right = XRegExp(right, basicFlags) + var esc + var _options = options, + escapeChar = _options.escapeChar + + if (escapeChar) { + var _context, _context2 + + if (escapeChar.length > 1) { + throw new Error('Cannot use more than one escape character') + } + + escapeChar = XRegExp.escape(escapeChar) // Example of concatenated `esc` regex: + // `escapeChar`: '%' + // `left`: '<' + // `right`: '>' + // Regex is: /(?:%[\S\s]|(?:(?!<|>)[^%])+)+/ + + esc = new RegExp( + (0, _concat['default'])( + (_context = (0, _concat['default'])((_context2 = '(?:'.concat(escapeChar, '[\\S\\s]|(?:(?!'))).call( + _context2, // Using `XRegExp.union` safely rewrites backreferences in `left` and `right`. + // Intentionally not passing `basicFlags` to `XRegExp.union` since any syntax + // transformation resulting from those flags was already applied to `left` and + // `right` when they were passed through the XRegExp constructor above. + XRegExp.union([left, right], '', { + conjunction: 'or', + }).source, + ')[^' + )) + ).call(_context, escapeChar, '])+)+'), // Flags `gy` not needed here + flags.replace(XRegExp._hasNativeFlag('s') ? /[^imsu]/g : /[^imu]/g, '') + ) + } + + var openTokens = 0 + var delimStart = 0 + var delimEnd = 0 + var lastOuterEnd = 0 + var outerStart + var innerStart + var leftMatch + var rightMatch + var vN = options.valueNames + var output = [] + + while (true) { + // If using an escape character, advance to the delimiter's next starting position, + // skipping any escaped characters in between + if (escapeChar) { + delimEnd += (XRegExp.exec(str, esc, delimEnd, 'sticky') || [''])[0].length + } + + leftMatch = XRegExp.exec(str, left, delimEnd) + rightMatch = XRegExp.exec(str, right, delimEnd) // Keep the leftmost match only + + if (leftMatch && rightMatch) { + if (leftMatch.index <= rightMatch.index) { + rightMatch = null + } else { + leftMatch = null + } + } // Paths (LM: leftMatch, RM: rightMatch, OT: openTokens): + // LM | RM | OT | Result + // 1 | 0 | 1 | loop + // 1 | 0 | 0 | loop + // 0 | 1 | 1 | loop + // 0 | 1 | 0 | throw + // 0 | 0 | 1 | throw + // 0 | 0 | 0 | break + // The paths above don't include the sticky mode special case. The loop ends after the + // first completed match if not `global`. + + if (leftMatch || rightMatch) { + delimStart = (leftMatch || rightMatch).index + delimEnd = delimStart + (leftMatch || rightMatch)[0].length + } else if (!openTokens) { + break + } + + if (sticky && !openTokens && delimStart > lastOuterEnd) { + break + } + + if (leftMatch) { + if (!openTokens) { + outerStart = delimStart + innerStart = delimEnd + } + + openTokens += 1 + } else if (rightMatch && openTokens) { + openTokens -= 1 + + if (!openTokens) { + if (vN) { + if (vN[0] && outerStart > lastOuterEnd) { + output.push( + row( + vN[0], + (0, _slice['default'])(str).call(str, lastOuterEnd, outerStart), + lastOuterEnd, + outerStart + ) + ) + } + + if (vN[1]) { + output.push( + row( + vN[1], + (0, _slice['default'])(str).call(str, outerStart, innerStart), + outerStart, + innerStart + ) + ) + } + + if (vN[2]) { + output.push( + row( + vN[2], + (0, _slice['default'])(str).call(str, innerStart, delimStart), + innerStart, + delimStart + ) + ) + } + + if (vN[3]) { + output.push( + row(vN[3], (0, _slice['default'])(str).call(str, delimStart, delimEnd), delimStart, delimEnd) + ) + } + } else { + output.push((0, _slice['default'])(str).call(str, innerStart, delimStart)) + } + + lastOuterEnd = delimEnd + + if (!global) { + break + } + } // Found unbalanced delimiter + } else { + var unbalanced = options.unbalanced || 'error' + + if (unbalanced === 'skip' || unbalanced === 'skip-lazy') { + if (rightMatch) { + rightMatch = null // No `leftMatch` for unbalanced left delimiter because we've reached the string end + } else { + if (unbalanced === 'skip') { + var outerStartDelimLength = XRegExp.exec(str, left, outerStart, 'sticky')[0].length + delimEnd = outerStart + (outerStartDelimLength || 1) + } else { + delimEnd = outerStart + 1 + } + + openTokens = 0 + } + } else if (unbalanced === 'error') { + var _context3 + + var delimSide = rightMatch ? 'right' : 'left' + var errorPos = rightMatch ? delimStart : outerStart + throw new Error( + (0, _concat['default'])( + (_context3 = 'Unbalanced '.concat(delimSide, ' delimiter found in string at position ')) + ).call(_context3, errorPos) + ) + } else { + throw new Error('Unsupported value for unbalanced: '.concat(unbalanced)) + } + } // If the delimiter matched an empty string, avoid an infinite loop + + if (delimStart === delimEnd) { + delimEnd += 1 + } + } + + if (global && output.length > 0 && !sticky && vN && vN[0] && str.length > lastOuterEnd) { + output.push(row(vN[0], (0, _slice['default'])(str).call(str, lastOuterEnd), lastOuterEnd, str.length)) + } + + return output + } } - } // Found unbalanced delimiter - } else { - var unbalanced = options.unbalanced || 'error'; - - if (unbalanced === 'skip' || unbalanced === 'skip-lazy') { - if (rightMatch) { - rightMatch = null; // No `leftMatch` for unbalanced left delimiter because we've reached the string end - } else { - if (unbalanced === 'skip') { - var outerStartDelimLength = XRegExp.exec(str, left, outerStart, 'sticky')[0].length; - delimEnd = outerStart + (outerStartDelimLength || 1); - } else { - delimEnd = outerStart + 1; + exports['default'] = _default + module.exports = exports.default + }, + { + '@babel/runtime-corejs3/core-js-stable/instance/concat': 11, + '@babel/runtime-corejs3/core-js-stable/instance/index-of': 14, + '@babel/runtime-corejs3/core-js-stable/instance/slice': 17, + '@babel/runtime-corejs3/core-js-stable/object/define-property': 20, + '@babel/runtime-corejs3/helpers/interopRequireDefault': 30, + }, + ], + 3: [ + function (require, module, exports) { + 'use strict' + + var _sliceInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/slice') + + var _Array$from = require('@babel/runtime-corejs3/core-js-stable/array/from') + + var _Symbol = require('@babel/runtime-corejs3/core-js-stable/symbol') + + var _getIteratorMethod = require('@babel/runtime-corejs3/core-js/get-iterator-method') + + var _Array$isArray = require('@babel/runtime-corejs3/core-js-stable/array/is-array') + + var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property') + + var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault') + + _Object$defineProperty(exports, '__esModule', { + value: true, + }) + + exports['default'] = void 0 + + var _slicedToArray2 = _interopRequireDefault(require('@babel/runtime-corejs3/helpers/slicedToArray')) + + var _forEach = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/for-each')) + + var _concat = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/concat')) + + var _indexOf = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/index-of')) + + function _createForOfIteratorHelper(o, allowArrayLike) { + var it = (typeof _Symbol !== 'undefined' && _getIteratorMethod(o)) || o['@@iterator'] + if (!it) { + if ( + _Array$isArray(o) || + (it = _unsupportedIterableToArray(o)) || + (allowArrayLike && o && typeof o.length === 'number') + ) { + if (it) o = it + var i = 0 + var F = function F() {} + return { + s: F, + n: function n() { + if (i >= o.length) return { done: true } + return { done: false, value: o[i++] } + }, + e: function e(_e) { + throw _e + }, + f: F, + } + } + throw new TypeError( + 'Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.' + ) + } + var normalCompletion = true, + didErr = false, + err + return { + s: function s() { + it = it.call(o) + }, + n: function n() { + var step = it.next() + normalCompletion = step.done + return step + }, + e: function e(_e2) { + didErr = true + err = _e2 + }, + f: function f() { + try { + if (!normalCompletion && it['return'] != null) it['return']() + } finally { + if (didErr) throw err + } + }, } - - openTokens = 0; } - } else if (unbalanced === 'error') { - var _context3; - var delimSide = rightMatch ? 'right' : 'left'; - var errorPos = rightMatch ? delimStart : outerStart; - throw new Error((0, _concat["default"])(_context3 = "Unbalanced ".concat(delimSide, " delimiter found in string at position ")).call(_context3, errorPos)); - } else { - throw new Error("Unsupported value for unbalanced: ".concat(unbalanced)); - } - } // If the delimiter matched an empty string, avoid an infinite loop + function _unsupportedIterableToArray(o, minLen) { + var _context4 + if (!o) return + if (typeof o === 'string') return _arrayLikeToArray(o, minLen) + var n = _sliceInstanceProperty((_context4 = Object.prototype.toString.call(o))).call(_context4, 8, -1) + if (n === 'Object' && o.constructor) n = o.constructor.name + if (n === 'Map' || n === 'Set') return _Array$from(o) + if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) + return _arrayLikeToArray(o, minLen) + } + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i] + } + return arr2 + } - if (delimStart === delimEnd) { - delimEnd += 1; - } - } + /*! + * XRegExp Unicode Base 5.1.0 + * + * Steven Levithan (c) 2008-present MIT License + */ + var _default = function _default(XRegExp) { + /** + * Adds base support for Unicode matching: + * - Adds syntax `\p{..}` for matching Unicode tokens. Tokens can be inverted using `\P{..}` or + * `\p{^..}`. Token names ignore case, spaces, hyphens, and underscores. You can omit the + * braces for token names that are a single letter (e.g. `\pL` or `PL`). + * - Adds flag A (astral), which enables 21-bit Unicode support. + * - Adds the `XRegExp.addUnicodeData` method used by other addons to provide character data. + * + * Unicode Base relies on externally provided Unicode character data. Official addons are + * available to provide data for Unicode categories, scripts, and properties. + * + * @requires XRegExp + */ + // ==--------------------------== + // Private stuff + // ==--------------------------== + // Storage for Unicode data + var unicode = {} + var unicodeTypes = {} // Reuse utils + + var dec = XRegExp._dec + var hex = XRegExp._hex + var pad4 = XRegExp._pad4 // Generates a token lookup name: lowercase, with hyphens, spaces, and underscores removed + + function normalize(name) { + return name.replace(/[- _]+/g, '').toLowerCase() + } // Gets the decimal code of a literal code unit, \xHH, \uHHHH, or a backslash-escaped literal + + function charCode(chr) { + var esc = /^\\[xu](.+)/.exec(chr) + return esc ? dec(esc[1]) : chr.charCodeAt(chr[0] === '\\' ? 1 : 0) + } // Inverts a list of ordered BMP characters and ranges + + function invertBmp(range) { + var output = '' + var lastEnd = -1 + ;(0, _forEach['default'])(XRegExp).call( + XRegExp, + range, + /(\\x..|\\u....|\\?[\s\S])(?:-(\\x..|\\u....|\\?[\s\S]))?/, + function (m) { + var start = charCode(m[1]) + + if (start > lastEnd + 1) { + output += '\\u'.concat(pad4(hex(lastEnd + 1))) + + if (start > lastEnd + 2) { + output += '-\\u'.concat(pad4(hex(start - 1))) + } + } + + lastEnd = charCode(m[2] || m[1]) + } + ) + + if (lastEnd < 0xffff) { + output += '\\u'.concat(pad4(hex(lastEnd + 1))) + + if (lastEnd < 0xfffe) { + output += '-\\uFFFF' + } + } + + return output + } // Generates an inverted BMP range on first use + + function cacheInvertedBmp(slug) { + var prop = 'b!' + return unicode[slug][prop] || (unicode[slug][prop] = invertBmp(unicode[slug].bmp)) + } // Combines and optionally negates BMP and astral data + + function buildAstral(slug, isNegated) { + var item = unicode[slug] + var combined = '' + + if (item.bmp && !item.isBmpLast) { + var _context + + combined = (0, _concat['default'])((_context = '['.concat(item.bmp, ']'))).call( + _context, + item.astral ? '|' : '' + ) + } + + if (item.astral) { + combined += item.astral + } + + if (item.isBmpLast && item.bmp) { + var _context2 + + combined += (0, _concat['default'])((_context2 = ''.concat(item.astral ? '|' : '', '['))).call( + _context2, + item.bmp, + ']' + ) + } // Astral Unicode tokens always match a code point, never a code unit + + return isNegated + ? '(?:(?!'.concat(combined, ')(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[\0-\uFFFF]))') + : '(?:'.concat(combined, ')') + } // Builds a complete astral pattern on first use + + function cacheAstral(slug, isNegated) { + var prop = isNegated ? 'a!' : 'a=' + return unicode[slug][prop] || (unicode[slug][prop] = buildAstral(slug, isNegated)) + } // ==--------------------------== + // Core functionality + // ==--------------------------== + + /* + * Add astral mode (flag A) and Unicode token syntax: `\p{..}`, `\P{..}`, `\p{^..}`, `\pC`. + */ + + XRegExp.addToken( + // Use `*` instead of `+` to avoid capturing `^` as the token name in `\p{^}` + /\\([pP])(?:{(\^?)(?:(\w+)=)?([^}]*)}|([A-Za-z]))/, + function (match, scope, flags) { + var ERR_DOUBLE_NEG = 'Invalid double negation ' + var ERR_UNKNOWN_NAME = 'Unknown Unicode token ' + var ERR_UNKNOWN_REF = 'Unicode token missing data ' + var ERR_ASTRAL_ONLY = 'Astral mode required for Unicode token ' + var ERR_ASTRAL_IN_CLASS = 'Astral mode does not support Unicode tokens within character classes' + + var _match = (0, _slicedToArray2['default'])(match, 6), + fullToken = _match[0], + pPrefix = _match[1], + caretNegation = _match[2], + typePrefix = _match[3], + tokenName = _match[4], + tokenSingleCharName = _match[5] // Negated via \P{..} or \p{^..} + + var isNegated = pPrefix === 'P' || !!caretNegation // Switch from BMP (0-FFFF) to astral (0-10FFFF) mode via flag A + + var isAstralMode = (0, _indexOf['default'])(flags).call(flags, 'A') !== -1 // Token lookup name. Check `tokenSingleCharName` first to avoid passing `undefined` + // via `\p{}` + + var slug = normalize(tokenSingleCharName || tokenName) // Token data object + + var item = unicode[slug] + + if (pPrefix === 'P' && caretNegation) { + throw new SyntaxError(ERR_DOUBLE_NEG + fullToken) + } + + if (!unicode.hasOwnProperty(slug)) { + throw new SyntaxError(ERR_UNKNOWN_NAME + fullToken) + } + + if (typePrefix) { + if (!(unicodeTypes[typePrefix] && unicodeTypes[typePrefix][slug])) { + throw new SyntaxError(ERR_UNKNOWN_NAME + fullToken) + } + } // Switch to the negated form of the referenced Unicode token + + if (item.inverseOf) { + slug = normalize(item.inverseOf) + + if (!unicode.hasOwnProperty(slug)) { + var _context3 + + throw new ReferenceError( + (0, _concat['default'])((_context3 = ''.concat(ERR_UNKNOWN_REF + fullToken, ' -> '))).call( + _context3, + item.inverseOf + ) + ) + } + + item = unicode[slug] + isNegated = !isNegated + } + + if (!(item.bmp || isAstralMode)) { + throw new SyntaxError(ERR_ASTRAL_ONLY + fullToken) + } + + if (isAstralMode) { + if (scope === 'class') { + throw new SyntaxError(ERR_ASTRAL_IN_CLASS) + } + + return cacheAstral(slug, isNegated) + } + + return scope === 'class' + ? isNegated + ? cacheInvertedBmp(slug) + : item.bmp + : ''.concat((isNegated ? '[^' : '[') + item.bmp, ']') + }, + { + scope: 'all', + optionalFlags: 'A', + leadChar: '\\', + } + ) + /** + * Adds to the list of Unicode tokens that XRegExp regexes can match via `\p` or `\P`. + * + * @memberOf XRegExp + * @param {Array} data Objects with named character ranges. Each object may have properties + * `name`, `alias`, `isBmpLast`, `inverseOf`, `bmp`, and `astral`. All but `name` are + * optional, although one of `bmp` or `astral` is required (unless `inverseOf` is set). If + * `astral` is absent, the `bmp` data is used for BMP and astral modes. If `bmp` is absent, + * the name errors in BMP mode but works in astral mode. If both `bmp` and `astral` are + * provided, the `bmp` data only is used in BMP mode, and the combination of `bmp` and + * `astral` data is used in astral mode. `isBmpLast` is needed when a token matches orphan + * high surrogates *and* uses surrogate pairs to match astral code points. The `bmp` and + * `astral` data should be a combination of literal characters and `\xHH` or `\uHHHH` escape + * sequences, with hyphens to create ranges. Any regex metacharacters in the data should be + * escaped, apart from range-creating hyphens. The `astral` data can additionally use + * character classes and alternation, and should use surrogate pairs to represent astral code + * points. `inverseOf` can be used to avoid duplicating character data if a Unicode token is + * defined as the exact inverse of another token. + * @param {String} [typePrefix] Enables optionally using this type as a prefix for all of the + * provided Unicode tokens, e.g. if given `'Type'`, then `\p{TokenName}` can also be written + * as `\p{Type=TokenName}`. + * @example + * + * // Basic use + * XRegExp.addUnicodeData([{ + * name: 'XDigit', + * alias: 'Hexadecimal', + * bmp: '0-9A-Fa-f' + * }]); + * XRegExp('\\p{XDigit}:\\p{Hexadecimal}+').test('0:3D'); // -> true + */ + + XRegExp.addUnicodeData = function (data, typePrefix) { + var ERR_NO_NAME = 'Unicode token requires name' + var ERR_NO_DATA = 'Unicode token has no character data ' + + if (typePrefix) { + // Case sensitive to match ES2018 + unicodeTypes[typePrefix] = {} + } + + var _iterator = _createForOfIteratorHelper(data), + _step + + try { + for (_iterator.s(); !(_step = _iterator.n()).done; ) { + var item = _step.value + + if (!item.name) { + throw new Error(ERR_NO_NAME) + } + + if (!(item.inverseOf || item.bmp || item.astral)) { + throw new Error(ERR_NO_DATA + item.name) + } + + var normalizedName = normalize(item.name) + unicode[normalizedName] = item + + if (typePrefix) { + unicodeTypes[typePrefix][normalizedName] = true + } + + if (item.alias) { + var normalizedAlias = normalize(item.alias) + unicode[normalizedAlias] = item + + if (typePrefix) { + unicodeTypes[typePrefix][normalizedAlias] = true + } + } + } // Reset the pattern cache used by the `XRegExp` constructor, since the same pattern and + // flags might now produce different results + } catch (err) { + _iterator.e(err) + } finally { + _iterator.f() + } + + XRegExp.cache.flush('patterns') + } + /** + * @ignore + * + * Return a reference to the internal Unicode definition structure for the given Unicode + * Property if the given name is a legal Unicode Property for use in XRegExp `\p` or `\P` regex + * constructs. + * + * @memberOf XRegExp + * @param {String} name Name by which the Unicode Property may be recognized (case-insensitive), + * e.g. `'N'` or `'Number'`. The given name is matched against all registered Unicode + * Properties and Property Aliases. + * @returns {Object} Reference to definition structure when the name matches a Unicode Property. + * + * @note + * For more info on Unicode Properties, see also http://unicode.org/reports/tr18/#Categories. + * + * @note + * This method is *not* part of the officially documented API and may change or be removed in + * the future. It is meant for userland code that wishes to reuse the (large) internal Unicode + * structures set up by XRegExp. + */ + + XRegExp._getUnicodeProperty = function (name) { + var slug = normalize(name) + return unicode[slug] + } + } - if (global && output.length > 0 && !sticky && vN && vN[0] && str.length > lastOuterEnd) { - output.push(row(vN[0], (0, _slice["default"])(str).call(str, lastOuterEnd), lastOuterEnd, str.length)); - } + exports['default'] = _default + module.exports = exports.default + }, + { + '@babel/runtime-corejs3/core-js-stable/array/from': 9, + '@babel/runtime-corejs3/core-js-stable/array/is-array': 10, + '@babel/runtime-corejs3/core-js-stable/instance/concat': 11, + '@babel/runtime-corejs3/core-js-stable/instance/for-each': 13, + '@babel/runtime-corejs3/core-js-stable/instance/index-of': 14, + '@babel/runtime-corejs3/core-js-stable/instance/slice': 17, + '@babel/runtime-corejs3/core-js-stable/object/define-property': 20, + '@babel/runtime-corejs3/core-js-stable/symbol': 22, + '@babel/runtime-corejs3/core-js/get-iterator-method': 25, + '@babel/runtime-corejs3/helpers/interopRequireDefault': 30, + '@babel/runtime-corejs3/helpers/slicedToArray': 33, + }, + ], + 4: [ + function (require, module, exports) { + 'use strict' + + var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property') + + var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault') + + _Object$defineProperty(exports, '__esModule', { + value: true, + }) + + exports['default'] = void 0 + + var _categories = _interopRequireDefault(require('../../tools/output/categories')) + + /*! + * XRegExp Unicode Categories 5.1.0 + * + * Steven Levithan (c) 2010-present MIT License + * Unicode data by Mathias Bynens + */ + var _default = function _default(XRegExp) { + /** + * Adds support for Unicode's general categories. E.g., `\p{Lu}` or `\p{Uppercase Letter}`. See + * category descriptions in UAX #44 . Token + * names are case insensitive, and any spaces, hyphens, and underscores are ignored. + * + * Uses Unicode 14.0.0. + * + * @requires XRegExp, Unicode Base + */ + if (!XRegExp.addUnicodeData) { + throw new ReferenceError('Unicode Base must be loaded before Unicode Categories') + } - return output; - }; -}; + XRegExp.addUnicodeData(_categories['default']) + } -exports["default"] = _default; -module.exports = exports.default; -},{"@babel/runtime-corejs3/core-js-stable/instance/concat":11,"@babel/runtime-corejs3/core-js-stable/instance/index-of":14,"@babel/runtime-corejs3/core-js-stable/instance/slice":17,"@babel/runtime-corejs3/core-js-stable/object/define-property":20,"@babel/runtime-corejs3/helpers/interopRequireDefault":30}],3:[function(require,module,exports){ -"use strict"; + exports['default'] = _default + module.exports = exports.default + }, + { + '../../tools/output/categories': 218, + '@babel/runtime-corejs3/core-js-stable/object/define-property': 20, + '@babel/runtime-corejs3/helpers/interopRequireDefault': 30, + }, + ], + 5: [ + function (require, module, exports) { + 'use strict' + + var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property') + + var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault') + + _Object$defineProperty(exports, '__esModule', { + value: true, + }) + + exports['default'] = void 0 + + var _properties = _interopRequireDefault(require('../../tools/output/properties')) + + /*! + * XRegExp Unicode Properties 5.1.0 + * + * Steven Levithan (c) 2012-present MIT License + * Unicode data by Mathias Bynens + */ + var _default = function _default(XRegExp) { + /** + * Adds properties to meet the UTS #18 Level 1 RL1.2 requirements for Unicode regex support. See + * . Following are definitions of these properties from + * UAX #44 : + * + * - Alphabetic + * Characters with the Alphabetic property. Generated from: Lowercase + Uppercase + Lt + Lm + + * Lo + Nl + Other_Alphabetic. + * + * - Default_Ignorable_Code_Point + * For programmatic determination of default ignorable code points. New characters that should + * be ignored in rendering (unless explicitly supported) will be assigned in these ranges, + * permitting programs to correctly handle the default rendering of such characters when not + * otherwise supported. + * + * - Lowercase + * Characters with the Lowercase property. Generated from: Ll + Other_Lowercase. + * + * - Noncharacter_Code_Point + * Code points permanently reserved for internal use. + * + * - Uppercase + * Characters with the Uppercase property. Generated from: Lu + Other_Uppercase. + * + * - White_Space + * Spaces, separator characters and other control characters which should be treated by + * programming languages as "white space" for the purpose of parsing elements. + * + * The properties ASCII, Any, and Assigned are also included but are not defined in UAX #44. UTS + * #18 RL1.2 additionally requires support for Unicode scripts and general categories. These are + * included in XRegExp's Unicode Categories and Unicode Scripts addons. + * + * Token names are case insensitive, and any spaces, hyphens, and underscores are ignored. + * + * Uses Unicode 14.0.0. + * + * @requires XRegExp, Unicode Base + */ + if (!XRegExp.addUnicodeData) { + throw new ReferenceError('Unicode Base must be loaded before Unicode Properties') + } -var _sliceInstanceProperty = require("@babel/runtime-corejs3/core-js-stable/instance/slice"); + var unicodeData = _properties['default'] // Add non-generated data -var _Array$from = require("@babel/runtime-corejs3/core-js-stable/array/from"); + unicodeData.push({ + name: 'Assigned', + // Since this is defined as the inverse of Unicode category Cn (Unassigned), the Unicode + // Categories addon is required to use this property + inverseOf: 'Cn', + }) + XRegExp.addUnicodeData(unicodeData) + } -var _Symbol = require("@babel/runtime-corejs3/core-js-stable/symbol"); + exports['default'] = _default + module.exports = exports.default + }, + { + '../../tools/output/properties': 219, + '@babel/runtime-corejs3/core-js-stable/object/define-property': 20, + '@babel/runtime-corejs3/helpers/interopRequireDefault': 30, + }, + ], + 6: [ + function (require, module, exports) { + 'use strict' + + var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property') + + var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault') + + _Object$defineProperty(exports, '__esModule', { + value: true, + }) + + exports['default'] = void 0 + + var _scripts = _interopRequireDefault(require('../../tools/output/scripts')) + + /*! + * XRegExp Unicode Scripts 5.1.0 + * + * Steven Levithan (c) 2010-present MIT License + * Unicode data by Mathias Bynens + */ + var _default = function _default(XRegExp) { + /** + * Adds support for all Unicode scripts. E.g., `\p{Latin}`. Token names are case insensitive, + * and any spaces, hyphens, and underscores are ignored. + * + * Uses Unicode 14.0.0. + * + * @requires XRegExp, Unicode Base + */ + if (!XRegExp.addUnicodeData) { + throw new ReferenceError('Unicode Base must be loaded before Unicode Scripts') + } -var _getIteratorMethod = require("@babel/runtime-corejs3/core-js/get-iterator-method"); + XRegExp.addUnicodeData(_scripts['default'], 'Script') + } -var _Array$isArray = require("@babel/runtime-corejs3/core-js-stable/array/is-array"); + exports['default'] = _default + module.exports = exports.default + }, + { + '../../tools/output/scripts': 220, + '@babel/runtime-corejs3/core-js-stable/object/define-property': 20, + '@babel/runtime-corejs3/helpers/interopRequireDefault': 30, + }, + ], + 7: [ + function (require, module, exports) { + 'use strict' -var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); + var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property') -var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); + var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault') -_Object$defineProperty(exports, "__esModule", { - value: true -}); + _Object$defineProperty(exports, '__esModule', { + value: true, + }) -exports["default"] = void 0; + exports['default'] = void 0 -var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/slicedToArray")); + var _xregexp = _interopRequireDefault(require('./xregexp')) -var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each")); + var _build = _interopRequireDefault(require('./addons/build')) -var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); + var _matchrecursive = _interopRequireDefault(require('./addons/matchrecursive')) -var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of")); + var _unicodeBase = _interopRequireDefault(require('./addons/unicode-base')) -function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof _Symbol !== "undefined" && _getIteratorMethod(o) || o["@@iterator"]; if (!it) { if (_Array$isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + var _unicodeCategories = _interopRequireDefault(require('./addons/unicode-categories')) -function _unsupportedIterableToArray(o, minLen) { var _context4; if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = _sliceInstanceProperty(_context4 = Object.prototype.toString.call(o)).call(_context4, 8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return _Array$from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + var _unicodeProperties = _interopRequireDefault(require('./addons/unicode-properties')) -function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + var _unicodeScripts = _interopRequireDefault(require('./addons/unicode-scripts')) -/*! - * XRegExp Unicode Base 5.1.0 - * - * Steven Levithan (c) 2008-present MIT License - */ -var _default = function _default(XRegExp) { - /** - * Adds base support for Unicode matching: - * - Adds syntax `\p{..}` for matching Unicode tokens. Tokens can be inverted using `\P{..}` or - * `\p{^..}`. Token names ignore case, spaces, hyphens, and underscores. You can omit the - * braces for token names that are a single letter (e.g. `\pL` or `PL`). - * - Adds flag A (astral), which enables 21-bit Unicode support. - * - Adds the `XRegExp.addUnicodeData` method used by other addons to provide character data. - * - * Unicode Base relies on externally provided Unicode character data. Official addons are - * available to provide data for Unicode categories, scripts, and properties. - * - * @requires XRegExp - */ - // ==--------------------------== - // Private stuff - // ==--------------------------== - // Storage for Unicode data - var unicode = {}; - var unicodeTypes = {}; // Reuse utils + ;(0, _build['default'])(_xregexp['default']) + ;(0, _matchrecursive['default'])(_xregexp['default']) + ;(0, _unicodeBase['default'])(_xregexp['default']) + ;(0, _unicodeCategories['default'])(_xregexp['default']) + ;(0, _unicodeProperties['default'])(_xregexp['default']) + ;(0, _unicodeScripts['default'])(_xregexp['default']) + var _default = _xregexp['default'] + exports['default'] = _default + module.exports = exports.default + }, + { + './addons/build': 1, + './addons/matchrecursive': 2, + './addons/unicode-base': 3, + './addons/unicode-categories': 4, + './addons/unicode-properties': 5, + './addons/unicode-scripts': 6, + './xregexp': 8, + '@babel/runtime-corejs3/core-js-stable/object/define-property': 20, + '@babel/runtime-corejs3/helpers/interopRequireDefault': 30, + }, + ], + 8: [ + function (require, module, exports) { + 'use strict' - var dec = XRegExp._dec; - var hex = XRegExp._hex; - var pad4 = XRegExp._pad4; // Generates a token lookup name: lowercase, with hyphens, spaces, and underscores removed + var _sliceInstanceProperty2 = require('@babel/runtime-corejs3/core-js-stable/instance/slice') - function normalize(name) { - return name.replace(/[- _]+/g, '').toLowerCase(); - } // Gets the decimal code of a literal code unit, \xHH, \uHHHH, or a backslash-escaped literal + var _Array$from = require('@babel/runtime-corejs3/core-js-stable/array/from') + var _Symbol = require('@babel/runtime-corejs3/core-js-stable/symbol') - function charCode(chr) { - var esc = /^\\[xu](.+)/.exec(chr); - return esc ? dec(esc[1]) : chr.charCodeAt(chr[0] === '\\' ? 1 : 0); - } // Inverts a list of ordered BMP characters and ranges + var _getIteratorMethod = require('@babel/runtime-corejs3/core-js/get-iterator-method') + var _Array$isArray = require('@babel/runtime-corejs3/core-js-stable/array/is-array') - function invertBmp(range) { - var output = ''; - var lastEnd = -1; - (0, _forEach["default"])(XRegExp).call(XRegExp, range, /(\\x..|\\u....|\\?[\s\S])(?:-(\\x..|\\u....|\\?[\s\S]))?/, function (m) { - var start = charCode(m[1]); + var _Object$defineProperty = require('@babel/runtime-corejs3/core-js-stable/object/define-property') - if (start > lastEnd + 1) { - output += "\\u".concat(pad4(hex(lastEnd + 1))); + var _interopRequireDefault = require('@babel/runtime-corejs3/helpers/interopRequireDefault') - if (start > lastEnd + 2) { - output += "-\\u".concat(pad4(hex(start - 1))); - } - } + _Object$defineProperty(exports, '__esModule', { + value: true, + }) - lastEnd = charCode(m[2] || m[1]); - }); + exports['default'] = void 0 - if (lastEnd < 0xFFFF) { - output += "\\u".concat(pad4(hex(lastEnd + 1))); + var _slicedToArray2 = _interopRequireDefault(require('@babel/runtime-corejs3/helpers/slicedToArray')) - if (lastEnd < 0xFFFE) { - output += '-\\uFFFF'; - } - } + var _flags = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/flags')) - return output; - } // Generates an inverted BMP range on first use + var _sort = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/sort')) + var _slice = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/slice')) - function cacheInvertedBmp(slug) { - var prop = 'b!'; - return unicode[slug][prop] || (unicode[slug][prop] = invertBmp(unicode[slug].bmp)); - } // Combines and optionally negates BMP and astral data + var _parseInt2 = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/parse-int')) + var _indexOf = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/index-of')) - function buildAstral(slug, isNegated) { - var item = unicode[slug]; - var combined = ''; + var _forEach = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/for-each')) - if (item.bmp && !item.isBmpLast) { - var _context; + var _create = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/object/create')) - combined = (0, _concat["default"])(_context = "[".concat(item.bmp, "]")).call(_context, item.astral ? '|' : ''); - } + var _concat = _interopRequireDefault(require('@babel/runtime-corejs3/core-js-stable/instance/concat')) - if (item.astral) { - combined += item.astral; - } + function _createForOfIteratorHelper(o, allowArrayLike) { + var it = (typeof _Symbol !== 'undefined' && _getIteratorMethod(o)) || o['@@iterator'] + if (!it) { + if ( + _Array$isArray(o) || + (it = _unsupportedIterableToArray(o)) || + (allowArrayLike && o && typeof o.length === 'number') + ) { + if (it) o = it + var i = 0 + var F = function F() {} + return { + s: F, + n: function n() { + if (i >= o.length) return { done: true } + return { done: false, value: o[i++] } + }, + e: function e(_e) { + throw _e + }, + f: F, + } + } + throw new TypeError( + 'Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.' + ) + } + var normalCompletion = true, + didErr = false, + err + return { + s: function s() { + it = it.call(o) + }, + n: function n() { + var step = it.next() + normalCompletion = step.done + return step + }, + e: function e(_e2) { + didErr = true + err = _e2 + }, + f: function f() { + try { + if (!normalCompletion && it['return'] != null) it['return']() + } finally { + if (didErr) throw err + } + }, + } + } - if (item.isBmpLast && item.bmp) { - var _context2; + function _unsupportedIterableToArray(o, minLen) { + var _context9 + if (!o) return + if (typeof o === 'string') return _arrayLikeToArray(o, minLen) + var n = _sliceInstanceProperty2((_context9 = Object.prototype.toString.call(o))).call(_context9, 8, -1) + if (n === 'Object' && o.constructor) n = o.constructor.name + if (n === 'Map' || n === 'Set') return _Array$from(o) + if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) + return _arrayLikeToArray(o, minLen) + } - combined += (0, _concat["default"])(_context2 = "".concat(item.astral ? '|' : '', "[")).call(_context2, item.bmp, "]"); - } // Astral Unicode tokens always match a code point, never a code unit + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i] + } + return arr2 + } + /*! + * XRegExp 5.1.0 + * + * Steven Levithan (c) 2007-present MIT License + */ + + /** + * XRegExp provides augmented, extensible regular expressions. You get additional regex syntax and + * flags, beyond what browsers support natively. XRegExp is also a regex utility belt with tools to + * make your client-side grepping simpler and more powerful, while freeing you from related + * cross-browser inconsistencies. + */ + // ==--------------------------== + // Private stuff + // ==--------------------------== + // Property name used for extended regex instance data + var REGEX_DATA = 'xregexp' // Optional features that can be installed and uninstalled + + var features = { + astral: false, + namespacing: true, + } // Storage for fixed/extended native methods + + var fixed = {} // Storage for regexes cached by `XRegExp.cache` + + var regexCache = {} // Storage for pattern details cached by the `XRegExp` constructor + + var patternCache = {} // Storage for regex syntax tokens added internally or by `XRegExp.addToken` + + var tokens = [] // Token scopes + + var defaultScope = 'default' + var classScope = 'class' // Regexes that match native regex syntax, including octals + + var nativeTokens = { + // Any native multicharacter token in default scope, or any single character + default: + /\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u(?:[\dA-Fa-f]{4}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|\(\?(?:[:=!]|<[=!])|[?*+]\?|{\d+(?:,\d*)?}\??|[\s\S]/, + // Any native multicharacter token in character class scope, or any single character + class: + /\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u(?:[\dA-Fa-f]{4}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|[\s\S]/, + } // Any backreference or dollar-prefixed character in replacement strings + + var replacementToken = /\$(?:\{([^\}]+)\}|<([^>]+)>|(\d\d?|[\s\S]?))/g // Check for correct `exec` handling of nonparticipating capturing groups + + var correctExecNpcg = /()??/.exec('')[1] === undefined // Check for ES6 `flags` prop support + + var hasFlagsProp = (0, _flags['default'])(/x/) !== undefined + + function hasNativeFlag(flag) { + // Can't check based on the presence of properties/getters since browsers might support such + // properties even when they don't support the corresponding flag in regex construction (tested + // in Chrome 48, where `'unicode' in /x/` is true but trying to construct a regex with flag `u` + // throws an error) + var isSupported = true + + try { + // Can't use regex literals for testing even in a `try` because regex literals with + // unsupported flags cause a compilation error in IE + new RegExp('', flag) // Work around a broken/incomplete IE11 polyfill for sticky introduced in core-js 3.6.0 + + if (flag === 'y') { + // Using function to avoid babel transform to regex literal + var gy = (function () { + return 'gy' + })() + + var incompleteY = '.a'.replace(new RegExp('a', gy), '.') === '..' + + if (incompleteY) { + isSupported = false + } + } + } catch (exception) { + isSupported = false + } - return isNegated ? "(?:(?!".concat(combined, ")(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[\0-\uFFFF]))") : "(?:".concat(combined, ")"); - } // Builds a complete astral pattern on first use + return isSupported + } // Check for ES2018 `s` flag support + + var hasNativeS = hasNativeFlag('s') // Check for ES6 `u` flag support + + var hasNativeU = hasNativeFlag('u') // Check for ES6 `y` flag support + + var hasNativeY = hasNativeFlag('y') // Tracker for known flags, including addon flags + + var registeredFlags = { + g: true, + i: true, + m: true, + s: hasNativeS, + u: hasNativeU, + y: hasNativeY, + } // Flags to remove when passing to native `RegExp` constructor + + var nonnativeFlags = hasNativeS ? /[^gimsuy]+/g : /[^gimuy]+/g + /** + * Attaches extended data and `XRegExp.prototype` properties to a regex object. + * + * @private + * @param {RegExp} regex Regex to augment. + * @param {Array} captureNames Array with capture names, or `null`. + * @param {String} xSource XRegExp pattern used to generate `regex`, or `null` if N/A. + * @param {String} xFlags XRegExp flags used to generate `regex`, or `null` if N/A. + * @param {Boolean} [isInternalOnly=false] Whether the regex will be used only for internal + * operations, and never exposed to users. For internal-only regexes, we can improve perf by + * skipping some operations like attaching `XRegExp.prototype` properties. + * @returns {!RegExp} Augmented regex. + */ + + function augment(regex, captureNames, xSource, xFlags, isInternalOnly) { + var _context + + regex[REGEX_DATA] = { + captureNames: captureNames, + } + if (isInternalOnly) { + return regex + } // Can't auto-inherit these since the XRegExp constructor returns a nonprimitive value - function cacheAstral(slug, isNegated) { - var prop = isNegated ? 'a!' : 'a='; - return unicode[slug][prop] || (unicode[slug][prop] = buildAstral(slug, isNegated)); - } // ==--------------------------== - // Core functionality - // ==--------------------------== + if (regex.__proto__) { + regex.__proto__ = XRegExp.prototype + } else { + for (var p in XRegExp.prototype) { + // An `XRegExp.prototype.hasOwnProperty(p)` check wouldn't be worth it here, since this + // is performance sensitive, and enumerable `Object.prototype` or `RegExp.prototype` + // extensions exist on `regex.prototype` anyway + regex[p] = XRegExp.prototype[p] + } + } - /* - * Add astral mode (flag A) and Unicode token syntax: `\p{..}`, `\P{..}`, `\p{^..}`, `\pC`. - */ + regex[REGEX_DATA].source = xSource // Emulate the ES6 `flags` prop by ensuring flags are in alphabetical order + regex[REGEX_DATA].flags = xFlags + ? (0, _sort['default'])((_context = xFlags.split(''))) + .call(_context) + .join('') + : xFlags + return regex + } + /** + * Removes any duplicate characters from the provided string. + * + * @private + * @param {String} str String to remove duplicate characters from. + * @returns {string} String with any duplicate characters removed. + */ + + function clipDuplicates(str) { + return str.replace(/([\s\S])(?=[\s\S]*\1)/g, '') + } + /** + * Copies a regex object while preserving extended data and augmenting with `XRegExp.prototype` + * properties. The copy has a fresh `lastIndex` property (set to zero). Allows adding and removing + * flags g and y while copying the regex. + * + * @private + * @param {RegExp} regex Regex to copy. + * @param {Object} [options] Options object with optional properties: + * - `addG` {Boolean} Add flag g while copying the regex. + * - `addY` {Boolean} Add flag y while copying the regex. + * - `removeG` {Boolean} Remove flag g while copying the regex. + * - `removeY` {Boolean} Remove flag y while copying the regex. + * - `isInternalOnly` {Boolean} Whether the copied regex will be used only for internal + * operations, and never exposed to users. For internal-only regexes, we can improve perf by + * skipping some operations like attaching `XRegExp.prototype` properties. + * - `source` {String} Overrides `.source`, for special cases. + * @returns {RegExp} Copy of the provided regex, possibly with modified flags. + */ + + function copyRegex(regex, options) { + var _context2 + + if (!XRegExp.isRegExp(regex)) { + throw new TypeError('Type RegExp expected') + } - XRegExp.addToken( // Use `*` instead of `+` to avoid capturing `^` as the token name in `\p{^}` - /\\([pP])(?:{(\^?)(?:(\w+)=)?([^}]*)}|([A-Za-z]))/, function (match, scope, flags) { - var ERR_DOUBLE_NEG = 'Invalid double negation '; - var ERR_UNKNOWN_NAME = 'Unknown Unicode token '; - var ERR_UNKNOWN_REF = 'Unicode token missing data '; - var ERR_ASTRAL_ONLY = 'Astral mode required for Unicode token '; - var ERR_ASTRAL_IN_CLASS = 'Astral mode does not support Unicode tokens within character classes'; + var xData = regex[REGEX_DATA] || {} + var flags = getNativeFlags(regex) + var flagsToAdd = '' + var flagsToRemove = '' + var xregexpSource = null + var xregexpFlags = null + options = options || {} - var _match = (0, _slicedToArray2["default"])(match, 6), - fullToken = _match[0], - pPrefix = _match[1], - caretNegation = _match[2], - typePrefix = _match[3], - tokenName = _match[4], - tokenSingleCharName = _match[5]; // Negated via \P{..} or \p{^..} + if (options.removeG) { + flagsToRemove += 'g' + } + if (options.removeY) { + flagsToRemove += 'y' + } - var isNegated = pPrefix === 'P' || !!caretNegation; // Switch from BMP (0-FFFF) to astral (0-10FFFF) mode via flag A + if (flagsToRemove) { + flags = flags.replace(new RegExp('['.concat(flagsToRemove, ']+'), 'g'), '') + } - var isAstralMode = (0, _indexOf["default"])(flags).call(flags, 'A') !== -1; // Token lookup name. Check `tokenSingleCharName` first to avoid passing `undefined` - // via `\p{}` + if (options.addG) { + flagsToAdd += 'g' + } - var slug = normalize(tokenSingleCharName || tokenName); // Token data object + if (options.addY) { + flagsToAdd += 'y' + } - var item = unicode[slug]; + if (flagsToAdd) { + flags = clipDuplicates(flags + flagsToAdd) + } - if (pPrefix === 'P' && caretNegation) { - throw new SyntaxError(ERR_DOUBLE_NEG + fullToken); - } + if (!options.isInternalOnly) { + if (xData.source !== undefined) { + xregexpSource = xData.source + } // null or undefined; don't want to add to `flags` if the previous value was null, since + // that indicates we're not tracking original precompilation flags + + if ((0, _flags['default'])(xData) != null) { + // Flags are only added for non-internal regexes by `XRegExp.globalize`. Flags are never + // removed for non-internal regexes, so don't need to handle it + xregexpFlags = flagsToAdd + ? clipDuplicates((0, _flags['default'])(xData) + flagsToAdd) + : (0, _flags['default'])(xData) + } + } // Augment with `XRegExp.prototype` properties, but use the native `RegExp` constructor to avoid + // searching for special tokens. That would be wrong for regexes constructed by `RegExp`, and + // unnecessary for regexes constructed by `XRegExp` because the regex has already undergone the + // translation to native regex syntax + + regex = augment( + new RegExp(options.source || regex.source, flags), + hasNamedCapture(regex) + ? (0, _slice['default'])((_context2 = xData.captureNames)).call(_context2, 0) + : null, + xregexpSource, + xregexpFlags, + options.isInternalOnly + ) + return regex + } + /** + * Converts hexadecimal to decimal. + * + * @private + * @param {String} hex + * @returns {number} + */ + + function dec(hex) { + return (0, _parseInt2['default'])(hex, 16) + } + /** + * Returns a pattern that can be used in a native RegExp in place of an ignorable token such as an + * inline comment or whitespace with flag x. This is used directly as a token handler function + * passed to `XRegExp.addToken`. + * + * @private + * @param {String} match Match arg of `XRegExp.addToken` handler + * @param {String} scope Scope arg of `XRegExp.addToken` handler + * @param {String} flags Flags arg of `XRegExp.addToken` handler + * @returns {string} Either '' or '(?:)', depending on which is needed in the context of the match. + */ + + function getContextualTokenSeparator(match, scope, flags) { + var matchEndPos = match.index + match[0].length + var precedingChar = match.input[match.index - 1] + var followingChar = match.input[matchEndPos] + + if ( + // No need to separate tokens if at the beginning or end of a group, before or after a + // group, or before or after a `|` + /^[()|]$/.test(precedingChar) || + /^[()|]$/.test(followingChar) || // No need to separate tokens if at the beginning or end of the pattern + match.index === 0 || + matchEndPos === match.input.length || // No need to separate tokens if at the beginning of a noncapturing group or lookaround. + // Looks only at the last 4 chars (at most) for perf when constructing long regexes. + /\(\?(?:[:=!]|<[=!])$/.test(match.input.substring(match.index - 4, match.index)) || // Avoid separating tokens when the following token is a quantifier + isQuantifierNext(match.input, matchEndPos, flags) + ) { + return '' + } // Keep tokens separated. This avoids e.g. inadvertedly changing `\1 1` or `\1(?#)1` to `\11`. + // This also ensures all tokens remain as discrete atoms, e.g. it prevents converting the + // syntax error `(? :` into `(?:`. + + return '(?:)' + } + /** + * Returns native `RegExp` flags used by a regex object. + * + * @private + * @param {RegExp} regex Regex to check. + * @returns {string} Native flags in use. + */ + + function getNativeFlags(regex) { + return hasFlagsProp + ? (0, _flags['default'])(regex) // Explicitly using `RegExp.prototype.toString` (rather than e.g. `String` or concatenation + : // with an empty string) allows this to continue working predictably when + // `XRegExp.proptotype.toString` is overridden + /\/([a-z]*)$/i.exec(RegExp.prototype.toString.call(regex))[1] + } + /** + * Determines whether a regex has extended instance data used to track capture names. + * + * @private + * @param {RegExp} regex Regex to check. + * @returns {boolean} Whether the regex uses named capture. + */ + + function hasNamedCapture(regex) { + return !!(regex[REGEX_DATA] && regex[REGEX_DATA].captureNames) + } + /** + * Converts decimal to hexadecimal. + * + * @private + * @param {Number|String} dec + * @returns {string} + */ + + function hex(dec) { + return (0, _parseInt2['default'])(dec, 10).toString(16) + } + /** + * Checks whether the next nonignorable token after the specified position is a quantifier. + * + * @private + * @param {String} pattern Pattern to search within. + * @param {Number} pos Index in `pattern` to search at. + * @param {String} flags Flags used by the pattern. + * @returns {Boolean} Whether the next nonignorable token is a quantifier. + */ + + function isQuantifierNext(pattern, pos, flags) { + var inlineCommentPattern = '\\(\\?#[^)]*\\)' + var lineCommentPattern = '#[^#\\n]*' + var quantifierPattern = '[?*+]|{\\d+(?:,\\d*)?}' + var regex = + (0, _indexOf['default'])(flags).call(flags, 'x') !== -1 // Ignore any leading whitespace, line comments, and inline comments + ? /^(?:\s|#[^#\n]*|\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/ // Ignore any leading inline comments + : /^(?:\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/ + return regex.test((0, _slice['default'])(pattern).call(pattern, pos)) + } + /** + * Determines whether a value is of the specified type, by resolving its internal [[Class]]. + * + * @private + * @param {*} value Object to check. + * @param {String} type Type to check for, in TitleCase. + * @returns {boolean} Whether the object matches the type. + */ + + function isType(value, type) { + return Object.prototype.toString.call(value) === '[object '.concat(type, ']') + } + /** + * Returns the object, or throws an error if it is `null` or `undefined`. This is used to follow + * the ES5 abstract operation `ToObject`. + * + * @private + * @param {*} value Object to check and return. + * @returns {*} The provided object. + */ + + function nullThrows(value) { + // null or undefined + if (value == null) { + throw new TypeError('Cannot convert null or undefined to object') + } - if (!unicode.hasOwnProperty(slug)) { - throw new SyntaxError(ERR_UNKNOWN_NAME + fullToken); - } + return value + } + /** + * Adds leading zeros if shorter than four characters. Used for fixed-length hexadecimal values. + * + * @private + * @param {String} str + * @returns {string} + */ + + function pad4(str) { + while (str.length < 4) { + str = '0'.concat(str) + } - if (typePrefix) { - if (!(unicodeTypes[typePrefix] && unicodeTypes[typePrefix][slug])) { - throw new SyntaxError(ERR_UNKNOWN_NAME + fullToken); - } - } // Switch to the negated form of the referenced Unicode token + return str + } + /** + * Checks for flag-related errors, and strips/applies flags in a leading mode modifier. Offloads + * the flag preparation logic from the `XRegExp` constructor. + * + * @private + * @param {String} pattern Regex pattern, possibly with a leading mode modifier. + * @param {String} flags Any combination of flags. + * @returns {!Object} Object with properties `pattern` and `flags`. + */ + + function prepareFlags(pattern, flags) { + // Recent browsers throw on duplicate flags, so copy this behavior for nonnative flags + if (clipDuplicates(flags) !== flags) { + throw new SyntaxError('Invalid duplicate regex flag '.concat(flags)) + } // Strip and apply a leading mode modifier with any combination of flags except g or y + + pattern = pattern.replace(/^\(\?([\w$]+)\)/, function ($0, $1) { + if (/[gy]/.test($1)) { + throw new SyntaxError('Cannot use flag g or y in mode modifier '.concat($0)) + } // Allow duplicate flags within the mode modifier + + flags = clipDuplicates(flags + $1) + return '' + }) // Throw on unknown native or nonnative flags + + var _iterator = _createForOfIteratorHelper(flags), + _step + + try { + for (_iterator.s(); !(_step = _iterator.n()).done; ) { + var flag = _step.value + + if (!registeredFlags[flag]) { + throw new SyntaxError('Unknown regex flag '.concat(flag)) + } + } + } catch (err) { + _iterator.e(err) + } finally { + _iterator.f() + } + return { + pattern: pattern, + flags: flags, + } + } + /** + * Prepares an options object from the given value. + * + * @private + * @param {String|Object} value Value to convert to an options object. + * @returns {Object} Options object. + */ + + function prepareOptions(value) { + var options = {} + + if (isType(value, 'String')) { + ;(0, _forEach['default'])(XRegExp).call(XRegExp, value, /[^\s,]+/, function (match) { + options[match] = true + }) + return options + } - if (item.inverseOf) { - slug = normalize(item.inverseOf); + return value + } + /** + * Registers a flag so it doesn't throw an 'unknown flag' error. + * + * @private + * @param {String} flag Single-character flag to register. + */ + + function registerFlag(flag) { + if (!/^[\w$]$/.test(flag)) { + throw new Error('Flag must be a single character A-Za-z0-9_$') + } - if (!unicode.hasOwnProperty(slug)) { - var _context3; + registeredFlags[flag] = true + } + /** + * Runs built-in and custom regex syntax tokens in reverse insertion order at the specified + * position, until a match is found. + * + * @private + * @param {String} pattern Original pattern from which an XRegExp object is being built. + * @param {String} flags Flags being used to construct the regex. + * @param {Number} pos Position to search for tokens within `pattern`. + * @param {Number} scope Regex scope to apply: 'default' or 'class'. + * @param {Object} context Context object to use for token handler functions. + * @returns {Object} Object with properties `matchLength`, `output`, and `reparse`; or `null`. + */ + + function runTokens(pattern, flags, pos, scope, context) { + var i = tokens.length + var leadChar = pattern[pos] + var result = null + var match + var t // Run in reverse insertion order + + while (i--) { + t = tokens[i] + + if ( + (t.leadChar && t.leadChar !== leadChar) || + (t.scope !== scope && t.scope !== 'all') || + (t.flag && !((0, _indexOf['default'])(flags).call(flags, t.flag) !== -1)) + ) { + continue + } + + match = XRegExp.exec(pattern, t.regex, pos, 'sticky') + + if (match) { + result = { + matchLength: match[0].length, + output: t.handler.call(context, match, scope, flags), + reparse: t.reparse, + } // Finished with token tests + + break + } + } - throw new ReferenceError((0, _concat["default"])(_context3 = "".concat(ERR_UNKNOWN_REF + fullToken, " -> ")).call(_context3, item.inverseOf)); - } + return result + } + /** + * Enables or disables implicit astral mode opt-in. When enabled, flag A is automatically added to + * all new regexes created by XRegExp. This causes an error to be thrown when creating regexes if + * the Unicode Base addon is not available, since flag A is registered by that addon. + * + * @private + * @param {Boolean} on `true` to enable; `false` to disable. + */ + + function setAstral(on) { + features.astral = on + } + /** + * Adds named capture groups to the `groups` property of match arrays. See here for details: + * https://github.com/tc39/proposal-regexp-named-groups + * + * @private + * @param {Boolean} on `true` to enable; `false` to disable. + */ + + function setNamespacing(on) { + features.namespacing = on + } // ==--------------------------== + // Constructor + // ==--------------------------== + + /** + * Creates an extended regular expression object for matching text with a pattern. Differs from a + * native regular expression in that additional syntax and flags are supported. The returned object + * is in fact a native `RegExp` and works with all native methods. + * + * @class XRegExp + * @constructor + * @param {String|RegExp} pattern Regex pattern string, or an existing regex object to copy. + * @param {String} [flags] Any combination of flags. + * Native flags: + * - `g` - global + * - `i` - ignore case + * - `m` - multiline anchors + * - `u` - unicode (ES6) + * - `y` - sticky (Firefox 3+, ES6) + * Additional XRegExp flags: + * - `n` - explicit capture + * - `s` - dot matches all (aka singleline) - works even when not natively supported + * - `x` - free-spacing and line comments (aka extended) + * - `A` - astral (requires the Unicode Base addon) + * Flags cannot be provided when constructing one `RegExp` from another. + * @returns {RegExp} Extended regular expression object. + * @example + * + * // With named capture and flag x + * XRegExp(`(? [0-9]{4} ) -? # year + * (? [0-9]{2} ) -? # month + * (? [0-9]{2} ) # day`, 'x'); + * + * // Providing a regex object copies it. Native regexes are recompiled using native (not XRegExp) + * // syntax. Copies maintain extended data, are augmented with `XRegExp.prototype` properties, and + * // have fresh `lastIndex` properties (set to zero). + * XRegExp(/regex/); + */ + + function XRegExp(pattern, flags) { + if (XRegExp.isRegExp(pattern)) { + if (flags !== undefined) { + throw new TypeError('Cannot supply flags when copying a RegExp') + } + + return copyRegex(pattern) + } // Copy the argument behavior of `RegExp` + + pattern = pattern === undefined ? '' : String(pattern) + flags = flags === undefined ? '' : String(flags) + + if (XRegExp.isInstalled('astral') && !((0, _indexOf['default'])(flags).call(flags, 'A') !== -1)) { + // This causes an error to be thrown if the Unicode Base addon is not available + flags += 'A' + } - item = unicode[slug]; - isNegated = !isNegated; - } + if (!patternCache[pattern]) { + patternCache[pattern] = {} + } - if (!(item.bmp || isAstralMode)) { - throw new SyntaxError(ERR_ASTRAL_ONLY + fullToken); - } + if (!patternCache[pattern][flags]) { + var context = { + hasNamedCapture: false, + captureNames: [], + } + var scope = defaultScope + var output = '' + var pos = 0 + var result // Check for flag-related errors, and strip/apply flags in a leading mode modifier + + var applied = prepareFlags(pattern, flags) + var appliedPattern = applied.pattern + var appliedFlags = (0, _flags['default'])(applied) // Use XRegExp's tokens to translate the pattern to a native regex pattern. + // `appliedPattern.length` may change on each iteration if tokens use `reparse` + + while (pos < appliedPattern.length) { + do { + // Check for custom tokens at the current position + result = runTokens(appliedPattern, appliedFlags, pos, scope, context) // If the matched token used the `reparse` option, splice its output into the + // pattern before running tokens again at the same position + + if (result && result.reparse) { + appliedPattern = + (0, _slice['default'])(appliedPattern).call(appliedPattern, 0, pos) + + result.output + + (0, _slice['default'])(appliedPattern).call(appliedPattern, pos + result.matchLength) + } + } while (result && result.reparse) + + if (result) { + output += result.output + pos += result.matchLength || 1 + } else { + // Get the native token at the current position + var _XRegExp$exec = XRegExp.exec(appliedPattern, nativeTokens[scope], pos, 'sticky'), + _XRegExp$exec2 = (0, _slicedToArray2['default'])(_XRegExp$exec, 1), + token = _XRegExp$exec2[0] + + output += token + pos += token.length + + if (token === '[' && scope === defaultScope) { + scope = classScope + } else if (token === ']' && scope === classScope) { + scope = defaultScope + } + } + } + + patternCache[pattern][flags] = { + // Use basic cleanup to collapse repeated empty groups like `(?:)(?:)` to `(?:)`. Empty + // groups are sometimes inserted during regex transpilation in order to keep tokens + // separated. However, more than one empty group in a row is never needed. + pattern: output.replace(/(?:\(\?:\))+/g, '(?:)'), + // Strip all but native flags + flags: appliedFlags.replace(nonnativeFlags, ''), + // `context.captureNames` has an item for each capturing group, even if unnamed + captures: context.hasNamedCapture ? context.captureNames : null, + } + } - if (isAstralMode) { - if (scope === 'class') { - throw new SyntaxError(ERR_ASTRAL_IN_CLASS); - } + var generated = patternCache[pattern][flags] + return augment( + new RegExp(generated.pattern, (0, _flags['default'])(generated)), + generated.captures, + pattern, + flags + ) + } // Add `RegExp.prototype` to the prototype chain + + XRegExp.prototype = /(?:)/ // ==--------------------------== + // Public properties + // ==--------------------------== + + /** + * The XRegExp version number as a string containing three dot-separated parts. For example, + * '2.0.0-beta-3'. + * + * @static + * @memberOf XRegExp + * @type String + */ + + XRegExp.version = '5.1.0' // ==--------------------------== + // Public methods + // ==--------------------------== + // Intentionally undocumented; used in tests and addons + + XRegExp._clipDuplicates = clipDuplicates + XRegExp._hasNativeFlag = hasNativeFlag + XRegExp._dec = dec + XRegExp._hex = hex + XRegExp._pad4 = pad4 + /** + * Extends XRegExp syntax and allows custom flags. This is used internally and can be used to + * create XRegExp addons. If more than one token can match the same string, the last added wins. + * + * @memberOf XRegExp + * @param {RegExp} regex Regex object that matches the new token. + * @param {Function} handler Function that returns a new pattern string (using native regex syntax) + * to replace the matched token within all future XRegExp regexes. Has access to persistent + * properties of the regex being built, through `this`. Invoked with three arguments: + * - The match array, with named backreference properties. + * - The regex scope where the match was found: 'default' or 'class'. + * - The flags used by the regex, including any flags in a leading mode modifier. + * The handler function becomes part of the XRegExp construction process, so be careful not to + * construct XRegExps within the function or you will trigger infinite recursion. + * @param {Object} [options] Options object with optional properties: + * - `scope` {String} Scope where the token applies: 'default', 'class', or 'all'. + * - `flag` {String} Single-character flag that triggers the token. This also registers the + * flag, which prevents XRegExp from throwing an 'unknown flag' error when the flag is used. + * - `optionalFlags` {String} Any custom flags checked for within the token `handler` that are + * not required to trigger the token. This registers the flags, to prevent XRegExp from + * throwing an 'unknown flag' error when any of the flags are used. + * - `reparse` {Boolean} Whether the `handler` function's output should not be treated as + * final, and instead be reparseable by other tokens (including the current token). Allows + * token chaining or deferring. + * - `leadChar` {String} Single character that occurs at the beginning of any successful match + * of the token (not always applicable). This doesn't change the behavior of the token unless + * you provide an erroneous value. However, providing it can increase the token's performance + * since the token can be skipped at any positions where this character doesn't appear. + * @example + * + * // Basic usage: Add \a for the ALERT control code + * XRegExp.addToken( + * /\\a/, + * () => '\\x07', + * {scope: 'all'} + * ); + * XRegExp('\\a[\\a-\\n]+').test('\x07\n\x07'); // -> true + * + * // Add the U (ungreedy) flag from PCRE and RE2, which reverses greedy and lazy quantifiers. + * // Since `scope` is not specified, it uses 'default' (i.e., transformations apply outside of + * // character classes only) + * XRegExp.addToken( + * /([?*+]|{\d+(?:,\d*)?})(\??)/, + * (match) => `${match[1]}${match[2] ? '' : '?'}`, + * {flag: 'U'} + * ); + * XRegExp('a+', 'U').exec('aaa')[0]; // -> 'a' + * XRegExp('a+?', 'U').exec('aaa')[0]; // -> 'aaa' + */ + + XRegExp.addToken = function (regex, handler, options) { + options = options || {} + var _options = options, + optionalFlags = _options.optionalFlags + + if (options.flag) { + registerFlag(options.flag) + } - return cacheAstral(slug, isNegated); - } + if (optionalFlags) { + optionalFlags = optionalFlags.split('') + + var _iterator2 = _createForOfIteratorHelper(optionalFlags), + _step2 + + try { + for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) { + var flag = _step2.value + registerFlag(flag) + } + } catch (err) { + _iterator2.e(err) + } finally { + _iterator2.f() + } + } // Add to the private list of syntax tokens + + tokens.push({ + regex: copyRegex(regex, { + addG: true, + addY: hasNativeY, + isInternalOnly: true, + }), + handler: handler, + scope: options.scope || defaultScope, + flag: options.flag, + reparse: options.reparse, + leadChar: options.leadChar, + }) // Reset the pattern cache used by the `XRegExp` constructor, since the same pattern and flags + // might now produce different results + + XRegExp.cache.flush('patterns') + } + /** + * Caches and returns the result of calling `XRegExp(pattern, flags)`. On any subsequent call with + * the same pattern and flag combination, the cached copy of the regex is returned. + * + * @memberOf XRegExp + * @param {String} pattern Regex pattern string. + * @param {String} [flags] Any combination of XRegExp flags. + * @returns {RegExp} Cached XRegExp object. + * @example + * + * let match; + * while (match = XRegExp.cache('.', 'gs').exec('abc')) { + * // The regex is compiled once only + * } + */ + + XRegExp.cache = function (pattern, flags) { + if (!regexCache[pattern]) { + regexCache[pattern] = {} + } - return scope === 'class' ? isNegated ? cacheInvertedBmp(slug) : item.bmp : "".concat((isNegated ? '[^' : '[') + item.bmp, "]"); - }, { - scope: 'all', - optionalFlags: 'A', - leadChar: '\\' - }); - /** - * Adds to the list of Unicode tokens that XRegExp regexes can match via `\p` or `\P`. - * - * @memberOf XRegExp - * @param {Array} data Objects with named character ranges. Each object may have properties - * `name`, `alias`, `isBmpLast`, `inverseOf`, `bmp`, and `astral`. All but `name` are - * optional, although one of `bmp` or `astral` is required (unless `inverseOf` is set). If - * `astral` is absent, the `bmp` data is used for BMP and astral modes. If `bmp` is absent, - * the name errors in BMP mode but works in astral mode. If both `bmp` and `astral` are - * provided, the `bmp` data only is used in BMP mode, and the combination of `bmp` and - * `astral` data is used in astral mode. `isBmpLast` is needed when a token matches orphan - * high surrogates *and* uses surrogate pairs to match astral code points. The `bmp` and - * `astral` data should be a combination of literal characters and `\xHH` or `\uHHHH` escape - * sequences, with hyphens to create ranges. Any regex metacharacters in the data should be - * escaped, apart from range-creating hyphens. The `astral` data can additionally use - * character classes and alternation, and should use surrogate pairs to represent astral code - * points. `inverseOf` can be used to avoid duplicating character data if a Unicode token is - * defined as the exact inverse of another token. - * @param {String} [typePrefix] Enables optionally using this type as a prefix for all of the - * provided Unicode tokens, e.g. if given `'Type'`, then `\p{TokenName}` can also be written - * as `\p{Type=TokenName}`. - * @example - * - * // Basic use - * XRegExp.addUnicodeData([{ - * name: 'XDigit', - * alias: 'Hexadecimal', - * bmp: '0-9A-Fa-f' - * }]); - * XRegExp('\\p{XDigit}:\\p{Hexadecimal}+').test('0:3D'); // -> true - */ - - XRegExp.addUnicodeData = function (data, typePrefix) { - var ERR_NO_NAME = 'Unicode token requires name'; - var ERR_NO_DATA = 'Unicode token has no character data '; - - if (typePrefix) { - // Case sensitive to match ES2018 - unicodeTypes[typePrefix] = {}; - } + return regexCache[pattern][flags] || (regexCache[pattern][flags] = XRegExp(pattern, flags)) + } // Intentionally undocumented; used in tests - var _iterator = _createForOfIteratorHelper(data), - _step; + XRegExp.cache.flush = function (cacheName) { + if (cacheName === 'patterns') { + // Flush the pattern cache used by the `XRegExp` constructor + patternCache = {} + } else { + // Flush the regex cache populated by `XRegExp.cache` + regexCache = {} + } + } + /** + * Escapes any regular expression metacharacters, for use when matching literal strings. The result + * can safely be used at any position within a regex that uses any flags. + * + * @memberOf XRegExp + * @param {String} str String to escape. + * @returns {string} String with regex metacharacters escaped. + * @example + * + * XRegExp.escape('Escaped? <.>'); + * // -> 'Escaped\?\u0020<\.>' + */ + // Following are the contexts where each metacharacter needs to be escaped because it would + // otherwise have a special meaning, change the meaning of surrounding characters, or cause an + // error. Context 'default' means outside character classes only. + // - `\` - context: all + // - `[()*+?.$|` - context: default + // - `]` - context: default with flag u or if forming the end of a character class + // - `{}` - context: default with flag u or if part of a valid/complete quantifier pattern + // - `,` - context: default if in a position that causes an unescaped `{` to turn into a quantifier. + // Ex: `/^a{1\,2}$/` matches `'a{1,2}'`, but `/^a{1,2}$/` matches `'a'` or `'aa'` + // - `#` and - context: default with flag x + // - `^` - context: default, and context: class if it's the first character in the class + // - `-` - context: class if part of a valid character class range + + XRegExp.escape = function (str) { + return String(nullThrows(str)) // Escape most special chars with a backslash + .replace(/[\\\[\]{}()*+?.^$|]/g, '\\$&') // Convert to \uNNNN for special chars that can't be escaped when used with ES6 flag `u` + .replace(/[\s#\-,]/g, function (match) { + return '\\u'.concat(pad4(hex(match.charCodeAt(0)))) + }) + } + /** + * Executes a regex search in a specified string. Returns a match array or `null`. If the provided + * regex uses named capture, named capture properties are included on the match array's `groups` + * property. Optional `pos` and `sticky` arguments specify the search start position, and whether + * the match must start at the specified position only. The `lastIndex` property of the provided + * regex is not used, but is updated for compatibility. Also fixes browser bugs compared to the + * native `RegExp.prototype.exec` and can be used reliably cross-browser. + * + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp} regex Regex to search with. + * @param {Number} [pos=0] Zero-based index at which to start the search. + * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position + * only. The string `'sticky'` is accepted as an alternative to `true`. + * @returns {Array} Match array with named capture properties on the `groups` object, or `null`. If + * the `namespacing` feature is off, named capture properties are directly on the match array. + * @example + * + * // Basic use, with named capturing group + * let match = XRegExp.exec('U+2620', XRegExp('U\\+(?[0-9A-F]{4})')); + * match.groups.hex; // -> '2620' + * + * // With pos and sticky, in a loop + * let pos = 2, result = [], match; + * while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) { + * result.push(match[1]); + * pos = match.index + match[0].length; + * } + * // result -> ['2', '3', '4'] + */ + + XRegExp.exec = function (str, regex, pos, sticky) { + var cacheKey = 'g' + var addY = false + var fakeY = false + var match + addY = hasNativeY && !!(sticky || (regex.sticky && sticky !== false)) + + if (addY) { + cacheKey += 'y' + } else if (sticky) { + // Simulate sticky matching by appending an empty capture to the original regex. The + // resulting regex will succeed no matter what at the current index (set with `lastIndex`), + // and will not search the rest of the subject string. We'll know that the original regex + // has failed if that last capture is `''` rather than `undefined` (i.e., if that last + // capture participated in the match). + fakeY = true + cacheKey += 'FakeY' + } - try { - for (_iterator.s(); !(_step = _iterator.n()).done;) { - var item = _step.value; + regex[REGEX_DATA] = regex[REGEX_DATA] || {} // Shares cached copies with `XRegExp.match`/`replace` + + var r2 = + regex[REGEX_DATA][cacheKey] || + (regex[REGEX_DATA][cacheKey] = copyRegex(regex, { + addG: true, + addY: addY, + source: fakeY ? ''.concat(regex.source, '|()') : undefined, + removeY: sticky === false, + isInternalOnly: true, + })) + pos = pos || 0 + r2.lastIndex = pos // Fixed `exec` required for `lastIndex` fix, named backreferences, etc. + + match = fixed.exec.call(r2, str) // Get rid of the capture added by the pseudo-sticky matcher if needed. An empty string means + // the original regexp failed (see above). + + if (fakeY && match && match.pop() === '') { + match = null + } - if (!item.name) { - throw new Error(ERR_NO_NAME); - } + if (regex.global) { + regex.lastIndex = match ? r2.lastIndex : 0 + } - if (!(item.inverseOf || item.bmp || item.astral)) { - throw new Error(ERR_NO_DATA + item.name); - } + return match + } + /** + * Executes a provided function once per regex match. Searches always start at the beginning of the + * string and continue until the end, regardless of the state of the regex's `global` property and + * initial `lastIndex`. + * + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp} regex Regex to search with. + * @param {Function} callback Function to execute for each match. Invoked with four arguments: + * - The match array, with named backreference properties. + * - The zero-based match index. + * - The string being traversed. + * - The regex object being used to traverse the string. + * @example + * + * // Extracts every other digit from a string + * const evens = []; + * XRegExp.forEach('1a2345', /\d/, (match, i) => { + * if (i % 2) evens.push(+match[0]); + * }); + * // evens -> [2, 4] + */ + + XRegExp.forEach = function (str, regex, callback) { + var pos = 0 + var i = -1 + var match + + while ((match = XRegExp.exec(str, regex, pos))) { + // Because `regex` is provided to `callback`, the function could use the deprecated/ + // nonstandard `RegExp.prototype.compile` to mutate the regex. However, since `XRegExp.exec` + // doesn't use `lastIndex` to set the search position, this can't lead to an infinite loop, + // at least. Actually, because of the way `XRegExp.exec` caches globalized versions of + // regexes, mutating the regex will not have any effect on the iteration or matched strings, + // which is a nice side effect that brings extra safety. + callback(match, ++i, str, regex) + pos = match.index + (match[0].length || 1) + } + } + /** + * Copies a regex object and adds flag `g`. The copy maintains extended data, is augmented with + * `XRegExp.prototype` properties, and has a fresh `lastIndex` property (set to zero). Native + * regexes are not recompiled using XRegExp syntax. + * + * @memberOf XRegExp + * @param {RegExp} regex Regex to globalize. + * @returns {RegExp} Copy of the provided regex with flag `g` added. + * @example + * + * const globalCopy = XRegExp.globalize(/regex/); + * globalCopy.global; // -> true + */ + + XRegExp.globalize = function (regex) { + return copyRegex(regex, { + addG: true, + }) + } + /** + * Installs optional features according to the specified options. Can be undone using + * `XRegExp.uninstall`. + * + * @memberOf XRegExp + * @param {Object|String} options Options object or string. + * @example + * + * // With an options object + * XRegExp.install({ + * // Enables support for astral code points in Unicode addons (implicitly sets flag A) + * astral: true, + * + * // Adds named capture groups to the `groups` property of matches + * namespacing: true + * }); + * + * // With an options string + * XRegExp.install('astral namespacing'); + */ + + XRegExp.install = function (options) { + options = prepareOptions(options) + + if (!features.astral && options.astral) { + setAstral(true) + } - var normalizedName = normalize(item.name); - unicode[normalizedName] = item; + if (!features.namespacing && options.namespacing) { + setNamespacing(true) + } + } + /** + * Checks whether an individual optional feature is installed. + * + * @memberOf XRegExp + * @param {String} feature Name of the feature to check. One of: + * - `astral` + * - `namespacing` + * @returns {boolean} Whether the feature is installed. + * @example + * + * XRegExp.isInstalled('astral'); + */ + + XRegExp.isInstalled = function (feature) { + return !!features[feature] + } + /** + * Returns `true` if an object is a regex; `false` if it isn't. This works correctly for regexes + * created in another frame, when `instanceof` and `constructor` checks would fail. + * + * @memberOf XRegExp + * @param {*} value Object to check. + * @returns {boolean} Whether the object is a `RegExp` object. + * @example + * + * XRegExp.isRegExp('string'); // -> false + * XRegExp.isRegExp(/regex/i); // -> true + * XRegExp.isRegExp(RegExp('^', 'm')); // -> true + * XRegExp.isRegExp(XRegExp('(?s).')); // -> true + */ + + XRegExp.isRegExp = function (value) { + return Object.prototype.toString.call(value) === '[object RegExp]' + } // Same as `isType(value, 'RegExp')`, but avoiding that function call here for perf since + // `isRegExp` is used heavily by internals including regex construction + + /** + * Returns the first matched string, or in global mode, an array containing all matched strings. + * This is essentially a more convenient re-implementation of `String.prototype.match` that gives + * the result types you actually want (string instead of `exec`-style array in match-first mode, + * and an empty array instead of `null` when no matches are found in match-all mode). It also lets + * you override flag g and ignore `lastIndex`, and fixes browser bugs. + * + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp} regex Regex to search with. + * @param {String} [scope='one'] Use 'one' to return the first match as a string. Use 'all' to + * return an array of all matched strings. If not explicitly specified and `regex` uses flag g, + * `scope` is 'all'. + * @returns {String|Array} In match-first mode: First match as a string, or `null`. In match-all + * mode: Array of all matched strings, or an empty array. + * @example + * + * // Match first + * XRegExp.match('abc', /\w/); // -> 'a' + * XRegExp.match('abc', /\w/g, 'one'); // -> 'a' + * XRegExp.match('abc', /x/g, 'one'); // -> null + * + * // Match all + * XRegExp.match('abc', /\w/g); // -> ['a', 'b', 'c'] + * XRegExp.match('abc', /\w/, 'all'); // -> ['a', 'b', 'c'] + * XRegExp.match('abc', /x/, 'all'); // -> [] + */ + + XRegExp.match = function (str, regex, scope) { + var global = (regex.global && scope !== 'one') || scope === 'all' + var cacheKey = (global ? 'g' : '') + (regex.sticky ? 'y' : '') || 'noGY' + regex[REGEX_DATA] = regex[REGEX_DATA] || {} // Shares cached copies with `XRegExp.exec`/`replace` + + var r2 = + regex[REGEX_DATA][cacheKey] || + (regex[REGEX_DATA][cacheKey] = copyRegex(regex, { + addG: !!global, + removeG: scope === 'one', + isInternalOnly: true, + })) + var result = String(nullThrows(str)).match(r2) + + if (regex.global) { + regex.lastIndex = scope === 'one' && result ? result.index + result[0].length : 0 + } - if (typePrefix) { - unicodeTypes[typePrefix][normalizedName] = true; - } + return global ? result || [] : result && result[0] + } + /** + * Retrieves the matches from searching a string using a chain of regexes that successively search + * within previous matches. The provided `chain` array can contain regexes and or objects with + * `regex` and `backref` properties. When a backreference is specified, the named or numbered + * backreference is passed forward to the next regex or returned. + * + * @memberOf XRegExp + * @param {String} str String to search. + * @param {Array} chain Regexes that each search for matches within preceding results. + * @returns {Array} Matches by the last regex in the chain, or an empty array. + * @example + * + * // Basic usage; matches numbers within tags + * XRegExp.matchChain('1 2 3 4 a 56', [ + * XRegExp('(?is).*?'), + * /\d+/ + * ]); + * // -> ['2', '4', '56'] + * + * // Passing forward and returning specific backreferences + * const html = `XRegExp + * Google`; + * XRegExp.matchChain(html, [ + * {regex: //i, backref: 1}, + * {regex: XRegExp('(?i)^https?://(?[^/?#]+)'), backref: 'domain'} + * ]); + * // -> ['xregexp.com', 'www.google.com'] + */ + + XRegExp.matchChain = function (str, chain) { + return (function recurseChain(values, level) { + var item = chain[level].regex + ? chain[level] + : { + regex: chain[level], + } + var matches = [] + + function addMatch(match) { + if (item.backref) { + var ERR_UNDEFINED_GROUP = 'Backreference to undefined group: '.concat(item.backref) + var isNamedBackref = isNaN(item.backref) + + if (isNamedBackref && XRegExp.isInstalled('namespacing')) { + // `groups` has `null` as prototype, so using `in` instead of `hasOwnProperty` + if (!(match.groups && item.backref in match.groups)) { + throw new ReferenceError(ERR_UNDEFINED_GROUP) + } + } else if (!match.hasOwnProperty(item.backref)) { + throw new ReferenceError(ERR_UNDEFINED_GROUP) + } + + var backrefValue = + isNamedBackref && XRegExp.isInstalled('namespacing') + ? match.groups[item.backref] + : match[item.backref] + matches.push(backrefValue || '') + } else { + matches.push(match[0]) + } + } + + var _iterator3 = _createForOfIteratorHelper(values), + _step3 + + try { + for (_iterator3.s(); !(_step3 = _iterator3.n()).done; ) { + var value = _step3.value + ;(0, _forEach['default'])(XRegExp).call(XRegExp, value, item.regex, addMatch) + } + } catch (err) { + _iterator3.e(err) + } finally { + _iterator3.f() + } + + return level === chain.length - 1 || !matches.length ? matches : recurseChain(matches, level + 1) + })([str], 0) + } + /** + * Returns a new string with one or all matches of a pattern replaced. The pattern can be a string + * or regex, and the replacement can be a string or a function to be called for each match. To + * perform a global search and replace, use the optional `scope` argument or include flag g if using + * a regex. Replacement strings can use `$` or `${n}` for named and numbered backreferences. + * Replacement functions can use named backreferences via the last argument. Also fixes browser bugs + * compared to the native `String.prototype.replace` and can be used reliably cross-browser. + * + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp|String} search Search pattern to be replaced. + * @param {String|Function} replacement Replacement string or a function invoked to create it. + * Replacement strings can include special replacement syntax: + * - $$ - Inserts a literal $ character. + * - $&, $0 - Inserts the matched substring. + * - $` - Inserts the string that precedes the matched substring (left context). + * - $' - Inserts the string that follows the matched substring (right context). + * - $n, $nn - Where n/nn are digits referencing an existing capturing group, inserts + * backreference n/nn. + * - $, ${n} - Where n is a name or any number of digits that reference an existing capturing + * group, inserts backreference n. + * Replacement functions are invoked with three or more arguments: + * - args[0] - The matched substring (corresponds to `$&` above). If the `namespacing` feature + * is off, named backreferences are accessible as properties of this argument. + * - args[1..n] - One argument for each backreference (corresponding to `$1`, `$2`, etc. above). + * If the regex has no capturing groups, no arguments appear in this position. + * - args[n+1] - The zero-based index of the match within the entire search string. + * - args[n+2] - The total string being searched. + * - args[n+3] - If the the search pattern is a regex with named capturing groups, the last + * argument is the groups object. Its keys are the backreference names and its values are the + * backreference values. If the `namespacing` feature is off, this argument is not present. + * @param {String} [scope] Use 'one' to replace the first match only, or 'all'. Defaults to 'one'. + * Defaults to 'all' if using a regex with flag g. + * @returns {String} New string with one or all matches replaced. + * @example + * + * // Regex search, using named backreferences in replacement string + * const name = XRegExp('(?\\w+) (?\\w+)'); + * XRegExp.replace('John Smith', name, '$, $'); + * // -> 'Smith, John' + * + * // Regex search, using named backreferences in replacement function + * XRegExp.replace('John Smith', name, (...args) => { + * const groups = args[args.length - 1]; + * return `${groups.last}, ${groups.first}`; + * }); + * // -> 'Smith, John' + * + * // String search, with replace-all + * XRegExp.replace('RegExp builds RegExps', 'RegExp', 'XRegExp', 'all'); + * // -> 'XRegExp builds XRegExps' + */ + + XRegExp.replace = function (str, search, replacement, scope) { + var isRegex = XRegExp.isRegExp(search) + var global = (search.global && scope !== 'one') || scope === 'all' + var cacheKey = (global ? 'g' : '') + (search.sticky ? 'y' : '') || 'noGY' + var s2 = search + + if (isRegex) { + search[REGEX_DATA] = search[REGEX_DATA] || {} // Shares cached copies with `XRegExp.exec`/`match`. Since a copy is used, `search`'s + // `lastIndex` isn't updated *during* replacement iterations + + s2 = + search[REGEX_DATA][cacheKey] || + (search[REGEX_DATA][cacheKey] = copyRegex(search, { + addG: !!global, + removeG: scope === 'one', + isInternalOnly: true, + })) + } else if (global) { + s2 = new RegExp(XRegExp.escape(String(search)), 'g') + } // Fixed `replace` required for named backreferences, etc. + + var result = fixed.replace.call(nullThrows(str), s2, replacement) + + if (isRegex && search.global) { + // Fixes IE, Safari bug (last tested IE 9, Safari 5.1) + search.lastIndex = 0 + } - if (item.alias) { - var normalizedAlias = normalize(item.alias); - unicode[normalizedAlias] = item; + return result + } + /** + * Performs batch processing of string replacements. Used like `XRegExp.replace`, but accepts an + * array of replacement details. Later replacements operate on the output of earlier replacements. + * Replacement details are accepted as an array with a regex or string to search for, the + * replacement string or function, and an optional scope of 'one' or 'all'. Uses the XRegExp + * replacement text syntax, which supports named backreference properties via `$` or + * `${name}`. + * + * @memberOf XRegExp + * @param {String} str String to search. + * @param {Array} replacements Array of replacement detail arrays. + * @returns {String} New string with all replacements. + * @example + * + * str = XRegExp.replaceEach(str, [ + * [XRegExp('(?a)'), 'z$'], + * [/b/gi, 'y'], + * [/c/g, 'x', 'one'], // scope 'one' overrides /g + * [/d/, 'w', 'all'], // scope 'all' overrides lack of /g + * ['e', 'v', 'all'], // scope 'all' allows replace-all for strings + * [/f/g, (match) => match.toUpperCase()] + * ]); + */ + + XRegExp.replaceEach = function (str, replacements) { + var _iterator4 = _createForOfIteratorHelper(replacements), + _step4 + + try { + for (_iterator4.s(); !(_step4 = _iterator4.n()).done; ) { + var r = _step4.value + str = XRegExp.replace(str, r[0], r[1], r[2]) + } + } catch (err) { + _iterator4.e(err) + } finally { + _iterator4.f() + } - if (typePrefix) { - unicodeTypes[typePrefix][normalizedAlias] = true; + return str } - } - } // Reset the pattern cache used by the `XRegExp` constructor, since the same pattern and - // flags might now produce different results + /** + * Splits a string into an array of strings using a regex or string separator. Matches of the + * separator are not included in the result array. However, if `separator` is a regex that contains + * capturing groups, backreferences are spliced into the result each time `separator` is matched. + * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably + * cross-browser. + * + * @memberOf XRegExp + * @param {String} str String to split. + * @param {RegExp|String} separator Regex or string to use for separating the string. + * @param {Number} [limit] Maximum number of items to include in the result array. + * @returns {Array} Array of substrings. + * @example + * + * // Basic use + * XRegExp.split('a b c', ' '); + * // -> ['a', 'b', 'c'] + * + * // With limit + * XRegExp.split('a b c', ' ', 2); + * // -> ['a', 'b'] + * + * // Backreferences in result array + * XRegExp.split('..word1..', /([a-z]+)(\d+)/i); + * // -> ['..', 'word', '1', '..'] + */ + + XRegExp.split = function (str, separator, limit) { + return fixed.split.call(nullThrows(str), separator, limit) + } + /** + * Executes a regex search in a specified string. Returns `true` or `false`. Optional `pos` and + * `sticky` arguments specify the search start position, and whether the match must start at the + * specified position only. The `lastIndex` property of the provided regex is not used, but is + * updated for compatibility. Also fixes browser bugs compared to the native + * `RegExp.prototype.test` and can be used reliably cross-browser. + * + * @memberOf XRegExp + * @param {String} str String to search. + * @param {RegExp} regex Regex to search with. + * @param {Number} [pos=0] Zero-based index at which to start the search. + * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position + * only. The string `'sticky'` is accepted as an alternative to `true`. + * @returns {boolean} Whether the regex matched the provided value. + * @example + * + * // Basic use + * XRegExp.test('abc', /c/); // -> true + * + * // With pos and sticky + * XRegExp.test('abc', /c/, 0, 'sticky'); // -> false + * XRegExp.test('abc', /c/, 2, 'sticky'); // -> true + */ + // Do this the easy way :-) + + XRegExp.test = function (str, regex, pos, sticky) { + return !!XRegExp.exec(str, regex, pos, sticky) + } + /** + * Uninstalls optional features according to the specified options. Used to undo the actions of + * `XRegExp.install`. + * + * @memberOf XRegExp + * @param {Object|String} options Options object or string. + * @example + * + * // With an options object + * XRegExp.uninstall({ + * // Disables support for astral code points in Unicode addons (unless enabled per regex) + * astral: true, + * + * // Don't add named capture groups to the `groups` property of matches + * namespacing: true + * }); + * + * // With an options string + * XRegExp.uninstall('astral namespacing'); + */ + + XRegExp.uninstall = function (options) { + options = prepareOptions(options) + + if (features.astral && options.astral) { + setAstral(false) + } - } catch (err) { - _iterator.e(err); - } finally { - _iterator.f(); - } + if (features.namespacing && options.namespacing) { + setNamespacing(false) + } + } + /** + * Returns an XRegExp object that is the union of the given patterns. Patterns can be provided as + * regex objects or strings. Metacharacters are escaped in patterns provided as strings. + * Backreferences in provided regex objects are automatically renumbered to work correctly within + * the larger combined pattern. Native flags used by provided regexes are ignored in favor of the + * `flags` argument. + * + * @memberOf XRegExp + * @param {Array} patterns Regexes and strings to combine. + * @param {String} [flags] Any combination of XRegExp flags. + * @param {Object} [options] Options object with optional properties: + * - `conjunction` {String} Type of conjunction to use: 'or' (default) or 'none'. + * @returns {RegExp} Union of the provided regexes and strings. + * @example + * + * XRegExp.union(['a+b*c', /(dogs)\1/, /(cats)\1/], 'i'); + * // -> /a\+b\*c|(dogs)\1|(cats)\2/i + * + * XRegExp.union([/man/, /bear/, /pig/], 'i', {conjunction: 'none'}); + * // -> /manbearpig/i + */ + + XRegExp.union = function (patterns, flags, options) { + options = options || {} + var conjunction = options.conjunction || 'or' + var numCaptures = 0 + var numPriorCaptures + var captureNames + + function rewrite(match, paren, backref) { + var name = captureNames[numCaptures - numPriorCaptures] // Capturing group + + if (paren) { + ++numCaptures // If the current capture has a name, preserve the name + + if (name) { + return '(?<'.concat(name, '>') + } // Backreference + } else if (backref) { + // Rewrite the backreference + return '\\'.concat(+backref + numPriorCaptures) + } + + return match + } - XRegExp.cache.flush('patterns'); - }; - /** - * @ignore - * - * Return a reference to the internal Unicode definition structure for the given Unicode - * Property if the given name is a legal Unicode Property for use in XRegExp `\p` or `\P` regex - * constructs. - * - * @memberOf XRegExp - * @param {String} name Name by which the Unicode Property may be recognized (case-insensitive), - * e.g. `'N'` or `'Number'`. The given name is matched against all registered Unicode - * Properties and Property Aliases. - * @returns {Object} Reference to definition structure when the name matches a Unicode Property. - * - * @note - * For more info on Unicode Properties, see also http://unicode.org/reports/tr18/#Categories. - * - * @note - * This method is *not* part of the officially documented API and may change or be removed in - * the future. It is meant for userland code that wishes to reuse the (large) internal Unicode - * structures set up by XRegExp. - */ - - - XRegExp._getUnicodeProperty = function (name) { - var slug = normalize(name); - return unicode[slug]; - }; -}; - -exports["default"] = _default; -module.exports = exports.default; -},{"@babel/runtime-corejs3/core-js-stable/array/from":9,"@babel/runtime-corejs3/core-js-stable/array/is-array":10,"@babel/runtime-corejs3/core-js-stable/instance/concat":11,"@babel/runtime-corejs3/core-js-stable/instance/for-each":13,"@babel/runtime-corejs3/core-js-stable/instance/index-of":14,"@babel/runtime-corejs3/core-js-stable/instance/slice":17,"@babel/runtime-corejs3/core-js-stable/object/define-property":20,"@babel/runtime-corejs3/core-js-stable/symbol":22,"@babel/runtime-corejs3/core-js/get-iterator-method":25,"@babel/runtime-corejs3/helpers/interopRequireDefault":30,"@babel/runtime-corejs3/helpers/slicedToArray":33}],4:[function(require,module,exports){ -"use strict"; - -var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); - -var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); - -_Object$defineProperty(exports, "__esModule", { - value: true -}); - -exports["default"] = void 0; - -var _categories = _interopRequireDefault(require("../../tools/output/categories")); - -/*! - * XRegExp Unicode Categories 5.1.0 - * - * Steven Levithan (c) 2010-present MIT License - * Unicode data by Mathias Bynens - */ -var _default = function _default(XRegExp) { - /** - * Adds support for Unicode's general categories. E.g., `\p{Lu}` or `\p{Uppercase Letter}`. See - * category descriptions in UAX #44 . Token - * names are case insensitive, and any spaces, hyphens, and underscores are ignored. - * - * Uses Unicode 14.0.0. - * - * @requires XRegExp, Unicode Base - */ - if (!XRegExp.addUnicodeData) { - throw new ReferenceError('Unicode Base must be loaded before Unicode Categories'); - } + if (!(isType(patterns, 'Array') && patterns.length)) { + throw new TypeError('Must provide a nonempty array of patterns to merge') + } - XRegExp.addUnicodeData(_categories["default"]); -}; - -exports["default"] = _default; -module.exports = exports.default; -},{"../../tools/output/categories":218,"@babel/runtime-corejs3/core-js-stable/object/define-property":20,"@babel/runtime-corejs3/helpers/interopRequireDefault":30}],5:[function(require,module,exports){ -"use strict"; - -var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); - -var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); - -_Object$defineProperty(exports, "__esModule", { - value: true -}); - -exports["default"] = void 0; - -var _properties = _interopRequireDefault(require("../../tools/output/properties")); - -/*! - * XRegExp Unicode Properties 5.1.0 - * - * Steven Levithan (c) 2012-present MIT License - * Unicode data by Mathias Bynens - */ -var _default = function _default(XRegExp) { - /** - * Adds properties to meet the UTS #18 Level 1 RL1.2 requirements for Unicode regex support. See - * . Following are definitions of these properties from - * UAX #44 : - * - * - Alphabetic - * Characters with the Alphabetic property. Generated from: Lowercase + Uppercase + Lt + Lm + - * Lo + Nl + Other_Alphabetic. - * - * - Default_Ignorable_Code_Point - * For programmatic determination of default ignorable code points. New characters that should - * be ignored in rendering (unless explicitly supported) will be assigned in these ranges, - * permitting programs to correctly handle the default rendering of such characters when not - * otherwise supported. - * - * - Lowercase - * Characters with the Lowercase property. Generated from: Ll + Other_Lowercase. - * - * - Noncharacter_Code_Point - * Code points permanently reserved for internal use. - * - * - Uppercase - * Characters with the Uppercase property. Generated from: Lu + Other_Uppercase. - * - * - White_Space - * Spaces, separator characters and other control characters which should be treated by - * programming languages as "white space" for the purpose of parsing elements. - * - * The properties ASCII, Any, and Assigned are also included but are not defined in UAX #44. UTS - * #18 RL1.2 additionally requires support for Unicode scripts and general categories. These are - * included in XRegExp's Unicode Categories and Unicode Scripts addons. - * - * Token names are case insensitive, and any spaces, hyphens, and underscores are ignored. - * - * Uses Unicode 14.0.0. - * - * @requires XRegExp, Unicode Base - */ - if (!XRegExp.addUnicodeData) { - throw new ReferenceError('Unicode Base must be loaded before Unicode Properties'); - } + var parts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*\]/g + var output = [] + + var _iterator5 = _createForOfIteratorHelper(patterns), + _step5 + + try { + for (_iterator5.s(); !(_step5 = _iterator5.n()).done; ) { + var pattern = _step5.value + + if (XRegExp.isRegExp(pattern)) { + numPriorCaptures = numCaptures + captureNames = (pattern[REGEX_DATA] && pattern[REGEX_DATA].captureNames) || [] // Rewrite backreferences. Passing to XRegExp dies on octals and ensures patterns are + // independently valid; helps keep this simple. Named captures are put back + + output.push(XRegExp(pattern.source).source.replace(parts, rewrite)) + } else { + output.push(XRegExp.escape(pattern)) + } + } + } catch (err) { + _iterator5.e(err) + } finally { + _iterator5.f() + } - var unicodeData = _properties["default"]; // Add non-generated data - - unicodeData.push({ - name: 'Assigned', - // Since this is defined as the inverse of Unicode category Cn (Unassigned), the Unicode - // Categories addon is required to use this property - inverseOf: 'Cn' - }); - XRegExp.addUnicodeData(unicodeData); -}; - -exports["default"] = _default; -module.exports = exports.default; -},{"../../tools/output/properties":219,"@babel/runtime-corejs3/core-js-stable/object/define-property":20,"@babel/runtime-corejs3/helpers/interopRequireDefault":30}],6:[function(require,module,exports){ -"use strict"; - -var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); - -var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); - -_Object$defineProperty(exports, "__esModule", { - value: true -}); - -exports["default"] = void 0; - -var _scripts = _interopRequireDefault(require("../../tools/output/scripts")); - -/*! - * XRegExp Unicode Scripts 5.1.0 - * - * Steven Levithan (c) 2010-present MIT License - * Unicode data by Mathias Bynens - */ -var _default = function _default(XRegExp) { - /** - * Adds support for all Unicode scripts. E.g., `\p{Latin}`. Token names are case insensitive, - * and any spaces, hyphens, and underscores are ignored. - * - * Uses Unicode 14.0.0. - * - * @requires XRegExp, Unicode Base - */ - if (!XRegExp.addUnicodeData) { - throw new ReferenceError('Unicode Base must be loaded before Unicode Scripts'); - } + var separator = conjunction === 'none' ? '' : '|' + return XRegExp(output.join(separator), flags) + } // ==--------------------------== + // Fixed/extended native methods + // ==--------------------------== + + /** + * Adds named capture support (with backreferences returned as `result.name`), and fixes browser + * bugs in the native `RegExp.prototype.exec`. Use via `XRegExp.exec`. + * + * @memberOf RegExp + * @param {String} str String to search. + * @returns {Array} Match array with named backreference properties, or `null`. + */ + + fixed.exec = function (str) { + var origLastIndex = this.lastIndex + var match = RegExp.prototype.exec.apply(this, arguments) + + if (match) { + // Fix browsers whose `exec` methods don't return `undefined` for nonparticipating capturing + // groups. This fixes IE 5.5-8, but not IE 9's quirks mode or emulation of older IEs. IE 9 + // in standards mode follows the spec. + if (!correctExecNpcg && match.length > 1 && (0, _indexOf['default'])(match).call(match, '') !== -1) { + var _context3 + + var r2 = copyRegex(this, { + removeG: true, + isInternalOnly: true, + }) // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed + // matching due to characters outside the match + + ;(0, _slice['default'])((_context3 = String(str))) + .call(_context3, match.index) + .replace(r2, function () { + var len = arguments.length // Skip index 0 and the last 2 + + for (var i = 1; i < len - 2; ++i) { + if ((i < 0 || arguments.length <= i ? undefined : arguments[i]) === undefined) { + match[i] = undefined + } + } + }) + } // Attach named capture properties + + if (this[REGEX_DATA] && this[REGEX_DATA].captureNames) { + var groupsObject = match + + if (XRegExp.isInstalled('namespacing')) { + // https://tc39.github.io/proposal-regexp-named-groups/#sec-regexpbuiltinexec + match.groups = (0, _create['default'])(null) + groupsObject = match.groups + } // Skip index 0 + + for (var i = 1; i < match.length; ++i) { + var name = this[REGEX_DATA].captureNames[i - 1] + + if (name) { + groupsObject[name] = match[i] + } + } // Preserve any existing `groups` obj that came from native ES2018 named capture + } else if (!match.groups && XRegExp.isInstalled('namespacing')) { + match.groups = undefined + } // Fix browsers that increment `lastIndex` after zero-length matches + + if (this.global && !match[0].length && this.lastIndex > match.index) { + this.lastIndex = match.index + } + } - XRegExp.addUnicodeData(_scripts["default"], 'Script'); -}; + if (!this.global) { + // Fixes IE, Opera bug (last tested IE 9, Opera 11.6) + this.lastIndex = origLastIndex + } -exports["default"] = _default; -module.exports = exports.default; -},{"../../tools/output/scripts":220,"@babel/runtime-corejs3/core-js-stable/object/define-property":20,"@babel/runtime-corejs3/helpers/interopRequireDefault":30}],7:[function(require,module,exports){ -"use strict"; + return match + } + /** + * Fixes browser bugs in the native `RegExp.prototype.test`. + * + * @memberOf RegExp + * @param {String} str String to search. + * @returns {boolean} Whether the regex matched the provided value. + */ + + fixed.test = function (str) { + // Do this the easy way :-) + return !!fixed.exec.call(this, str) + } + /** + * Adds named capture support (with backreferences returned as `result.name`), and fixes browser + * bugs in the native `String.prototype.match`. + * + * @memberOf String + * @param {RegExp|*} regex Regex to search with. If not a regex object, it is passed to `RegExp`. + * @returns {Array} If `regex` uses flag g, an array of match strings or `null`. Without flag g, + * the result of calling `regex.exec(this)`. + */ + + fixed.match = function (regex) { + if (!XRegExp.isRegExp(regex)) { + // Use the native `RegExp` rather than `XRegExp` + regex = new RegExp(regex) + } else if (regex.global) { + var result = String.prototype.match.apply(this, arguments) // Fixes IE bug + + regex.lastIndex = 0 + return result + } -var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); + return fixed.exec.call(regex, nullThrows(this)) + } + /** + * Adds support for `${n}` (or `$`) tokens for named and numbered backreferences in replacement + * text, and provides named backreferences to replacement functions as `arguments[0].name`. Also + * fixes browser bugs in replacement text syntax when performing a replacement using a nonregex + * search value, and the value of a replacement regex's `lastIndex` property during replacement + * iterations and upon completion. Note that this doesn't support SpiderMonkey's proprietary third + * (`flags`) argument. Use via `XRegExp.replace`. + * + * @memberOf String + * @param {RegExp|String} search Search pattern to be replaced. + * @param {String|Function} replacement Replacement string or a function invoked to create it. + * @returns {string} New string with one or all matches replaced. + */ + + fixed.replace = function (search, replacement) { + var isRegex = XRegExp.isRegExp(search) + var origLastIndex + var captureNames + var result + + if (isRegex) { + if (search[REGEX_DATA]) { + captureNames = search[REGEX_DATA].captureNames + } // Only needed if `search` is nonglobal + + origLastIndex = search.lastIndex + } else { + search += '' // Type-convert + } // Don't use `typeof`; some older browsers return 'function' for regex objects + + if (isType(replacement, 'Function')) { + // Stringifying `this` fixes a bug in IE < 9 where the last argument in replacement + // functions isn't type-converted to a string + result = String(this).replace(search, function () { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key] + } + + if (captureNames) { + var groupsObject + + if (XRegExp.isInstalled('namespacing')) { + // https://tc39.github.io/proposal-regexp-named-groups/#sec-regexpbuiltinexec + groupsObject = (0, _create['default'])(null) + args.push(groupsObject) + } else { + // Change the `args[0]` string primitive to a `String` object that can store + // properties. This really does need to use `String` as a constructor + args[0] = new String(args[0]) + groupsObject = args[0] + } // Store named backreferences + + for (var i = 0; i < captureNames.length; ++i) { + if (captureNames[i]) { + groupsObject[captureNames[i]] = args[i + 1] + } + } + } // ES6 specs the context for replacement functions as `undefined` + + return replacement.apply(void 0, args) + }) + } else { + // Ensure that the last value of `args` will be a string when given nonstring `this`, + // while still throwing on null or undefined context + result = String(nullThrows(this)).replace(search, function () { + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2] + } + + return String(replacement).replace(replacementToken, replacer) + + function replacer($0, bracketed, angled, dollarToken) { + bracketed = bracketed || angled // ES2018 added a new trailing `groups` arg that's passed to replacement functions + // when the search regex uses native named capture + + var numNonCaptureArgs = isType(args[args.length - 1], 'Object') ? 4 : 3 + var numCaptures = args.length - numNonCaptureArgs // Handle named or numbered backreference with curly or angled braces: ${n}, $ + + if (bracketed) { + // Handle backreference to numbered capture, if `bracketed` is an integer. Use + // `0` for the entire match. Any number of leading zeros may be used. + if (/^\d+$/.test(bracketed)) { + // Type-convert and drop leading zeros + var _n = +bracketed + + if (_n <= numCaptures) { + return args[_n] || '' + } + } // Handle backreference to named capture. If the name does not refer to an + // existing capturing group, it's an error. Also handles the error for numbered + // backference that does not refer to an existing group. + // Using `indexOf` since having groups with the same name is already an error, + // otherwise would need `lastIndexOf`. + + var n = captureNames ? (0, _indexOf['default'])(captureNames).call(captureNames, bracketed) : -1 + + if (n < 0) { + throw new SyntaxError('Backreference to undefined group '.concat($0)) + } + + return args[n + 1] || '' + } // Handle `$`-prefixed variable + // Handle space/blank first because type conversion with `+` drops space padding + // and converts spaces and empty strings to `0` + + if (dollarToken === '' || dollarToken === ' ') { + throw new SyntaxError('Invalid token '.concat($0)) + } + + if (dollarToken === '&' || +dollarToken === 0) { + // $&, $0 (not followed by 1-9), $00 + return args[0] + } + + if (dollarToken === '$') { + // $$ + return '$' + } + + if (dollarToken === '`') { + var _context4 + + // $` (left context) + return (0, _slice['default'])((_context4 = args[args.length - 1])).call( + _context4, + 0, + args[args.length - 2] + ) + } + + if (dollarToken === "'") { + var _context5 + + // $' (right context) + return (0, _slice['default'])((_context5 = args[args.length - 1])).call( + _context5, + args[args.length - 2] + args[0].length + ) + } // Handle numbered backreference without braces + // Type-convert and drop leading zero + + dollarToken = +dollarToken // XRegExp behavior for `$n` and `$nn`: + // - Backrefs end after 1 or 2 digits. Use `${..}` or `$<..>` for more digits. + // - `$1` is an error if no capturing groups. + // - `$10` is an error if less than 10 capturing groups. Use `${1}0` or `$<1>0` + // instead. + // - `$01` is `$1` if at least one capturing group, else it's an error. + // - `$0` (not followed by 1-9) and `$00` are the entire match. + // Native behavior, for comparison: + // - Backrefs end after 1 or 2 digits. Cannot reference capturing group 100+. + // - `$1` is a literal `$1` if no capturing groups. + // - `$10` is `$1` followed by a literal `0` if less than 10 capturing groups. + // - `$01` is `$1` if at least one capturing group, else it's a literal `$01`. + // - `$0` is a literal `$0`. + + if (!isNaN(dollarToken)) { + if (dollarToken > numCaptures) { + throw new SyntaxError('Backreference to undefined group '.concat($0)) + } + + return args[dollarToken] || '' + } // `$` followed by an unsupported char is an error, unlike native JS + + throw new SyntaxError('Invalid token '.concat($0)) + } + }) + } -var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); + if (isRegex) { + if (search.global) { + // Fixes IE, Safari bug (last tested IE 9, Safari 5.1) + search.lastIndex = 0 + } else { + // Fixes IE, Opera bug (last tested IE 9, Opera 11.6) + search.lastIndex = origLastIndex + } + } -_Object$defineProperty(exports, "__esModule", { - value: true -}); + return result + } + /** + * Fixes browser bugs in the native `String.prototype.split`. Use via `XRegExp.split`. + * + * @memberOf String + * @param {RegExp|String} separator Regex or string to use for separating the string. + * @param {Number} [limit] Maximum number of items to include in the result array. + * @returns {!Array} Array of substrings. + */ + + fixed.split = function (separator, limit) { + if (!XRegExp.isRegExp(separator)) { + // Browsers handle nonregex split correctly, so use the faster native method + return String.prototype.split.apply(this, arguments) + } -exports["default"] = void 0; + var str = String(this) + var output = [] + var origLastIndex = separator.lastIndex + var lastLastIndex = 0 + var lastLength // Values for `limit`, per the spec: + // If undefined: pow(2,32) - 1 + // If 0, Infinity, or NaN: 0 + // If positive number: limit = floor(limit); if (limit >= pow(2,32)) limit -= pow(2,32); + // If negative number: pow(2,32) - floor(abs(limit)) + // If other: Type-convert, then use the above rules + // This line fails in very strange ways for some values of `limit` in Opera 10.5-10.63, unless + // Opera Dragonfly is open (go figure). It works in at least Opera 9.5-10.1 and 11+ + + limit = (limit === undefined ? -1 : limit) >>> 0 + ;(0, _forEach['default'])(XRegExp).call(XRegExp, str, separator, function (match) { + // This condition is not the same as `if (match[0].length)` + if (match.index + match[0].length > lastLastIndex) { + output.push((0, _slice['default'])(str).call(str, lastLastIndex, match.index)) + + if (match.length > 1 && match.index < str.length) { + Array.prototype.push.apply(output, (0, _slice['default'])(match).call(match, 1)) + } + + lastLength = match[0].length + lastLastIndex = match.index + lastLength + } + }) + + if (lastLastIndex === str.length) { + if (!separator.test('') || lastLength) { + output.push('') + } + } else { + output.push((0, _slice['default'])(str).call(str, lastLastIndex)) + } -var _xregexp = _interopRequireDefault(require("./xregexp")); + separator.lastIndex = origLastIndex + return output.length > limit ? (0, _slice['default'])(output).call(output, 0, limit) : output + } // ==--------------------------== + // Built-in syntax/flag tokens + // ==--------------------------== + + /* + * Letter escapes that natively match literal characters: `\a`, `\A`, etc. These should be + * SyntaxErrors but are allowed in web reality. XRegExp makes them errors for cross-browser + * consistency and to reserve their syntax, but lets them be superseded by addons. + */ + + XRegExp.addToken( + /\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4}|{[\dA-Fa-f]+})|x(?![\dA-Fa-f]{2}))/, + function (match, scope) { + // \B is allowed in default scope only + if (match[1] === 'B' && scope === defaultScope) { + return match[0] + } + + throw new SyntaxError('Invalid escape '.concat(match[0])) + }, + { + scope: 'all', + leadChar: '\\', + } + ) + /* + * Unicode code point escape with curly braces: `\u{N..}`. `N..` is any one or more digit + * hexadecimal number from 0-10FFFF, and can include leading zeros. Requires the native ES6 `u` flag + * to support code points greater than U+FFFF. Avoids converting code points above U+FFFF to + * surrogate pairs (which could be done without flag `u`), since that could lead to broken behavior + * if you follow a `\u{N..}` token that references a code point above U+FFFF with a quantifier, or + * if you use the same in a character class. + */ + + XRegExp.addToken( + /\\u{([\dA-Fa-f]+)}/, + function (match, scope, flags) { + var code = dec(match[1]) + + if (code > 0x10ffff) { + throw new SyntaxError('Invalid Unicode code point '.concat(match[0])) + } + + if (code <= 0xffff) { + // Converting to \uNNNN avoids needing to escape the literal character and keep it + // separate from preceding tokens + return '\\u'.concat(pad4(hex(code))) + } // If `code` is between 0xFFFF and 0x10FFFF, require and defer to native handling + + if (hasNativeU && (0, _indexOf['default'])(flags).call(flags, 'u') !== -1) { + return match[0] + } + + throw new SyntaxError('Cannot use Unicode code point above \\u{FFFF} without flag u') + }, + { + scope: 'all', + leadChar: '\\', + } + ) + /* + * Comment pattern: `(?# )`. Inline comments are an alternative to the line comments allowed in + * free-spacing mode (flag x). + */ + + XRegExp.addToken(/\(\?#[^)]*\)/, getContextualTokenSeparator, { + leadChar: '(', + }) + /* + * Whitespace and line comments, in free-spacing mode (aka extended mode, flag x) only. + */ + + XRegExp.addToken(/\s+|#[^\n]*\n?/, getContextualTokenSeparator, { + flag: 'x', + }) + /* + * Dot, in dotAll mode (aka singleline mode, flag s) only. + */ + + if (!hasNativeS) { + XRegExp.addToken( + /\./, + function () { + return '[\\s\\S]' + }, + { + flag: 's', + leadChar: '.', + } + ) + } + /* + * Named backreference: `\k`. Backreference names can use RegExpIdentifierName characters + * only. Also allows numbered backreferences as `\k`. + */ + + XRegExp.addToken( + /\\k<([^>]+)>/, + function (match) { + var _context6, _context7 + + // Groups with the same name is an error, else would need `lastIndexOf` + var index = isNaN(match[1]) + ? (0, _indexOf['default'])((_context6 = this.captureNames)).call(_context6, match[1]) + 1 + : +match[1] + var endIndex = match.index + match[0].length + + if (!index || index > this.captureNames.length) { + throw new SyntaxError('Backreference to undefined group '.concat(match[0])) + } // Keep backreferences separate from subsequent literal numbers. This avoids e.g. + // inadvertedly changing `(?)\k1` to `()\11`. + + return (0, _concat['default'])((_context7 = '\\'.concat(index))).call( + _context7, + endIndex === match.input.length || isNaN(match.input[endIndex]) ? '' : '(?:)' + ) + }, + { + leadChar: '\\', + } + ) + /* + * Numbered backreference or octal, plus any following digits: `\0`, `\11`, etc. Octals except `\0` + * not followed by 0-9 and backreferences to unopened capture groups throw an error. Other matches + * are returned unaltered. IE < 9 doesn't support backreferences above `\99` in regex syntax. + */ + + XRegExp.addToken( + /\\(\d+)/, + function (match, scope) { + if ( + !(scope === defaultScope && /^[1-9]/.test(match[1]) && +match[1] <= this.captureNames.length) && + match[1] !== '0' + ) { + throw new SyntaxError('Cannot use octal escape or backreference to undefined group '.concat(match[0])) + } + + return match[0] + }, + { + scope: 'all', + leadChar: '\\', + } + ) + /* + * Named capturing group; match the opening delimiter only: `(?`. Capture names can use the + * RegExpIdentifierName characters only. Names can't be integers. Supports Python-style + * `(?P` as an alternate syntax to avoid issues in some older versions of Opera which natively + * supported the Python-style syntax. Otherwise, XRegExp might treat numbered backreferences to + * Python-style named capture as octals. + */ + + XRegExp.addToken( + /\(\?P?<((?:[\$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDEC0-\uDEEB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])(?:[\$0-9A-Z_a-z\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05EF-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u07FD\u0800-\u082D\u0840-\u085B\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u08D3-\u08E1\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u09FC\u09FE\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D00-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D81-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1878\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1ABF\u1AC0\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CD0-\u1CD2\u1CD4-\u1CFA\u1D00-\u1DF9\u1DFB-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA827\uA82C\uA840-\uA873\uA880-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD27\uDD30-\uDD39\uDE80-\uDEA9\uDEAB\uDEAC\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF50\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD44-\uDD47\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDC9-\uDDCC\uDDCE-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3B-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC00-\uDC4A\uDC50-\uDC59\uDC5E-\uDC61\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDC00-\uDC3A\uDCA0-\uDCE9\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD35\uDD37\uDD38\uDD3B-\uDD43\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD7\uDDDA-\uDDE1\uDDE3\uDDE4\uDE00-\uDE3E\uDE47\uDE50-\uDE99\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD8E\uDD90\uDD91\uDD93-\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF6\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF4F-\uDF87\uDF8F-\uDF9F\uDFE0\uDFE1\uDFE3\uDFE4\uDFF0\uDFF1]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A\uDD00-\uDD2C\uDD30-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6\uDD00-\uDD4B\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]|\uDB40[\uDD00-\uDDEF])*)>/, + function (match) { + var _context8 + + if (!XRegExp.isInstalled('namespacing') && (match[1] === 'length' || match[1] === '__proto__')) { + throw new SyntaxError('Cannot use reserved word as capture name '.concat(match[0])) + } + + if ((0, _indexOf['default'])((_context8 = this.captureNames)).call(_context8, match[1]) !== -1) { + throw new SyntaxError('Cannot use same name for multiple groups '.concat(match[0])) + } + + this.captureNames.push(match[1]) + this.hasNamedCapture = true + return '(' + }, + { + leadChar: '(', + } + ) + /* + * Capturing group; match the opening parenthesis only. Required for support of named capturing + * groups. Also adds explicit capture mode (flag n). + */ + + XRegExp.addToken( + /\((?!\?)/, + function (match, scope, flags) { + if ((0, _indexOf['default'])(flags).call(flags, 'n') !== -1) { + return '(?:' + } + + this.captureNames.push(null) + return '(' + }, + { + optionalFlags: 'n', + leadChar: '(', + } + ) + var _default = XRegExp + exports['default'] = _default + module.exports = exports.default + }, + { + '@babel/runtime-corejs3/core-js-stable/array/from': 9, + '@babel/runtime-corejs3/core-js-stable/array/is-array': 10, + '@babel/runtime-corejs3/core-js-stable/instance/concat': 11, + '@babel/runtime-corejs3/core-js-stable/instance/flags': 12, + '@babel/runtime-corejs3/core-js-stable/instance/for-each': 13, + '@babel/runtime-corejs3/core-js-stable/instance/index-of': 14, + '@babel/runtime-corejs3/core-js-stable/instance/slice': 17, + '@babel/runtime-corejs3/core-js-stable/instance/sort': 18, + '@babel/runtime-corejs3/core-js-stable/object/create': 19, + '@babel/runtime-corejs3/core-js-stable/object/define-property': 20, + '@babel/runtime-corejs3/core-js-stable/parse-int': 21, + '@babel/runtime-corejs3/core-js-stable/symbol': 22, + '@babel/runtime-corejs3/core-js/get-iterator-method': 25, + '@babel/runtime-corejs3/helpers/interopRequireDefault': 30, + '@babel/runtime-corejs3/helpers/slicedToArray': 33, + }, + ], + 9: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/array/from') + }, + { 'core-js-pure/stable/array/from': 203 }, + ], + 10: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/array/is-array') + }, + { 'core-js-pure/stable/array/is-array': 204 }, + ], + 11: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/instance/concat') + }, + { 'core-js-pure/stable/instance/concat': 206 }, + ], + 12: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/instance/flags') + }, + { 'core-js-pure/stable/instance/flags': 207 }, + ], + 13: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/instance/for-each') + }, + { 'core-js-pure/stable/instance/for-each': 208 }, + ], + 14: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/instance/index-of') + }, + { 'core-js-pure/stable/instance/index-of': 209 }, + ], + 15: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/instance/map') + }, + { 'core-js-pure/stable/instance/map': 210 }, + ], + 16: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/instance/reduce') + }, + { 'core-js-pure/stable/instance/reduce': 211 }, + ], + 17: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/instance/slice') + }, + { 'core-js-pure/stable/instance/slice': 212 }, + ], + 18: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/instance/sort') + }, + { 'core-js-pure/stable/instance/sort': 213 }, + ], + 19: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/object/create') + }, + { 'core-js-pure/stable/object/create': 214 }, + ], + 20: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/object/define-property') + }, + { 'core-js-pure/stable/object/define-property': 215 }, + ], + 21: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/parse-int') + }, + { 'core-js-pure/stable/parse-int': 216 }, + ], + 22: [ + function (require, module, exports) { + module.exports = require('core-js-pure/stable/symbol') + }, + { 'core-js-pure/stable/symbol': 217 }, + ], + 23: [ + function (require, module, exports) { + module.exports = require('core-js-pure/features/array/from') + }, + { 'core-js-pure/features/array/from': 56 }, + ], + 24: [ + function (require, module, exports) { + module.exports = require('core-js-pure/features/array/is-array') + }, + { 'core-js-pure/features/array/is-array': 57 }, + ], + 25: [ + function (require, module, exports) { + module.exports = require('core-js-pure/features/get-iterator-method') + }, + { 'core-js-pure/features/get-iterator-method': 58 }, + ], + 26: [ + function (require, module, exports) { + module.exports = require('core-js-pure/features/instance/slice') + }, + { 'core-js-pure/features/instance/slice': 59 }, + ], + 27: [ + function (require, module, exports) { + module.exports = require('core-js-pure/features/symbol') + }, + { 'core-js-pure/features/symbol': 60 }, + ], + 28: [ + function (require, module, exports) { + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length + + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i] + } -var _build = _interopRequireDefault(require("./addons/build")); + return arr2 + } -var _matchrecursive = _interopRequireDefault(require("./addons/matchrecursive")); + module.exports = _arrayLikeToArray + ;(module.exports['default'] = module.exports), (module.exports.__esModule = true) + }, + {}, + ], + 29: [ + function (require, module, exports) { + var _Array$isArray = require('@babel/runtime-corejs3/core-js/array/is-array') + + function _arrayWithHoles(arr) { + if (_Array$isArray(arr)) return arr + } -var _unicodeBase = _interopRequireDefault(require("./addons/unicode-base")); + module.exports = _arrayWithHoles + ;(module.exports['default'] = module.exports), (module.exports.__esModule = true) + }, + { '@babel/runtime-corejs3/core-js/array/is-array': 24 }, + ], + 30: [ + function (require, module, exports) { + function _interopRequireDefault(obj) { + return obj && obj.__esModule + ? obj + : { + default: obj, + } + } -var _unicodeCategories = _interopRequireDefault(require("./addons/unicode-categories")); + module.exports = _interopRequireDefault + ;(module.exports['default'] = module.exports), (module.exports.__esModule = true) + }, + {}, + ], + 31: [ + function (require, module, exports) { + var _Symbol = require('@babel/runtime-corejs3/core-js/symbol') + + var _getIteratorMethod = require('@babel/runtime-corejs3/core-js/get-iterator-method') + + function _iterableToArrayLimit(arr, i) { + var _i = + arr == null ? null : (typeof _Symbol !== 'undefined' && _getIteratorMethod(arr)) || arr['@@iterator'] + + if (_i == null) return + var _arr = [] + var _n = true + var _d = false + + var _s, _e + + try { + for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value) + + if (i && _arr.length === i) break + } + } catch (err) { + _d = true + _e = err + } finally { + try { + if (!_n && _i['return'] != null) _i['return']() + } finally { + if (_d) throw _e + } + } -var _unicodeProperties = _interopRequireDefault(require("./addons/unicode-properties")); + return _arr + } -var _unicodeScripts = _interopRequireDefault(require("./addons/unicode-scripts")); + module.exports = _iterableToArrayLimit + ;(module.exports['default'] = module.exports), (module.exports.__esModule = true) + }, + { '@babel/runtime-corejs3/core-js/get-iterator-method': 25, '@babel/runtime-corejs3/core-js/symbol': 27 }, + ], + 32: [ + function (require, module, exports) { + function _nonIterableRest() { + throw new TypeError( + 'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.' + ) + } -(0, _build["default"])(_xregexp["default"]); -(0, _matchrecursive["default"])(_xregexp["default"]); -(0, _unicodeBase["default"])(_xregexp["default"]); -(0, _unicodeCategories["default"])(_xregexp["default"]); -(0, _unicodeProperties["default"])(_xregexp["default"]); -(0, _unicodeScripts["default"])(_xregexp["default"]); -var _default = _xregexp["default"]; -exports["default"] = _default; -module.exports = exports.default; -},{"./addons/build":1,"./addons/matchrecursive":2,"./addons/unicode-base":3,"./addons/unicode-categories":4,"./addons/unicode-properties":5,"./addons/unicode-scripts":6,"./xregexp":8,"@babel/runtime-corejs3/core-js-stable/object/define-property":20,"@babel/runtime-corejs3/helpers/interopRequireDefault":30}],8:[function(require,module,exports){ -"use strict"; + module.exports = _nonIterableRest + ;(module.exports['default'] = module.exports), (module.exports.__esModule = true) + }, + {}, + ], + 33: [ + function (require, module, exports) { + var arrayWithHoles = require('./arrayWithHoles.js') -var _sliceInstanceProperty2 = require("@babel/runtime-corejs3/core-js-stable/instance/slice"); + var iterableToArrayLimit = require('./iterableToArrayLimit.js') -var _Array$from = require("@babel/runtime-corejs3/core-js-stable/array/from"); + var unsupportedIterableToArray = require('./unsupportedIterableToArray.js') -var _Symbol = require("@babel/runtime-corejs3/core-js-stable/symbol"); + var nonIterableRest = require('./nonIterableRest.js') -var _getIteratorMethod = require("@babel/runtime-corejs3/core-js/get-iterator-method"); + function _slicedToArray(arr, i) { + return ( + arrayWithHoles(arr) || + iterableToArrayLimit(arr, i) || + unsupportedIterableToArray(arr, i) || + nonIterableRest() + ) + } -var _Array$isArray = require("@babel/runtime-corejs3/core-js-stable/array/is-array"); + module.exports = _slicedToArray + ;(module.exports['default'] = module.exports), (module.exports.__esModule = true) + }, + { + './arrayWithHoles.js': 29, + './iterableToArrayLimit.js': 31, + './nonIterableRest.js': 32, + './unsupportedIterableToArray.js': 34, + }, + ], + 34: [ + function (require, module, exports) { + var _sliceInstanceProperty = require('@babel/runtime-corejs3/core-js/instance/slice') -var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property"); + var _Array$from = require('@babel/runtime-corejs3/core-js/array/from') -var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault"); + var arrayLikeToArray = require('./arrayLikeToArray.js') -_Object$defineProperty(exports, "__esModule", { - value: true -}); + function _unsupportedIterableToArray(o, minLen) { + var _context -exports["default"] = void 0; + if (!o) return + if (typeof o === 'string') return arrayLikeToArray(o, minLen) -var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/slicedToArray")); + var n = _sliceInstanceProperty((_context = Object.prototype.toString.call(o))).call(_context, 8, -1) -var _flags = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/flags")); + if (n === 'Object' && o.constructor) n = o.constructor.name + if (n === 'Map' || n === 'Set') return _Array$from(o) + if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) + return arrayLikeToArray(o, minLen) + } -var _sort = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/sort")); + module.exports = _unsupportedIterableToArray + ;(module.exports['default'] = module.exports), (module.exports.__esModule = true) + }, + { + './arrayLikeToArray.js': 28, + '@babel/runtime-corejs3/core-js/array/from': 23, + '@babel/runtime-corejs3/core-js/instance/slice': 26, + }, + ], + 35: [ + function (require, module, exports) { + require('../../modules/es.string.iterator') + require('../../modules/es.array.from') + var path = require('../../internals/path') + + module.exports = path.Array.from + }, + { '../../internals/path': 136, '../../modules/es.array.from': 163, '../../modules/es.string.iterator': 179 }, + ], + 36: [ + function (require, module, exports) { + require('../../modules/es.array.is-array') + var path = require('../../internals/path') + + module.exports = path.Array.isArray + }, + { '../../internals/path': 136, '../../modules/es.array.is-array': 165 }, + ], + 37: [ + function (require, module, exports) { + require('../../../modules/es.array.concat') + var entryVirtual = require('../../../internals/entry-virtual') + + module.exports = entryVirtual('Array').concat + }, + { '../../../internals/entry-virtual': 95, '../../../modules/es.array.concat': 161 }, + ], + 38: [ + function (require, module, exports) { + require('../../../modules/es.array.for-each') + var entryVirtual = require('../../../internals/entry-virtual') + + module.exports = entryVirtual('Array').forEach + }, + { '../../../internals/entry-virtual': 95, '../../../modules/es.array.for-each': 162 }, + ], + 39: [ + function (require, module, exports) { + require('../../../modules/es.array.index-of') + var entryVirtual = require('../../../internals/entry-virtual') + + module.exports = entryVirtual('Array').indexOf + }, + { '../../../internals/entry-virtual': 95, '../../../modules/es.array.index-of': 164 }, + ], + 40: [ + function (require, module, exports) { + require('../../../modules/es.array.map') + var entryVirtual = require('../../../internals/entry-virtual') + + module.exports = entryVirtual('Array').map + }, + { '../../../internals/entry-virtual': 95, '../../../modules/es.array.map': 167 }, + ], + 41: [ + function (require, module, exports) { + require('../../../modules/es.array.reduce') + var entryVirtual = require('../../../internals/entry-virtual') + + module.exports = entryVirtual('Array').reduce + }, + { '../../../internals/entry-virtual': 95, '../../../modules/es.array.reduce': 168 }, + ], + 42: [ + function (require, module, exports) { + require('../../../modules/es.array.slice') + var entryVirtual = require('../../../internals/entry-virtual') + + module.exports = entryVirtual('Array').slice + }, + { '../../../internals/entry-virtual': 95, '../../../modules/es.array.slice': 169 }, + ], + 43: [ + function (require, module, exports) { + require('../../../modules/es.array.sort') + var entryVirtual = require('../../../internals/entry-virtual') + + module.exports = entryVirtual('Array').sort + }, + { '../../../internals/entry-virtual': 95, '../../../modules/es.array.sort': 170 }, + ], + 44: [ + function (require, module, exports) { + var concat = require('../array/virtual/concat') + + var ArrayPrototype = Array.prototype + + module.exports = function (it) { + var own = it.concat + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.concat) ? concat : own + } + }, + { '../array/virtual/concat': 37 }, + ], + 45: [ + function (require, module, exports) { + var flags = require('../regexp/flags') -var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice")); + var RegExpPrototype = RegExp.prototype -var _parseInt2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/parse-int")); + module.exports = function (it) { + return (it === RegExpPrototype || it instanceof RegExp) && !('flags' in it) ? flags(it) : it.flags + } + }, + { '../regexp/flags': 54 }, + ], + 46: [ + function (require, module, exports) { + var indexOf = require('../array/virtual/index-of') + + var ArrayPrototype = Array.prototype + + module.exports = function (it) { + var own = it.indexOf + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.indexOf) ? indexOf : own + } + }, + { '../array/virtual/index-of': 39 }, + ], + 47: [ + function (require, module, exports) { + var map = require('../array/virtual/map') + + var ArrayPrototype = Array.prototype + + module.exports = function (it) { + var own = it.map + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.map) ? map : own + } + }, + { '../array/virtual/map': 40 }, + ], + 48: [ + function (require, module, exports) { + var reduce = require('../array/virtual/reduce') + + var ArrayPrototype = Array.prototype + + module.exports = function (it) { + var own = it.reduce + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.reduce) ? reduce : own + } + }, + { '../array/virtual/reduce': 41 }, + ], + 49: [ + function (require, module, exports) { + var slice = require('../array/virtual/slice') + + var ArrayPrototype = Array.prototype + + module.exports = function (it) { + var own = it.slice + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.slice) ? slice : own + } + }, + { '../array/virtual/slice': 42 }, + ], + 50: [ + function (require, module, exports) { + var sort = require('../array/virtual/sort') + + var ArrayPrototype = Array.prototype + + module.exports = function (it) { + var own = it.sort + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.sort) ? sort : own + } + }, + { '../array/virtual/sort': 43 }, + ], + 51: [ + function (require, module, exports) { + require('../../modules/es.object.create') + var path = require('../../internals/path') + + var Object = path.Object + + module.exports = function create(P, D) { + return Object.create(P, D) + } + }, + { '../../internals/path': 136, '../../modules/es.object.create': 173 }, + ], + 52: [ + function (require, module, exports) { + require('../../modules/es.object.define-property') + var path = require('../../internals/path') + + var Object = path.Object + + var defineProperty = (module.exports = function defineProperty(it, key, desc) { + return Object.defineProperty(it, key, desc) + }) + + if (Object.defineProperty.sham) defineProperty.sham = true + }, + { '../../internals/path': 136, '../../modules/es.object.define-property': 174 }, + ], + 53: [ + function (require, module, exports) { + require('../modules/es.parse-int') + var path = require('../internals/path') + + module.exports = path.parseInt + }, + { '../internals/path': 136, '../modules/es.parse-int': 176 }, + ], + 54: [ + function (require, module, exports) { + require('../../modules/es.regexp.flags') + var flags = require('../../internals/regexp-flags') + + module.exports = function (it) { + return flags.call(it) + } + }, + { '../../internals/regexp-flags': 138, '../../modules/es.regexp.flags': 178 }, + ], + 55: [ + function (require, module, exports) { + require('../../modules/es.array.concat') + require('../../modules/es.object.to-string') + require('../../modules/es.symbol') + require('../../modules/es.symbol.async-iterator') + require('../../modules/es.symbol.description') + require('../../modules/es.symbol.has-instance') + require('../../modules/es.symbol.is-concat-spreadable') + require('../../modules/es.symbol.iterator') + require('../../modules/es.symbol.match') + require('../../modules/es.symbol.match-all') + require('../../modules/es.symbol.replace') + require('../../modules/es.symbol.search') + require('../../modules/es.symbol.species') + require('../../modules/es.symbol.split') + require('../../modules/es.symbol.to-primitive') + require('../../modules/es.symbol.to-string-tag') + require('../../modules/es.symbol.unscopables') + require('../../modules/es.json.to-string-tag') + require('../../modules/es.math.to-string-tag') + require('../../modules/es.reflect.to-string-tag') + var path = require('../../internals/path') + + module.exports = path.Symbol + }, + { + '../../internals/path': 136, + '../../modules/es.array.concat': 161, + '../../modules/es.json.to-string-tag': 171, + '../../modules/es.math.to-string-tag': 172, + '../../modules/es.object.to-string': 175, + '../../modules/es.reflect.to-string-tag': 177, + '../../modules/es.symbol': 185, + '../../modules/es.symbol.async-iterator': 180, + '../../modules/es.symbol.description': 181, + '../../modules/es.symbol.has-instance': 182, + '../../modules/es.symbol.is-concat-spreadable': 183, + '../../modules/es.symbol.iterator': 184, + '../../modules/es.symbol.match': 187, + '../../modules/es.symbol.match-all': 186, + '../../modules/es.symbol.replace': 188, + '../../modules/es.symbol.search': 189, + '../../modules/es.symbol.species': 190, + '../../modules/es.symbol.split': 191, + '../../modules/es.symbol.to-primitive': 192, + '../../modules/es.symbol.to-string-tag': 193, + '../../modules/es.symbol.unscopables': 194, + }, + ], + 56: [ + function (require, module, exports) { + var parent = require('../../es/array/from') + + module.exports = parent + }, + { '../../es/array/from': 35 }, + ], + 57: [ + function (require, module, exports) { + var parent = require('../../es/array/is-array') + + module.exports = parent + }, + { '../../es/array/is-array': 36 }, + ], + 58: [ + function (require, module, exports) { + require('../modules/es.array.iterator') + require('../modules/es.string.iterator') + require('../modules/web.dom-collections.iterator') + var getIteratorMethod = require('../internals/get-iterator-method') + + module.exports = getIteratorMethod + }, + { + '../internals/get-iterator-method': 101, + '../modules/es.array.iterator': 166, + '../modules/es.string.iterator': 179, + '../modules/web.dom-collections.iterator': 202, + }, + ], + 59: [ + function (require, module, exports) { + var parent = require('../../es/instance/slice') + + module.exports = parent + }, + { '../../es/instance/slice': 49 }, + ], + 60: [ + function (require, module, exports) { + var parent = require('../../es/symbol') + require('../../modules/esnext.symbol.async-dispose') + require('../../modules/esnext.symbol.dispose') + require('../../modules/esnext.symbol.matcher') + require('../../modules/esnext.symbol.metadata') + require('../../modules/esnext.symbol.observable') + // TODO: Remove from `core-js@4` + require('../../modules/esnext.symbol.pattern-match') + // TODO: Remove from `core-js@4` + require('../../modules/esnext.symbol.replace-all') + + module.exports = parent + }, + { + '../../es/symbol': 55, + '../../modules/esnext.symbol.async-dispose': 195, + '../../modules/esnext.symbol.dispose': 196, + '../../modules/esnext.symbol.matcher': 197, + '../../modules/esnext.symbol.metadata': 198, + '../../modules/esnext.symbol.observable': 199, + '../../modules/esnext.symbol.pattern-match': 200, + '../../modules/esnext.symbol.replace-all': 201, + }, + ], + 61: [ + function (require, module, exports) { + module.exports = function (it) { + if (typeof it != 'function') { + throw TypeError(String(it) + ' is not a function') + } + return it + } + }, + {}, + ], + 62: [ + function (require, module, exports) { + var isObject = require('../internals/is-object') + + module.exports = function (it) { + if (!isObject(it) && it !== null) { + throw TypeError("Can't set " + String(it) + ' as a prototype') + } + return it + } + }, + { '../internals/is-object': 113 }, + ], + 63: [ + function (require, module, exports) { + module.exports = function () { + /* empty */ + } + }, + {}, + ], + 64: [ + function (require, module, exports) { + var isObject = require('../internals/is-object') + + module.exports = function (it) { + if (!isObject(it)) { + throw TypeError(String(it) + ' is not an object') + } + return it + } + }, + { '../internals/is-object': 113 }, + ], + 65: [ + function (require, module, exports) { + 'use strict' + var $forEach = require('../internals/array-iteration').forEach + var arrayMethodIsStrict = require('../internals/array-method-is-strict') + + var STRICT_METHOD = arrayMethodIsStrict('forEach') + + // `Array.prototype.forEach` method implementation + // https://tc39.es/ecma262/#sec-array.prototype.foreach + module.exports = !STRICT_METHOD + ? function forEach(callbackfn /* , thisArg */) { + return $forEach(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined) + // eslint-disable-next-line es/no-array-prototype-foreach -- safe + } + : [].forEach + }, + { '../internals/array-iteration': 68, '../internals/array-method-is-strict': 70 }, + ], + 66: [ + function (require, module, exports) { + 'use strict' + var bind = require('../internals/function-bind-context') + var toObject = require('../internals/to-object') + var callWithSafeIterationClosing = require('../internals/call-with-safe-iteration-closing') + var isArrayIteratorMethod = require('../internals/is-array-iterator-method') + var toLength = require('../internals/to-length') + var createProperty = require('../internals/create-property') + var getIteratorMethod = require('../internals/get-iterator-method') + + // `Array.from` method implementation + // https://tc39.es/ecma262/#sec-array.from + module.exports = function from(arrayLike /* , mapfn = undefined, thisArg = undefined */) { + var O = toObject(arrayLike) + var C = typeof this == 'function' ? this : Array + var argumentsLength = arguments.length + var mapfn = argumentsLength > 1 ? arguments[1] : undefined + var mapping = mapfn !== undefined + var iteratorMethod = getIteratorMethod(O) + var index = 0 + var length, result, step, iterator, next, value + if (mapping) mapfn = bind(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2) + // if the target is not iterable or it's an array with the default iterator - use a simple case + if (iteratorMethod != undefined && !(C == Array && isArrayIteratorMethod(iteratorMethod))) { + iterator = iteratorMethod.call(O) + next = iterator.next + result = new C() + for (; !(step = next.call(iterator)).done; index++) { + value = mapping ? callWithSafeIterationClosing(iterator, mapfn, [step.value, index], true) : step.value + createProperty(result, index, value) + } + } else { + length = toLength(O.length) + result = new C(length) + for (; length > index; index++) { + value = mapping ? mapfn(O[index], index) : O[index] + createProperty(result, index, value) + } + } + result.length = index + return result + } + }, + { + '../internals/call-with-safe-iteration-closing': 75, + '../internals/create-property': 83, + '../internals/function-bind-context': 99, + '../internals/get-iterator-method': 101, + '../internals/is-array-iterator-method': 110, + '../internals/to-length': 150, + '../internals/to-object': 151, + }, + ], + 67: [ + function (require, module, exports) { + var toIndexedObject = require('../internals/to-indexed-object') + var toLength = require('../internals/to-length') + var toAbsoluteIndex = require('../internals/to-absolute-index') + + // `Array.prototype.{ indexOf, includes }` methods implementation + var createMethod = function (IS_INCLUDES) { + return function ($this, el, fromIndex) { + var O = toIndexedObject($this) + var length = toLength(O.length) + var index = toAbsoluteIndex(fromIndex, length) + var value + // Array#includes uses SameValueZero equality algorithm + // eslint-disable-next-line no-self-compare -- NaN check + if (IS_INCLUDES && el != el) + while (length > index) { + value = O[index++] + // eslint-disable-next-line no-self-compare -- NaN check + if (value != value) return true + // Array#indexOf ignores holes, Array#includes - not + } + else + for (; length > index; index++) { + if ((IS_INCLUDES || index in O) && O[index] === el) return IS_INCLUDES || index || 0 + } + return !IS_INCLUDES && -1 + } + } -var _indexOf = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/index-of")); + module.exports = { + // `Array.prototype.includes` method + // https://tc39.es/ecma262/#sec-array.prototype.includes + includes: createMethod(true), + // `Array.prototype.indexOf` method + // https://tc39.es/ecma262/#sec-array.prototype.indexof + indexOf: createMethod(false), + } + }, + { '../internals/to-absolute-index': 147, '../internals/to-indexed-object': 148, '../internals/to-length': 150 }, + ], + 68: [ + function (require, module, exports) { + var bind = require('../internals/function-bind-context') + var IndexedObject = require('../internals/indexed-object') + var toObject = require('../internals/to-object') + var toLength = require('../internals/to-length') + var arraySpeciesCreate = require('../internals/array-species-create') + + var push = [].push + + // `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterReject }` methods implementation + var createMethod = function (TYPE) { + var IS_MAP = TYPE == 1 + var IS_FILTER = TYPE == 2 + var IS_SOME = TYPE == 3 + var IS_EVERY = TYPE == 4 + var IS_FIND_INDEX = TYPE == 6 + var IS_FILTER_REJECT = TYPE == 7 + var NO_HOLES = TYPE == 5 || IS_FIND_INDEX + return function ($this, callbackfn, that, specificCreate) { + var O = toObject($this) + var self = IndexedObject(O) + var boundFunction = bind(callbackfn, that, 3) + var length = toLength(self.length) + var index = 0 + var create = specificCreate || arraySpeciesCreate + var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_REJECT ? create($this, 0) : undefined + var value, result + for (; length > index; index++) + if (NO_HOLES || index in self) { + value = self[index] + result = boundFunction(value, index, O) + if (TYPE) { + if (IS_MAP) target[index] = result // map + else if (result) + switch (TYPE) { + case 3: + return true // some + case 5: + return value // find + case 6: + return index // findIndex + case 2: + push.call(target, value) // filter + } + else + switch (TYPE) { + case 4: + return false // every + case 7: + push.call(target, value) // filterReject + } + } + } + return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target + } + } -var _forEach = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/for-each")); + module.exports = { + // `Array.prototype.forEach` method + // https://tc39.es/ecma262/#sec-array.prototype.foreach + forEach: createMethod(0), + // `Array.prototype.map` method + // https://tc39.es/ecma262/#sec-array.prototype.map + map: createMethod(1), + // `Array.prototype.filter` method + // https://tc39.es/ecma262/#sec-array.prototype.filter + filter: createMethod(2), + // `Array.prototype.some` method + // https://tc39.es/ecma262/#sec-array.prototype.some + some: createMethod(3), + // `Array.prototype.every` method + // https://tc39.es/ecma262/#sec-array.prototype.every + every: createMethod(4), + // `Array.prototype.find` method + // https://tc39.es/ecma262/#sec-array.prototype.find + find: createMethod(5), + // `Array.prototype.findIndex` method + // https://tc39.es/ecma262/#sec-array.prototype.findIndex + findIndex: createMethod(6), + // `Array.prototype.filterReject` method + // https://github.com/tc39/proposal-array-filtering + filterReject: createMethod(7), + } + }, + { + '../internals/array-species-create': 74, + '../internals/function-bind-context': 99, + '../internals/indexed-object': 107, + '../internals/to-length': 150, + '../internals/to-object': 151, + }, + ], + 69: [ + function (require, module, exports) { + var fails = require('../internals/fails') + var wellKnownSymbol = require('../internals/well-known-symbol') + var V8_VERSION = require('../internals/engine-v8-version') + + var SPECIES = wellKnownSymbol('species') + + module.exports = function (METHOD_NAME) { + // We can't use this feature detection in V8 since it causes + // deoptimization and serious performance degradation + // https://github.com/zloirock/core-js/issues/677 + return ( + V8_VERSION >= 51 || + !fails(function () { + var array = [] + var constructor = (array.constructor = {}) + constructor[SPECIES] = function () { + return { foo: 1 } + } + return array[METHOD_NAME](Boolean).foo !== 1 + }) + ) + } + }, + { '../internals/engine-v8-version': 93, '../internals/fails': 98, '../internals/well-known-symbol': 159 }, + ], + 70: [ + function (require, module, exports) { + 'use strict' + var fails = require('../internals/fails') + + module.exports = function (METHOD_NAME, argument) { + var method = [][METHOD_NAME] + return ( + !!method && + fails(function () { + // eslint-disable-next-line no-useless-call,no-throw-literal -- required for testing + method.call( + null, + argument || + function () { + throw 1 + }, + 1 + ) + }) + ) + } + }, + { '../internals/fails': 98 }, + ], + 71: [ + function (require, module, exports) { + var aFunction = require('../internals/a-function') + var toObject = require('../internals/to-object') + var IndexedObject = require('../internals/indexed-object') + var toLength = require('../internals/to-length') + + // `Array.prototype.{ reduce, reduceRight }` methods implementation + var createMethod = function (IS_RIGHT) { + return function (that, callbackfn, argumentsLength, memo) { + aFunction(callbackfn) + var O = toObject(that) + var self = IndexedObject(O) + var length = toLength(O.length) + var index = IS_RIGHT ? length - 1 : 0 + var i = IS_RIGHT ? -1 : 1 + if (argumentsLength < 2) + while (true) { + if (index in self) { + memo = self[index] + index += i + break + } + index += i + if (IS_RIGHT ? index < 0 : length <= index) { + throw TypeError('Reduce of empty array with no initial value') + } + } + for (; IS_RIGHT ? index >= 0 : length > index; index += i) + if (index in self) { + memo = callbackfn(memo, self[index], index, O) + } + return memo + } + } -var _create = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/create")); + module.exports = { + // `Array.prototype.reduce` method + // https://tc39.es/ecma262/#sec-array.prototype.reduce + left: createMethod(false), + // `Array.prototype.reduceRight` method + // https://tc39.es/ecma262/#sec-array.prototype.reduceright + right: createMethod(true), + } + }, + { + '../internals/a-function': 61, + '../internals/indexed-object': 107, + '../internals/to-length': 150, + '../internals/to-object': 151, + }, + ], + 72: [ + function (require, module, exports) { + // TODO: use something more complex like timsort? + var floor = Math.floor + + var mergeSort = function (array, comparefn) { + var length = array.length + var middle = floor(length / 2) + return length < 8 + ? insertionSort(array, comparefn) + : merge( + mergeSort(array.slice(0, middle), comparefn), + mergeSort(array.slice(middle), comparefn), + comparefn + ) + } -var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat")); + var insertionSort = function (array, comparefn) { + var length = array.length + var i = 1 + var element, j + + while (i < length) { + j = i + element = array[i] + while (j && comparefn(array[j - 1], element) > 0) { + array[j] = array[--j] + } + if (j !== i++) array[j] = element + } + return array + } -function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof _Symbol !== "undefined" && _getIteratorMethod(o) || o["@@iterator"]; if (!it) { if (_Array$isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } + var merge = function (left, right, comparefn) { + var llength = left.length + var rlength = right.length + var lindex = 0 + var rindex = 0 + var result = [] + + while (lindex < llength || rindex < rlength) { + if (lindex < llength && rindex < rlength) { + result.push(comparefn(left[lindex], right[rindex]) <= 0 ? left[lindex++] : right[rindex++]) + } else { + result.push(lindex < llength ? left[lindex++] : right[rindex++]) + } + } + return result + } -function _unsupportedIterableToArray(o, minLen) { var _context9; if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = _sliceInstanceProperty2(_context9 = Object.prototype.toString.call(o)).call(_context9, 8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return _Array$from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + module.exports = mergeSort + }, + {}, + ], + 73: [ + function (require, module, exports) { + var isObject = require('../internals/is-object') + var isArray = require('../internals/is-array') + var wellKnownSymbol = require('../internals/well-known-symbol') + + var SPECIES = wellKnownSymbol('species') + + // a part of `ArraySpeciesCreate` abstract operation + // https://tc39.es/ecma262/#sec-arrayspeciescreate + module.exports = function (originalArray) { + var C + if (isArray(originalArray)) { + C = originalArray.constructor + // cross-realm fallback + if (typeof C == 'function' && (C === Array || isArray(C.prototype))) C = undefined + else if (isObject(C)) { + C = C[SPECIES] + if (C === null) C = undefined + } + } + return C === undefined ? Array : C + } + }, + { '../internals/is-array': 111, '../internals/is-object': 113, '../internals/well-known-symbol': 159 }, + ], + 74: [ + function (require, module, exports) { + var arraySpeciesConstructor = require('../internals/array-species-constructor') + + // `ArraySpeciesCreate` abstract operation + // https://tc39.es/ecma262/#sec-arrayspeciescreate + module.exports = function (originalArray, length) { + return new (arraySpeciesConstructor(originalArray))(length === 0 ? 0 : length) + } + }, + { '../internals/array-species-constructor': 73 }, + ], + 75: [ + function (require, module, exports) { + var anObject = require('../internals/an-object') + var iteratorClose = require('../internals/iterator-close') + + // call something on iterator step with safe closing on error + module.exports = function (iterator, fn, value, ENTRIES) { + try { + return ENTRIES ? fn(anObject(value)[0], value[1]) : fn(value) + } catch (error) { + iteratorClose(iterator) + throw error + } + } + }, + { '../internals/an-object': 64, '../internals/iterator-close': 116 }, + ], + 76: [ + function (require, module, exports) { + var wellKnownSymbol = require('../internals/well-known-symbol') + + var ITERATOR = wellKnownSymbol('iterator') + var SAFE_CLOSING = false + + try { + var called = 0 + var iteratorWithReturn = { + next: function () { + return { done: !!called++ } + }, + return: function () { + SAFE_CLOSING = true + }, + } + iteratorWithReturn[ITERATOR] = function () { + return this + } + // eslint-disable-next-line es/no-array-from, no-throw-literal -- required for testing + Array.from(iteratorWithReturn, function () { + throw 2 + }) + } catch (error) { + /* empty */ + } -function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + module.exports = function (exec, SKIP_CLOSING) { + if (!SKIP_CLOSING && !SAFE_CLOSING) return false + var ITERATION_SUPPORT = false + try { + var object = {} + object[ITERATOR] = function () { + return { + next: function () { + return { done: (ITERATION_SUPPORT = true) } + }, + } + } + exec(object) + } catch (error) { + /* empty */ + } + return ITERATION_SUPPORT + } + }, + { '../internals/well-known-symbol': 159 }, + ], + 77: [ + function (require, module, exports) { + var toString = {}.toString + + module.exports = function (it) { + return toString.call(it).slice(8, -1) + } + }, + {}, + ], + 78: [ + function (require, module, exports) { + var TO_STRING_TAG_SUPPORT = require('../internals/to-string-tag-support') + var classofRaw = require('../internals/classof-raw') + var wellKnownSymbol = require('../internals/well-known-symbol') + + var TO_STRING_TAG = wellKnownSymbol('toStringTag') + // ES3 wrong here + var CORRECT_ARGUMENTS = + classofRaw( + (function () { + return arguments + })() + ) == 'Arguments' + + // fallback for IE11 Script Access Denied error + var tryGet = function (it, key) { + try { + return it[key] + } catch (error) { + /* empty */ + } + } -/*! - * XRegExp 5.1.0 - * - * Steven Levithan (c) 2007-present MIT License - */ - -/** - * XRegExp provides augmented, extensible regular expressions. You get additional regex syntax and - * flags, beyond what browsers support natively. XRegExp is also a regex utility belt with tools to - * make your client-side grepping simpler and more powerful, while freeing you from related - * cross-browser inconsistencies. - */ -// ==--------------------------== -// Private stuff -// ==--------------------------== -// Property name used for extended regex instance data -var REGEX_DATA = 'xregexp'; // Optional features that can be installed and uninstalled - -var features = { - astral: false, - namespacing: true -}; // Storage for fixed/extended native methods - -var fixed = {}; // Storage for regexes cached by `XRegExp.cache` - -var regexCache = {}; // Storage for pattern details cached by the `XRegExp` constructor - -var patternCache = {}; // Storage for regex syntax tokens added internally or by `XRegExp.addToken` - -var tokens = []; // Token scopes - -var defaultScope = 'default'; -var classScope = 'class'; // Regexes that match native regex syntax, including octals - -var nativeTokens = { - // Any native multicharacter token in default scope, or any single character - 'default': /\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\d*|x[\dA-Fa-f]{2}|u(?:[\dA-Fa-f]{4}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|\(\?(?:[:=!]|<[=!])|[?*+]\?|{\d+(?:,\d*)?}\??|[\s\S]/, - // Any native multicharacter token in character class scope, or any single character - 'class': /\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u(?:[\dA-Fa-f]{4}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|[\s\S]/ -}; // Any backreference or dollar-prefixed character in replacement strings - -var replacementToken = /\$(?:\{([^\}]+)\}|<([^>]+)>|(\d\d?|[\s\S]?))/g; // Check for correct `exec` handling of nonparticipating capturing groups - -var correctExecNpcg = /()??/.exec('')[1] === undefined; // Check for ES6 `flags` prop support - -var hasFlagsProp = (0, _flags["default"])(/x/) !== undefined; - -function hasNativeFlag(flag) { - // Can't check based on the presence of properties/getters since browsers might support such - // properties even when they don't support the corresponding flag in regex construction (tested - // in Chrome 48, where `'unicode' in /x/` is true but trying to construct a regex with flag `u` - // throws an error) - var isSupported = true; - - try { - // Can't use regex literals for testing even in a `try` because regex literals with - // unsupported flags cause a compilation error in IE - new RegExp('', flag); // Work around a broken/incomplete IE11 polyfill for sticky introduced in core-js 3.6.0 - - if (flag === 'y') { - // Using function to avoid babel transform to regex literal - var gy = function () { - return 'gy'; - }(); - - var incompleteY = '.a'.replace(new RegExp('a', gy), '.') === '..'; - - if (incompleteY) { - isSupported = false; - } - } - } catch (exception) { - isSupported = false; - } - - return isSupported; -} // Check for ES2018 `s` flag support - - -var hasNativeS = hasNativeFlag('s'); // Check for ES6 `u` flag support - -var hasNativeU = hasNativeFlag('u'); // Check for ES6 `y` flag support - -var hasNativeY = hasNativeFlag('y'); // Tracker for known flags, including addon flags - -var registeredFlags = { - g: true, - i: true, - m: true, - s: hasNativeS, - u: hasNativeU, - y: hasNativeY -}; // Flags to remove when passing to native `RegExp` constructor - -var nonnativeFlags = hasNativeS ? /[^gimsuy]+/g : /[^gimuy]+/g; -/** - * Attaches extended data and `XRegExp.prototype` properties to a regex object. - * - * @private - * @param {RegExp} regex Regex to augment. - * @param {Array} captureNames Array with capture names, or `null`. - * @param {String} xSource XRegExp pattern used to generate `regex`, or `null` if N/A. - * @param {String} xFlags XRegExp flags used to generate `regex`, or `null` if N/A. - * @param {Boolean} [isInternalOnly=false] Whether the regex will be used only for internal - * operations, and never exposed to users. For internal-only regexes, we can improve perf by - * skipping some operations like attaching `XRegExp.prototype` properties. - * @returns {!RegExp} Augmented regex. - */ - -function augment(regex, captureNames, xSource, xFlags, isInternalOnly) { - var _context; - - regex[REGEX_DATA] = { - captureNames: captureNames - }; - - if (isInternalOnly) { - return regex; - } // Can't auto-inherit these since the XRegExp constructor returns a nonprimitive value - - - if (regex.__proto__) { - regex.__proto__ = XRegExp.prototype; - } else { - for (var p in XRegExp.prototype) { - // An `XRegExp.prototype.hasOwnProperty(p)` check wouldn't be worth it here, since this - // is performance sensitive, and enumerable `Object.prototype` or `RegExp.prototype` - // extensions exist on `regex.prototype` anyway - regex[p] = XRegExp.prototype[p]; - } - } - - regex[REGEX_DATA].source = xSource; // Emulate the ES6 `flags` prop by ensuring flags are in alphabetical order - - regex[REGEX_DATA].flags = xFlags ? (0, _sort["default"])(_context = xFlags.split('')).call(_context).join('') : xFlags; - return regex; -} -/** - * Removes any duplicate characters from the provided string. - * - * @private - * @param {String} str String to remove duplicate characters from. - * @returns {string} String with any duplicate characters removed. - */ - - -function clipDuplicates(str) { - return str.replace(/([\s\S])(?=[\s\S]*\1)/g, ''); -} -/** - * Copies a regex object while preserving extended data and augmenting with `XRegExp.prototype` - * properties. The copy has a fresh `lastIndex` property (set to zero). Allows adding and removing - * flags g and y while copying the regex. - * - * @private - * @param {RegExp} regex Regex to copy. - * @param {Object} [options] Options object with optional properties: - * - `addG` {Boolean} Add flag g while copying the regex. - * - `addY` {Boolean} Add flag y while copying the regex. - * - `removeG` {Boolean} Remove flag g while copying the regex. - * - `removeY` {Boolean} Remove flag y while copying the regex. - * - `isInternalOnly` {Boolean} Whether the copied regex will be used only for internal - * operations, and never exposed to users. For internal-only regexes, we can improve perf by - * skipping some operations like attaching `XRegExp.prototype` properties. - * - `source` {String} Overrides `.source`, for special cases. - * @returns {RegExp} Copy of the provided regex, possibly with modified flags. - */ - - -function copyRegex(regex, options) { - var _context2; - - if (!XRegExp.isRegExp(regex)) { - throw new TypeError('Type RegExp expected'); - } - - var xData = regex[REGEX_DATA] || {}; - var flags = getNativeFlags(regex); - var flagsToAdd = ''; - var flagsToRemove = ''; - var xregexpSource = null; - var xregexpFlags = null; - options = options || {}; - - if (options.removeG) { - flagsToRemove += 'g'; - } - - if (options.removeY) { - flagsToRemove += 'y'; - } - - if (flagsToRemove) { - flags = flags.replace(new RegExp("[".concat(flagsToRemove, "]+"), 'g'), ''); - } - - if (options.addG) { - flagsToAdd += 'g'; - } - - if (options.addY) { - flagsToAdd += 'y'; - } - - if (flagsToAdd) { - flags = clipDuplicates(flags + flagsToAdd); - } - - if (!options.isInternalOnly) { - if (xData.source !== undefined) { - xregexpSource = xData.source; - } // null or undefined; don't want to add to `flags` if the previous value was null, since - // that indicates we're not tracking original precompilation flags - - - if ((0, _flags["default"])(xData) != null) { - // Flags are only added for non-internal regexes by `XRegExp.globalize`. Flags are never - // removed for non-internal regexes, so don't need to handle it - xregexpFlags = flagsToAdd ? clipDuplicates((0, _flags["default"])(xData) + flagsToAdd) : (0, _flags["default"])(xData); - } - } // Augment with `XRegExp.prototype` properties, but use the native `RegExp` constructor to avoid - // searching for special tokens. That would be wrong for regexes constructed by `RegExp`, and - // unnecessary for regexes constructed by `XRegExp` because the regex has already undergone the - // translation to native regex syntax - - - regex = augment(new RegExp(options.source || regex.source, flags), hasNamedCapture(regex) ? (0, _slice["default"])(_context2 = xData.captureNames).call(_context2, 0) : null, xregexpSource, xregexpFlags, options.isInternalOnly); - return regex; -} -/** - * Converts hexadecimal to decimal. - * - * @private - * @param {String} hex - * @returns {number} - */ - - -function dec(hex) { - return (0, _parseInt2["default"])(hex, 16); -} -/** - * Returns a pattern that can be used in a native RegExp in place of an ignorable token such as an - * inline comment or whitespace with flag x. This is used directly as a token handler function - * passed to `XRegExp.addToken`. - * - * @private - * @param {String} match Match arg of `XRegExp.addToken` handler - * @param {String} scope Scope arg of `XRegExp.addToken` handler - * @param {String} flags Flags arg of `XRegExp.addToken` handler - * @returns {string} Either '' or '(?:)', depending on which is needed in the context of the match. - */ - - -function getContextualTokenSeparator(match, scope, flags) { - var matchEndPos = match.index + match[0].length; - var precedingChar = match.input[match.index - 1]; - var followingChar = match.input[matchEndPos]; - - if ( // No need to separate tokens if at the beginning or end of a group, before or after a - // group, or before or after a `|` - /^[()|]$/.test(precedingChar) || /^[()|]$/.test(followingChar) || // No need to separate tokens if at the beginning or end of the pattern - match.index === 0 || matchEndPos === match.input.length || // No need to separate tokens if at the beginning of a noncapturing group or lookaround. - // Looks only at the last 4 chars (at most) for perf when constructing long regexes. - /\(\?(?:[:=!]|<[=!])$/.test(match.input.substring(match.index - 4, match.index)) || // Avoid separating tokens when the following token is a quantifier - isQuantifierNext(match.input, matchEndPos, flags)) { - return ''; - } // Keep tokens separated. This avoids e.g. inadvertedly changing `\1 1` or `\1(?#)1` to `\11`. - // This also ensures all tokens remain as discrete atoms, e.g. it prevents converting the - // syntax error `(? :` into `(?:`. - - - return '(?:)'; -} -/** - * Returns native `RegExp` flags used by a regex object. - * - * @private - * @param {RegExp} regex Regex to check. - * @returns {string} Native flags in use. - */ - - -function getNativeFlags(regex) { - return hasFlagsProp ? (0, _flags["default"])(regex) : // Explicitly using `RegExp.prototype.toString` (rather than e.g. `String` or concatenation - // with an empty string) allows this to continue working predictably when - // `XRegExp.proptotype.toString` is overridden - /\/([a-z]*)$/i.exec(RegExp.prototype.toString.call(regex))[1]; -} -/** - * Determines whether a regex has extended instance data used to track capture names. - * - * @private - * @param {RegExp} regex Regex to check. - * @returns {boolean} Whether the regex uses named capture. - */ - - -function hasNamedCapture(regex) { - return !!(regex[REGEX_DATA] && regex[REGEX_DATA].captureNames); -} -/** - * Converts decimal to hexadecimal. - * - * @private - * @param {Number|String} dec - * @returns {string} - */ - - -function hex(dec) { - return (0, _parseInt2["default"])(dec, 10).toString(16); -} -/** - * Checks whether the next nonignorable token after the specified position is a quantifier. - * - * @private - * @param {String} pattern Pattern to search within. - * @param {Number} pos Index in `pattern` to search at. - * @param {String} flags Flags used by the pattern. - * @returns {Boolean} Whether the next nonignorable token is a quantifier. - */ - - -function isQuantifierNext(pattern, pos, flags) { - var inlineCommentPattern = '\\(\\?#[^)]*\\)'; - var lineCommentPattern = '#[^#\\n]*'; - var quantifierPattern = '[?*+]|{\\d+(?:,\\d*)?}'; - var regex = (0, _indexOf["default"])(flags).call(flags, 'x') !== -1 ? // Ignore any leading whitespace, line comments, and inline comments - /^(?:\s|#[^#\n]*|\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/ : // Ignore any leading inline comments - /^(?:\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/; - return regex.test((0, _slice["default"])(pattern).call(pattern, pos)); -} -/** - * Determines whether a value is of the specified type, by resolving its internal [[Class]]. - * - * @private - * @param {*} value Object to check. - * @param {String} type Type to check for, in TitleCase. - * @returns {boolean} Whether the object matches the type. - */ - - -function isType(value, type) { - return Object.prototype.toString.call(value) === "[object ".concat(type, "]"); -} -/** - * Returns the object, or throws an error if it is `null` or `undefined`. This is used to follow - * the ES5 abstract operation `ToObject`. - * - * @private - * @param {*} value Object to check and return. - * @returns {*} The provided object. - */ - - -function nullThrows(value) { - // null or undefined - if (value == null) { - throw new TypeError('Cannot convert null or undefined to object'); - } - - return value; -} -/** - * Adds leading zeros if shorter than four characters. Used for fixed-length hexadecimal values. - * - * @private - * @param {String} str - * @returns {string} - */ - - -function pad4(str) { - while (str.length < 4) { - str = "0".concat(str); - } + // getting tag from ES6+ `Object.prototype.toString` + module.exports = TO_STRING_TAG_SUPPORT + ? classofRaw + : function (it) { + var O, tag, result + return it === undefined + ? 'Undefined' + : it === null + ? 'Null' + : // @@toStringTag case + typeof (tag = tryGet((O = Object(it)), TO_STRING_TAG)) == 'string' + ? tag + : // builtinTag case + CORRECT_ARGUMENTS + ? classofRaw(O) + : // ES3 arguments fallback + (result = classofRaw(O)) == 'Object' && typeof O.callee == 'function' + ? 'Arguments' + : result + } + }, + { + '../internals/classof-raw': 77, + '../internals/to-string-tag-support': 154, + '../internals/well-known-symbol': 159, + }, + ], + 79: [ + function (require, module, exports) { + var fails = require('../internals/fails') + + module.exports = !fails(function () { + function F() { + /* empty */ + } + F.prototype.constructor = null + // eslint-disable-next-line es/no-object-getprototypeof -- required for testing + return Object.getPrototypeOf(new F()) !== F.prototype + }) + }, + { '../internals/fails': 98 }, + ], + 80: [ + function (require, module, exports) { + 'use strict' + var IteratorPrototype = require('../internals/iterators-core').IteratorPrototype + var create = require('../internals/object-create') + var createPropertyDescriptor = require('../internals/create-property-descriptor') + var setToStringTag = require('../internals/set-to-string-tag') + var Iterators = require('../internals/iterators') + + var returnThis = function () { + return this + } - return str; -} -/** - * Checks for flag-related errors, and strips/applies flags in a leading mode modifier. Offloads - * the flag preparation logic from the `XRegExp` constructor. - * - * @private - * @param {String} pattern Regex pattern, possibly with a leading mode modifier. - * @param {String} flags Any combination of flags. - * @returns {!Object} Object with properties `pattern` and `flags`. - */ + module.exports = function (IteratorConstructor, NAME, next) { + var TO_STRING_TAG = NAME + ' Iterator' + IteratorConstructor.prototype = create(IteratorPrototype, { next: createPropertyDescriptor(1, next) }) + setToStringTag(IteratorConstructor, TO_STRING_TAG, false, true) + Iterators[TO_STRING_TAG] = returnThis + return IteratorConstructor + } + }, + { + '../internals/create-property-descriptor': 82, + '../internals/iterators': 118, + '../internals/iterators-core': 117, + '../internals/object-create': 122, + '../internals/set-to-string-tag': 141, + }, + ], + 81: [ + function (require, module, exports) { + var DESCRIPTORS = require('../internals/descriptors') + var definePropertyModule = require('../internals/object-define-property') + var createPropertyDescriptor = require('../internals/create-property-descriptor') + + module.exports = DESCRIPTORS + ? function (object, key, value) { + return definePropertyModule.f(object, key, createPropertyDescriptor(1, value)) + } + : function (object, key, value) { + object[key] = value + return object + } + }, + { + '../internals/create-property-descriptor': 82, + '../internals/descriptors': 86, + '../internals/object-define-property': 124, + }, + ], + 82: [ + function (require, module, exports) { + module.exports = function (bitmap, value) { + return { + enumerable: !(bitmap & 1), + configurable: !(bitmap & 2), + writable: !(bitmap & 4), + value: value, + } + } + }, + {}, + ], + 83: [ + function (require, module, exports) { + 'use strict' + var toPropertyKey = require('../internals/to-property-key') + var definePropertyModule = require('../internals/object-define-property') + var createPropertyDescriptor = require('../internals/create-property-descriptor') + + module.exports = function (object, key, value) { + var propertyKey = toPropertyKey(key) + if (propertyKey in object) definePropertyModule.f(object, propertyKey, createPropertyDescriptor(0, value)) + else object[propertyKey] = value + } + }, + { + '../internals/create-property-descriptor': 82, + '../internals/object-define-property': 124, + '../internals/to-property-key': 153, + }, + ], + 84: [ + function (require, module, exports) { + 'use strict' + var $ = require('../internals/export') + var createIteratorConstructor = require('../internals/create-iterator-constructor') + var getPrototypeOf = require('../internals/object-get-prototype-of') + var setPrototypeOf = require('../internals/object-set-prototype-of') + var setToStringTag = require('../internals/set-to-string-tag') + var createNonEnumerableProperty = require('../internals/create-non-enumerable-property') + var redefine = require('../internals/redefine') + var wellKnownSymbol = require('../internals/well-known-symbol') + var IS_PURE = require('../internals/is-pure') + var Iterators = require('../internals/iterators') + var IteratorsCore = require('../internals/iterators-core') + + var IteratorPrototype = IteratorsCore.IteratorPrototype + var BUGGY_SAFARI_ITERATORS = IteratorsCore.BUGGY_SAFARI_ITERATORS + var ITERATOR = wellKnownSymbol('iterator') + var KEYS = 'keys' + var VALUES = 'values' + var ENTRIES = 'entries' + + var returnThis = function () { + return this + } + module.exports = function (Iterable, NAME, IteratorConstructor, next, DEFAULT, IS_SET, FORCED) { + createIteratorConstructor(IteratorConstructor, NAME, next) + + var getIterationMethod = function (KIND) { + if (KIND === DEFAULT && defaultIterator) return defaultIterator + if (!BUGGY_SAFARI_ITERATORS && KIND in IterablePrototype) return IterablePrototype[KIND] + switch (KIND) { + case KEYS: + return function keys() { + return new IteratorConstructor(this, KIND) + } + case VALUES: + return function values() { + return new IteratorConstructor(this, KIND) + } + case ENTRIES: + return function entries() { + return new IteratorConstructor(this, KIND) + } + } + return function () { + return new IteratorConstructor(this) + } + } -function prepareFlags(pattern, flags) { - // Recent browsers throw on duplicate flags, so copy this behavior for nonnative flags - if (clipDuplicates(flags) !== flags) { - throw new SyntaxError("Invalid duplicate regex flag ".concat(flags)); - } // Strip and apply a leading mode modifier with any combination of flags except g or y + var TO_STRING_TAG = NAME + ' Iterator' + var INCORRECT_VALUES_NAME = false + var IterablePrototype = Iterable.prototype + var nativeIterator = + IterablePrototype[ITERATOR] || IterablePrototype['@@iterator'] || (DEFAULT && IterablePrototype[DEFAULT]) + var defaultIterator = (!BUGGY_SAFARI_ITERATORS && nativeIterator) || getIterationMethod(DEFAULT) + var anyNativeIterator = NAME == 'Array' ? IterablePrototype.entries || nativeIterator : nativeIterator + var CurrentIteratorPrototype, methods, KEY + + // fix native + if (anyNativeIterator) { + CurrentIteratorPrototype = getPrototypeOf(anyNativeIterator.call(new Iterable())) + if (IteratorPrototype !== Object.prototype && CurrentIteratorPrototype.next) { + if (!IS_PURE && getPrototypeOf(CurrentIteratorPrototype) !== IteratorPrototype) { + if (setPrototypeOf) { + setPrototypeOf(CurrentIteratorPrototype, IteratorPrototype) + } else if (typeof CurrentIteratorPrototype[ITERATOR] != 'function') { + createNonEnumerableProperty(CurrentIteratorPrototype, ITERATOR, returnThis) + } + } + // Set @@toStringTag to native iterators + setToStringTag(CurrentIteratorPrototype, TO_STRING_TAG, true, true) + if (IS_PURE) Iterators[TO_STRING_TAG] = returnThis + } + } + // fix Array.prototype.{ values, @@iterator }.name in V8 / FF + if (DEFAULT == VALUES && nativeIterator && nativeIterator.name !== VALUES) { + INCORRECT_VALUES_NAME = true + defaultIterator = function values() { + return nativeIterator.call(this) + } + } - pattern = pattern.replace(/^\(\?([\w$]+)\)/, function ($0, $1) { - if (/[gy]/.test($1)) { - throw new SyntaxError("Cannot use flag g or y in mode modifier ".concat($0)); - } // Allow duplicate flags within the mode modifier + // define iterator + if ((!IS_PURE || FORCED) && IterablePrototype[ITERATOR] !== defaultIterator) { + createNonEnumerableProperty(IterablePrototype, ITERATOR, defaultIterator) + } + Iterators[NAME] = defaultIterator + + // export additional methods + if (DEFAULT) { + methods = { + values: getIterationMethod(VALUES), + keys: IS_SET ? defaultIterator : getIterationMethod(KEYS), + entries: getIterationMethod(ENTRIES), + } + if (FORCED) + for (KEY in methods) { + if (BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME || !(KEY in IterablePrototype)) { + redefine(IterablePrototype, KEY, methods[KEY]) + } + } + else $({ target: NAME, proto: true, forced: BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME }, methods) + } + return methods + } + }, + { + '../internals/create-iterator-constructor': 80, + '../internals/create-non-enumerable-property': 81, + '../internals/export': 97, + '../internals/is-pure': 114, + '../internals/iterators': 118, + '../internals/iterators-core': 117, + '../internals/object-get-prototype-of': 129, + '../internals/object-set-prototype-of': 133, + '../internals/redefine': 137, + '../internals/set-to-string-tag': 141, + '../internals/well-known-symbol': 159, + }, + ], + 85: [ + function (require, module, exports) { + var path = require('../internals/path') + var has = require('../internals/has') + var wrappedWellKnownSymbolModule = require('../internals/well-known-symbol-wrapped') + var defineProperty = require('../internals/object-define-property').f + + module.exports = function (NAME) { + var Symbol = path.Symbol || (path.Symbol = {}) + if (!has(Symbol, NAME)) + defineProperty(Symbol, NAME, { + value: wrappedWellKnownSymbolModule.f(NAME), + }) + } + }, + { + '../internals/has': 103, + '../internals/object-define-property': 124, + '../internals/path': 136, + '../internals/well-known-symbol-wrapped': 158, + }, + ], + 86: [ + function (require, module, exports) { + var fails = require('../internals/fails') + + // Detect IE8's incomplete defineProperty implementation + module.exports = !fails(function () { + // eslint-disable-next-line es/no-object-defineproperty -- required for testing + return ( + Object.defineProperty({}, 1, { + get: function () { + return 7 + }, + })[1] != 7 + ) + }) + }, + { '../internals/fails': 98 }, + ], + 87: [ + function (require, module, exports) { + var global = require('../internals/global') + var isObject = require('../internals/is-object') + + var document = global.document + // typeof document.createElement is 'object' in old IE + var EXISTS = isObject(document) && isObject(document.createElement) + + module.exports = function (it) { + return EXISTS ? document.createElement(it) : {} + } + }, + { '../internals/global': 102, '../internals/is-object': 113 }, + ], + 88: [ + function (require, module, exports) { + // iterable DOM collections + // flag - `iterable` interface - 'entries', 'keys', 'values', 'forEach' methods + module.exports = { + CSSRuleList: 0, + CSSStyleDeclaration: 0, + CSSValueList: 0, + ClientRectList: 0, + DOMRectList: 0, + DOMStringList: 0, + DOMTokenList: 1, + DataTransferItemList: 0, + FileList: 0, + HTMLAllCollection: 0, + HTMLCollection: 0, + HTMLFormElement: 0, + HTMLSelectElement: 0, + MediaList: 0, + MimeTypeArray: 0, + NamedNodeMap: 0, + NodeList: 1, + PaintRequestList: 0, + Plugin: 0, + PluginArray: 0, + SVGLengthList: 0, + SVGNumberList: 0, + SVGPathSegList: 0, + SVGPointList: 0, + SVGStringList: 0, + SVGTransformList: 0, + SourceBufferList: 0, + StyleSheetList: 0, + TextTrackCueList: 0, + TextTrackList: 0, + TouchList: 0, + } + }, + {}, + ], + 89: [ + function (require, module, exports) { + var userAgent = require('../internals/engine-user-agent') + + var firefox = userAgent.match(/firefox\/(\d+)/i) + + module.exports = !!firefox && +firefox[1] + }, + { '../internals/engine-user-agent': 92 }, + ], + 90: [ + function (require, module, exports) { + var UA = require('../internals/engine-user-agent') + + module.exports = /MSIE|Trident/.test(UA) + }, + { '../internals/engine-user-agent': 92 }, + ], + 91: [ + function (require, module, exports) { + var classof = require('../internals/classof-raw') + var global = require('../internals/global') + + module.exports = classof(global.process) == 'process' + }, + { '../internals/classof-raw': 77, '../internals/global': 102 }, + ], + 92: [ + function (require, module, exports) { + var getBuiltIn = require('../internals/get-built-in') + + module.exports = getBuiltIn('navigator', 'userAgent') || '' + }, + { '../internals/get-built-in': 100 }, + ], + 93: [ + function (require, module, exports) { + var global = require('../internals/global') + var userAgent = require('../internals/engine-user-agent') + + var process = global.process + var Deno = global.Deno + var versions = (process && process.versions) || (Deno && Deno.version) + var v8 = versions && versions.v8 + var match, version + + if (v8) { + match = v8.split('.') + version = match[0] < 4 ? 1 : match[0] + match[1] + } else if (userAgent) { + match = userAgent.match(/Edge\/(\d+)/) + if (!match || match[1] >= 74) { + match = userAgent.match(/Chrome\/(\d+)/) + if (match) version = match[1] + } + } - flags = clipDuplicates(flags + $1); - return ''; - }); // Throw on unknown native or nonnative flags + module.exports = version && +version + }, + { '../internals/engine-user-agent': 92, '../internals/global': 102 }, + ], + 94: [ + function (require, module, exports) { + var userAgent = require('../internals/engine-user-agent') + + var webkit = userAgent.match(/AppleWebKit\/(\d+)\./) + + module.exports = !!webkit && +webkit[1] + }, + { '../internals/engine-user-agent': 92 }, + ], + 95: [ + function (require, module, exports) { + var path = require('../internals/path') + + module.exports = function (CONSTRUCTOR) { + return path[CONSTRUCTOR + 'Prototype'] + } + }, + { '../internals/path': 136 }, + ], + 96: [ + function (require, module, exports) { + // IE8- don't enum bug keys + module.exports = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf', + ] + }, + {}, + ], + 97: [ + function (require, module, exports) { + 'use strict' + var global = require('../internals/global') + var getOwnPropertyDescriptor = require('../internals/object-get-own-property-descriptor').f + var isForced = require('../internals/is-forced') + var path = require('../internals/path') + var bind = require('../internals/function-bind-context') + var createNonEnumerableProperty = require('../internals/create-non-enumerable-property') + var has = require('../internals/has') + + var wrapConstructor = function (NativeConstructor) { + var Wrapper = function (a, b, c) { + if (this instanceof NativeConstructor) { + switch (arguments.length) { + case 0: + return new NativeConstructor() + case 1: + return new NativeConstructor(a) + case 2: + return new NativeConstructor(a, b) + } + return new NativeConstructor(a, b, c) + } + return NativeConstructor.apply(this, arguments) + } + Wrapper.prototype = NativeConstructor.prototype + return Wrapper + } - var _iterator = _createForOfIteratorHelper(flags), - _step; + /* + options.target - name of the target object + options.global - target is the global object + options.stat - export as static methods of target + options.proto - export as prototype methods of target + options.real - real prototype method for the `pure` version + options.forced - export even if the native feature is available + options.bind - bind methods to the target, required for the `pure` version + options.wrap - wrap constructors to preventing global pollution, required for the `pure` version + options.unsafe - use the simple assignment of property instead of delete + defineProperty + options.sham - add a flag to not completely full polyfills + options.enumerable - export as enumerable property + options.noTargetGet - prevent calling a getter on target +*/ + module.exports = function (options, source) { + var TARGET = options.target + var GLOBAL = options.global + var STATIC = options.stat + var PROTO = options.proto + + var nativeSource = GLOBAL ? global : STATIC ? global[TARGET] : (global[TARGET] || {}).prototype + + var target = GLOBAL ? path : path[TARGET] || (path[TARGET] = {}) + var targetPrototype = target.prototype + + var FORCED, USE_NATIVE, VIRTUAL_PROTOTYPE + var key, sourceProperty, targetProperty, nativeProperty, resultProperty, descriptor + + for (key in source) { + FORCED = isForced(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced) + // contains in native + USE_NATIVE = !FORCED && nativeSource && has(nativeSource, key) + + targetProperty = target[key] + + if (USE_NATIVE) + if (options.noTargetGet) { + descriptor = getOwnPropertyDescriptor(nativeSource, key) + nativeProperty = descriptor && descriptor.value + } else nativeProperty = nativeSource[key] + + // export native or implementation + sourceProperty = USE_NATIVE && nativeProperty ? nativeProperty : source[key] + + if (USE_NATIVE && typeof targetProperty === typeof sourceProperty) continue + + // bind timers to global for call from export context + if (options.bind && USE_NATIVE) resultProperty = bind(sourceProperty, global) + // wrap global constructors for prevent changs in this version + else if (options.wrap && USE_NATIVE) resultProperty = wrapConstructor(sourceProperty) + // make static versions for prototype methods + else if (PROTO && typeof sourceProperty == 'function') + resultProperty = bind(Function.call, sourceProperty) + // default case + else resultProperty = sourceProperty + + // add a flag to not completely full polyfills + if (options.sham || (sourceProperty && sourceProperty.sham) || (targetProperty && targetProperty.sham)) { + createNonEnumerableProperty(resultProperty, 'sham', true) + } + + target[key] = resultProperty + + if (PROTO) { + VIRTUAL_PROTOTYPE = TARGET + 'Prototype' + if (!has(path, VIRTUAL_PROTOTYPE)) { + createNonEnumerableProperty(path, VIRTUAL_PROTOTYPE, {}) + } + // export virtual prototype methods + path[VIRTUAL_PROTOTYPE][key] = sourceProperty + // export real prototype methods + if (options.real && targetPrototype && !targetPrototype[key]) { + createNonEnumerableProperty(targetPrototype, key, sourceProperty) + } + } + } + } + }, + { + '../internals/create-non-enumerable-property': 81, + '../internals/function-bind-context': 99, + '../internals/global': 102, + '../internals/has': 103, + '../internals/is-forced': 112, + '../internals/object-get-own-property-descriptor': 125, + '../internals/path': 136, + }, + ], + 98: [ + function (require, module, exports) { + module.exports = function (exec) { + try { + return !!exec() + } catch (error) { + return true + } + } + }, + {}, + ], + 99: [ + function (require, module, exports) { + var aFunction = require('../internals/a-function') + + // optional / simple context binding + module.exports = function (fn, that, length) { + aFunction(fn) + if (that === undefined) return fn + switch (length) { + case 0: + return function () { + return fn.call(that) + } + case 1: + return function (a) { + return fn.call(that, a) + } + case 2: + return function (a, b) { + return fn.call(that, a, b) + } + case 3: + return function (a, b, c) { + return fn.call(that, a, b, c) + } + } + return function (/* ...args */) { + return fn.apply(that, arguments) + } + } + }, + { '../internals/a-function': 61 }, + ], + 100: [ + function (require, module, exports) { + var path = require('../internals/path') + var global = require('../internals/global') + + var aFunction = function (variable) { + return typeof variable == 'function' ? variable : undefined + } - try { - for (_iterator.s(); !(_step = _iterator.n()).done;) { - var flag = _step.value; + module.exports = function (namespace, method) { + return arguments.length < 2 + ? aFunction(path[namespace]) || aFunction(global[namespace]) + : (path[namespace] && path[namespace][method]) || (global[namespace] && global[namespace][method]) + } + }, + { '../internals/global': 102, '../internals/path': 136 }, + ], + 101: [ + function (require, module, exports) { + var classof = require('../internals/classof') + var Iterators = require('../internals/iterators') + var wellKnownSymbol = require('../internals/well-known-symbol') + + var ITERATOR = wellKnownSymbol('iterator') + + module.exports = function (it) { + if (it != undefined) return it[ITERATOR] || it['@@iterator'] || Iterators[classof(it)] + } + }, + { '../internals/classof': 78, '../internals/iterators': 118, '../internals/well-known-symbol': 159 }, + ], + 102: [ + function (require, module, exports) { + ;(function (global) { + ;(function () { + var check = function (it) { + return it && it.Math == Math && it + } + + // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 + module.exports = + // eslint-disable-next-line es/no-global-this -- safe + check(typeof globalThis == 'object' && globalThis) || + check(typeof window == 'object' && window) || + // eslint-disable-next-line no-restricted-globals -- safe + check(typeof self == 'object' && self) || + check(typeof global == 'object' && global) || + // eslint-disable-next-line no-new-func -- fallback + (function () { + return this + })() || + Function('return this')() + }.call(this)) + }.call( + this, + typeof global !== 'undefined' + ? global + : typeof self !== 'undefined' + ? self + : typeof window !== 'undefined' + ? window + : {} + )) + }, + {}, + ], + 103: [ + function (require, module, exports) { + var toObject = require('../internals/to-object') + + var hasOwnProperty = {}.hasOwnProperty + + module.exports = + Object.hasOwn || + function hasOwn(it, key) { + return hasOwnProperty.call(toObject(it), key) + } + }, + { '../internals/to-object': 151 }, + ], + 104: [ + function (require, module, exports) { + module.exports = {} + }, + {}, + ], + 105: [ + function (require, module, exports) { + var getBuiltIn = require('../internals/get-built-in') + + module.exports = getBuiltIn('document', 'documentElement') + }, + { '../internals/get-built-in': 100 }, + ], + 106: [ + function (require, module, exports) { + var DESCRIPTORS = require('../internals/descriptors') + var fails = require('../internals/fails') + var createElement = require('../internals/document-create-element') + + // Thank's IE8 for his funny defineProperty + module.exports = + !DESCRIPTORS && + !fails(function () { + // eslint-disable-next-line es/no-object-defineproperty -- requied for testing + return ( + Object.defineProperty(createElement('div'), 'a', { + get: function () { + return 7 + }, + }).a != 7 + ) + }) + }, + { '../internals/descriptors': 86, '../internals/document-create-element': 87, '../internals/fails': 98 }, + ], + 107: [ + function (require, module, exports) { + var fails = require('../internals/fails') + var classof = require('../internals/classof-raw') + + var split = ''.split + + // fallback for non-array-like ES3 and non-enumerable old V8 strings + module.exports = fails(function () { + // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346 + // eslint-disable-next-line no-prototype-builtins -- safe + return !Object('z').propertyIsEnumerable(0) + }) + ? function (it) { + return classof(it) == 'String' ? split.call(it, '') : Object(it) + } + : Object + }, + { '../internals/classof-raw': 77, '../internals/fails': 98 }, + ], + 108: [ + function (require, module, exports) { + var store = require('../internals/shared-store') + + var functionToString = Function.toString + + // this helper broken in `core-js@3.4.1-3.4.4`, so we can't use `shared` helper + if (typeof store.inspectSource != 'function') { + store.inspectSource = function (it) { + return functionToString.call(it) + } + } - if (!registeredFlags[flag]) { - throw new SyntaxError("Unknown regex flag ".concat(flag)); - } - } - } catch (err) { - _iterator.e(err); - } finally { - _iterator.f(); - } + module.exports = store.inspectSource + }, + { '../internals/shared-store': 143 }, + ], + 109: [ + function (require, module, exports) { + var NATIVE_WEAK_MAP = require('../internals/native-weak-map') + var global = require('../internals/global') + var isObject = require('../internals/is-object') + var createNonEnumerableProperty = require('../internals/create-non-enumerable-property') + var objectHas = require('../internals/has') + var shared = require('../internals/shared-store') + var sharedKey = require('../internals/shared-key') + var hiddenKeys = require('../internals/hidden-keys') + + var OBJECT_ALREADY_INITIALIZED = 'Object already initialized' + var WeakMap = global.WeakMap + var set, get, has + + var enforce = function (it) { + return has(it) ? get(it) : set(it, {}) + } - return { - pattern: pattern, - flags: flags - }; -} -/** - * Prepares an options object from the given value. - * - * @private - * @param {String|Object} value Value to convert to an options object. - * @returns {Object} Options object. - */ - - -function prepareOptions(value) { - var options = {}; - - if (isType(value, 'String')) { - (0, _forEach["default"])(XRegExp).call(XRegExp, value, /[^\s,]+/, function (match) { - options[match] = true; - }); - return options; - } + var getterFor = function (TYPE) { + return function (it) { + var state + if (!isObject(it) || (state = get(it)).type !== TYPE) { + throw TypeError('Incompatible receiver, ' + TYPE + ' required') + } + return state + } + } - return value; -} -/** - * Registers a flag so it doesn't throw an 'unknown flag' error. - * - * @private - * @param {String} flag Single-character flag to register. - */ + if (NATIVE_WEAK_MAP || shared.state) { + var store = shared.state || (shared.state = new WeakMap()) + var wmget = store.get + var wmhas = store.has + var wmset = store.set + set = function (it, metadata) { + if (wmhas.call(store, it)) throw new TypeError(OBJECT_ALREADY_INITIALIZED) + metadata.facade = it + wmset.call(store, it, metadata) + return metadata + } + get = function (it) { + return wmget.call(store, it) || {} + } + has = function (it) { + return wmhas.call(store, it) + } + } else { + var STATE = sharedKey('state') + hiddenKeys[STATE] = true + set = function (it, metadata) { + if (objectHas(it, STATE)) throw new TypeError(OBJECT_ALREADY_INITIALIZED) + metadata.facade = it + createNonEnumerableProperty(it, STATE, metadata) + return metadata + } + get = function (it) { + return objectHas(it, STATE) ? it[STATE] : {} + } + has = function (it) { + return objectHas(it, STATE) + } + } + module.exports = { + set: set, + get: get, + has: has, + enforce: enforce, + getterFor: getterFor, + } + }, + { + '../internals/create-non-enumerable-property': 81, + '../internals/global': 102, + '../internals/has': 103, + '../internals/hidden-keys': 104, + '../internals/is-object': 113, + '../internals/native-weak-map': 120, + '../internals/shared-key': 142, + '../internals/shared-store': 143, + }, + ], + 110: [ + function (require, module, exports) { + var wellKnownSymbol = require('../internals/well-known-symbol') + var Iterators = require('../internals/iterators') + + var ITERATOR = wellKnownSymbol('iterator') + var ArrayPrototype = Array.prototype + + // check on default Array iterator + module.exports = function (it) { + return it !== undefined && (Iterators.Array === it || ArrayPrototype[ITERATOR] === it) + } + }, + { '../internals/iterators': 118, '../internals/well-known-symbol': 159 }, + ], + 111: [ + function (require, module, exports) { + var classof = require('../internals/classof-raw') + + // `IsArray` abstract operation + // https://tc39.es/ecma262/#sec-isarray + // eslint-disable-next-line es/no-array-isarray -- safe + module.exports = + Array.isArray || + function isArray(arg) { + return classof(arg) == 'Array' + } + }, + { '../internals/classof-raw': 77 }, + ], + 112: [ + function (require, module, exports) { + var fails = require('../internals/fails') + + var replacement = /#|\.prototype\./ + + var isForced = function (feature, detection) { + var value = data[normalize(feature)] + return value == POLYFILL + ? true + : value == NATIVE + ? false + : typeof detection == 'function' + ? fails(detection) + : !!detection + } -function registerFlag(flag) { - if (!/^[\w$]$/.test(flag)) { - throw new Error('Flag must be a single character A-Za-z0-9_$'); - } + var normalize = (isForced.normalize = function (string) { + return String(string).replace(replacement, '.').toLowerCase() + }) + + var data = (isForced.data = {}) + var NATIVE = (isForced.NATIVE = 'N') + var POLYFILL = (isForced.POLYFILL = 'P') + + module.exports = isForced + }, + { '../internals/fails': 98 }, + ], + 113: [ + function (require, module, exports) { + module.exports = function (it) { + return typeof it === 'object' ? it !== null : typeof it === 'function' + } + }, + {}, + ], + 114: [ + function (require, module, exports) { + module.exports = true + }, + {}, + ], + 115: [ + function (require, module, exports) { + var getBuiltIn = require('../internals/get-built-in') + var USE_SYMBOL_AS_UID = require('../internals/use-symbol-as-uid') + + module.exports = USE_SYMBOL_AS_UID + ? function (it) { + return typeof it == 'symbol' + } + : function (it) { + var $Symbol = getBuiltIn('Symbol') + return typeof $Symbol == 'function' && Object(it) instanceof $Symbol + } + }, + { '../internals/get-built-in': 100, '../internals/use-symbol-as-uid': 157 }, + ], + 116: [ + function (require, module, exports) { + var anObject = require('../internals/an-object') + + module.exports = function (iterator) { + var returnMethod = iterator['return'] + if (returnMethod !== undefined) { + return anObject(returnMethod.call(iterator)).value + } + } + }, + { '../internals/an-object': 64 }, + ], + 117: [ + function (require, module, exports) { + 'use strict' + var fails = require('../internals/fails') + var getPrototypeOf = require('../internals/object-get-prototype-of') + var createNonEnumerableProperty = require('../internals/create-non-enumerable-property') + var has = require('../internals/has') + var wellKnownSymbol = require('../internals/well-known-symbol') + var IS_PURE = require('../internals/is-pure') + + var ITERATOR = wellKnownSymbol('iterator') + var BUGGY_SAFARI_ITERATORS = false + + var returnThis = function () { + return this + } - registeredFlags[flag] = true; -} -/** - * Runs built-in and custom regex syntax tokens in reverse insertion order at the specified - * position, until a match is found. - * - * @private - * @param {String} pattern Original pattern from which an XRegExp object is being built. - * @param {String} flags Flags being used to construct the regex. - * @param {Number} pos Position to search for tokens within `pattern`. - * @param {Number} scope Regex scope to apply: 'default' or 'class'. - * @param {Object} context Context object to use for token handler functions. - * @returns {Object} Object with properties `matchLength`, `output`, and `reparse`; or `null`. - */ - - -function runTokens(pattern, flags, pos, scope, context) { - var i = tokens.length; - var leadChar = pattern[pos]; - var result = null; - var match; - var t; // Run in reverse insertion order - - while (i--) { - t = tokens[i]; - - if (t.leadChar && t.leadChar !== leadChar || t.scope !== scope && t.scope !== 'all' || t.flag && !((0, _indexOf["default"])(flags).call(flags, t.flag) !== -1)) { - continue; - } + // `%IteratorPrototype%` object + // https://tc39.es/ecma262/#sec-%iteratorprototype%-object + var IteratorPrototype, PrototypeOfArrayIteratorPrototype, arrayIterator + + /* eslint-disable es/no-array-prototype-keys -- safe */ + if ([].keys) { + arrayIterator = [].keys() + // Safari 8 has buggy iterators w/o `next` + if (!('next' in arrayIterator)) BUGGY_SAFARI_ITERATORS = true + else { + PrototypeOfArrayIteratorPrototype = getPrototypeOf(getPrototypeOf(arrayIterator)) + if (PrototypeOfArrayIteratorPrototype !== Object.prototype) + IteratorPrototype = PrototypeOfArrayIteratorPrototype + } + } - match = XRegExp.exec(pattern, t.regex, pos, 'sticky'); + var NEW_ITERATOR_PROTOTYPE = + IteratorPrototype == undefined || + fails(function () { + var test = {} + // FF44- legacy iterators case + return IteratorPrototype[ITERATOR].call(test) !== test + }) - if (match) { - result = { - matchLength: match[0].length, - output: t.handler.call(context, match, scope, flags), - reparse: t.reparse - }; // Finished with token tests + if (NEW_ITERATOR_PROTOTYPE) IteratorPrototype = {} - break; - } - } + // `%IteratorPrototype%[@@iterator]()` method + // https://tc39.es/ecma262/#sec-%iteratorprototype%-@@iterator + if ((!IS_PURE || NEW_ITERATOR_PROTOTYPE) && !has(IteratorPrototype, ITERATOR)) { + createNonEnumerableProperty(IteratorPrototype, ITERATOR, returnThis) + } - return result; -} -/** - * Enables or disables implicit astral mode opt-in. When enabled, flag A is automatically added to - * all new regexes created by XRegExp. This causes an error to be thrown when creating regexes if - * the Unicode Base addon is not available, since flag A is registered by that addon. - * - * @private - * @param {Boolean} on `true` to enable; `false` to disable. - */ - - -function setAstral(on) { - features.astral = on; -} -/** - * Adds named capture groups to the `groups` property of match arrays. See here for details: - * https://github.com/tc39/proposal-regexp-named-groups - * - * @private - * @param {Boolean} on `true` to enable; `false` to disable. - */ - - -function setNamespacing(on) { - features.namespacing = on; -} // ==--------------------------== -// Constructor -// ==--------------------------== - -/** - * Creates an extended regular expression object for matching text with a pattern. Differs from a - * native regular expression in that additional syntax and flags are supported. The returned object - * is in fact a native `RegExp` and works with all native methods. - * - * @class XRegExp - * @constructor - * @param {String|RegExp} pattern Regex pattern string, or an existing regex object to copy. - * @param {String} [flags] Any combination of flags. - * Native flags: - * - `g` - global - * - `i` - ignore case - * - `m` - multiline anchors - * - `u` - unicode (ES6) - * - `y` - sticky (Firefox 3+, ES6) - * Additional XRegExp flags: - * - `n` - explicit capture - * - `s` - dot matches all (aka singleline) - works even when not natively supported - * - `x` - free-spacing and line comments (aka extended) - * - `A` - astral (requires the Unicode Base addon) - * Flags cannot be provided when constructing one `RegExp` from another. - * @returns {RegExp} Extended regular expression object. - * @example - * - * // With named capture and flag x - * XRegExp(`(? [0-9]{4} ) -? # year - * (? [0-9]{2} ) -? # month - * (? [0-9]{2} ) # day`, 'x'); - * - * // Providing a regex object copies it. Native regexes are recompiled using native (not XRegExp) - * // syntax. Copies maintain extended data, are augmented with `XRegExp.prototype` properties, and - * // have fresh `lastIndex` properties (set to zero). - * XRegExp(/regex/); - */ - - -function XRegExp(pattern, flags) { - if (XRegExp.isRegExp(pattern)) { - if (flags !== undefined) { - throw new TypeError('Cannot supply flags when copying a RegExp'); - } + module.exports = { + IteratorPrototype: IteratorPrototype, + BUGGY_SAFARI_ITERATORS: BUGGY_SAFARI_ITERATORS, + } + }, + { + '../internals/create-non-enumerable-property': 81, + '../internals/fails': 98, + '../internals/has': 103, + '../internals/is-pure': 114, + '../internals/object-get-prototype-of': 129, + '../internals/well-known-symbol': 159, + }, + ], + 118: [ + function (require, module, exports) { + arguments[4][104][0].apply(exports, arguments) + }, + { dup: 104 }, + ], + 119: [ + function (require, module, exports) { + /* eslint-disable es/no-symbol -- required for testing */ + var V8_VERSION = require('../internals/engine-v8-version') + var fails = require('../internals/fails') + + // eslint-disable-next-line es/no-object-getownpropertysymbols -- required for testing + module.exports = + !!Object.getOwnPropertySymbols && + !fails(function () { + var symbol = Symbol() + // Chrome 38 Symbol has incorrect toString conversion + // `get-own-property-symbols` polyfill symbols converted to object are not Symbol instances + return ( + !String(symbol) || + !(Object(symbol) instanceof Symbol) || + // Chrome 38-40 symbols are not inherited from DOM collections prototypes to instances + (!Symbol.sham && V8_VERSION && V8_VERSION < 41) + ) + }) + }, + { '../internals/engine-v8-version': 93, '../internals/fails': 98 }, + ], + 120: [ + function (require, module, exports) { + var global = require('../internals/global') + var inspectSource = require('../internals/inspect-source') + + var WeakMap = global.WeakMap + + module.exports = typeof WeakMap === 'function' && /native code/.test(inspectSource(WeakMap)) + }, + { '../internals/global': 102, '../internals/inspect-source': 108 }, + ], + 121: [ + function (require, module, exports) { + var global = require('../internals/global') + var toString = require('../internals/to-string') + var trim = require('../internals/string-trim').trim + var whitespaces = require('../internals/whitespaces') + + var $parseInt = global.parseInt + var hex = /^[+-]?0[Xx]/ + var FORCED = $parseInt(whitespaces + '08') !== 8 || $parseInt(whitespaces + '0x16') !== 22 + + // `parseInt` method + // https://tc39.es/ecma262/#sec-parseint-string-radix + module.exports = FORCED + ? function parseInt(string, radix) { + var S = trim(toString(string)) + return $parseInt(S, radix >>> 0 || (hex.test(S) ? 16 : 10)) + } + : $parseInt + }, + { + '../internals/global': 102, + '../internals/string-trim': 146, + '../internals/to-string': 155, + '../internals/whitespaces': 160, + }, + ], + 122: [ + function (require, module, exports) { + /* global ActiveXObject -- old IE, WSH */ + var anObject = require('../internals/an-object') + var defineProperties = require('../internals/object-define-properties') + var enumBugKeys = require('../internals/enum-bug-keys') + var hiddenKeys = require('../internals/hidden-keys') + var html = require('../internals/html') + var documentCreateElement = require('../internals/document-create-element') + var sharedKey = require('../internals/shared-key') + + var GT = '>' + var LT = '<' + var PROTOTYPE = 'prototype' + var SCRIPT = 'script' + var IE_PROTO = sharedKey('IE_PROTO') + + var EmptyConstructor = function () { + /* empty */ + } - return copyRegex(pattern); - } // Copy the argument behavior of `RegExp` + var scriptTag = function (content) { + return LT + SCRIPT + GT + content + LT + '/' + SCRIPT + GT + } + // Create object with fake `null` prototype: use ActiveX Object with cleared prototype + var NullProtoObjectViaActiveX = function (activeXDocument) { + activeXDocument.write(scriptTag('')) + activeXDocument.close() + var temp = activeXDocument.parentWindow.Object + activeXDocument = null // avoid memory leak + return temp + } - pattern = pattern === undefined ? '' : String(pattern); - flags = flags === undefined ? '' : String(flags); + // Create object with fake `null` prototype: use iframe Object with cleared prototype + var NullProtoObjectViaIFrame = function () { + // Thrash, waste and sodomy: IE GC bug + var iframe = documentCreateElement('iframe') + var JS = 'java' + SCRIPT + ':' + var iframeDocument + if (iframe.style) { + iframe.style.display = 'none' + html.appendChild(iframe) + // https://github.com/zloirock/core-js/issues/475 + iframe.src = String(JS) + iframeDocument = iframe.contentWindow.document + iframeDocument.open() + iframeDocument.write(scriptTag('document.F=Object')) + iframeDocument.close() + return iframeDocument.F + } + } - if (XRegExp.isInstalled('astral') && !((0, _indexOf["default"])(flags).call(flags, 'A') !== -1)) { - // This causes an error to be thrown if the Unicode Base addon is not available - flags += 'A'; - } + // Check for document.domain and active x support + // No need to use active x approach when document.domain is not set + // see https://github.com/es-shims/es5-shim/issues/150 + // variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346 + // avoid IE GC bug + var activeXDocument + var NullProtoObject = function () { + try { + activeXDocument = new ActiveXObject('htmlfile') + } catch (error) { + /* ignore */ + } + NullProtoObject = + document.domain && activeXDocument + ? NullProtoObjectViaActiveX(activeXDocument) // old IE + : NullProtoObjectViaIFrame() || NullProtoObjectViaActiveX(activeXDocument) // WSH + var length = enumBugKeys.length + while (length--) delete NullProtoObject[PROTOTYPE][enumBugKeys[length]] + return NullProtoObject() + } - if (!patternCache[pattern]) { - patternCache[pattern] = {}; - } + hiddenKeys[IE_PROTO] = true + + // `Object.create` method + // https://tc39.es/ecma262/#sec-object.create + module.exports = + Object.create || + function create(O, Properties) { + var result + if (O !== null) { + EmptyConstructor[PROTOTYPE] = anObject(O) + result = new EmptyConstructor() + EmptyConstructor[PROTOTYPE] = null + // add "__proto__" for Object.getPrototypeOf polyfill + result[IE_PROTO] = O + } else result = NullProtoObject() + return Properties === undefined ? result : defineProperties(result, Properties) + } + }, + { + '../internals/an-object': 64, + '../internals/document-create-element': 87, + '../internals/enum-bug-keys': 96, + '../internals/hidden-keys': 104, + '../internals/html': 105, + '../internals/object-define-properties': 123, + '../internals/shared-key': 142, + }, + ], + 123: [ + function (require, module, exports) { + var DESCRIPTORS = require('../internals/descriptors') + var definePropertyModule = require('../internals/object-define-property') + var anObject = require('../internals/an-object') + var objectKeys = require('../internals/object-keys') + + // `Object.defineProperties` method + // https://tc39.es/ecma262/#sec-object.defineproperties + // eslint-disable-next-line es/no-object-defineproperties -- safe + module.exports = DESCRIPTORS + ? Object.defineProperties + : function defineProperties(O, Properties) { + anObject(O) + var keys = objectKeys(Properties) + var length = keys.length + var index = 0 + var key + while (length > index) definePropertyModule.f(O, (key = keys[index++]), Properties[key]) + return O + } + }, + { + '../internals/an-object': 64, + '../internals/descriptors': 86, + '../internals/object-define-property': 124, + '../internals/object-keys': 131, + }, + ], + 124: [ + function (require, module, exports) { + var DESCRIPTORS = require('../internals/descriptors') + var IE8_DOM_DEFINE = require('../internals/ie8-dom-define') + var anObject = require('../internals/an-object') + var toPropertyKey = require('../internals/to-property-key') + + // eslint-disable-next-line es/no-object-defineproperty -- safe + var $defineProperty = Object.defineProperty + + // `Object.defineProperty` method + // https://tc39.es/ecma262/#sec-object.defineproperty + exports.f = DESCRIPTORS + ? $defineProperty + : function defineProperty(O, P, Attributes) { + anObject(O) + P = toPropertyKey(P) + anObject(Attributes) + if (IE8_DOM_DEFINE) + try { + return $defineProperty(O, P, Attributes) + } catch (error) { + /* empty */ + } + if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported') + if ('value' in Attributes) O[P] = Attributes.value + return O + } + }, + { + '../internals/an-object': 64, + '../internals/descriptors': 86, + '../internals/ie8-dom-define': 106, + '../internals/to-property-key': 153, + }, + ], + 125: [ + function (require, module, exports) { + var DESCRIPTORS = require('../internals/descriptors') + var propertyIsEnumerableModule = require('../internals/object-property-is-enumerable') + var createPropertyDescriptor = require('../internals/create-property-descriptor') + var toIndexedObject = require('../internals/to-indexed-object') + var toPropertyKey = require('../internals/to-property-key') + var has = require('../internals/has') + var IE8_DOM_DEFINE = require('../internals/ie8-dom-define') + + // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe + var $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor + + // `Object.getOwnPropertyDescriptor` method + // https://tc39.es/ecma262/#sec-object.getownpropertydescriptor + exports.f = DESCRIPTORS + ? $getOwnPropertyDescriptor + : function getOwnPropertyDescriptor(O, P) { + O = toIndexedObject(O) + P = toPropertyKey(P) + if (IE8_DOM_DEFINE) + try { + return $getOwnPropertyDescriptor(O, P) + } catch (error) { + /* empty */ + } + if (has(O, P)) return createPropertyDescriptor(!propertyIsEnumerableModule.f.call(O, P), O[P]) + } + }, + { + '../internals/create-property-descriptor': 82, + '../internals/descriptors': 86, + '../internals/has': 103, + '../internals/ie8-dom-define': 106, + '../internals/object-property-is-enumerable': 132, + '../internals/to-indexed-object': 148, + '../internals/to-property-key': 153, + }, + ], + 126: [ + function (require, module, exports) { + /* eslint-disable es/no-object-getownpropertynames -- safe */ + var toIndexedObject = require('../internals/to-indexed-object') + var $getOwnPropertyNames = require('../internals/object-get-own-property-names').f + + var toString = {}.toString + + var windowNames = + typeof window == 'object' && window && Object.getOwnPropertyNames ? Object.getOwnPropertyNames(window) : [] + + var getWindowNames = function (it) { + try { + return $getOwnPropertyNames(it) + } catch (error) { + return windowNames.slice() + } + } - if (!patternCache[pattern][flags]) { - var context = { - hasNamedCapture: false, - captureNames: [] - }; - var scope = defaultScope; - var output = ''; - var pos = 0; - var result; // Check for flag-related errors, and strip/apply flags in a leading mode modifier - - var applied = prepareFlags(pattern, flags); - var appliedPattern = applied.pattern; - var appliedFlags = (0, _flags["default"])(applied); // Use XRegExp's tokens to translate the pattern to a native regex pattern. - // `appliedPattern.length` may change on each iteration if tokens use `reparse` - - while (pos < appliedPattern.length) { - do { - // Check for custom tokens at the current position - result = runTokens(appliedPattern, appliedFlags, pos, scope, context); // If the matched token used the `reparse` option, splice its output into the - // pattern before running tokens again at the same position - - if (result && result.reparse) { - appliedPattern = (0, _slice["default"])(appliedPattern).call(appliedPattern, 0, pos) + result.output + (0, _slice["default"])(appliedPattern).call(appliedPattern, pos + result.matchLength); - } - } while (result && result.reparse); - - if (result) { - output += result.output; - pos += result.matchLength || 1; - } else { - // Get the native token at the current position - var _XRegExp$exec = XRegExp.exec(appliedPattern, nativeTokens[scope], pos, 'sticky'), - _XRegExp$exec2 = (0, _slicedToArray2["default"])(_XRegExp$exec, 1), - token = _XRegExp$exec2[0]; - - output += token; - pos += token.length; - - if (token === '[' && scope === defaultScope) { - scope = classScope; - } else if (token === ']' && scope === classScope) { - scope = defaultScope; - } - } - } + // fallback for IE11 buggy Object.getOwnPropertyNames with iframe and window + module.exports.f = function getOwnPropertyNames(it) { + return windowNames && toString.call(it) == '[object Window]' + ? getWindowNames(it) + : $getOwnPropertyNames(toIndexedObject(it)) + } + }, + { '../internals/object-get-own-property-names': 127, '../internals/to-indexed-object': 148 }, + ], + 127: [ + function (require, module, exports) { + var internalObjectKeys = require('../internals/object-keys-internal') + var enumBugKeys = require('../internals/enum-bug-keys') + + var hiddenKeys = enumBugKeys.concat('length', 'prototype') + + // `Object.getOwnPropertyNames` method + // https://tc39.es/ecma262/#sec-object.getownpropertynames + // eslint-disable-next-line es/no-object-getownpropertynames -- safe + exports.f = + Object.getOwnPropertyNames || + function getOwnPropertyNames(O) { + return internalObjectKeys(O, hiddenKeys) + } + }, + { '../internals/enum-bug-keys': 96, '../internals/object-keys-internal': 130 }, + ], + 128: [ + function (require, module, exports) { + // eslint-disable-next-line es/no-object-getownpropertysymbols -- safe + exports.f = Object.getOwnPropertySymbols + }, + {}, + ], + 129: [ + function (require, module, exports) { + var has = require('../internals/has') + var toObject = require('../internals/to-object') + var sharedKey = require('../internals/shared-key') + var CORRECT_PROTOTYPE_GETTER = require('../internals/correct-prototype-getter') + + var IE_PROTO = sharedKey('IE_PROTO') + var ObjectPrototype = Object.prototype + + // `Object.getPrototypeOf` method + // https://tc39.es/ecma262/#sec-object.getprototypeof + // eslint-disable-next-line es/no-object-getprototypeof -- safe + module.exports = CORRECT_PROTOTYPE_GETTER + ? Object.getPrototypeOf + : function (O) { + O = toObject(O) + if (has(O, IE_PROTO)) return O[IE_PROTO] + if (typeof O.constructor == 'function' && O instanceof O.constructor) { + return O.constructor.prototype + } + return O instanceof Object ? ObjectPrototype : null + } + }, + { + '../internals/correct-prototype-getter': 79, + '../internals/has': 103, + '../internals/shared-key': 142, + '../internals/to-object': 151, + }, + ], + 130: [ + function (require, module, exports) { + var has = require('../internals/has') + var toIndexedObject = require('../internals/to-indexed-object') + var indexOf = require('../internals/array-includes').indexOf + var hiddenKeys = require('../internals/hidden-keys') + + module.exports = function (object, names) { + var O = toIndexedObject(object) + var i = 0 + var result = [] + var key + for (key in O) !has(hiddenKeys, key) && has(O, key) && result.push(key) + // Don't enum bug & hidden keys + while (names.length > i) + if (has(O, (key = names[i++]))) { + ~indexOf(result, key) || result.push(key) + } + return result + } + }, + { + '../internals/array-includes': 67, + '../internals/has': 103, + '../internals/hidden-keys': 104, + '../internals/to-indexed-object': 148, + }, + ], + 131: [ + function (require, module, exports) { + var internalObjectKeys = require('../internals/object-keys-internal') + var enumBugKeys = require('../internals/enum-bug-keys') + + // `Object.keys` method + // https://tc39.es/ecma262/#sec-object.keys + // eslint-disable-next-line es/no-object-keys -- safe + module.exports = + Object.keys || + function keys(O) { + return internalObjectKeys(O, enumBugKeys) + } + }, + { '../internals/enum-bug-keys': 96, '../internals/object-keys-internal': 130 }, + ], + 132: [ + function (require, module, exports) { + 'use strict' + var $propertyIsEnumerable = {}.propertyIsEnumerable + // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe + var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor + + // Nashorn ~ JDK8 bug + var NASHORN_BUG = getOwnPropertyDescriptor && !$propertyIsEnumerable.call({ 1: 2 }, 1) + + // `Object.prototype.propertyIsEnumerable` method implementation + // https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable + exports.f = NASHORN_BUG + ? function propertyIsEnumerable(V) { + var descriptor = getOwnPropertyDescriptor(this, V) + return !!descriptor && descriptor.enumerable + } + : $propertyIsEnumerable + }, + {}, + ], + 133: [ + function (require, module, exports) { + /* eslint-disable no-proto -- safe */ + var anObject = require('../internals/an-object') + var aPossiblePrototype = require('../internals/a-possible-prototype') + + // `Object.setPrototypeOf` method + // https://tc39.es/ecma262/#sec-object.setprototypeof + // Works with __proto__ only. Old v8 can't work with null proto objects. + // eslint-disable-next-line es/no-object-setprototypeof -- safe + module.exports = + Object.setPrototypeOf || + ('__proto__' in {} + ? (function () { + var CORRECT_SETTER = false + var test = {} + var setter + try { + // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe + setter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set + setter.call(test, []) + CORRECT_SETTER = test instanceof Array + } catch (error) { + /* empty */ + } + return function setPrototypeOf(O, proto) { + anObject(O) + aPossiblePrototype(proto) + if (CORRECT_SETTER) setter.call(O, proto) + else O.__proto__ = proto + return O + } + })() + : undefined) + }, + { '../internals/a-possible-prototype': 62, '../internals/an-object': 64 }, + ], + 134: [ + function (require, module, exports) { + 'use strict' + var TO_STRING_TAG_SUPPORT = require('../internals/to-string-tag-support') + var classof = require('../internals/classof') + + // `Object.prototype.toString` method implementation + // https://tc39.es/ecma262/#sec-object.prototype.tostring + module.exports = TO_STRING_TAG_SUPPORT + ? {}.toString + : function toString() { + return '[object ' + classof(this) + ']' + } + }, + { '../internals/classof': 78, '../internals/to-string-tag-support': 154 }, + ], + 135: [ + function (require, module, exports) { + var isObject = require('../internals/is-object') + + // `OrdinaryToPrimitive` abstract operation + // https://tc39.es/ecma262/#sec-ordinarytoprimitive + module.exports = function (input, pref) { + var fn, val + if (pref === 'string' && typeof (fn = input.toString) == 'function' && !isObject((val = fn.call(input)))) + return val + if (typeof (fn = input.valueOf) == 'function' && !isObject((val = fn.call(input)))) return val + if (pref !== 'string' && typeof (fn = input.toString) == 'function' && !isObject((val = fn.call(input)))) + return val + throw TypeError("Can't convert object to primitive value") + } + }, + { '../internals/is-object': 113 }, + ], + 136: [ + function (require, module, exports) { + arguments[4][104][0].apply(exports, arguments) + }, + { dup: 104 }, + ], + 137: [ + function (require, module, exports) { + var createNonEnumerableProperty = require('../internals/create-non-enumerable-property') + + module.exports = function (target, key, value, options) { + if (options && options.enumerable) target[key] = value + else createNonEnumerableProperty(target, key, value) + } + }, + { '../internals/create-non-enumerable-property': 81 }, + ], + 138: [ + function (require, module, exports) { + 'use strict' + var anObject = require('../internals/an-object') + + // `RegExp.prototype.flags` getter implementation + // https://tc39.es/ecma262/#sec-get-regexp.prototype.flags + module.exports = function () { + var that = anObject(this) + var result = '' + if (that.global) result += 'g' + if (that.ignoreCase) result += 'i' + if (that.multiline) result += 'm' + if (that.dotAll) result += 's' + if (that.unicode) result += 'u' + if (that.sticky) result += 'y' + return result + } + }, + { '../internals/an-object': 64 }, + ], + 139: [ + function (require, module, exports) { + // `RequireObjectCoercible` abstract operation + // https://tc39.es/ecma262/#sec-requireobjectcoercible + module.exports = function (it) { + if (it == undefined) throw TypeError("Can't call method on " + it) + return it + } + }, + {}, + ], + 140: [ + function (require, module, exports) { + var global = require('../internals/global') + + module.exports = function (key, value) { + try { + // eslint-disable-next-line es/no-object-defineproperty -- safe + Object.defineProperty(global, key, { value: value, configurable: true, writable: true }) + } catch (error) { + global[key] = value + } + return value + } + }, + { '../internals/global': 102 }, + ], + 141: [ + function (require, module, exports) { + var TO_STRING_TAG_SUPPORT = require('../internals/to-string-tag-support') + var defineProperty = require('../internals/object-define-property').f + var createNonEnumerableProperty = require('../internals/create-non-enumerable-property') + var has = require('../internals/has') + var toString = require('../internals/object-to-string') + var wellKnownSymbol = require('../internals/well-known-symbol') + + var TO_STRING_TAG = wellKnownSymbol('toStringTag') + + module.exports = function (it, TAG, STATIC, SET_METHOD) { + if (it) { + var target = STATIC ? it : it.prototype + if (!has(target, TO_STRING_TAG)) { + defineProperty(target, TO_STRING_TAG, { configurable: true, value: TAG }) + } + if (SET_METHOD && !TO_STRING_TAG_SUPPORT) { + createNonEnumerableProperty(target, 'toString', toString) + } + } + } + }, + { + '../internals/create-non-enumerable-property': 81, + '../internals/has': 103, + '../internals/object-define-property': 124, + '../internals/object-to-string': 134, + '../internals/to-string-tag-support': 154, + '../internals/well-known-symbol': 159, + }, + ], + 142: [ + function (require, module, exports) { + var shared = require('../internals/shared') + var uid = require('../internals/uid') + + var keys = shared('keys') + + module.exports = function (key) { + return keys[key] || (keys[key] = uid(key)) + } + }, + { '../internals/shared': 144, '../internals/uid': 156 }, + ], + 143: [ + function (require, module, exports) { + var global = require('../internals/global') + var setGlobal = require('../internals/set-global') + + var SHARED = '__core-js_shared__' + var store = global[SHARED] || setGlobal(SHARED, {}) + + module.exports = store + }, + { '../internals/global': 102, '../internals/set-global': 140 }, + ], + 144: [ + function (require, module, exports) { + var IS_PURE = require('../internals/is-pure') + var store = require('../internals/shared-store') + + ;(module.exports = function (key, value) { + return store[key] || (store[key] = value !== undefined ? value : {}) + })('versions', []).push({ + version: '3.16.0', + mode: IS_PURE ? 'pure' : 'global', + copyright: '© 2021 Denis Pushkarev (zloirock.ru)', + }) + }, + { '../internals/is-pure': 114, '../internals/shared-store': 143 }, + ], + 145: [ + function (require, module, exports) { + var toInteger = require('../internals/to-integer') + var toString = require('../internals/to-string') + var requireObjectCoercible = require('../internals/require-object-coercible') + + // `String.prototype.codePointAt` methods implementation + var createMethod = function (CONVERT_TO_STRING) { + return function ($this, pos) { + var S = toString(requireObjectCoercible($this)) + var position = toInteger(pos) + var size = S.length + var first, second + if (position < 0 || position >= size) return CONVERT_TO_STRING ? '' : undefined + first = S.charCodeAt(position) + return first < 0xd800 || + first > 0xdbff || + position + 1 === size || + (second = S.charCodeAt(position + 1)) < 0xdc00 || + second > 0xdfff + ? CONVERT_TO_STRING + ? S.charAt(position) + : first + : CONVERT_TO_STRING + ? S.slice(position, position + 2) + : ((first - 0xd800) << 10) + (second - 0xdc00) + 0x10000 + } + } - patternCache[pattern][flags] = { - // Use basic cleanup to collapse repeated empty groups like `(?:)(?:)` to `(?:)`. Empty - // groups are sometimes inserted during regex transpilation in order to keep tokens - // separated. However, more than one empty group in a row is never needed. - pattern: output.replace(/(?:\(\?:\))+/g, '(?:)'), - // Strip all but native flags - flags: appliedFlags.replace(nonnativeFlags, ''), - // `context.captureNames` has an item for each capturing group, even if unnamed - captures: context.hasNamedCapture ? context.captureNames : null - }; - } + module.exports = { + // `String.prototype.codePointAt` method + // https://tc39.es/ecma262/#sec-string.prototype.codepointat + codeAt: createMethod(false), + // `String.prototype.at` method + // https://github.com/mathiasbynens/String.prototype.at + charAt: createMethod(true), + } + }, + { '../internals/require-object-coercible': 139, '../internals/to-integer': 149, '../internals/to-string': 155 }, + ], + 146: [ + function (require, module, exports) { + var requireObjectCoercible = require('../internals/require-object-coercible') + var toString = require('../internals/to-string') + var whitespaces = require('../internals/whitespaces') + + var whitespace = '[' + whitespaces + ']' + var ltrim = RegExp('^' + whitespace + whitespace + '*') + var rtrim = RegExp(whitespace + whitespace + '*$') + + // `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation + var createMethod = function (TYPE) { + return function ($this) { + var string = toString(requireObjectCoercible($this)) + if (TYPE & 1) string = string.replace(ltrim, '') + if (TYPE & 2) string = string.replace(rtrim, '') + return string + } + } - var generated = patternCache[pattern][flags]; - return augment(new RegExp(generated.pattern, (0, _flags["default"])(generated)), generated.captures, pattern, flags); -} // Add `RegExp.prototype` to the prototype chain - - -XRegExp.prototype = /(?:)/; // ==--------------------------== -// Public properties -// ==--------------------------== - -/** - * The XRegExp version number as a string containing three dot-separated parts. For example, - * '2.0.0-beta-3'. - * - * @static - * @memberOf XRegExp - * @type String - */ - -XRegExp.version = '5.1.0'; // ==--------------------------== -// Public methods -// ==--------------------------== -// Intentionally undocumented; used in tests and addons - -XRegExp._clipDuplicates = clipDuplicates; -XRegExp._hasNativeFlag = hasNativeFlag; -XRegExp._dec = dec; -XRegExp._hex = hex; -XRegExp._pad4 = pad4; -/** - * Extends XRegExp syntax and allows custom flags. This is used internally and can be used to - * create XRegExp addons. If more than one token can match the same string, the last added wins. - * - * @memberOf XRegExp - * @param {RegExp} regex Regex object that matches the new token. - * @param {Function} handler Function that returns a new pattern string (using native regex syntax) - * to replace the matched token within all future XRegExp regexes. Has access to persistent - * properties of the regex being built, through `this`. Invoked with three arguments: - * - The match array, with named backreference properties. - * - The regex scope where the match was found: 'default' or 'class'. - * - The flags used by the regex, including any flags in a leading mode modifier. - * The handler function becomes part of the XRegExp construction process, so be careful not to - * construct XRegExps within the function or you will trigger infinite recursion. - * @param {Object} [options] Options object with optional properties: - * - `scope` {String} Scope where the token applies: 'default', 'class', or 'all'. - * - `flag` {String} Single-character flag that triggers the token. This also registers the - * flag, which prevents XRegExp from throwing an 'unknown flag' error when the flag is used. - * - `optionalFlags` {String} Any custom flags checked for within the token `handler` that are - * not required to trigger the token. This registers the flags, to prevent XRegExp from - * throwing an 'unknown flag' error when any of the flags are used. - * - `reparse` {Boolean} Whether the `handler` function's output should not be treated as - * final, and instead be reparseable by other tokens (including the current token). Allows - * token chaining or deferring. - * - `leadChar` {String} Single character that occurs at the beginning of any successful match - * of the token (not always applicable). This doesn't change the behavior of the token unless - * you provide an erroneous value. However, providing it can increase the token's performance - * since the token can be skipped at any positions where this character doesn't appear. - * @example - * - * // Basic usage: Add \a for the ALERT control code - * XRegExp.addToken( - * /\\a/, - * () => '\\x07', - * {scope: 'all'} - * ); - * XRegExp('\\a[\\a-\\n]+').test('\x07\n\x07'); // -> true - * - * // Add the U (ungreedy) flag from PCRE and RE2, which reverses greedy and lazy quantifiers. - * // Since `scope` is not specified, it uses 'default' (i.e., transformations apply outside of - * // character classes only) - * XRegExp.addToken( - * /([?*+]|{\d+(?:,\d*)?})(\??)/, - * (match) => `${match[1]}${match[2] ? '' : '?'}`, - * {flag: 'U'} - * ); - * XRegExp('a+', 'U').exec('aaa')[0]; // -> 'a' - * XRegExp('a+?', 'U').exec('aaa')[0]; // -> 'aaa' - */ - -XRegExp.addToken = function (regex, handler, options) { - options = options || {}; - var _options = options, - optionalFlags = _options.optionalFlags; - - if (options.flag) { - registerFlag(options.flag); - } + module.exports = { + // `String.prototype.{ trimLeft, trimStart }` methods + // https://tc39.es/ecma262/#sec-string.prototype.trimstart + start: createMethod(1), + // `String.prototype.{ trimRight, trimEnd }` methods + // https://tc39.es/ecma262/#sec-string.prototype.trimend + end: createMethod(2), + // `String.prototype.trim` method + // https://tc39.es/ecma262/#sec-string.prototype.trim + trim: createMethod(3), + } + }, + { + '../internals/require-object-coercible': 139, + '../internals/to-string': 155, + '../internals/whitespaces': 160, + }, + ], + 147: [ + function (require, module, exports) { + var toInteger = require('../internals/to-integer') + + var max = Math.max + var min = Math.min + + // Helper for a popular repeating case of the spec: + // Let integer be ? ToInteger(index). + // If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length). + module.exports = function (index, length) { + var integer = toInteger(index) + return integer < 0 ? max(integer + length, 0) : min(integer, length) + } + }, + { '../internals/to-integer': 149 }, + ], + 148: [ + function (require, module, exports) { + // toObject with fallback for non-array-like ES3 strings + var IndexedObject = require('../internals/indexed-object') + var requireObjectCoercible = require('../internals/require-object-coercible') + + module.exports = function (it) { + return IndexedObject(requireObjectCoercible(it)) + } + }, + { '../internals/indexed-object': 107, '../internals/require-object-coercible': 139 }, + ], + 149: [ + function (require, module, exports) { + var ceil = Math.ceil + var floor = Math.floor + + // `ToInteger` abstract operation + // https://tc39.es/ecma262/#sec-tointeger + module.exports = function (argument) { + return isNaN((argument = +argument)) ? 0 : (argument > 0 ? floor : ceil)(argument) + } + }, + {}, + ], + 150: [ + function (require, module, exports) { + var toInteger = require('../internals/to-integer') + + var min = Math.min + + // `ToLength` abstract operation + // https://tc39.es/ecma262/#sec-tolength + module.exports = function (argument) { + return argument > 0 ? min(toInteger(argument), 0x1fffffffffffff) : 0 // 2 ** 53 - 1 == 9007199254740991 + } + }, + { '../internals/to-integer': 149 }, + ], + 151: [ + function (require, module, exports) { + var requireObjectCoercible = require('../internals/require-object-coercible') + + // `ToObject` abstract operation + // https://tc39.es/ecma262/#sec-toobject + module.exports = function (argument) { + return Object(requireObjectCoercible(argument)) + } + }, + { '../internals/require-object-coercible': 139 }, + ], + 152: [ + function (require, module, exports) { + var isObject = require('../internals/is-object') + var isSymbol = require('../internals/is-symbol') + var ordinaryToPrimitive = require('../internals/ordinary-to-primitive') + var wellKnownSymbol = require('../internals/well-known-symbol') + + var TO_PRIMITIVE = wellKnownSymbol('toPrimitive') + + // `ToPrimitive` abstract operation + // https://tc39.es/ecma262/#sec-toprimitive + module.exports = function (input, pref) { + if (!isObject(input) || isSymbol(input)) return input + var exoticToPrim = input[TO_PRIMITIVE] + var result + if (exoticToPrim !== undefined) { + if (pref === undefined) pref = 'default' + result = exoticToPrim.call(input, pref) + if (!isObject(result) || isSymbol(result)) return result + throw TypeError("Can't convert object to primitive value") + } + if (pref === undefined) pref = 'number' + return ordinaryToPrimitive(input, pref) + } + }, + { + '../internals/is-object': 113, + '../internals/is-symbol': 115, + '../internals/ordinary-to-primitive': 135, + '../internals/well-known-symbol': 159, + }, + ], + 153: [ + function (require, module, exports) { + var toPrimitive = require('../internals/to-primitive') + var isSymbol = require('../internals/is-symbol') + + // `ToPropertyKey` abstract operation + // https://tc39.es/ecma262/#sec-topropertykey + module.exports = function (argument) { + var key = toPrimitive(argument, 'string') + return isSymbol(key) ? key : String(key) + } + }, + { '../internals/is-symbol': 115, '../internals/to-primitive': 152 }, + ], + 154: [ + function (require, module, exports) { + var wellKnownSymbol = require('../internals/well-known-symbol') + + var TO_STRING_TAG = wellKnownSymbol('toStringTag') + var test = {} + + test[TO_STRING_TAG] = 'z' + + module.exports = String(test) === '[object z]' + }, + { '../internals/well-known-symbol': 159 }, + ], + 155: [ + function (require, module, exports) { + var isSymbol = require('../internals/is-symbol') + + module.exports = function (argument) { + if (isSymbol(argument)) throw TypeError('Cannot convert a Symbol value to a string') + return String(argument) + } + }, + { '../internals/is-symbol': 115 }, + ], + 156: [ + function (require, module, exports) { + var id = 0 + var postfix = Math.random() + + module.exports = function (key) { + return 'Symbol(' + String(key === undefined ? '' : key) + ')_' + (++id + postfix).toString(36) + } + }, + {}, + ], + 157: [ + function (require, module, exports) { + /* eslint-disable es/no-symbol -- required for testing */ + var NATIVE_SYMBOL = require('../internals/native-symbol') + + module.exports = NATIVE_SYMBOL && !Symbol.sham && typeof Symbol.iterator == 'symbol' + }, + { '../internals/native-symbol': 119 }, + ], + 158: [ + function (require, module, exports) { + var wellKnownSymbol = require('../internals/well-known-symbol') + + exports.f = wellKnownSymbol + }, + { '../internals/well-known-symbol': 159 }, + ], + 159: [ + function (require, module, exports) { + var global = require('../internals/global') + var shared = require('../internals/shared') + var has = require('../internals/has') + var uid = require('../internals/uid') + var NATIVE_SYMBOL = require('../internals/native-symbol') + var USE_SYMBOL_AS_UID = require('../internals/use-symbol-as-uid') + + var WellKnownSymbolsStore = shared('wks') + var Symbol = global.Symbol + var createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol : (Symbol && Symbol.withoutSetter) || uid + + module.exports = function (name) { + if ( + !has(WellKnownSymbolsStore, name) || + !(NATIVE_SYMBOL || typeof WellKnownSymbolsStore[name] == 'string') + ) { + if (NATIVE_SYMBOL && has(Symbol, name)) { + WellKnownSymbolsStore[name] = Symbol[name] + } else { + WellKnownSymbolsStore[name] = createWellKnownSymbol('Symbol.' + name) + } + } + return WellKnownSymbolsStore[name] + } + }, + { + '../internals/global': 102, + '../internals/has': 103, + '../internals/native-symbol': 119, + '../internals/shared': 144, + '../internals/uid': 156, + '../internals/use-symbol-as-uid': 157, + }, + ], + 160: [ + function (require, module, exports) { + // a string of all valid unicode whitespaces + module.exports = + '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002' + + '\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF' + }, + {}, + ], + 161: [ + function (require, module, exports) { + 'use strict' + var $ = require('../internals/export') + var fails = require('../internals/fails') + var isArray = require('../internals/is-array') + var isObject = require('../internals/is-object') + var toObject = require('../internals/to-object') + var toLength = require('../internals/to-length') + var createProperty = require('../internals/create-property') + var arraySpeciesCreate = require('../internals/array-species-create') + var arrayMethodHasSpeciesSupport = require('../internals/array-method-has-species-support') + var wellKnownSymbol = require('../internals/well-known-symbol') + var V8_VERSION = require('../internals/engine-v8-version') + + var IS_CONCAT_SPREADABLE = wellKnownSymbol('isConcatSpreadable') + var MAX_SAFE_INTEGER = 0x1fffffffffffff + var MAXIMUM_ALLOWED_INDEX_EXCEEDED = 'Maximum allowed index exceeded' + + // We can't use this feature detection in V8 since it causes + // deoptimization and serious performance degradation + // https://github.com/zloirock/core-js/issues/679 + var IS_CONCAT_SPREADABLE_SUPPORT = + V8_VERSION >= 51 || + !fails(function () { + var array = [] + array[IS_CONCAT_SPREADABLE] = false + return array.concat()[0] !== array + }) + + var SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('concat') + + var isConcatSpreadable = function (O) { + if (!isObject(O)) return false + var spreadable = O[IS_CONCAT_SPREADABLE] + return spreadable !== undefined ? !!spreadable : isArray(O) + } - if (optionalFlags) { - optionalFlags = optionalFlags.split(''); + var FORCED = !IS_CONCAT_SPREADABLE_SUPPORT || !SPECIES_SUPPORT + + // `Array.prototype.concat` method + // https://tc39.es/ecma262/#sec-array.prototype.concat + // with adding support of @@isConcatSpreadable and @@species + $( + { target: 'Array', proto: true, forced: FORCED }, + { + // eslint-disable-next-line no-unused-vars -- required for `.length` + concat: function concat(arg) { + var O = toObject(this) + var A = arraySpeciesCreate(O, 0) + var n = 0 + var i, k, length, len, E + for (i = -1, length = arguments.length; i < length; i++) { + E = i === -1 ? O : arguments[i] + if (isConcatSpreadable(E)) { + len = toLength(E.length) + if (n + len > MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED) + for (k = 0; k < len; k++, n++) if (k in E) createProperty(A, n, E[k]) + } else { + if (n >= MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED) + createProperty(A, n++, E) + } + } + A.length = n + return A + }, + } + ) + }, + { + '../internals/array-method-has-species-support': 69, + '../internals/array-species-create': 74, + '../internals/create-property': 83, + '../internals/engine-v8-version': 93, + '../internals/export': 97, + '../internals/fails': 98, + '../internals/is-array': 111, + '../internals/is-object': 113, + '../internals/to-length': 150, + '../internals/to-object': 151, + '../internals/well-known-symbol': 159, + }, + ], + 162: [ + function (require, module, exports) { + 'use strict' + var $ = require('../internals/export') + var forEach = require('../internals/array-for-each') + + // `Array.prototype.forEach` method + // https://tc39.es/ecma262/#sec-array.prototype.foreach + // eslint-disable-next-line es/no-array-prototype-foreach -- safe + $( + { target: 'Array', proto: true, forced: [].forEach != forEach }, + { + forEach: forEach, + } + ) + }, + { '../internals/array-for-each': 65, '../internals/export': 97 }, + ], + 163: [ + function (require, module, exports) { + var $ = require('../internals/export') + var from = require('../internals/array-from') + var checkCorrectnessOfIteration = require('../internals/check-correctness-of-iteration') + + var INCORRECT_ITERATION = !checkCorrectnessOfIteration(function (iterable) { + // eslint-disable-next-line es/no-array-from -- required for testing + Array.from(iterable) + }) + + // `Array.from` method + // https://tc39.es/ecma262/#sec-array.from + $( + { target: 'Array', stat: true, forced: INCORRECT_ITERATION }, + { + from: from, + } + ) + }, + { '../internals/array-from': 66, '../internals/check-correctness-of-iteration': 76, '../internals/export': 97 }, + ], + 164: [ + function (require, module, exports) { + 'use strict' + /* eslint-disable es/no-array-prototype-indexof -- required for testing */ + var $ = require('../internals/export') + var $indexOf = require('../internals/array-includes').indexOf + var arrayMethodIsStrict = require('../internals/array-method-is-strict') + + var nativeIndexOf = [].indexOf + + var NEGATIVE_ZERO = !!nativeIndexOf && 1 / [1].indexOf(1, -0) < 0 + var STRICT_METHOD = arrayMethodIsStrict('indexOf') + + // `Array.prototype.indexOf` method + // https://tc39.es/ecma262/#sec-array.prototype.indexof + $( + { target: 'Array', proto: true, forced: NEGATIVE_ZERO || !STRICT_METHOD }, + { + indexOf: function indexOf(searchElement /* , fromIndex = 0 */) { + return NEGATIVE_ZERO + ? // convert -0 to +0 + nativeIndexOf.apply(this, arguments) || 0 + : $indexOf(this, searchElement, arguments.length > 1 ? arguments[1] : undefined) + }, + } + ) + }, + { '../internals/array-includes': 67, '../internals/array-method-is-strict': 70, '../internals/export': 97 }, + ], + 165: [ + function (require, module, exports) { + var $ = require('../internals/export') + var isArray = require('../internals/is-array') + + // `Array.isArray` method + // https://tc39.es/ecma262/#sec-array.isarray + $( + { target: 'Array', stat: true }, + { + isArray: isArray, + } + ) + }, + { '../internals/export': 97, '../internals/is-array': 111 }, + ], + 166: [ + function (require, module, exports) { + 'use strict' + var toIndexedObject = require('../internals/to-indexed-object') + var addToUnscopables = require('../internals/add-to-unscopables') + var Iterators = require('../internals/iterators') + var InternalStateModule = require('../internals/internal-state') + var defineIterator = require('../internals/define-iterator') + + var ARRAY_ITERATOR = 'Array Iterator' + var setInternalState = InternalStateModule.set + var getInternalState = InternalStateModule.getterFor(ARRAY_ITERATOR) + + // `Array.prototype.entries` method + // https://tc39.es/ecma262/#sec-array.prototype.entries + // `Array.prototype.keys` method + // https://tc39.es/ecma262/#sec-array.prototype.keys + // `Array.prototype.values` method + // https://tc39.es/ecma262/#sec-array.prototype.values + // `Array.prototype[@@iterator]` method + // https://tc39.es/ecma262/#sec-array.prototype-@@iterator + // `CreateArrayIterator` internal method + // https://tc39.es/ecma262/#sec-createarrayiterator + module.exports = defineIterator( + Array, + 'Array', + function (iterated, kind) { + setInternalState(this, { + type: ARRAY_ITERATOR, + target: toIndexedObject(iterated), // target + index: 0, // next index + kind: kind, // kind + }) + // `%ArrayIteratorPrototype%.next` method + // https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next + }, + function () { + var state = getInternalState(this) + var target = state.target + var kind = state.kind + var index = state.index++ + if (!target || index >= target.length) { + state.target = undefined + return { value: undefined, done: true } + } + if (kind == 'keys') return { value: index, done: false } + if (kind == 'values') return { value: target[index], done: false } + return { value: [index, target[index]], done: false } + }, + 'values' + ) + + // argumentsList[@@iterator] is %ArrayProto_values% + // https://tc39.es/ecma262/#sec-createunmappedargumentsobject + // https://tc39.es/ecma262/#sec-createmappedargumentsobject + Iterators.Arguments = Iterators.Array + + // https://tc39.es/ecma262/#sec-array.prototype-@@unscopables + addToUnscopables('keys') + addToUnscopables('values') + addToUnscopables('entries') + }, + { + '../internals/add-to-unscopables': 63, + '../internals/define-iterator': 84, + '../internals/internal-state': 109, + '../internals/iterators': 118, + '../internals/to-indexed-object': 148, + }, + ], + 167: [ + function (require, module, exports) { + 'use strict' + var $ = require('../internals/export') + var $map = require('../internals/array-iteration').map + var arrayMethodHasSpeciesSupport = require('../internals/array-method-has-species-support') + + var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('map') + + // `Array.prototype.map` method + // https://tc39.es/ecma262/#sec-array.prototype.map + // with adding support of @@species + $( + { target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, + { + map: function map(callbackfn /* , thisArg */) { + return $map(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined) + }, + } + ) + }, + { + '../internals/array-iteration': 68, + '../internals/array-method-has-species-support': 69, + '../internals/export': 97, + }, + ], + 168: [ + function (require, module, exports) { + 'use strict' + var $ = require('../internals/export') + var $reduce = require('../internals/array-reduce').left + var arrayMethodIsStrict = require('../internals/array-method-is-strict') + var CHROME_VERSION = require('../internals/engine-v8-version') + var IS_NODE = require('../internals/engine-is-node') + + var STRICT_METHOD = arrayMethodIsStrict('reduce') + // Chrome 80-82 has a critical bug + // https://bugs.chromium.org/p/chromium/issues/detail?id=1049982 + var CHROME_BUG = !IS_NODE && CHROME_VERSION > 79 && CHROME_VERSION < 83 + + // `Array.prototype.reduce` method + // https://tc39.es/ecma262/#sec-array.prototype.reduce + $( + { target: 'Array', proto: true, forced: !STRICT_METHOD || CHROME_BUG }, + { + reduce: function reduce(callbackfn /* , initialValue */) { + return $reduce(this, callbackfn, arguments.length, arguments.length > 1 ? arguments[1] : undefined) + }, + } + ) + }, + { + '../internals/array-method-is-strict': 70, + '../internals/array-reduce': 71, + '../internals/engine-is-node': 91, + '../internals/engine-v8-version': 93, + '../internals/export': 97, + }, + ], + 169: [ + function (require, module, exports) { + 'use strict' + var $ = require('../internals/export') + var isObject = require('../internals/is-object') + var isArray = require('../internals/is-array') + var toAbsoluteIndex = require('../internals/to-absolute-index') + var toLength = require('../internals/to-length') + var toIndexedObject = require('../internals/to-indexed-object') + var createProperty = require('../internals/create-property') + var wellKnownSymbol = require('../internals/well-known-symbol') + var arrayMethodHasSpeciesSupport = require('../internals/array-method-has-species-support') + + var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('slice') + + var SPECIES = wellKnownSymbol('species') + var nativeSlice = [].slice + var max = Math.max + + // `Array.prototype.slice` method + // https://tc39.es/ecma262/#sec-array.prototype.slice + // fallback for not array-like ES3 strings and DOM objects + $( + { target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, + { + slice: function slice(start, end) { + var O = toIndexedObject(this) + var length = toLength(O.length) + var k = toAbsoluteIndex(start, length) + var fin = toAbsoluteIndex(end === undefined ? length : end, length) + // inline `ArraySpeciesCreate` for usage native `Array#slice` where it's possible + var Constructor, result, n + if (isArray(O)) { + Constructor = O.constructor + // cross-realm fallback + if (typeof Constructor == 'function' && (Constructor === Array || isArray(Constructor.prototype))) { + Constructor = undefined + } else if (isObject(Constructor)) { + Constructor = Constructor[SPECIES] + if (Constructor === null) Constructor = undefined + } + if (Constructor === Array || Constructor === undefined) { + return nativeSlice.call(O, k, fin) + } + } + result = new (Constructor === undefined ? Array : Constructor)(max(fin - k, 0)) + for (n = 0; k < fin; k++, n++) if (k in O) createProperty(result, n, O[k]) + result.length = n + return result + }, + } + ) + }, + { + '../internals/array-method-has-species-support': 69, + '../internals/create-property': 83, + '../internals/export': 97, + '../internals/is-array': 111, + '../internals/is-object': 113, + '../internals/to-absolute-index': 147, + '../internals/to-indexed-object': 148, + '../internals/to-length': 150, + '../internals/well-known-symbol': 159, + }, + ], + 170: [ + function (require, module, exports) { + 'use strict' + var $ = require('../internals/export') + var aFunction = require('../internals/a-function') + var toObject = require('../internals/to-object') + var toLength = require('../internals/to-length') + var toString = require('../internals/to-string') + var fails = require('../internals/fails') + var internalSort = require('../internals/array-sort') + var arrayMethodIsStrict = require('../internals/array-method-is-strict') + var FF = require('../internals/engine-ff-version') + var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge') + var V8 = require('../internals/engine-v8-version') + var WEBKIT = require('../internals/engine-webkit-version') + + var test = [] + var nativeSort = test.sort + + // IE8- + var FAILS_ON_UNDEFINED = fails(function () { + test.sort(undefined) + }) + // V8 bug + var FAILS_ON_NULL = fails(function () { + test.sort(null) + }) + // Old WebKit + var STRICT_METHOD = arrayMethodIsStrict('sort') + + var STABLE_SORT = !fails(function () { + // feature detection can be too slow, so check engines versions + if (V8) return V8 < 70 + if (FF && FF > 3) return + if (IE_OR_EDGE) return true + if (WEBKIT) return WEBKIT < 603 + + var result = '' + var code, chr, value, index + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code) + + switch (code) { + case 66: + case 69: + case 70: + case 72: + value = 3 + break + case 68: + case 71: + value = 4 + break + default: + value = 2 + } + + for (index = 0; index < 47; index++) { + test.push({ k: chr + index, v: value }) + } + } - var _iterator2 = _createForOfIteratorHelper(optionalFlags), - _step2; + test.sort(function (a, b) { + return b.v - a.v + }) - try { - for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { - var flag = _step2.value; - registerFlag(flag); - } - } catch (err) { - _iterator2.e(err); - } finally { - _iterator2.f(); - } - } // Add to the private list of syntax tokens - - - tokens.push({ - regex: copyRegex(regex, { - addG: true, - addY: hasNativeY, - isInternalOnly: true - }), - handler: handler, - scope: options.scope || defaultScope, - flag: options.flag, - reparse: options.reparse, - leadChar: options.leadChar - }); // Reset the pattern cache used by the `XRegExp` constructor, since the same pattern and flags - // might now produce different results - - XRegExp.cache.flush('patterns'); -}; -/** - * Caches and returns the result of calling `XRegExp(pattern, flags)`. On any subsequent call with - * the same pattern and flag combination, the cached copy of the regex is returned. - * - * @memberOf XRegExp - * @param {String} pattern Regex pattern string. - * @param {String} [flags] Any combination of XRegExp flags. - * @returns {RegExp} Cached XRegExp object. - * @example - * - * let match; - * while (match = XRegExp.cache('.', 'gs').exec('abc')) { - * // The regex is compiled once only - * } - */ - - -XRegExp.cache = function (pattern, flags) { - if (!regexCache[pattern]) { - regexCache[pattern] = {}; - } + for (index = 0; index < test.length; index++) { + chr = test[index].k.charAt(0) + if (result.charAt(result.length - 1) !== chr) result += chr + } - return regexCache[pattern][flags] || (regexCache[pattern][flags] = XRegExp(pattern, flags)); -}; // Intentionally undocumented; used in tests + return result !== 'DGBEFHACIJK' + }) + var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT -XRegExp.cache.flush = function (cacheName) { - if (cacheName === 'patterns') { - // Flush the pattern cache used by the `XRegExp` constructor - patternCache = {}; - } else { - // Flush the regex cache populated by `XRegExp.cache` - regexCache = {}; - } -}; -/** - * Escapes any regular expression metacharacters, for use when matching literal strings. The result - * can safely be used at any position within a regex that uses any flags. - * - * @memberOf XRegExp - * @param {String} str String to escape. - * @returns {string} String with regex metacharacters escaped. - * @example - * - * XRegExp.escape('Escaped? <.>'); - * // -> 'Escaped\?\u0020<\.>' - */ -// Following are the contexts where each metacharacter needs to be escaped because it would -// otherwise have a special meaning, change the meaning of surrounding characters, or cause an -// error. Context 'default' means outside character classes only. -// - `\` - context: all -// - `[()*+?.$|` - context: default -// - `]` - context: default with flag u or if forming the end of a character class -// - `{}` - context: default with flag u or if part of a valid/complete quantifier pattern -// - `,` - context: default if in a position that causes an unescaped `{` to turn into a quantifier. -// Ex: `/^a{1\,2}$/` matches `'a{1,2}'`, but `/^a{1,2}$/` matches `'a'` or `'aa'` -// - `#` and - context: default with flag x -// - `^` - context: default, and context: class if it's the first character in the class -// - `-` - context: class if part of a valid character class range - - -XRegExp.escape = function (str) { - return String(nullThrows(str)). // Escape most special chars with a backslash - replace(/[\\\[\]{}()*+?.^$|]/g, '\\$&'). // Convert to \uNNNN for special chars that can't be escaped when used with ES6 flag `u` - replace(/[\s#\-,]/g, function (match) { - return "\\u".concat(pad4(hex(match.charCodeAt(0)))); - }); -}; -/** - * Executes a regex search in a specified string. Returns a match array or `null`. If the provided - * regex uses named capture, named capture properties are included on the match array's `groups` - * property. Optional `pos` and `sticky` arguments specify the search start position, and whether - * the match must start at the specified position only. The `lastIndex` property of the provided - * regex is not used, but is updated for compatibility. Also fixes browser bugs compared to the - * native `RegExp.prototype.exec` and can be used reliably cross-browser. - * - * @memberOf XRegExp - * @param {String} str String to search. - * @param {RegExp} regex Regex to search with. - * @param {Number} [pos=0] Zero-based index at which to start the search. - * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position - * only. The string `'sticky'` is accepted as an alternative to `true`. - * @returns {Array} Match array with named capture properties on the `groups` object, or `null`. If - * the `namespacing` feature is off, named capture properties are directly on the match array. - * @example - * - * // Basic use, with named capturing group - * let match = XRegExp.exec('U+2620', XRegExp('U\\+(?[0-9A-F]{4})')); - * match.groups.hex; // -> '2620' - * - * // With pos and sticky, in a loop - * let pos = 2, result = [], match; - * while (match = XRegExp.exec('<1><2><3><4>5<6>', /<(\d)>/, pos, 'sticky')) { - * result.push(match[1]); - * pos = match.index + match[0].length; - * } - * // result -> ['2', '3', '4'] - */ - - -XRegExp.exec = function (str, regex, pos, sticky) { - var cacheKey = 'g'; - var addY = false; - var fakeY = false; - var match; - addY = hasNativeY && !!(sticky || regex.sticky && sticky !== false); - - if (addY) { - cacheKey += 'y'; - } else if (sticky) { - // Simulate sticky matching by appending an empty capture to the original regex. The - // resulting regex will succeed no matter what at the current index (set with `lastIndex`), - // and will not search the rest of the subject string. We'll know that the original regex - // has failed if that last capture is `''` rather than `undefined` (i.e., if that last - // capture participated in the match). - fakeY = true; - cacheKey += 'FakeY'; - } + var getSortCompare = function (comparefn) { + return function (x, y) { + if (y === undefined) return -1 + if (x === undefined) return 1 + if (comparefn !== undefined) return +comparefn(x, y) || 0 + return toString(x) > toString(y) ? 1 : -1 + } + } - regex[REGEX_DATA] = regex[REGEX_DATA] || {}; // Shares cached copies with `XRegExp.match`/`replace` + // `Array.prototype.sort` method + // https://tc39.es/ecma262/#sec-array.prototype.sort + $( + { target: 'Array', proto: true, forced: FORCED }, + { + sort: function sort(comparefn) { + if (comparefn !== undefined) aFunction(comparefn) - var r2 = regex[REGEX_DATA][cacheKey] || (regex[REGEX_DATA][cacheKey] = copyRegex(regex, { - addG: true, - addY: addY, - source: fakeY ? "".concat(regex.source, "|()") : undefined, - removeY: sticky === false, - isInternalOnly: true - })); - pos = pos || 0; - r2.lastIndex = pos; // Fixed `exec` required for `lastIndex` fix, named backreferences, etc. + var array = toObject(this) - match = fixed.exec.call(r2, str); // Get rid of the capture added by the pseudo-sticky matcher if needed. An empty string means - // the original regexp failed (see above). + if (STABLE_SORT) + return comparefn === undefined ? nativeSort.call(array) : nativeSort.call(array, comparefn) - if (fakeY && match && match.pop() === '') { - match = null; - } + var items = [] + var arrayLength = toLength(array.length) + var itemsLength, index - if (regex.global) { - regex.lastIndex = match ? r2.lastIndex : 0; - } + for (index = 0; index < arrayLength; index++) { + if (index in array) items.push(array[index]) + } - return match; -}; -/** - * Executes a provided function once per regex match. Searches always start at the beginning of the - * string and continue until the end, regardless of the state of the regex's `global` property and - * initial `lastIndex`. - * - * @memberOf XRegExp - * @param {String} str String to search. - * @param {RegExp} regex Regex to search with. - * @param {Function} callback Function to execute for each match. Invoked with four arguments: - * - The match array, with named backreference properties. - * - The zero-based match index. - * - The string being traversed. - * - The regex object being used to traverse the string. - * @example - * - * // Extracts every other digit from a string - * const evens = []; - * XRegExp.forEach('1a2345', /\d/, (match, i) => { - * if (i % 2) evens.push(+match[0]); - * }); - * // evens -> [2, 4] - */ - - -XRegExp.forEach = function (str, regex, callback) { - var pos = 0; - var i = -1; - var match; - - while (match = XRegExp.exec(str, regex, pos)) { - // Because `regex` is provided to `callback`, the function could use the deprecated/ - // nonstandard `RegExp.prototype.compile` to mutate the regex. However, since `XRegExp.exec` - // doesn't use `lastIndex` to set the search position, this can't lead to an infinite loop, - // at least. Actually, because of the way `XRegExp.exec` caches globalized versions of - // regexes, mutating the regex will not have any effect on the iteration or matched strings, - // which is a nice side effect that brings extra safety. - callback(match, ++i, str, regex); - pos = match.index + (match[0].length || 1); - } -}; -/** - * Copies a regex object and adds flag `g`. The copy maintains extended data, is augmented with - * `XRegExp.prototype` properties, and has a fresh `lastIndex` property (set to zero). Native - * regexes are not recompiled using XRegExp syntax. - * - * @memberOf XRegExp - * @param {RegExp} regex Regex to globalize. - * @returns {RegExp} Copy of the provided regex with flag `g` added. - * @example - * - * const globalCopy = XRegExp.globalize(/regex/); - * globalCopy.global; // -> true - */ - - -XRegExp.globalize = function (regex) { - return copyRegex(regex, { - addG: true - }); -}; -/** - * Installs optional features according to the specified options. Can be undone using - * `XRegExp.uninstall`. - * - * @memberOf XRegExp - * @param {Object|String} options Options object or string. - * @example - * - * // With an options object - * XRegExp.install({ - * // Enables support for astral code points in Unicode addons (implicitly sets flag A) - * astral: true, - * - * // Adds named capture groups to the `groups` property of matches - * namespacing: true - * }); - * - * // With an options string - * XRegExp.install('astral namespacing'); - */ - - -XRegExp.install = function (options) { - options = prepareOptions(options); - - if (!features.astral && options.astral) { - setAstral(true); - } + items = internalSort(items, getSortCompare(comparefn)) + itemsLength = items.length + index = 0 - if (!features.namespacing && options.namespacing) { - setNamespacing(true); - } -}; -/** - * Checks whether an individual optional feature is installed. - * - * @memberOf XRegExp - * @param {String} feature Name of the feature to check. One of: - * - `astral` - * - `namespacing` - * @returns {boolean} Whether the feature is installed. - * @example - * - * XRegExp.isInstalled('astral'); - */ - - -XRegExp.isInstalled = function (feature) { - return !!features[feature]; -}; -/** - * Returns `true` if an object is a regex; `false` if it isn't. This works correctly for regexes - * created in another frame, when `instanceof` and `constructor` checks would fail. - * - * @memberOf XRegExp - * @param {*} value Object to check. - * @returns {boolean} Whether the object is a `RegExp` object. - * @example - * - * XRegExp.isRegExp('string'); // -> false - * XRegExp.isRegExp(/regex/i); // -> true - * XRegExp.isRegExp(RegExp('^', 'm')); // -> true - * XRegExp.isRegExp(XRegExp('(?s).')); // -> true - */ - - -XRegExp.isRegExp = function (value) { - return Object.prototype.toString.call(value) === '[object RegExp]'; -}; // Same as `isType(value, 'RegExp')`, but avoiding that function call here for perf since -// `isRegExp` is used heavily by internals including regex construction - -/** - * Returns the first matched string, or in global mode, an array containing all matched strings. - * This is essentially a more convenient re-implementation of `String.prototype.match` that gives - * the result types you actually want (string instead of `exec`-style array in match-first mode, - * and an empty array instead of `null` when no matches are found in match-all mode). It also lets - * you override flag g and ignore `lastIndex`, and fixes browser bugs. - * - * @memberOf XRegExp - * @param {String} str String to search. - * @param {RegExp} regex Regex to search with. - * @param {String} [scope='one'] Use 'one' to return the first match as a string. Use 'all' to - * return an array of all matched strings. If not explicitly specified and `regex` uses flag g, - * `scope` is 'all'. - * @returns {String|Array} In match-first mode: First match as a string, or `null`. In match-all - * mode: Array of all matched strings, or an empty array. - * @example - * - * // Match first - * XRegExp.match('abc', /\w/); // -> 'a' - * XRegExp.match('abc', /\w/g, 'one'); // -> 'a' - * XRegExp.match('abc', /x/g, 'one'); // -> null - * - * // Match all - * XRegExp.match('abc', /\w/g); // -> ['a', 'b', 'c'] - * XRegExp.match('abc', /\w/, 'all'); // -> ['a', 'b', 'c'] - * XRegExp.match('abc', /x/, 'all'); // -> [] - */ - - -XRegExp.match = function (str, regex, scope) { - var global = regex.global && scope !== 'one' || scope === 'all'; - var cacheKey = (global ? 'g' : '') + (regex.sticky ? 'y' : '') || 'noGY'; - regex[REGEX_DATA] = regex[REGEX_DATA] || {}; // Shares cached copies with `XRegExp.exec`/`replace` - - var r2 = regex[REGEX_DATA][cacheKey] || (regex[REGEX_DATA][cacheKey] = copyRegex(regex, { - addG: !!global, - removeG: scope === 'one', - isInternalOnly: true - })); - var result = String(nullThrows(str)).match(r2); - - if (regex.global) { - regex.lastIndex = scope === 'one' && result ? result.index + result[0].length : 0; - } + while (index < itemsLength) array[index] = items[index++] + while (index < arrayLength) delete array[index++] - return global ? result || [] : result && result[0]; -}; -/** - * Retrieves the matches from searching a string using a chain of regexes that successively search - * within previous matches. The provided `chain` array can contain regexes and or objects with - * `regex` and `backref` properties. When a backreference is specified, the named or numbered - * backreference is passed forward to the next regex or returned. - * - * @memberOf XRegExp - * @param {String} str String to search. - * @param {Array} chain Regexes that each search for matches within preceding results. - * @returns {Array} Matches by the last regex in the chain, or an empty array. - * @example - * - * // Basic usage; matches numbers within tags - * XRegExp.matchChain('1 2 3 4 a 56', [ - * XRegExp('(?is).*?'), - * /\d+/ - * ]); - * // -> ['2', '4', '56'] - * - * // Passing forward and returning specific backreferences - * const html = `XRegExp - * Google`; - * XRegExp.matchChain(html, [ - * {regex: //i, backref: 1}, - * {regex: XRegExp('(?i)^https?://(?[^/?#]+)'), backref: 'domain'} - * ]); - * // -> ['xregexp.com', 'www.google.com'] - */ - - -XRegExp.matchChain = function (str, chain) { - return function recurseChain(values, level) { - var item = chain[level].regex ? chain[level] : { - regex: chain[level] - }; - var matches = []; - - function addMatch(match) { - if (item.backref) { - var ERR_UNDEFINED_GROUP = "Backreference to undefined group: ".concat(item.backref); - var isNamedBackref = isNaN(item.backref); - - if (isNamedBackref && XRegExp.isInstalled('namespacing')) { - // `groups` has `null` as prototype, so using `in` instead of `hasOwnProperty` - if (!(match.groups && item.backref in match.groups)) { - throw new ReferenceError(ERR_UNDEFINED_GROUP); - } - } else if (!match.hasOwnProperty(item.backref)) { - throw new ReferenceError(ERR_UNDEFINED_GROUP); - } + return array + }, + } + ) + }, + { + '../internals/a-function': 61, + '../internals/array-method-is-strict': 70, + '../internals/array-sort': 72, + '../internals/engine-ff-version': 89, + '../internals/engine-is-ie-or-edge': 90, + '../internals/engine-v8-version': 93, + '../internals/engine-webkit-version': 94, + '../internals/export': 97, + '../internals/fails': 98, + '../internals/to-length': 150, + '../internals/to-object': 151, + '../internals/to-string': 155, + }, + ], + 171: [ + function (require, module, exports) { + var global = require('../internals/global') + var setToStringTag = require('../internals/set-to-string-tag') + + // JSON[@@toStringTag] property + // https://tc39.es/ecma262/#sec-json-@@tostringtag + setToStringTag(global.JSON, 'JSON', true) + }, + { '../internals/global': 102, '../internals/set-to-string-tag': 141 }, + ], + 172: [ + function (require, module, exports) { + // empty + }, + {}, + ], + 173: [ + function (require, module, exports) { + var $ = require('../internals/export') + var DESCRIPTORS = require('../internals/descriptors') + var create = require('../internals/object-create') + + // `Object.create` method + // https://tc39.es/ecma262/#sec-object.create + $( + { target: 'Object', stat: true, sham: !DESCRIPTORS }, + { + create: create, + } + ) + }, + { '../internals/descriptors': 86, '../internals/export': 97, '../internals/object-create': 122 }, + ], + 174: [ + function (require, module, exports) { + var $ = require('../internals/export') + var DESCRIPTORS = require('../internals/descriptors') + var objectDefinePropertyModile = require('../internals/object-define-property') + + // `Object.defineProperty` method + // https://tc39.es/ecma262/#sec-object.defineproperty + $( + { target: 'Object', stat: true, forced: !DESCRIPTORS, sham: !DESCRIPTORS }, + { + defineProperty: objectDefinePropertyModile.f, + } + ) + }, + { '../internals/descriptors': 86, '../internals/export': 97, '../internals/object-define-property': 124 }, + ], + 175: [ + function (require, module, exports) { + arguments[4][172][0].apply(exports, arguments) + }, + { dup: 172 }, + ], + 176: [ + function (require, module, exports) { + var $ = require('../internals/export') + var parseIntImplementation = require('../internals/number-parse-int') + + // `parseInt` method + // https://tc39.es/ecma262/#sec-parseint-string-radix + $( + { global: true, forced: parseInt != parseIntImplementation }, + { + parseInt: parseIntImplementation, + } + ) + }, + { '../internals/export': 97, '../internals/number-parse-int': 121 }, + ], + 177: [ + function (require, module, exports) { + arguments[4][172][0].apply(exports, arguments) + }, + { dup: 172 }, + ], + 178: [ + function (require, module, exports) { + arguments[4][172][0].apply(exports, arguments) + }, + { dup: 172 }, + ], + 179: [ + function (require, module, exports) { + 'use strict' + var charAt = require('../internals/string-multibyte').charAt + var toString = require('../internals/to-string') + var InternalStateModule = require('../internals/internal-state') + var defineIterator = require('../internals/define-iterator') + + var STRING_ITERATOR = 'String Iterator' + var setInternalState = InternalStateModule.set + var getInternalState = InternalStateModule.getterFor(STRING_ITERATOR) + + // `String.prototype[@@iterator]` method + // https://tc39.es/ecma262/#sec-string.prototype-@@iterator + defineIterator( + String, + 'String', + function (iterated) { + setInternalState(this, { + type: STRING_ITERATOR, + string: toString(iterated), + index: 0, + }) + // `%StringIteratorPrototype%.next` method + // https://tc39.es/ecma262/#sec-%stringiteratorprototype%.next + }, + function next() { + var state = getInternalState(this) + var string = state.string + var index = state.index + var point + if (index >= string.length) return { value: undefined, done: true } + point = charAt(string, index) + state.index += point.length + return { value: point, done: false } + } + ) + }, + { + '../internals/define-iterator': 84, + '../internals/internal-state': 109, + '../internals/string-multibyte': 145, + '../internals/to-string': 155, + }, + ], + 180: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.asyncIterator` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.asynciterator + defineWellKnownSymbol('asyncIterator') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 181: [ + function (require, module, exports) { + arguments[4][172][0].apply(exports, arguments) + }, + { dup: 172 }, + ], + 182: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.hasInstance` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.hasinstance + defineWellKnownSymbol('hasInstance') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 183: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.isConcatSpreadable` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.isconcatspreadable + defineWellKnownSymbol('isConcatSpreadable') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 184: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.iterator` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.iterator + defineWellKnownSymbol('iterator') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 185: [ + function (require, module, exports) { + 'use strict' + var $ = require('../internals/export') + var global = require('../internals/global') + var getBuiltIn = require('../internals/get-built-in') + var IS_PURE = require('../internals/is-pure') + var DESCRIPTORS = require('../internals/descriptors') + var NATIVE_SYMBOL = require('../internals/native-symbol') + var fails = require('../internals/fails') + var has = require('../internals/has') + var isArray = require('../internals/is-array') + var isObject = require('../internals/is-object') + var isSymbol = require('../internals/is-symbol') + var anObject = require('../internals/an-object') + var toObject = require('../internals/to-object') + var toIndexedObject = require('../internals/to-indexed-object') + var toPropertyKey = require('../internals/to-property-key') + var $toString = require('../internals/to-string') + var createPropertyDescriptor = require('../internals/create-property-descriptor') + var nativeObjectCreate = require('../internals/object-create') + var objectKeys = require('../internals/object-keys') + var getOwnPropertyNamesModule = require('../internals/object-get-own-property-names') + var getOwnPropertyNamesExternal = require('../internals/object-get-own-property-names-external') + var getOwnPropertySymbolsModule = require('../internals/object-get-own-property-symbols') + var getOwnPropertyDescriptorModule = require('../internals/object-get-own-property-descriptor') + var definePropertyModule = require('../internals/object-define-property') + var propertyIsEnumerableModule = require('../internals/object-property-is-enumerable') + var createNonEnumerableProperty = require('../internals/create-non-enumerable-property') + var redefine = require('../internals/redefine') + var shared = require('../internals/shared') + var sharedKey = require('../internals/shared-key') + var hiddenKeys = require('../internals/hidden-keys') + var uid = require('../internals/uid') + var wellKnownSymbol = require('../internals/well-known-symbol') + var wrappedWellKnownSymbolModule = require('../internals/well-known-symbol-wrapped') + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + var setToStringTag = require('../internals/set-to-string-tag') + var InternalStateModule = require('../internals/internal-state') + var $forEach = require('../internals/array-iteration').forEach + + var HIDDEN = sharedKey('hidden') + var SYMBOL = 'Symbol' + var PROTOTYPE = 'prototype' + var TO_PRIMITIVE = wellKnownSymbol('toPrimitive') + var setInternalState = InternalStateModule.set + var getInternalState = InternalStateModule.getterFor(SYMBOL) + var ObjectPrototype = Object[PROTOTYPE] + var $Symbol = global.Symbol + var $stringify = getBuiltIn('JSON', 'stringify') + var nativeGetOwnPropertyDescriptor = getOwnPropertyDescriptorModule.f + var nativeDefineProperty = definePropertyModule.f + var nativeGetOwnPropertyNames = getOwnPropertyNamesExternal.f + var nativePropertyIsEnumerable = propertyIsEnumerableModule.f + var AllSymbols = shared('symbols') + var ObjectPrototypeSymbols = shared('op-symbols') + var StringToSymbolRegistry = shared('string-to-symbol-registry') + var SymbolToStringRegistry = shared('symbol-to-string-registry') + var WellKnownSymbolsStore = shared('wks') + var QObject = global.QObject + // Don't use setters in Qt Script, https://github.com/zloirock/core-js/issues/173 + var USE_SETTER = !QObject || !QObject[PROTOTYPE] || !QObject[PROTOTYPE].findChild + + // fallback for old Android, https://code.google.com/p/v8/issues/detail?id=687 + var setSymbolDescriptor = + DESCRIPTORS && + fails(function () { + return ( + nativeObjectCreate( + nativeDefineProperty({}, 'a', { + get: function () { + return nativeDefineProperty(this, 'a', { value: 7 }).a + }, + }) + ).a != 7 + ) + }) + ? function (O, P, Attributes) { + var ObjectPrototypeDescriptor = nativeGetOwnPropertyDescriptor(ObjectPrototype, P) + if (ObjectPrototypeDescriptor) delete ObjectPrototype[P] + nativeDefineProperty(O, P, Attributes) + if (ObjectPrototypeDescriptor && O !== ObjectPrototype) { + nativeDefineProperty(ObjectPrototype, P, ObjectPrototypeDescriptor) + } + } + : nativeDefineProperty + + var wrap = function (tag, description) { + var symbol = (AllSymbols[tag] = nativeObjectCreate($Symbol[PROTOTYPE])) + setInternalState(symbol, { + type: SYMBOL, + tag: tag, + description: description, + }) + if (!DESCRIPTORS) symbol.description = description + return symbol + } - var backrefValue = isNamedBackref && XRegExp.isInstalled('namespacing') ? match.groups[item.backref] : match[item.backref]; - matches.push(backrefValue || ''); - } else { - matches.push(match[0]); - } - } + var $defineProperty = function defineProperty(O, P, Attributes) { + if (O === ObjectPrototype) $defineProperty(ObjectPrototypeSymbols, P, Attributes) + anObject(O) + var key = toPropertyKey(P) + anObject(Attributes) + if (has(AllSymbols, key)) { + if (!Attributes.enumerable) { + if (!has(O, HIDDEN)) nativeDefineProperty(O, HIDDEN, createPropertyDescriptor(1, {})) + O[HIDDEN][key] = true + } else { + if (has(O, HIDDEN) && O[HIDDEN][key]) O[HIDDEN][key] = false + Attributes = nativeObjectCreate(Attributes, { enumerable: createPropertyDescriptor(0, false) }) + } + return setSymbolDescriptor(O, key, Attributes) + } + return nativeDefineProperty(O, key, Attributes) + } - var _iterator3 = _createForOfIteratorHelper(values), - _step3; + var $defineProperties = function defineProperties(O, Properties) { + anObject(O) + var properties = toIndexedObject(Properties) + var keys = objectKeys(properties).concat($getOwnPropertySymbols(properties)) + $forEach(keys, function (key) { + if (!DESCRIPTORS || $propertyIsEnumerable.call(properties, key)) $defineProperty(O, key, properties[key]) + }) + return O + } - try { - for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { - var value = _step3.value; - (0, _forEach["default"])(XRegExp).call(XRegExp, value, item.regex, addMatch); - } - } catch (err) { - _iterator3.e(err); - } finally { - _iterator3.f(); - } + var $create = function create(O, Properties) { + return Properties === undefined + ? nativeObjectCreate(O) + : $defineProperties(nativeObjectCreate(O), Properties) + } - return level === chain.length - 1 || !matches.length ? matches : recurseChain(matches, level + 1); - }([str], 0); -}; -/** - * Returns a new string with one or all matches of a pattern replaced. The pattern can be a string - * or regex, and the replacement can be a string or a function to be called for each match. To - * perform a global search and replace, use the optional `scope` argument or include flag g if using - * a regex. Replacement strings can use `$` or `${n}` for named and numbered backreferences. - * Replacement functions can use named backreferences via the last argument. Also fixes browser bugs - * compared to the native `String.prototype.replace` and can be used reliably cross-browser. - * - * @memberOf XRegExp - * @param {String} str String to search. - * @param {RegExp|String} search Search pattern to be replaced. - * @param {String|Function} replacement Replacement string or a function invoked to create it. - * Replacement strings can include special replacement syntax: - * - $$ - Inserts a literal $ character. - * - $&, $0 - Inserts the matched substring. - * - $` - Inserts the string that precedes the matched substring (left context). - * - $' - Inserts the string that follows the matched substring (right context). - * - $n, $nn - Where n/nn are digits referencing an existing capturing group, inserts - * backreference n/nn. - * - $, ${n} - Where n is a name or any number of digits that reference an existing capturing - * group, inserts backreference n. - * Replacement functions are invoked with three or more arguments: - * - args[0] - The matched substring (corresponds to `$&` above). If the `namespacing` feature - * is off, named backreferences are accessible as properties of this argument. - * - args[1..n] - One argument for each backreference (corresponding to `$1`, `$2`, etc. above). - * If the regex has no capturing groups, no arguments appear in this position. - * - args[n+1] - The zero-based index of the match within the entire search string. - * - args[n+2] - The total string being searched. - * - args[n+3] - If the the search pattern is a regex with named capturing groups, the last - * argument is the groups object. Its keys are the backreference names and its values are the - * backreference values. If the `namespacing` feature is off, this argument is not present. - * @param {String} [scope] Use 'one' to replace the first match only, or 'all'. Defaults to 'one'. - * Defaults to 'all' if using a regex with flag g. - * @returns {String} New string with one or all matches replaced. - * @example - * - * // Regex search, using named backreferences in replacement string - * const name = XRegExp('(?\\w+) (?\\w+)'); - * XRegExp.replace('John Smith', name, '$, $'); - * // -> 'Smith, John' - * - * // Regex search, using named backreferences in replacement function - * XRegExp.replace('John Smith', name, (...args) => { - * const groups = args[args.length - 1]; - * return `${groups.last}, ${groups.first}`; - * }); - * // -> 'Smith, John' - * - * // String search, with replace-all - * XRegExp.replace('RegExp builds RegExps', 'RegExp', 'XRegExp', 'all'); - * // -> 'XRegExp builds XRegExps' - */ - - -XRegExp.replace = function (str, search, replacement, scope) { - var isRegex = XRegExp.isRegExp(search); - var global = search.global && scope !== 'one' || scope === 'all'; - var cacheKey = (global ? 'g' : '') + (search.sticky ? 'y' : '') || 'noGY'; - var s2 = search; - - if (isRegex) { - search[REGEX_DATA] = search[REGEX_DATA] || {}; // Shares cached copies with `XRegExp.exec`/`match`. Since a copy is used, `search`'s - // `lastIndex` isn't updated *during* replacement iterations - - s2 = search[REGEX_DATA][cacheKey] || (search[REGEX_DATA][cacheKey] = copyRegex(search, { - addG: !!global, - removeG: scope === 'one', - isInternalOnly: true - })); - } else if (global) { - s2 = new RegExp(XRegExp.escape(String(search)), 'g'); - } // Fixed `replace` required for named backreferences, etc. - - - var result = fixed.replace.call(nullThrows(str), s2, replacement); - - if (isRegex && search.global) { - // Fixes IE, Safari bug (last tested IE 9, Safari 5.1) - search.lastIndex = 0; - } + var $propertyIsEnumerable = function propertyIsEnumerable(V) { + var P = toPropertyKey(V) + var enumerable = nativePropertyIsEnumerable.call(this, P) + if (this === ObjectPrototype && has(AllSymbols, P) && !has(ObjectPrototypeSymbols, P)) return false + return enumerable || !has(this, P) || !has(AllSymbols, P) || (has(this, HIDDEN) && this[HIDDEN][P]) + ? enumerable + : true + } - return result; -}; -/** - * Performs batch processing of string replacements. Used like `XRegExp.replace`, but accepts an - * array of replacement details. Later replacements operate on the output of earlier replacements. - * Replacement details are accepted as an array with a regex or string to search for, the - * replacement string or function, and an optional scope of 'one' or 'all'. Uses the XRegExp - * replacement text syntax, which supports named backreference properties via `$` or - * `${name}`. - * - * @memberOf XRegExp - * @param {String} str String to search. - * @param {Array} replacements Array of replacement detail arrays. - * @returns {String} New string with all replacements. - * @example - * - * str = XRegExp.replaceEach(str, [ - * [XRegExp('(?a)'), 'z$'], - * [/b/gi, 'y'], - * [/c/g, 'x', 'one'], // scope 'one' overrides /g - * [/d/, 'w', 'all'], // scope 'all' overrides lack of /g - * ['e', 'v', 'all'], // scope 'all' allows replace-all for strings - * [/f/g, (match) => match.toUpperCase()] - * ]); - */ - - -XRegExp.replaceEach = function (str, replacements) { - var _iterator4 = _createForOfIteratorHelper(replacements), - _step4; - - try { - for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { - var r = _step4.value; - str = XRegExp.replace(str, r[0], r[1], r[2]); - } - } catch (err) { - _iterator4.e(err); - } finally { - _iterator4.f(); - } + var $getOwnPropertyDescriptor = function getOwnPropertyDescriptor(O, P) { + var it = toIndexedObject(O) + var key = toPropertyKey(P) + if (it === ObjectPrototype && has(AllSymbols, key) && !has(ObjectPrototypeSymbols, key)) return + var descriptor = nativeGetOwnPropertyDescriptor(it, key) + if (descriptor && has(AllSymbols, key) && !(has(it, HIDDEN) && it[HIDDEN][key])) { + descriptor.enumerable = true + } + return descriptor + } - return str; -}; -/** - * Splits a string into an array of strings using a regex or string separator. Matches of the - * separator are not included in the result array. However, if `separator` is a regex that contains - * capturing groups, backreferences are spliced into the result each time `separator` is matched. - * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably - * cross-browser. - * - * @memberOf XRegExp - * @param {String} str String to split. - * @param {RegExp|String} separator Regex or string to use for separating the string. - * @param {Number} [limit] Maximum number of items to include in the result array. - * @returns {Array} Array of substrings. - * @example - * - * // Basic use - * XRegExp.split('a b c', ' '); - * // -> ['a', 'b', 'c'] - * - * // With limit - * XRegExp.split('a b c', ' ', 2); - * // -> ['a', 'b'] - * - * // Backreferences in result array - * XRegExp.split('..word1..', /([a-z]+)(\d+)/i); - * // -> ['..', 'word', '1', '..'] - */ - - -XRegExp.split = function (str, separator, limit) { - return fixed.split.call(nullThrows(str), separator, limit); -}; -/** - * Executes a regex search in a specified string. Returns `true` or `false`. Optional `pos` and - * `sticky` arguments specify the search start position, and whether the match must start at the - * specified position only. The `lastIndex` property of the provided regex is not used, but is - * updated for compatibility. Also fixes browser bugs compared to the native - * `RegExp.prototype.test` and can be used reliably cross-browser. - * - * @memberOf XRegExp - * @param {String} str String to search. - * @param {RegExp} regex Regex to search with. - * @param {Number} [pos=0] Zero-based index at which to start the search. - * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position - * only. The string `'sticky'` is accepted as an alternative to `true`. - * @returns {boolean} Whether the regex matched the provided value. - * @example - * - * // Basic use - * XRegExp.test('abc', /c/); // -> true - * - * // With pos and sticky - * XRegExp.test('abc', /c/, 0, 'sticky'); // -> false - * XRegExp.test('abc', /c/, 2, 'sticky'); // -> true - */ -// Do this the easy way :-) - - -XRegExp.test = function (str, regex, pos, sticky) { - return !!XRegExp.exec(str, regex, pos, sticky); -}; -/** - * Uninstalls optional features according to the specified options. Used to undo the actions of - * `XRegExp.install`. - * - * @memberOf XRegExp - * @param {Object|String} options Options object or string. - * @example - * - * // With an options object - * XRegExp.uninstall({ - * // Disables support for astral code points in Unicode addons (unless enabled per regex) - * astral: true, - * - * // Don't add named capture groups to the `groups` property of matches - * namespacing: true - * }); - * - * // With an options string - * XRegExp.uninstall('astral namespacing'); - */ - - -XRegExp.uninstall = function (options) { - options = prepareOptions(options); - - if (features.astral && options.astral) { - setAstral(false); - } + var $getOwnPropertyNames = function getOwnPropertyNames(O) { + var names = nativeGetOwnPropertyNames(toIndexedObject(O)) + var result = [] + $forEach(names, function (key) { + if (!has(AllSymbols, key) && !has(hiddenKeys, key)) result.push(key) + }) + return result + } - if (features.namespacing && options.namespacing) { - setNamespacing(false); - } -}; -/** - * Returns an XRegExp object that is the union of the given patterns. Patterns can be provided as - * regex objects or strings. Metacharacters are escaped in patterns provided as strings. - * Backreferences in provided regex objects are automatically renumbered to work correctly within - * the larger combined pattern. Native flags used by provided regexes are ignored in favor of the - * `flags` argument. - * - * @memberOf XRegExp - * @param {Array} patterns Regexes and strings to combine. - * @param {String} [flags] Any combination of XRegExp flags. - * @param {Object} [options] Options object with optional properties: - * - `conjunction` {String} Type of conjunction to use: 'or' (default) or 'none'. - * @returns {RegExp} Union of the provided regexes and strings. - * @example - * - * XRegExp.union(['a+b*c', /(dogs)\1/, /(cats)\1/], 'i'); - * // -> /a\+b\*c|(dogs)\1|(cats)\2/i - * - * XRegExp.union([/man/, /bear/, /pig/], 'i', {conjunction: 'none'}); - * // -> /manbearpig/i - */ - - -XRegExp.union = function (patterns, flags, options) { - options = options || {}; - var conjunction = options.conjunction || 'or'; - var numCaptures = 0; - var numPriorCaptures; - var captureNames; - - function rewrite(match, paren, backref) { - var name = captureNames[numCaptures - numPriorCaptures]; // Capturing group - - if (paren) { - ++numCaptures; // If the current capture has a name, preserve the name - - if (name) { - return "(?<".concat(name, ">"); - } // Backreference - - } else if (backref) { - // Rewrite the backreference - return "\\".concat(+backref + numPriorCaptures); - } + var $getOwnPropertySymbols = function getOwnPropertySymbols(O) { + var IS_OBJECT_PROTOTYPE = O === ObjectPrototype + var names = nativeGetOwnPropertyNames(IS_OBJECT_PROTOTYPE ? ObjectPrototypeSymbols : toIndexedObject(O)) + var result = [] + $forEach(names, function (key) { + if (has(AllSymbols, key) && (!IS_OBJECT_PROTOTYPE || has(ObjectPrototype, key))) { + result.push(AllSymbols[key]) + } + }) + return result + } - return match; - } + // `Symbol` constructor + // https://tc39.es/ecma262/#sec-symbol-constructor + if (!NATIVE_SYMBOL) { + $Symbol = function Symbol() { + if (this instanceof $Symbol) throw TypeError('Symbol is not a constructor') + var description = !arguments.length || arguments[0] === undefined ? undefined : $toString(arguments[0]) + var tag = uid(description) + var setter = function (value) { + if (this === ObjectPrototype) setter.call(ObjectPrototypeSymbols, value) + if (has(this, HIDDEN) && has(this[HIDDEN], tag)) this[HIDDEN][tag] = false + setSymbolDescriptor(this, tag, createPropertyDescriptor(1, value)) + } + if (DESCRIPTORS && USE_SETTER) + setSymbolDescriptor(ObjectPrototype, tag, { configurable: true, set: setter }) + return wrap(tag, description) + } - if (!(isType(patterns, 'Array') && patterns.length)) { - throw new TypeError('Must provide a nonempty array of patterns to merge'); - } + redefine($Symbol[PROTOTYPE], 'toString', function toString() { + return getInternalState(this).tag + }) - var parts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*\]/g; - var output = []; + redefine($Symbol, 'withoutSetter', function (description) { + return wrap(uid(description), description) + }) - var _iterator5 = _createForOfIteratorHelper(patterns), - _step5; + propertyIsEnumerableModule.f = $propertyIsEnumerable + definePropertyModule.f = $defineProperty + getOwnPropertyDescriptorModule.f = $getOwnPropertyDescriptor + getOwnPropertyNamesModule.f = getOwnPropertyNamesExternal.f = $getOwnPropertyNames + getOwnPropertySymbolsModule.f = $getOwnPropertySymbols - try { - for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { - var pattern = _step5.value; + wrappedWellKnownSymbolModule.f = function (name) { + return wrap(wellKnownSymbol(name), name) + } - if (XRegExp.isRegExp(pattern)) { - numPriorCaptures = numCaptures; - captureNames = pattern[REGEX_DATA] && pattern[REGEX_DATA].captureNames || []; // Rewrite backreferences. Passing to XRegExp dies on octals and ensures patterns are - // independently valid; helps keep this simple. Named captures are put back + if (DESCRIPTORS) { + // https://github.com/tc39/proposal-Symbol-description + nativeDefineProperty($Symbol[PROTOTYPE], 'description', { + configurable: true, + get: function description() { + return getInternalState(this).description + }, + }) + if (!IS_PURE) { + redefine(ObjectPrototype, 'propertyIsEnumerable', $propertyIsEnumerable, { unsafe: true }) + } + } + } - output.push(XRegExp(pattern.source).source.replace(parts, rewrite)); - } else { - output.push(XRegExp.escape(pattern)); - } - } - } catch (err) { - _iterator5.e(err); - } finally { - _iterator5.f(); - } + $( + { global: true, wrap: true, forced: !NATIVE_SYMBOL, sham: !NATIVE_SYMBOL }, + { + Symbol: $Symbol, + } + ) + + $forEach(objectKeys(WellKnownSymbolsStore), function (name) { + defineWellKnownSymbol(name) + }) + + $( + { target: SYMBOL, stat: true, forced: !NATIVE_SYMBOL }, + { + // `Symbol.for` method + // https://tc39.es/ecma262/#sec-symbol.for + for: function (key) { + var string = $toString(key) + if (has(StringToSymbolRegistry, string)) return StringToSymbolRegistry[string] + var symbol = $Symbol(string) + StringToSymbolRegistry[string] = symbol + SymbolToStringRegistry[symbol] = string + return symbol + }, + // `Symbol.keyFor` method + // https://tc39.es/ecma262/#sec-symbol.keyfor + keyFor: function keyFor(sym) { + if (!isSymbol(sym)) throw TypeError(sym + ' is not a symbol') + if (has(SymbolToStringRegistry, sym)) return SymbolToStringRegistry[sym] + }, + useSetter: function () { + USE_SETTER = true + }, + useSimple: function () { + USE_SETTER = false + }, + } + ) + + $( + { target: 'Object', stat: true, forced: !NATIVE_SYMBOL, sham: !DESCRIPTORS }, + { + // `Object.create` method + // https://tc39.es/ecma262/#sec-object.create + create: $create, + // `Object.defineProperty` method + // https://tc39.es/ecma262/#sec-object.defineproperty + defineProperty: $defineProperty, + // `Object.defineProperties` method + // https://tc39.es/ecma262/#sec-object.defineproperties + defineProperties: $defineProperties, + // `Object.getOwnPropertyDescriptor` method + // https://tc39.es/ecma262/#sec-object.getownpropertydescriptors + getOwnPropertyDescriptor: $getOwnPropertyDescriptor, + } + ) + + $( + { target: 'Object', stat: true, forced: !NATIVE_SYMBOL }, + { + // `Object.getOwnPropertyNames` method + // https://tc39.es/ecma262/#sec-object.getownpropertynames + getOwnPropertyNames: $getOwnPropertyNames, + // `Object.getOwnPropertySymbols` method + // https://tc39.es/ecma262/#sec-object.getownpropertysymbols + getOwnPropertySymbols: $getOwnPropertySymbols, + } + ) + + // Chrome 38 and 39 `Object.getOwnPropertySymbols` fails on primitives + // https://bugs.chromium.org/p/v8/issues/detail?id=3443 + $( + { + target: 'Object', + stat: true, + forced: fails(function () { + getOwnPropertySymbolsModule.f(1) + }), + }, + { + getOwnPropertySymbols: function getOwnPropertySymbols(it) { + return getOwnPropertySymbolsModule.f(toObject(it)) + }, + } + ) + + // `JSON.stringify` method behavior with symbols + // https://tc39.es/ecma262/#sec-json.stringify + if ($stringify) { + var FORCED_JSON_STRINGIFY = + !NATIVE_SYMBOL || + fails(function () { + var symbol = $Symbol() + // MS Edge converts symbol values to JSON as {} + return ( + $stringify([symbol]) != '[null]' || + // WebKit converts symbol values to JSON as null + $stringify({ a: symbol }) != '{}' || + // V8 throws on boxed symbols + $stringify(Object(symbol)) != '{}' + ) + }) + + $( + { target: 'JSON', stat: true, forced: FORCED_JSON_STRINGIFY }, + { + // eslint-disable-next-line no-unused-vars -- required for `.length` + stringify: function stringify(it, replacer, space) { + var args = [it] + var index = 1 + var $replacer + while (arguments.length > index) args.push(arguments[index++]) + $replacer = replacer + if ((!isObject(replacer) && it === undefined) || isSymbol(it)) return // IE8 returns string on undefined + if (!isArray(replacer)) + replacer = function (key, value) { + if (typeof $replacer == 'function') value = $replacer.call(this, key, value) + if (!isSymbol(value)) return value + } + args[1] = replacer + return $stringify.apply(null, args) + }, + } + ) + } - var separator = conjunction === 'none' ? '' : '|'; - return XRegExp(output.join(separator), flags); -}; // ==--------------------------== -// Fixed/extended native methods -// ==--------------------------== - -/** - * Adds named capture support (with backreferences returned as `result.name`), and fixes browser - * bugs in the native `RegExp.prototype.exec`. Use via `XRegExp.exec`. - * - * @memberOf RegExp - * @param {String} str String to search. - * @returns {Array} Match array with named backreference properties, or `null`. - */ - - -fixed.exec = function (str) { - var origLastIndex = this.lastIndex; - var match = RegExp.prototype.exec.apply(this, arguments); - - if (match) { - // Fix browsers whose `exec` methods don't return `undefined` for nonparticipating capturing - // groups. This fixes IE 5.5-8, but not IE 9's quirks mode or emulation of older IEs. IE 9 - // in standards mode follows the spec. - if (!correctExecNpcg && match.length > 1 && (0, _indexOf["default"])(match).call(match, '') !== -1) { - var _context3; - - var r2 = copyRegex(this, { - removeG: true, - isInternalOnly: true - }); // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed - // matching due to characters outside the match - - (0, _slice["default"])(_context3 = String(str)).call(_context3, match.index).replace(r2, function () { - var len = arguments.length; // Skip index 0 and the last 2 - - for (var i = 1; i < len - 2; ++i) { - if ((i < 0 || arguments.length <= i ? undefined : arguments[i]) === undefined) { - match[i] = undefined; + // `Symbol.prototype[@@toPrimitive]` method + // https://tc39.es/ecma262/#sec-symbol.prototype-@@toprimitive + if (!$Symbol[PROTOTYPE][TO_PRIMITIVE]) { + createNonEnumerableProperty($Symbol[PROTOTYPE], TO_PRIMITIVE, $Symbol[PROTOTYPE].valueOf) + } + // `Symbol.prototype[@@toStringTag]` property + // https://tc39.es/ecma262/#sec-symbol.prototype-@@tostringtag + setToStringTag($Symbol, SYMBOL) + + hiddenKeys[HIDDEN] = true + }, + { + '../internals/an-object': 64, + '../internals/array-iteration': 68, + '../internals/create-non-enumerable-property': 81, + '../internals/create-property-descriptor': 82, + '../internals/define-well-known-symbol': 85, + '../internals/descriptors': 86, + '../internals/export': 97, + '../internals/fails': 98, + '../internals/get-built-in': 100, + '../internals/global': 102, + '../internals/has': 103, + '../internals/hidden-keys': 104, + '../internals/internal-state': 109, + '../internals/is-array': 111, + '../internals/is-object': 113, + '../internals/is-pure': 114, + '../internals/is-symbol': 115, + '../internals/native-symbol': 119, + '../internals/object-create': 122, + '../internals/object-define-property': 124, + '../internals/object-get-own-property-descriptor': 125, + '../internals/object-get-own-property-names': 127, + '../internals/object-get-own-property-names-external': 126, + '../internals/object-get-own-property-symbols': 128, + '../internals/object-keys': 131, + '../internals/object-property-is-enumerable': 132, + '../internals/redefine': 137, + '../internals/set-to-string-tag': 141, + '../internals/shared': 144, + '../internals/shared-key': 142, + '../internals/to-indexed-object': 148, + '../internals/to-object': 151, + '../internals/to-property-key': 153, + '../internals/to-string': 155, + '../internals/uid': 156, + '../internals/well-known-symbol': 159, + '../internals/well-known-symbol-wrapped': 158, + }, + ], + 186: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.matchAll` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.matchall + defineWellKnownSymbol('matchAll') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 187: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.match` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.match + defineWellKnownSymbol('match') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 188: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.replace` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.replace + defineWellKnownSymbol('replace') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 189: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.search` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.search + defineWellKnownSymbol('search') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 190: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.species` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.species + defineWellKnownSymbol('species') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 191: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.split` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.split + defineWellKnownSymbol('split') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 192: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.toPrimitive` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.toprimitive + defineWellKnownSymbol('toPrimitive') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 193: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.toStringTag` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.tostringtag + defineWellKnownSymbol('toStringTag') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 194: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.unscopables` well-known symbol + // https://tc39.es/ecma262/#sec-symbol.unscopables + defineWellKnownSymbol('unscopables') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 195: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.asyncDispose` well-known symbol + // https://github.com/tc39/proposal-using-statement + defineWellKnownSymbol('asyncDispose') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 196: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.dispose` well-known symbol + // https://github.com/tc39/proposal-using-statement + defineWellKnownSymbol('dispose') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 197: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.matcher` well-known symbol + // https://github.com/tc39/proposal-pattern-matching + defineWellKnownSymbol('matcher') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 198: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.metadata` well-known symbol + // https://github.com/tc39/proposal-decorators + defineWellKnownSymbol('metadata') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 199: [ + function (require, module, exports) { + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.observable` well-known symbol + // https://github.com/tc39/proposal-observable + defineWellKnownSymbol('observable') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 200: [ + function (require, module, exports) { + // TODO: remove from `core-js@4` + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + // `Symbol.patternMatch` well-known symbol + // https://github.com/tc39/proposal-pattern-matching + defineWellKnownSymbol('patternMatch') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 201: [ + function (require, module, exports) { + // TODO: remove from `core-js@4` + var defineWellKnownSymbol = require('../internals/define-well-known-symbol') + + defineWellKnownSymbol('replaceAll') + }, + { '../internals/define-well-known-symbol': 85 }, + ], + 202: [ + function (require, module, exports) { + require('./es.array.iterator') + var DOMIterables = require('../internals/dom-iterables') + var global = require('../internals/global') + var classof = require('../internals/classof') + var createNonEnumerableProperty = require('../internals/create-non-enumerable-property') + var Iterators = require('../internals/iterators') + var wellKnownSymbol = require('../internals/well-known-symbol') + + var TO_STRING_TAG = wellKnownSymbol('toStringTag') + + for (var COLLECTION_NAME in DOMIterables) { + var Collection = global[COLLECTION_NAME] + var CollectionPrototype = Collection && Collection.prototype + if (CollectionPrototype && classof(CollectionPrototype) !== TO_STRING_TAG) { + createNonEnumerableProperty(CollectionPrototype, TO_STRING_TAG, COLLECTION_NAME) + } + Iterators[COLLECTION_NAME] = Iterators.Array + } + }, + { + '../internals/classof': 78, + '../internals/create-non-enumerable-property': 81, + '../internals/dom-iterables': 88, + '../internals/global': 102, + '../internals/iterators': 118, + '../internals/well-known-symbol': 159, + './es.array.iterator': 166, + }, + ], + 203: [ + function (require, module, exports) { + arguments[4][56][0].apply(exports, arguments) + }, + { '../../es/array/from': 35, dup: 56 }, + ], + 204: [ + function (require, module, exports) { + arguments[4][57][0].apply(exports, arguments) + }, + { '../../es/array/is-array': 36, dup: 57 }, + ], + 205: [ + function (require, module, exports) { + var parent = require('../../../es/array/virtual/for-each') + + module.exports = parent + }, + { '../../../es/array/virtual/for-each': 38 }, + ], + 206: [ + function (require, module, exports) { + var parent = require('../../es/instance/concat') + + module.exports = parent + }, + { '../../es/instance/concat': 44 }, + ], + 207: [ + function (require, module, exports) { + var parent = require('../../es/instance/flags') + + module.exports = parent + }, + { '../../es/instance/flags': 45 }, + ], + 208: [ + function (require, module, exports) { + require('../../modules/web.dom-collections.iterator') + var forEach = require('../array/virtual/for-each') + var classof = require('../../internals/classof') + var ArrayPrototype = Array.prototype + + var DOMIterables = { + DOMTokenList: true, + NodeList: true, } - } - }); - } // Attach named capture properties - - if (this[REGEX_DATA] && this[REGEX_DATA].captureNames) { - var groupsObject = match; - - if (XRegExp.isInstalled('namespacing')) { - // https://tc39.github.io/proposal-regexp-named-groups/#sec-regexpbuiltinexec - match.groups = (0, _create["default"])(null); - groupsObject = match.groups; - } // Skip index 0 - - - for (var i = 1; i < match.length; ++i) { - var name = this[REGEX_DATA].captureNames[i - 1]; - - if (name) { - groupsObject[name] = match[i]; - } - } // Preserve any existing `groups` obj that came from native ES2018 named capture - - } else if (!match.groups && XRegExp.isInstalled('namespacing')) { - match.groups = undefined; - } // Fix browsers that increment `lastIndex` after zero-length matches - - - if (this.global && !match[0].length && this.lastIndex > match.index) { - this.lastIndex = match.index; - } - } - - if (!this.global) { - // Fixes IE, Opera bug (last tested IE 9, Opera 11.6) - this.lastIndex = origLastIndex; - } - - return match; -}; -/** - * Fixes browser bugs in the native `RegExp.prototype.test`. - * - * @memberOf RegExp - * @param {String} str String to search. - * @returns {boolean} Whether the regex matched the provided value. - */ - - -fixed.test = function (str) { - // Do this the easy way :-) - return !!fixed.exec.call(this, str); -}; -/** - * Adds named capture support (with backreferences returned as `result.name`), and fixes browser - * bugs in the native `String.prototype.match`. - * - * @memberOf String - * @param {RegExp|*} regex Regex to search with. If not a regex object, it is passed to `RegExp`. - * @returns {Array} If `regex` uses flag g, an array of match strings or `null`. Without flag g, - * the result of calling `regex.exec(this)`. - */ - - -fixed.match = function (regex) { - if (!XRegExp.isRegExp(regex)) { - // Use the native `RegExp` rather than `XRegExp` - regex = new RegExp(regex); - } else if (regex.global) { - var result = String.prototype.match.apply(this, arguments); // Fixes IE bug - - regex.lastIndex = 0; - return result; - } - - return fixed.exec.call(regex, nullThrows(this)); -}; -/** - * Adds support for `${n}` (or `$`) tokens for named and numbered backreferences in replacement - * text, and provides named backreferences to replacement functions as `arguments[0].name`. Also - * fixes browser bugs in replacement text syntax when performing a replacement using a nonregex - * search value, and the value of a replacement regex's `lastIndex` property during replacement - * iterations and upon completion. Note that this doesn't support SpiderMonkey's proprietary third - * (`flags`) argument. Use via `XRegExp.replace`. - * - * @memberOf String - * @param {RegExp|String} search Search pattern to be replaced. - * @param {String|Function} replacement Replacement string or a function invoked to create it. - * @returns {string} New string with one or all matches replaced. - */ - - -fixed.replace = function (search, replacement) { - var isRegex = XRegExp.isRegExp(search); - var origLastIndex; - var captureNames; - var result; - - if (isRegex) { - if (search[REGEX_DATA]) { - captureNames = search[REGEX_DATA].captureNames; - } // Only needed if `search` is nonglobal - - - origLastIndex = search.lastIndex; - } else { - search += ''; // Type-convert - } // Don't use `typeof`; some older browsers return 'function' for regex objects - - - if (isType(replacement, 'Function')) { - // Stringifying `this` fixes a bug in IE < 9 where the last argument in replacement - // functions isn't type-converted to a string - result = String(this).replace(search, function () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - if (captureNames) { - var groupsObject; - - if (XRegExp.isInstalled('namespacing')) { - // https://tc39.github.io/proposal-regexp-named-groups/#sec-regexpbuiltinexec - groupsObject = (0, _create["default"])(null); - args.push(groupsObject); - } else { - // Change the `args[0]` string primitive to a `String` object that can store - // properties. This really does need to use `String` as a constructor - args[0] = new String(args[0]); - groupsObject = args[0]; - } // Store named backreferences - - - for (var i = 0; i < captureNames.length; ++i) { - if (captureNames[i]) { - groupsObject[captureNames[i]] = args[i + 1]; - } - } - } // ES6 specs the context for replacement functions as `undefined` - - - return replacement.apply(void 0, args); - }); - } else { - // Ensure that the last value of `args` will be a string when given nonstring `this`, - // while still throwing on null or undefined context - result = String(nullThrows(this)).replace(search, function () { - for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; - } - - return String(replacement).replace(replacementToken, replacer); - - function replacer($0, bracketed, angled, dollarToken) { - bracketed = bracketed || angled; // ES2018 added a new trailing `groups` arg that's passed to replacement functions - // when the search regex uses native named capture - - var numNonCaptureArgs = isType(args[args.length - 1], 'Object') ? 4 : 3; - var numCaptures = args.length - numNonCaptureArgs; // Handle named or numbered backreference with curly or angled braces: ${n}, $ - - if (bracketed) { - // Handle backreference to numbered capture, if `bracketed` is an integer. Use - // `0` for the entire match. Any number of leading zeros may be used. - if (/^\d+$/.test(bracketed)) { - // Type-convert and drop leading zeros - var _n = +bracketed; - - if (_n <= numCaptures) { - return args[_n] || ''; - } - } // Handle backreference to named capture. If the name does not refer to an - // existing capturing group, it's an error. Also handles the error for numbered - // backference that does not refer to an existing group. - // Using `indexOf` since having groups with the same name is already an error, - // otherwise would need `lastIndexOf`. - - - var n = captureNames ? (0, _indexOf["default"])(captureNames).call(captureNames, bracketed) : -1; - - if (n < 0) { - throw new SyntaxError("Backreference to undefined group ".concat($0)); - } - - return args[n + 1] || ''; - } // Handle `$`-prefixed variable - // Handle space/blank first because type conversion with `+` drops space padding - // and converts spaces and empty strings to `0` - - - if (dollarToken === '' || dollarToken === ' ') { - throw new SyntaxError("Invalid token ".concat($0)); - } - - if (dollarToken === '&' || +dollarToken === 0) { - // $&, $0 (not followed by 1-9), $00 - return args[0]; - } - - if (dollarToken === '$') { - // $$ - return '$'; - } - - if (dollarToken === '`') { - var _context4; - - // $` (left context) - return (0, _slice["default"])(_context4 = args[args.length - 1]).call(_context4, 0, args[args.length - 2]); - } - - if (dollarToken === "'") { - var _context5; - - // $' (right context) - return (0, _slice["default"])(_context5 = args[args.length - 1]).call(_context5, args[args.length - 2] + args[0].length); - } // Handle numbered backreference without braces - // Type-convert and drop leading zero - - - dollarToken = +dollarToken; // XRegExp behavior for `$n` and `$nn`: - // - Backrefs end after 1 or 2 digits. Use `${..}` or `$<..>` for more digits. - // - `$1` is an error if no capturing groups. - // - `$10` is an error if less than 10 capturing groups. Use `${1}0` or `$<1>0` - // instead. - // - `$01` is `$1` if at least one capturing group, else it's an error. - // - `$0` (not followed by 1-9) and `$00` are the entire match. - // Native behavior, for comparison: - // - Backrefs end after 1 or 2 digits. Cannot reference capturing group 100+. - // - `$1` is a literal `$1` if no capturing groups. - // - `$10` is `$1` followed by a literal `0` if less than 10 capturing groups. - // - `$01` is `$1` if at least one capturing group, else it's a literal `$01`. - // - `$0` is a literal `$0`. - - if (!isNaN(dollarToken)) { - if (dollarToken > numCaptures) { - throw new SyntaxError("Backreference to undefined group ".concat($0)); + module.exports = function (it) { + var own = it.forEach + return it === ArrayPrototype || + (it instanceof Array && own === ArrayPrototype.forEach) || + // eslint-disable-next-line no-prototype-builtins -- safe + DOMIterables.hasOwnProperty(classof(it)) + ? forEach + : own } - - return args[dollarToken] || ''; - } // `$` followed by an unsupported char is an error, unlike native JS - - - throw new SyntaxError("Invalid token ".concat($0)); - } - }); - } - - if (isRegex) { - if (search.global) { - // Fixes IE, Safari bug (last tested IE 9, Safari 5.1) - search.lastIndex = 0; - } else { - // Fixes IE, Opera bug (last tested IE 9, Opera 11.6) - search.lastIndex = origLastIndex; - } - } - - return result; -}; -/** - * Fixes browser bugs in the native `String.prototype.split`. Use via `XRegExp.split`. - * - * @memberOf String - * @param {RegExp|String} separator Regex or string to use for separating the string. - * @param {Number} [limit] Maximum number of items to include in the result array. - * @returns {!Array} Array of substrings. - */ - - -fixed.split = function (separator, limit) { - if (!XRegExp.isRegExp(separator)) { - // Browsers handle nonregex split correctly, so use the faster native method - return String.prototype.split.apply(this, arguments); - } - - var str = String(this); - var output = []; - var origLastIndex = separator.lastIndex; - var lastLastIndex = 0; - var lastLength; // Values for `limit`, per the spec: - // If undefined: pow(2,32) - 1 - // If 0, Infinity, or NaN: 0 - // If positive number: limit = floor(limit); if (limit >= pow(2,32)) limit -= pow(2,32); - // If negative number: pow(2,32) - floor(abs(limit)) - // If other: Type-convert, then use the above rules - // This line fails in very strange ways for some values of `limit` in Opera 10.5-10.63, unless - // Opera Dragonfly is open (go figure). It works in at least Opera 9.5-10.1 and 11+ - - limit = (limit === undefined ? -1 : limit) >>> 0; - (0, _forEach["default"])(XRegExp).call(XRegExp, str, separator, function (match) { - // This condition is not the same as `if (match[0].length)` - if (match.index + match[0].length > lastLastIndex) { - output.push((0, _slice["default"])(str).call(str, lastLastIndex, match.index)); - - if (match.length > 1 && match.index < str.length) { - Array.prototype.push.apply(output, (0, _slice["default"])(match).call(match, 1)); - } - - lastLength = match[0].length; - lastLastIndex = match.index + lastLength; - } - }); - - if (lastLastIndex === str.length) { - if (!separator.test('') || lastLength) { - output.push(''); - } - } else { - output.push((0, _slice["default"])(str).call(str, lastLastIndex)); - } - - separator.lastIndex = origLastIndex; - return output.length > limit ? (0, _slice["default"])(output).call(output, 0, limit) : output; -}; // ==--------------------------== -// Built-in syntax/flag tokens -// ==--------------------------== - -/* - * Letter escapes that natively match literal characters: `\a`, `\A`, etc. These should be - * SyntaxErrors but are allowed in web reality. XRegExp makes them errors for cross-browser - * consistency and to reserve their syntax, but lets them be superseded by addons. - */ - - -XRegExp.addToken(/\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4}|{[\dA-Fa-f]+})|x(?![\dA-Fa-f]{2}))/, function (match, scope) { - // \B is allowed in default scope only - if (match[1] === 'B' && scope === defaultScope) { - return match[0]; - } - - throw new SyntaxError("Invalid escape ".concat(match[0])); -}, { - scope: 'all', - leadChar: '\\' -}); -/* - * Unicode code point escape with curly braces: `\u{N..}`. `N..` is any one or more digit - * hexadecimal number from 0-10FFFF, and can include leading zeros. Requires the native ES6 `u` flag - * to support code points greater than U+FFFF. Avoids converting code points above U+FFFF to - * surrogate pairs (which could be done without flag `u`), since that could lead to broken behavior - * if you follow a `\u{N..}` token that references a code point above U+FFFF with a quantifier, or - * if you use the same in a character class. - */ - -XRegExp.addToken(/\\u{([\dA-Fa-f]+)}/, function (match, scope, flags) { - var code = dec(match[1]); - - if (code > 0x10FFFF) { - throw new SyntaxError("Invalid Unicode code point ".concat(match[0])); - } - - if (code <= 0xFFFF) { - // Converting to \uNNNN avoids needing to escape the literal character and keep it - // separate from preceding tokens - return "\\u".concat(pad4(hex(code))); - } // If `code` is between 0xFFFF and 0x10FFFF, require and defer to native handling - - - if (hasNativeU && (0, _indexOf["default"])(flags).call(flags, 'u') !== -1) { - return match[0]; - } - - throw new SyntaxError('Cannot use Unicode code point above \\u{FFFF} without flag u'); -}, { - scope: 'all', - leadChar: '\\' -}); -/* - * Comment pattern: `(?# )`. Inline comments are an alternative to the line comments allowed in - * free-spacing mode (flag x). - */ - -XRegExp.addToken(/\(\?#[^)]*\)/, getContextualTokenSeparator, { - leadChar: '(' -}); -/* - * Whitespace and line comments, in free-spacing mode (aka extended mode, flag x) only. - */ - -XRegExp.addToken(/\s+|#[^\n]*\n?/, getContextualTokenSeparator, { - flag: 'x' -}); -/* - * Dot, in dotAll mode (aka singleline mode, flag s) only. - */ - -if (!hasNativeS) { - XRegExp.addToken(/\./, function () { - return '[\\s\\S]'; - }, { - flag: 's', - leadChar: '.' - }); -} -/* - * Named backreference: `\k`. Backreference names can use RegExpIdentifierName characters - * only. Also allows numbered backreferences as `\k`. - */ - - -XRegExp.addToken(/\\k<([^>]+)>/, function (match) { - var _context6, _context7; - - // Groups with the same name is an error, else would need `lastIndexOf` - var index = isNaN(match[1]) ? (0, _indexOf["default"])(_context6 = this.captureNames).call(_context6, match[1]) + 1 : +match[1]; - var endIndex = match.index + match[0].length; - - if (!index || index > this.captureNames.length) { - throw new SyntaxError("Backreference to undefined group ".concat(match[0])); - } // Keep backreferences separate from subsequent literal numbers. This avoids e.g. - // inadvertedly changing `(?)\k1` to `()\11`. - - - return (0, _concat["default"])(_context7 = "\\".concat(index)).call(_context7, endIndex === match.input.length || isNaN(match.input[endIndex]) ? '' : '(?:)'); -}, { - leadChar: '\\' -}); -/* - * Numbered backreference or octal, plus any following digits: `\0`, `\11`, etc. Octals except `\0` - * not followed by 0-9 and backreferences to unopened capture groups throw an error. Other matches - * are returned unaltered. IE < 9 doesn't support backreferences above `\99` in regex syntax. - */ - -XRegExp.addToken(/\\(\d+)/, function (match, scope) { - if (!(scope === defaultScope && /^[1-9]/.test(match[1]) && +match[1] <= this.captureNames.length) && match[1] !== '0') { - throw new SyntaxError("Cannot use octal escape or backreference to undefined group ".concat(match[0])); - } - - return match[0]; -}, { - scope: 'all', - leadChar: '\\' -}); -/* - * Named capturing group; match the opening delimiter only: `(?`. Capture names can use the - * RegExpIdentifierName characters only. Names can't be integers. Supports Python-style - * `(?P` as an alternate syntax to avoid issues in some older versions of Opera which natively - * supported the Python-style syntax. Otherwise, XRegExp might treat numbered backreferences to - * Python-style named capture as octals. - */ - -XRegExp.addToken(/\(\?P?<((?:[\$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDEC0-\uDEEB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])(?:[\$0-9A-Z_a-z\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05EF-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u07FD\u0800-\u082D\u0840-\u085B\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u08D3-\u08E1\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u09FC\u09FE\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D00-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D81-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1878\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1ABF\u1AC0\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CD0-\u1CD2\u1CD4-\u1CFA\u1D00-\u1DF9\u1DFB-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA827\uA82C\uA840-\uA873\uA880-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD27\uDD30-\uDD39\uDE80-\uDEA9\uDEAB\uDEAC\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF50\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD44-\uDD47\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDC9-\uDDCC\uDDCE-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3B-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC00-\uDC4A\uDC50-\uDC59\uDC5E-\uDC61\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDC00-\uDC3A\uDCA0-\uDCE9\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD35\uDD37\uDD38\uDD3B-\uDD43\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD7\uDDDA-\uDDE1\uDDE3\uDDE4\uDE00-\uDE3E\uDE47\uDE50-\uDE99\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD8E\uDD90\uDD91\uDD93-\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF6\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF4F-\uDF87\uDF8F-\uDF9F\uDFE0\uDFE1\uDFE3\uDFE4\uDFF0\uDFF1]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A\uDD00-\uDD2C\uDD30-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6\uDD00-\uDD4B\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]|\uDB40[\uDD00-\uDDEF])*)>/, function (match) { - var _context8; - - if (!XRegExp.isInstalled('namespacing') && (match[1] === 'length' || match[1] === '__proto__')) { - throw new SyntaxError("Cannot use reserved word as capture name ".concat(match[0])); - } - - if ((0, _indexOf["default"])(_context8 = this.captureNames).call(_context8, match[1]) !== -1) { - throw new SyntaxError("Cannot use same name for multiple groups ".concat(match[0])); - } - - this.captureNames.push(match[1]); - this.hasNamedCapture = true; - return '('; -}, { - leadChar: '(' -}); -/* - * Capturing group; match the opening parenthesis only. Required for support of named capturing - * groups. Also adds explicit capture mode (flag n). - */ - -XRegExp.addToken(/\((?!\?)/, function (match, scope, flags) { - if ((0, _indexOf["default"])(flags).call(flags, 'n') !== -1) { - return '(?:'; - } - - this.captureNames.push(null); - return '('; -}, { - optionalFlags: 'n', - leadChar: '(' -}); -var _default = XRegExp; -exports["default"] = _default; -module.exports = exports.default; -},{"@babel/runtime-corejs3/core-js-stable/array/from":9,"@babel/runtime-corejs3/core-js-stable/array/is-array":10,"@babel/runtime-corejs3/core-js-stable/instance/concat":11,"@babel/runtime-corejs3/core-js-stable/instance/flags":12,"@babel/runtime-corejs3/core-js-stable/instance/for-each":13,"@babel/runtime-corejs3/core-js-stable/instance/index-of":14,"@babel/runtime-corejs3/core-js-stable/instance/slice":17,"@babel/runtime-corejs3/core-js-stable/instance/sort":18,"@babel/runtime-corejs3/core-js-stable/object/create":19,"@babel/runtime-corejs3/core-js-stable/object/define-property":20,"@babel/runtime-corejs3/core-js-stable/parse-int":21,"@babel/runtime-corejs3/core-js-stable/symbol":22,"@babel/runtime-corejs3/core-js/get-iterator-method":25,"@babel/runtime-corejs3/helpers/interopRequireDefault":30,"@babel/runtime-corejs3/helpers/slicedToArray":33}],9:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/array/from"); -},{"core-js-pure/stable/array/from":203}],10:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/array/is-array"); -},{"core-js-pure/stable/array/is-array":204}],11:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/instance/concat"); -},{"core-js-pure/stable/instance/concat":206}],12:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/instance/flags"); -},{"core-js-pure/stable/instance/flags":207}],13:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/instance/for-each"); -},{"core-js-pure/stable/instance/for-each":208}],14:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/instance/index-of"); -},{"core-js-pure/stable/instance/index-of":209}],15:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/instance/map"); -},{"core-js-pure/stable/instance/map":210}],16:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/instance/reduce"); -},{"core-js-pure/stable/instance/reduce":211}],17:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/instance/slice"); -},{"core-js-pure/stable/instance/slice":212}],18:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/instance/sort"); -},{"core-js-pure/stable/instance/sort":213}],19:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/object/create"); -},{"core-js-pure/stable/object/create":214}],20:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/object/define-property"); -},{"core-js-pure/stable/object/define-property":215}],21:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/parse-int"); -},{"core-js-pure/stable/parse-int":216}],22:[function(require,module,exports){ -module.exports = require("core-js-pure/stable/symbol"); -},{"core-js-pure/stable/symbol":217}],23:[function(require,module,exports){ -module.exports = require("core-js-pure/features/array/from"); -},{"core-js-pure/features/array/from":56}],24:[function(require,module,exports){ -module.exports = require("core-js-pure/features/array/is-array"); -},{"core-js-pure/features/array/is-array":57}],25:[function(require,module,exports){ -module.exports = require("core-js-pure/features/get-iterator-method"); -},{"core-js-pure/features/get-iterator-method":58}],26:[function(require,module,exports){ -module.exports = require("core-js-pure/features/instance/slice"); -},{"core-js-pure/features/instance/slice":59}],27:[function(require,module,exports){ -module.exports = require("core-js-pure/features/symbol"); -},{"core-js-pure/features/symbol":60}],28:[function(require,module,exports){ -function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - - for (var i = 0, arr2 = new Array(len); i < len; i++) { - arr2[i] = arr[i]; - } - - return arr2; -} - -module.exports = _arrayLikeToArray; -module.exports["default"] = module.exports, module.exports.__esModule = true; -},{}],29:[function(require,module,exports){ -var _Array$isArray = require("@babel/runtime-corejs3/core-js/array/is-array"); - -function _arrayWithHoles(arr) { - if (_Array$isArray(arr)) return arr; -} - -module.exports = _arrayWithHoles; -module.exports["default"] = module.exports, module.exports.__esModule = true; -},{"@babel/runtime-corejs3/core-js/array/is-array":24}],30:[function(require,module,exports){ -function _interopRequireDefault(obj) { - return obj && obj.__esModule ? obj : { - "default": obj - }; -} - -module.exports = _interopRequireDefault; -module.exports["default"] = module.exports, module.exports.__esModule = true; -},{}],31:[function(require,module,exports){ -var _Symbol = require("@babel/runtime-corejs3/core-js/symbol"); - -var _getIteratorMethod = require("@babel/runtime-corejs3/core-js/get-iterator-method"); - -function _iterableToArrayLimit(arr, i) { - var _i = arr == null ? null : typeof _Symbol !== "undefined" && _getIteratorMethod(arr) || arr["@@iterator"]; - - if (_i == null) return; - var _arr = []; - var _n = true; - var _d = false; - - var _s, _e; - - try { - for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"] != null) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; -} - -module.exports = _iterableToArrayLimit; -module.exports["default"] = module.exports, module.exports.__esModule = true; -},{"@babel/runtime-corejs3/core-js/get-iterator-method":25,"@babel/runtime-corejs3/core-js/symbol":27}],32:[function(require,module,exports){ -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); -} - -module.exports = _nonIterableRest; -module.exports["default"] = module.exports, module.exports.__esModule = true; -},{}],33:[function(require,module,exports){ -var arrayWithHoles = require("./arrayWithHoles.js"); - -var iterableToArrayLimit = require("./iterableToArrayLimit.js"); - -var unsupportedIterableToArray = require("./unsupportedIterableToArray.js"); - -var nonIterableRest = require("./nonIterableRest.js"); - -function _slicedToArray(arr, i) { - return arrayWithHoles(arr) || iterableToArrayLimit(arr, i) || unsupportedIterableToArray(arr, i) || nonIterableRest(); -} - -module.exports = _slicedToArray; -module.exports["default"] = module.exports, module.exports.__esModule = true; -},{"./arrayWithHoles.js":29,"./iterableToArrayLimit.js":31,"./nonIterableRest.js":32,"./unsupportedIterableToArray.js":34}],34:[function(require,module,exports){ -var _sliceInstanceProperty = require("@babel/runtime-corejs3/core-js/instance/slice"); - -var _Array$from = require("@babel/runtime-corejs3/core-js/array/from"); - -var arrayLikeToArray = require("./arrayLikeToArray.js"); - -function _unsupportedIterableToArray(o, minLen) { - var _context; - - if (!o) return; - if (typeof o === "string") return arrayLikeToArray(o, minLen); - - var n = _sliceInstanceProperty(_context = Object.prototype.toString.call(o)).call(_context, 8, -1); - - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return _Array$from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen); -} - -module.exports = _unsupportedIterableToArray; -module.exports["default"] = module.exports, module.exports.__esModule = true; -},{"./arrayLikeToArray.js":28,"@babel/runtime-corejs3/core-js/array/from":23,"@babel/runtime-corejs3/core-js/instance/slice":26}],35:[function(require,module,exports){ -require('../../modules/es.string.iterator'); -require('../../modules/es.array.from'); -var path = require('../../internals/path'); - -module.exports = path.Array.from; - -},{"../../internals/path":136,"../../modules/es.array.from":163,"../../modules/es.string.iterator":179}],36:[function(require,module,exports){ -require('../../modules/es.array.is-array'); -var path = require('../../internals/path'); - -module.exports = path.Array.isArray; - -},{"../../internals/path":136,"../../modules/es.array.is-array":165}],37:[function(require,module,exports){ -require('../../../modules/es.array.concat'); -var entryVirtual = require('../../../internals/entry-virtual'); - -module.exports = entryVirtual('Array').concat; - -},{"../../../internals/entry-virtual":95,"../../../modules/es.array.concat":161}],38:[function(require,module,exports){ -require('../../../modules/es.array.for-each'); -var entryVirtual = require('../../../internals/entry-virtual'); - -module.exports = entryVirtual('Array').forEach; - -},{"../../../internals/entry-virtual":95,"../../../modules/es.array.for-each":162}],39:[function(require,module,exports){ -require('../../../modules/es.array.index-of'); -var entryVirtual = require('../../../internals/entry-virtual'); - -module.exports = entryVirtual('Array').indexOf; - -},{"../../../internals/entry-virtual":95,"../../../modules/es.array.index-of":164}],40:[function(require,module,exports){ -require('../../../modules/es.array.map'); -var entryVirtual = require('../../../internals/entry-virtual'); - -module.exports = entryVirtual('Array').map; - -},{"../../../internals/entry-virtual":95,"../../../modules/es.array.map":167}],41:[function(require,module,exports){ -require('../../../modules/es.array.reduce'); -var entryVirtual = require('../../../internals/entry-virtual'); - -module.exports = entryVirtual('Array').reduce; - -},{"../../../internals/entry-virtual":95,"../../../modules/es.array.reduce":168}],42:[function(require,module,exports){ -require('../../../modules/es.array.slice'); -var entryVirtual = require('../../../internals/entry-virtual'); - -module.exports = entryVirtual('Array').slice; - -},{"../../../internals/entry-virtual":95,"../../../modules/es.array.slice":169}],43:[function(require,module,exports){ -require('../../../modules/es.array.sort'); -var entryVirtual = require('../../../internals/entry-virtual'); - -module.exports = entryVirtual('Array').sort; - -},{"../../../internals/entry-virtual":95,"../../../modules/es.array.sort":170}],44:[function(require,module,exports){ -var concat = require('../array/virtual/concat'); - -var ArrayPrototype = Array.prototype; - -module.exports = function (it) { - var own = it.concat; - return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.concat) ? concat : own; -}; - -},{"../array/virtual/concat":37}],45:[function(require,module,exports){ -var flags = require('../regexp/flags'); - -var RegExpPrototype = RegExp.prototype; - -module.exports = function (it) { - return (it === RegExpPrototype || it instanceof RegExp) && !('flags' in it) ? flags(it) : it.flags; -}; - -},{"../regexp/flags":54}],46:[function(require,module,exports){ -var indexOf = require('../array/virtual/index-of'); - -var ArrayPrototype = Array.prototype; - -module.exports = function (it) { - var own = it.indexOf; - return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.indexOf) ? indexOf : own; -}; - -},{"../array/virtual/index-of":39}],47:[function(require,module,exports){ -var map = require('../array/virtual/map'); - -var ArrayPrototype = Array.prototype; - -module.exports = function (it) { - var own = it.map; - return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.map) ? map : own; -}; - -},{"../array/virtual/map":40}],48:[function(require,module,exports){ -var reduce = require('../array/virtual/reduce'); - -var ArrayPrototype = Array.prototype; - -module.exports = function (it) { - var own = it.reduce; - return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.reduce) ? reduce : own; -}; - -},{"../array/virtual/reduce":41}],49:[function(require,module,exports){ -var slice = require('../array/virtual/slice'); - -var ArrayPrototype = Array.prototype; - -module.exports = function (it) { - var own = it.slice; - return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.slice) ? slice : own; -}; - -},{"../array/virtual/slice":42}],50:[function(require,module,exports){ -var sort = require('../array/virtual/sort'); - -var ArrayPrototype = Array.prototype; - -module.exports = function (it) { - var own = it.sort; - return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.sort) ? sort : own; -}; - -},{"../array/virtual/sort":43}],51:[function(require,module,exports){ -require('../../modules/es.object.create'); -var path = require('../../internals/path'); - -var Object = path.Object; - -module.exports = function create(P, D) { - return Object.create(P, D); -}; - -},{"../../internals/path":136,"../../modules/es.object.create":173}],52:[function(require,module,exports){ -require('../../modules/es.object.define-property'); -var path = require('../../internals/path'); - -var Object = path.Object; - -var defineProperty = module.exports = function defineProperty(it, key, desc) { - return Object.defineProperty(it, key, desc); -}; - -if (Object.defineProperty.sham) defineProperty.sham = true; - -},{"../../internals/path":136,"../../modules/es.object.define-property":174}],53:[function(require,module,exports){ -require('../modules/es.parse-int'); -var path = require('../internals/path'); - -module.exports = path.parseInt; - -},{"../internals/path":136,"../modules/es.parse-int":176}],54:[function(require,module,exports){ -require('../../modules/es.regexp.flags'); -var flags = require('../../internals/regexp-flags'); - -module.exports = function (it) { - return flags.call(it); -}; - -},{"../../internals/regexp-flags":138,"../../modules/es.regexp.flags":178}],55:[function(require,module,exports){ -require('../../modules/es.array.concat'); -require('../../modules/es.object.to-string'); -require('../../modules/es.symbol'); -require('../../modules/es.symbol.async-iterator'); -require('../../modules/es.symbol.description'); -require('../../modules/es.symbol.has-instance'); -require('../../modules/es.symbol.is-concat-spreadable'); -require('../../modules/es.symbol.iterator'); -require('../../modules/es.symbol.match'); -require('../../modules/es.symbol.match-all'); -require('../../modules/es.symbol.replace'); -require('../../modules/es.symbol.search'); -require('../../modules/es.symbol.species'); -require('../../modules/es.symbol.split'); -require('../../modules/es.symbol.to-primitive'); -require('../../modules/es.symbol.to-string-tag'); -require('../../modules/es.symbol.unscopables'); -require('../../modules/es.json.to-string-tag'); -require('../../modules/es.math.to-string-tag'); -require('../../modules/es.reflect.to-string-tag'); -var path = require('../../internals/path'); - -module.exports = path.Symbol; - -},{"../../internals/path":136,"../../modules/es.array.concat":161,"../../modules/es.json.to-string-tag":171,"../../modules/es.math.to-string-tag":172,"../../modules/es.object.to-string":175,"../../modules/es.reflect.to-string-tag":177,"../../modules/es.symbol":185,"../../modules/es.symbol.async-iterator":180,"../../modules/es.symbol.description":181,"../../modules/es.symbol.has-instance":182,"../../modules/es.symbol.is-concat-spreadable":183,"../../modules/es.symbol.iterator":184,"../../modules/es.symbol.match":187,"../../modules/es.symbol.match-all":186,"../../modules/es.symbol.replace":188,"../../modules/es.symbol.search":189,"../../modules/es.symbol.species":190,"../../modules/es.symbol.split":191,"../../modules/es.symbol.to-primitive":192,"../../modules/es.symbol.to-string-tag":193,"../../modules/es.symbol.unscopables":194}],56:[function(require,module,exports){ -var parent = require('../../es/array/from'); - -module.exports = parent; - -},{"../../es/array/from":35}],57:[function(require,module,exports){ -var parent = require('../../es/array/is-array'); - -module.exports = parent; - -},{"../../es/array/is-array":36}],58:[function(require,module,exports){ -require('../modules/es.array.iterator'); -require('../modules/es.string.iterator'); -require('../modules/web.dom-collections.iterator'); -var getIteratorMethod = require('../internals/get-iterator-method'); - -module.exports = getIteratorMethod; - -},{"../internals/get-iterator-method":101,"../modules/es.array.iterator":166,"../modules/es.string.iterator":179,"../modules/web.dom-collections.iterator":202}],59:[function(require,module,exports){ -var parent = require('../../es/instance/slice'); - -module.exports = parent; - -},{"../../es/instance/slice":49}],60:[function(require,module,exports){ -var parent = require('../../es/symbol'); -require('../../modules/esnext.symbol.async-dispose'); -require('../../modules/esnext.symbol.dispose'); -require('../../modules/esnext.symbol.matcher'); -require('../../modules/esnext.symbol.metadata'); -require('../../modules/esnext.symbol.observable'); -// TODO: Remove from `core-js@4` -require('../../modules/esnext.symbol.pattern-match'); -// TODO: Remove from `core-js@4` -require('../../modules/esnext.symbol.replace-all'); - -module.exports = parent; - -},{"../../es/symbol":55,"../../modules/esnext.symbol.async-dispose":195,"../../modules/esnext.symbol.dispose":196,"../../modules/esnext.symbol.matcher":197,"../../modules/esnext.symbol.metadata":198,"../../modules/esnext.symbol.observable":199,"../../modules/esnext.symbol.pattern-match":200,"../../modules/esnext.symbol.replace-all":201}],61:[function(require,module,exports){ -module.exports = function (it) { - if (typeof it != 'function') { - throw TypeError(String(it) + ' is not a function'); - } return it; -}; - -},{}],62:[function(require,module,exports){ -var isObject = require('../internals/is-object'); - -module.exports = function (it) { - if (!isObject(it) && it !== null) { - throw TypeError("Can't set " + String(it) + ' as a prototype'); - } return it; -}; - -},{"../internals/is-object":113}],63:[function(require,module,exports){ -module.exports = function () { /* empty */ }; - -},{}],64:[function(require,module,exports){ -var isObject = require('../internals/is-object'); - -module.exports = function (it) { - if (!isObject(it)) { - throw TypeError(String(it) + ' is not an object'); - } return it; -}; - -},{"../internals/is-object":113}],65:[function(require,module,exports){ -'use strict'; -var $forEach = require('../internals/array-iteration').forEach; -var arrayMethodIsStrict = require('../internals/array-method-is-strict'); - -var STRICT_METHOD = arrayMethodIsStrict('forEach'); - -// `Array.prototype.forEach` method implementation -// https://tc39.es/ecma262/#sec-array.prototype.foreach -module.exports = !STRICT_METHOD ? function forEach(callbackfn /* , thisArg */) { - return $forEach(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined); -// eslint-disable-next-line es/no-array-prototype-foreach -- safe -} : [].forEach; - -},{"../internals/array-iteration":68,"../internals/array-method-is-strict":70}],66:[function(require,module,exports){ -'use strict'; -var bind = require('../internals/function-bind-context'); -var toObject = require('../internals/to-object'); -var callWithSafeIterationClosing = require('../internals/call-with-safe-iteration-closing'); -var isArrayIteratorMethod = require('../internals/is-array-iterator-method'); -var toLength = require('../internals/to-length'); -var createProperty = require('../internals/create-property'); -var getIteratorMethod = require('../internals/get-iterator-method'); - -// `Array.from` method implementation -// https://tc39.es/ecma262/#sec-array.from -module.exports = function from(arrayLike /* , mapfn = undefined, thisArg = undefined */) { - var O = toObject(arrayLike); - var C = typeof this == 'function' ? this : Array; - var argumentsLength = arguments.length; - var mapfn = argumentsLength > 1 ? arguments[1] : undefined; - var mapping = mapfn !== undefined; - var iteratorMethod = getIteratorMethod(O); - var index = 0; - var length, result, step, iterator, next, value; - if (mapping) mapfn = bind(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2); - // if the target is not iterable or it's an array with the default iterator - use a simple case - if (iteratorMethod != undefined && !(C == Array && isArrayIteratorMethod(iteratorMethod))) { - iterator = iteratorMethod.call(O); - next = iterator.next; - result = new C(); - for (;!(step = next.call(iterator)).done; index++) { - value = mapping ? callWithSafeIterationClosing(iterator, mapfn, [step.value, index], true) : step.value; - createProperty(result, index, value); - } - } else { - length = toLength(O.length); - result = new C(length); - for (;length > index; index++) { - value = mapping ? mapfn(O[index], index) : O[index]; - createProperty(result, index, value); - } - } - result.length = index; - return result; -}; - -},{"../internals/call-with-safe-iteration-closing":75,"../internals/create-property":83,"../internals/function-bind-context":99,"../internals/get-iterator-method":101,"../internals/is-array-iterator-method":110,"../internals/to-length":150,"../internals/to-object":151}],67:[function(require,module,exports){ -var toIndexedObject = require('../internals/to-indexed-object'); -var toLength = require('../internals/to-length'); -var toAbsoluteIndex = require('../internals/to-absolute-index'); - -// `Array.prototype.{ indexOf, includes }` methods implementation -var createMethod = function (IS_INCLUDES) { - return function ($this, el, fromIndex) { - var O = toIndexedObject($this); - var length = toLength(O.length); - var index = toAbsoluteIndex(fromIndex, length); - var value; - // Array#includes uses SameValueZero equality algorithm - // eslint-disable-next-line no-self-compare -- NaN check - if (IS_INCLUDES && el != el) while (length > index) { - value = O[index++]; - // eslint-disable-next-line no-self-compare -- NaN check - if (value != value) return true; - // Array#indexOf ignores holes, Array#includes - not - } else for (;length > index; index++) { - if ((IS_INCLUDES || index in O) && O[index] === el) return IS_INCLUDES || index || 0; - } return !IS_INCLUDES && -1; - }; -}; - -module.exports = { - // `Array.prototype.includes` method - // https://tc39.es/ecma262/#sec-array.prototype.includes - includes: createMethod(true), - // `Array.prototype.indexOf` method - // https://tc39.es/ecma262/#sec-array.prototype.indexof - indexOf: createMethod(false) -}; - -},{"../internals/to-absolute-index":147,"../internals/to-indexed-object":148,"../internals/to-length":150}],68:[function(require,module,exports){ -var bind = require('../internals/function-bind-context'); -var IndexedObject = require('../internals/indexed-object'); -var toObject = require('../internals/to-object'); -var toLength = require('../internals/to-length'); -var arraySpeciesCreate = require('../internals/array-species-create'); - -var push = [].push; - -// `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterReject }` methods implementation -var createMethod = function (TYPE) { - var IS_MAP = TYPE == 1; - var IS_FILTER = TYPE == 2; - var IS_SOME = TYPE == 3; - var IS_EVERY = TYPE == 4; - var IS_FIND_INDEX = TYPE == 6; - var IS_FILTER_REJECT = TYPE == 7; - var NO_HOLES = TYPE == 5 || IS_FIND_INDEX; - return function ($this, callbackfn, that, specificCreate) { - var O = toObject($this); - var self = IndexedObject(O); - var boundFunction = bind(callbackfn, that, 3); - var length = toLength(self.length); - var index = 0; - var create = specificCreate || arraySpeciesCreate; - var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_REJECT ? create($this, 0) : undefined; - var value, result; - for (;length > index; index++) if (NO_HOLES || index in self) { - value = self[index]; - result = boundFunction(value, index, O); - if (TYPE) { - if (IS_MAP) target[index] = result; // map - else if (result) switch (TYPE) { - case 3: return true; // some - case 5: return value; // find - case 6: return index; // findIndex - case 2: push.call(target, value); // filter - } else switch (TYPE) { - case 4: return false; // every - case 7: push.call(target, value); // filterReject - } - } - } - return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target; - }; -}; - -module.exports = { - // `Array.prototype.forEach` method - // https://tc39.es/ecma262/#sec-array.prototype.foreach - forEach: createMethod(0), - // `Array.prototype.map` method - // https://tc39.es/ecma262/#sec-array.prototype.map - map: createMethod(1), - // `Array.prototype.filter` method - // https://tc39.es/ecma262/#sec-array.prototype.filter - filter: createMethod(2), - // `Array.prototype.some` method - // https://tc39.es/ecma262/#sec-array.prototype.some - some: createMethod(3), - // `Array.prototype.every` method - // https://tc39.es/ecma262/#sec-array.prototype.every - every: createMethod(4), - // `Array.prototype.find` method - // https://tc39.es/ecma262/#sec-array.prototype.find - find: createMethod(5), - // `Array.prototype.findIndex` method - // https://tc39.es/ecma262/#sec-array.prototype.findIndex - findIndex: createMethod(6), - // `Array.prototype.filterReject` method - // https://github.com/tc39/proposal-array-filtering - filterReject: createMethod(7) -}; - -},{"../internals/array-species-create":74,"../internals/function-bind-context":99,"../internals/indexed-object":107,"../internals/to-length":150,"../internals/to-object":151}],69:[function(require,module,exports){ -var fails = require('../internals/fails'); -var wellKnownSymbol = require('../internals/well-known-symbol'); -var V8_VERSION = require('../internals/engine-v8-version'); - -var SPECIES = wellKnownSymbol('species'); - -module.exports = function (METHOD_NAME) { - // We can't use this feature detection in V8 since it causes - // deoptimization and serious performance degradation - // https://github.com/zloirock/core-js/issues/677 - return V8_VERSION >= 51 || !fails(function () { - var array = []; - var constructor = array.constructor = {}; - constructor[SPECIES] = function () { - return { foo: 1 }; - }; - return array[METHOD_NAME](Boolean).foo !== 1; - }); -}; - -},{"../internals/engine-v8-version":93,"../internals/fails":98,"../internals/well-known-symbol":159}],70:[function(require,module,exports){ -'use strict'; -var fails = require('../internals/fails'); - -module.exports = function (METHOD_NAME, argument) { - var method = [][METHOD_NAME]; - return !!method && fails(function () { - // eslint-disable-next-line no-useless-call,no-throw-literal -- required for testing - method.call(null, argument || function () { throw 1; }, 1); - }); -}; - -},{"../internals/fails":98}],71:[function(require,module,exports){ -var aFunction = require('../internals/a-function'); -var toObject = require('../internals/to-object'); -var IndexedObject = require('../internals/indexed-object'); -var toLength = require('../internals/to-length'); - -// `Array.prototype.{ reduce, reduceRight }` methods implementation -var createMethod = function (IS_RIGHT) { - return function (that, callbackfn, argumentsLength, memo) { - aFunction(callbackfn); - var O = toObject(that); - var self = IndexedObject(O); - var length = toLength(O.length); - var index = IS_RIGHT ? length - 1 : 0; - var i = IS_RIGHT ? -1 : 1; - if (argumentsLength < 2) while (true) { - if (index in self) { - memo = self[index]; - index += i; - break; - } - index += i; - if (IS_RIGHT ? index < 0 : length <= index) { - throw TypeError('Reduce of empty array with no initial value'); - } - } - for (;IS_RIGHT ? index >= 0 : length > index; index += i) if (index in self) { - memo = callbackfn(memo, self[index], index, O); - } - return memo; - }; -}; - -module.exports = { - // `Array.prototype.reduce` method - // https://tc39.es/ecma262/#sec-array.prototype.reduce - left: createMethod(false), - // `Array.prototype.reduceRight` method - // https://tc39.es/ecma262/#sec-array.prototype.reduceright - right: createMethod(true) -}; - -},{"../internals/a-function":61,"../internals/indexed-object":107,"../internals/to-length":150,"../internals/to-object":151}],72:[function(require,module,exports){ -// TODO: use something more complex like timsort? -var floor = Math.floor; - -var mergeSort = function (array, comparefn) { - var length = array.length; - var middle = floor(length / 2); - return length < 8 ? insertionSort(array, comparefn) : merge( - mergeSort(array.slice(0, middle), comparefn), - mergeSort(array.slice(middle), comparefn), - comparefn - ); -}; - -var insertionSort = function (array, comparefn) { - var length = array.length; - var i = 1; - var element, j; - - while (i < length) { - j = i; - element = array[i]; - while (j && comparefn(array[j - 1], element) > 0) { - array[j] = array[--j]; - } - if (j !== i++) array[j] = element; - } return array; -}; - -var merge = function (left, right, comparefn) { - var llength = left.length; - var rlength = right.length; - var lindex = 0; - var rindex = 0; - var result = []; - - while (lindex < llength || rindex < rlength) { - if (lindex < llength && rindex < rlength) { - result.push(comparefn(left[lindex], right[rindex]) <= 0 ? left[lindex++] : right[rindex++]); - } else { - result.push(lindex < llength ? left[lindex++] : right[rindex++]); - } - } return result; -}; - -module.exports = mergeSort; - -},{}],73:[function(require,module,exports){ -var isObject = require('../internals/is-object'); -var isArray = require('../internals/is-array'); -var wellKnownSymbol = require('../internals/well-known-symbol'); - -var SPECIES = wellKnownSymbol('species'); - -// a part of `ArraySpeciesCreate` abstract operation -// https://tc39.es/ecma262/#sec-arrayspeciescreate -module.exports = function (originalArray) { - var C; - if (isArray(originalArray)) { - C = originalArray.constructor; - // cross-realm fallback - if (typeof C == 'function' && (C === Array || isArray(C.prototype))) C = undefined; - else if (isObject(C)) { - C = C[SPECIES]; - if (C === null) C = undefined; - } - } return C === undefined ? Array : C; -}; - -},{"../internals/is-array":111,"../internals/is-object":113,"../internals/well-known-symbol":159}],74:[function(require,module,exports){ -var arraySpeciesConstructor = require('../internals/array-species-constructor'); - -// `ArraySpeciesCreate` abstract operation -// https://tc39.es/ecma262/#sec-arrayspeciescreate -module.exports = function (originalArray, length) { - return new (arraySpeciesConstructor(originalArray))(length === 0 ? 0 : length); -}; - -},{"../internals/array-species-constructor":73}],75:[function(require,module,exports){ -var anObject = require('../internals/an-object'); -var iteratorClose = require('../internals/iterator-close'); - -// call something on iterator step with safe closing on error -module.exports = function (iterator, fn, value, ENTRIES) { - try { - return ENTRIES ? fn(anObject(value)[0], value[1]) : fn(value); - } catch (error) { - iteratorClose(iterator); - throw error; - } -}; - -},{"../internals/an-object":64,"../internals/iterator-close":116}],76:[function(require,module,exports){ -var wellKnownSymbol = require('../internals/well-known-symbol'); - -var ITERATOR = wellKnownSymbol('iterator'); -var SAFE_CLOSING = false; - -try { - var called = 0; - var iteratorWithReturn = { - next: function () { - return { done: !!called++ }; - }, - 'return': function () { - SAFE_CLOSING = true; - } - }; - iteratorWithReturn[ITERATOR] = function () { - return this; - }; - // eslint-disable-next-line es/no-array-from, no-throw-literal -- required for testing - Array.from(iteratorWithReturn, function () { throw 2; }); -} catch (error) { /* empty */ } - -module.exports = function (exec, SKIP_CLOSING) { - if (!SKIP_CLOSING && !SAFE_CLOSING) return false; - var ITERATION_SUPPORT = false; - try { - var object = {}; - object[ITERATOR] = function () { - return { - next: function () { - return { done: ITERATION_SUPPORT = true }; - } - }; - }; - exec(object); - } catch (error) { /* empty */ } - return ITERATION_SUPPORT; -}; - -},{"../internals/well-known-symbol":159}],77:[function(require,module,exports){ -var toString = {}.toString; - -module.exports = function (it) { - return toString.call(it).slice(8, -1); -}; - -},{}],78:[function(require,module,exports){ -var TO_STRING_TAG_SUPPORT = require('../internals/to-string-tag-support'); -var classofRaw = require('../internals/classof-raw'); -var wellKnownSymbol = require('../internals/well-known-symbol'); - -var TO_STRING_TAG = wellKnownSymbol('toStringTag'); -// ES3 wrong here -var CORRECT_ARGUMENTS = classofRaw(function () { return arguments; }()) == 'Arguments'; - -// fallback for IE11 Script Access Denied error -var tryGet = function (it, key) { - try { - return it[key]; - } catch (error) { /* empty */ } -}; - -// getting tag from ES6+ `Object.prototype.toString` -module.exports = TO_STRING_TAG_SUPPORT ? classofRaw : function (it) { - var O, tag, result; - return it === undefined ? 'Undefined' : it === null ? 'Null' - // @@toStringTag case - : typeof (tag = tryGet(O = Object(it), TO_STRING_TAG)) == 'string' ? tag - // builtinTag case - : CORRECT_ARGUMENTS ? classofRaw(O) - // ES3 arguments fallback - : (result = classofRaw(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : result; -}; - -},{"../internals/classof-raw":77,"../internals/to-string-tag-support":154,"../internals/well-known-symbol":159}],79:[function(require,module,exports){ -var fails = require('../internals/fails'); - -module.exports = !fails(function () { - function F() { /* empty */ } - F.prototype.constructor = null; - // eslint-disable-next-line es/no-object-getprototypeof -- required for testing - return Object.getPrototypeOf(new F()) !== F.prototype; -}); - -},{"../internals/fails":98}],80:[function(require,module,exports){ -'use strict'; -var IteratorPrototype = require('../internals/iterators-core').IteratorPrototype; -var create = require('../internals/object-create'); -var createPropertyDescriptor = require('../internals/create-property-descriptor'); -var setToStringTag = require('../internals/set-to-string-tag'); -var Iterators = require('../internals/iterators'); - -var returnThis = function () { return this; }; - -module.exports = function (IteratorConstructor, NAME, next) { - var TO_STRING_TAG = NAME + ' Iterator'; - IteratorConstructor.prototype = create(IteratorPrototype, { next: createPropertyDescriptor(1, next) }); - setToStringTag(IteratorConstructor, TO_STRING_TAG, false, true); - Iterators[TO_STRING_TAG] = returnThis; - return IteratorConstructor; -}; - -},{"../internals/create-property-descriptor":82,"../internals/iterators":118,"../internals/iterators-core":117,"../internals/object-create":122,"../internals/set-to-string-tag":141}],81:[function(require,module,exports){ -var DESCRIPTORS = require('../internals/descriptors'); -var definePropertyModule = require('../internals/object-define-property'); -var createPropertyDescriptor = require('../internals/create-property-descriptor'); - -module.exports = DESCRIPTORS ? function (object, key, value) { - return definePropertyModule.f(object, key, createPropertyDescriptor(1, value)); -} : function (object, key, value) { - object[key] = value; - return object; -}; - -},{"../internals/create-property-descriptor":82,"../internals/descriptors":86,"../internals/object-define-property":124}],82:[function(require,module,exports){ -module.exports = function (bitmap, value) { - return { - enumerable: !(bitmap & 1), - configurable: !(bitmap & 2), - writable: !(bitmap & 4), - value: value - }; -}; - -},{}],83:[function(require,module,exports){ -'use strict'; -var toPropertyKey = require('../internals/to-property-key'); -var definePropertyModule = require('../internals/object-define-property'); -var createPropertyDescriptor = require('../internals/create-property-descriptor'); - -module.exports = function (object, key, value) { - var propertyKey = toPropertyKey(key); - if (propertyKey in object) definePropertyModule.f(object, propertyKey, createPropertyDescriptor(0, value)); - else object[propertyKey] = value; -}; - -},{"../internals/create-property-descriptor":82,"../internals/object-define-property":124,"../internals/to-property-key":153}],84:[function(require,module,exports){ -'use strict'; -var $ = require('../internals/export'); -var createIteratorConstructor = require('../internals/create-iterator-constructor'); -var getPrototypeOf = require('../internals/object-get-prototype-of'); -var setPrototypeOf = require('../internals/object-set-prototype-of'); -var setToStringTag = require('../internals/set-to-string-tag'); -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var redefine = require('../internals/redefine'); -var wellKnownSymbol = require('../internals/well-known-symbol'); -var IS_PURE = require('../internals/is-pure'); -var Iterators = require('../internals/iterators'); -var IteratorsCore = require('../internals/iterators-core'); - -var IteratorPrototype = IteratorsCore.IteratorPrototype; -var BUGGY_SAFARI_ITERATORS = IteratorsCore.BUGGY_SAFARI_ITERATORS; -var ITERATOR = wellKnownSymbol('iterator'); -var KEYS = 'keys'; -var VALUES = 'values'; -var ENTRIES = 'entries'; - -var returnThis = function () { return this; }; - -module.exports = function (Iterable, NAME, IteratorConstructor, next, DEFAULT, IS_SET, FORCED) { - createIteratorConstructor(IteratorConstructor, NAME, next); - - var getIterationMethod = function (KIND) { - if (KIND === DEFAULT && defaultIterator) return defaultIterator; - if (!BUGGY_SAFARI_ITERATORS && KIND in IterablePrototype) return IterablePrototype[KIND]; - switch (KIND) { - case KEYS: return function keys() { return new IteratorConstructor(this, KIND); }; - case VALUES: return function values() { return new IteratorConstructor(this, KIND); }; - case ENTRIES: return function entries() { return new IteratorConstructor(this, KIND); }; - } return function () { return new IteratorConstructor(this); }; - }; - - var TO_STRING_TAG = NAME + ' Iterator'; - var INCORRECT_VALUES_NAME = false; - var IterablePrototype = Iterable.prototype; - var nativeIterator = IterablePrototype[ITERATOR] - || IterablePrototype['@@iterator'] - || DEFAULT && IterablePrototype[DEFAULT]; - var defaultIterator = !BUGGY_SAFARI_ITERATORS && nativeIterator || getIterationMethod(DEFAULT); - var anyNativeIterator = NAME == 'Array' ? IterablePrototype.entries || nativeIterator : nativeIterator; - var CurrentIteratorPrototype, methods, KEY; - - // fix native - if (anyNativeIterator) { - CurrentIteratorPrototype = getPrototypeOf(anyNativeIterator.call(new Iterable())); - if (IteratorPrototype !== Object.prototype && CurrentIteratorPrototype.next) { - if (!IS_PURE && getPrototypeOf(CurrentIteratorPrototype) !== IteratorPrototype) { - if (setPrototypeOf) { - setPrototypeOf(CurrentIteratorPrototype, IteratorPrototype); - } else if (typeof CurrentIteratorPrototype[ITERATOR] != 'function') { - createNonEnumerableProperty(CurrentIteratorPrototype, ITERATOR, returnThis); - } - } - // Set @@toStringTag to native iterators - setToStringTag(CurrentIteratorPrototype, TO_STRING_TAG, true, true); - if (IS_PURE) Iterators[TO_STRING_TAG] = returnThis; - } - } - - // fix Array.prototype.{ values, @@iterator }.name in V8 / FF - if (DEFAULT == VALUES && nativeIterator && nativeIterator.name !== VALUES) { - INCORRECT_VALUES_NAME = true; - defaultIterator = function values() { return nativeIterator.call(this); }; - } - - // define iterator - if ((!IS_PURE || FORCED) && IterablePrototype[ITERATOR] !== defaultIterator) { - createNonEnumerableProperty(IterablePrototype, ITERATOR, defaultIterator); - } - Iterators[NAME] = defaultIterator; - - // export additional methods - if (DEFAULT) { - methods = { - values: getIterationMethod(VALUES), - keys: IS_SET ? defaultIterator : getIterationMethod(KEYS), - entries: getIterationMethod(ENTRIES) - }; - if (FORCED) for (KEY in methods) { - if (BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME || !(KEY in IterablePrototype)) { - redefine(IterablePrototype, KEY, methods[KEY]); - } - } else $({ target: NAME, proto: true, forced: BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME }, methods); - } - - return methods; -}; - -},{"../internals/create-iterator-constructor":80,"../internals/create-non-enumerable-property":81,"../internals/export":97,"../internals/is-pure":114,"../internals/iterators":118,"../internals/iterators-core":117,"../internals/object-get-prototype-of":129,"../internals/object-set-prototype-of":133,"../internals/redefine":137,"../internals/set-to-string-tag":141,"../internals/well-known-symbol":159}],85:[function(require,module,exports){ -var path = require('../internals/path'); -var has = require('../internals/has'); -var wrappedWellKnownSymbolModule = require('../internals/well-known-symbol-wrapped'); -var defineProperty = require('../internals/object-define-property').f; - -module.exports = function (NAME) { - var Symbol = path.Symbol || (path.Symbol = {}); - if (!has(Symbol, NAME)) defineProperty(Symbol, NAME, { - value: wrappedWellKnownSymbolModule.f(NAME) - }); -}; - -},{"../internals/has":103,"../internals/object-define-property":124,"../internals/path":136,"../internals/well-known-symbol-wrapped":158}],86:[function(require,module,exports){ -var fails = require('../internals/fails'); - -// Detect IE8's incomplete defineProperty implementation -module.exports = !fails(function () { - // eslint-disable-next-line es/no-object-defineproperty -- required for testing - return Object.defineProperty({}, 1, { get: function () { return 7; } })[1] != 7; -}); - -},{"../internals/fails":98}],87:[function(require,module,exports){ -var global = require('../internals/global'); -var isObject = require('../internals/is-object'); - -var document = global.document; -// typeof document.createElement is 'object' in old IE -var EXISTS = isObject(document) && isObject(document.createElement); - -module.exports = function (it) { - return EXISTS ? document.createElement(it) : {}; -}; - -},{"../internals/global":102,"../internals/is-object":113}],88:[function(require,module,exports){ -// iterable DOM collections -// flag - `iterable` interface - 'entries', 'keys', 'values', 'forEach' methods -module.exports = { - CSSRuleList: 0, - CSSStyleDeclaration: 0, - CSSValueList: 0, - ClientRectList: 0, - DOMRectList: 0, - DOMStringList: 0, - DOMTokenList: 1, - DataTransferItemList: 0, - FileList: 0, - HTMLAllCollection: 0, - HTMLCollection: 0, - HTMLFormElement: 0, - HTMLSelectElement: 0, - MediaList: 0, - MimeTypeArray: 0, - NamedNodeMap: 0, - NodeList: 1, - PaintRequestList: 0, - Plugin: 0, - PluginArray: 0, - SVGLengthList: 0, - SVGNumberList: 0, - SVGPathSegList: 0, - SVGPointList: 0, - SVGStringList: 0, - SVGTransformList: 0, - SourceBufferList: 0, - StyleSheetList: 0, - TextTrackCueList: 0, - TextTrackList: 0, - TouchList: 0 -}; - -},{}],89:[function(require,module,exports){ -var userAgent = require('../internals/engine-user-agent'); - -var firefox = userAgent.match(/firefox\/(\d+)/i); - -module.exports = !!firefox && +firefox[1]; - -},{"../internals/engine-user-agent":92}],90:[function(require,module,exports){ -var UA = require('../internals/engine-user-agent'); - -module.exports = /MSIE|Trident/.test(UA); - -},{"../internals/engine-user-agent":92}],91:[function(require,module,exports){ -var classof = require('../internals/classof-raw'); -var global = require('../internals/global'); - -module.exports = classof(global.process) == 'process'; - -},{"../internals/classof-raw":77,"../internals/global":102}],92:[function(require,module,exports){ -var getBuiltIn = require('../internals/get-built-in'); - -module.exports = getBuiltIn('navigator', 'userAgent') || ''; - -},{"../internals/get-built-in":100}],93:[function(require,module,exports){ -var global = require('../internals/global'); -var userAgent = require('../internals/engine-user-agent'); - -var process = global.process; -var Deno = global.Deno; -var versions = process && process.versions || Deno && Deno.version; -var v8 = versions && versions.v8; -var match, version; - -if (v8) { - match = v8.split('.'); - version = match[0] < 4 ? 1 : match[0] + match[1]; -} else if (userAgent) { - match = userAgent.match(/Edge\/(\d+)/); - if (!match || match[1] >= 74) { - match = userAgent.match(/Chrome\/(\d+)/); - if (match) version = match[1]; - } -} - -module.exports = version && +version; - -},{"../internals/engine-user-agent":92,"../internals/global":102}],94:[function(require,module,exports){ -var userAgent = require('../internals/engine-user-agent'); - -var webkit = userAgent.match(/AppleWebKit\/(\d+)\./); - -module.exports = !!webkit && +webkit[1]; - -},{"../internals/engine-user-agent":92}],95:[function(require,module,exports){ -var path = require('../internals/path'); - -module.exports = function (CONSTRUCTOR) { - return path[CONSTRUCTOR + 'Prototype']; -}; - -},{"../internals/path":136}],96:[function(require,module,exports){ -// IE8- don't enum bug keys -module.exports = [ - 'constructor', - 'hasOwnProperty', - 'isPrototypeOf', - 'propertyIsEnumerable', - 'toLocaleString', - 'toString', - 'valueOf' -]; - -},{}],97:[function(require,module,exports){ -'use strict'; -var global = require('../internals/global'); -var getOwnPropertyDescriptor = require('../internals/object-get-own-property-descriptor').f; -var isForced = require('../internals/is-forced'); -var path = require('../internals/path'); -var bind = require('../internals/function-bind-context'); -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var has = require('../internals/has'); - -var wrapConstructor = function (NativeConstructor) { - var Wrapper = function (a, b, c) { - if (this instanceof NativeConstructor) { - switch (arguments.length) { - case 0: return new NativeConstructor(); - case 1: return new NativeConstructor(a); - case 2: return new NativeConstructor(a, b); - } return new NativeConstructor(a, b, c); - } return NativeConstructor.apply(this, arguments); - }; - Wrapper.prototype = NativeConstructor.prototype; - return Wrapper; -}; - -/* - options.target - name of the target object - options.global - target is the global object - options.stat - export as static methods of target - options.proto - export as prototype methods of target - options.real - real prototype method for the `pure` version - options.forced - export even if the native feature is available - options.bind - bind methods to the target, required for the `pure` version - options.wrap - wrap constructors to preventing global pollution, required for the `pure` version - options.unsafe - use the simple assignment of property instead of delete + defineProperty - options.sham - add a flag to not completely full polyfills - options.enumerable - export as enumerable property - options.noTargetGet - prevent calling a getter on target -*/ -module.exports = function (options, source) { - var TARGET = options.target; - var GLOBAL = options.global; - var STATIC = options.stat; - var PROTO = options.proto; - - var nativeSource = GLOBAL ? global : STATIC ? global[TARGET] : (global[TARGET] || {}).prototype; - - var target = GLOBAL ? path : path[TARGET] || (path[TARGET] = {}); - var targetPrototype = target.prototype; - - var FORCED, USE_NATIVE, VIRTUAL_PROTOTYPE; - var key, sourceProperty, targetProperty, nativeProperty, resultProperty, descriptor; - - for (key in source) { - FORCED = isForced(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced); - // contains in native - USE_NATIVE = !FORCED && nativeSource && has(nativeSource, key); - - targetProperty = target[key]; - - if (USE_NATIVE) if (options.noTargetGet) { - descriptor = getOwnPropertyDescriptor(nativeSource, key); - nativeProperty = descriptor && descriptor.value; - } else nativeProperty = nativeSource[key]; - - // export native or implementation - sourceProperty = (USE_NATIVE && nativeProperty) ? nativeProperty : source[key]; - - if (USE_NATIVE && typeof targetProperty === typeof sourceProperty) continue; - - // bind timers to global for call from export context - if (options.bind && USE_NATIVE) resultProperty = bind(sourceProperty, global); - // wrap global constructors for prevent changs in this version - else if (options.wrap && USE_NATIVE) resultProperty = wrapConstructor(sourceProperty); - // make static versions for prototype methods - else if (PROTO && typeof sourceProperty == 'function') resultProperty = bind(Function.call, sourceProperty); - // default case - else resultProperty = sourceProperty; - - // add a flag to not completely full polyfills - if (options.sham || (sourceProperty && sourceProperty.sham) || (targetProperty && targetProperty.sham)) { - createNonEnumerableProperty(resultProperty, 'sham', true); - } - - target[key] = resultProperty; - - if (PROTO) { - VIRTUAL_PROTOTYPE = TARGET + 'Prototype'; - if (!has(path, VIRTUAL_PROTOTYPE)) { - createNonEnumerableProperty(path, VIRTUAL_PROTOTYPE, {}); - } - // export virtual prototype methods - path[VIRTUAL_PROTOTYPE][key] = sourceProperty; - // export real prototype methods - if (options.real && targetPrototype && !targetPrototype[key]) { - createNonEnumerableProperty(targetPrototype, key, sourceProperty); - } - } - } -}; - -},{"../internals/create-non-enumerable-property":81,"../internals/function-bind-context":99,"../internals/global":102,"../internals/has":103,"../internals/is-forced":112,"../internals/object-get-own-property-descriptor":125,"../internals/path":136}],98:[function(require,module,exports){ -module.exports = function (exec) { - try { - return !!exec(); - } catch (error) { - return true; - } -}; - -},{}],99:[function(require,module,exports){ -var aFunction = require('../internals/a-function'); - -// optional / simple context binding -module.exports = function (fn, that, length) { - aFunction(fn); - if (that === undefined) return fn; - switch (length) { - case 0: return function () { - return fn.call(that); - }; - case 1: return function (a) { - return fn.call(that, a); - }; - case 2: return function (a, b) { - return fn.call(that, a, b); - }; - case 3: return function (a, b, c) { - return fn.call(that, a, b, c); - }; - } - return function (/* ...args */) { - return fn.apply(that, arguments); - }; -}; - -},{"../internals/a-function":61}],100:[function(require,module,exports){ -var path = require('../internals/path'); -var global = require('../internals/global'); - -var aFunction = function (variable) { - return typeof variable == 'function' ? variable : undefined; -}; - -module.exports = function (namespace, method) { - return arguments.length < 2 ? aFunction(path[namespace]) || aFunction(global[namespace]) - : path[namespace] && path[namespace][method] || global[namespace] && global[namespace][method]; -}; - -},{"../internals/global":102,"../internals/path":136}],101:[function(require,module,exports){ -var classof = require('../internals/classof'); -var Iterators = require('../internals/iterators'); -var wellKnownSymbol = require('../internals/well-known-symbol'); - -var ITERATOR = wellKnownSymbol('iterator'); - -module.exports = function (it) { - if (it != undefined) return it[ITERATOR] - || it['@@iterator'] - || Iterators[classof(it)]; -}; - -},{"../internals/classof":78,"../internals/iterators":118,"../internals/well-known-symbol":159}],102:[function(require,module,exports){ -(function (global){(function (){ -var check = function (it) { - return it && it.Math == Math && it; -}; - -// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 -module.exports = - // eslint-disable-next-line es/no-global-this -- safe - check(typeof globalThis == 'object' && globalThis) || - check(typeof window == 'object' && window) || - // eslint-disable-next-line no-restricted-globals -- safe - check(typeof self == 'object' && self) || - check(typeof global == 'object' && global) || - // eslint-disable-next-line no-new-func -- fallback - (function () { return this; })() || Function('return this')(); - -}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],103:[function(require,module,exports){ -var toObject = require('../internals/to-object'); - -var hasOwnProperty = {}.hasOwnProperty; - -module.exports = Object.hasOwn || function hasOwn(it, key) { - return hasOwnProperty.call(toObject(it), key); -}; - -},{"../internals/to-object":151}],104:[function(require,module,exports){ -module.exports = {}; - -},{}],105:[function(require,module,exports){ -var getBuiltIn = require('../internals/get-built-in'); - -module.exports = getBuiltIn('document', 'documentElement'); - -},{"../internals/get-built-in":100}],106:[function(require,module,exports){ -var DESCRIPTORS = require('../internals/descriptors'); -var fails = require('../internals/fails'); -var createElement = require('../internals/document-create-element'); - -// Thank's IE8 for his funny defineProperty -module.exports = !DESCRIPTORS && !fails(function () { - // eslint-disable-next-line es/no-object-defineproperty -- requied for testing - return Object.defineProperty(createElement('div'), 'a', { - get: function () { return 7; } - }).a != 7; -}); - -},{"../internals/descriptors":86,"../internals/document-create-element":87,"../internals/fails":98}],107:[function(require,module,exports){ -var fails = require('../internals/fails'); -var classof = require('../internals/classof-raw'); - -var split = ''.split; - -// fallback for non-array-like ES3 and non-enumerable old V8 strings -module.exports = fails(function () { - // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346 - // eslint-disable-next-line no-prototype-builtins -- safe - return !Object('z').propertyIsEnumerable(0); -}) ? function (it) { - return classof(it) == 'String' ? split.call(it, '') : Object(it); -} : Object; - -},{"../internals/classof-raw":77,"../internals/fails":98}],108:[function(require,module,exports){ -var store = require('../internals/shared-store'); - -var functionToString = Function.toString; - -// this helper broken in `core-js@3.4.1-3.4.4`, so we can't use `shared` helper -if (typeof store.inspectSource != 'function') { - store.inspectSource = function (it) { - return functionToString.call(it); - }; -} - -module.exports = store.inspectSource; - -},{"../internals/shared-store":143}],109:[function(require,module,exports){ -var NATIVE_WEAK_MAP = require('../internals/native-weak-map'); -var global = require('../internals/global'); -var isObject = require('../internals/is-object'); -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var objectHas = require('../internals/has'); -var shared = require('../internals/shared-store'); -var sharedKey = require('../internals/shared-key'); -var hiddenKeys = require('../internals/hidden-keys'); - -var OBJECT_ALREADY_INITIALIZED = 'Object already initialized'; -var WeakMap = global.WeakMap; -var set, get, has; - -var enforce = function (it) { - return has(it) ? get(it) : set(it, {}); -}; - -var getterFor = function (TYPE) { - return function (it) { - var state; - if (!isObject(it) || (state = get(it)).type !== TYPE) { - throw TypeError('Incompatible receiver, ' + TYPE + ' required'); - } return state; - }; -}; - -if (NATIVE_WEAK_MAP || shared.state) { - var store = shared.state || (shared.state = new WeakMap()); - var wmget = store.get; - var wmhas = store.has; - var wmset = store.set; - set = function (it, metadata) { - if (wmhas.call(store, it)) throw new TypeError(OBJECT_ALREADY_INITIALIZED); - metadata.facade = it; - wmset.call(store, it, metadata); - return metadata; - }; - get = function (it) { - return wmget.call(store, it) || {}; - }; - has = function (it) { - return wmhas.call(store, it); - }; -} else { - var STATE = sharedKey('state'); - hiddenKeys[STATE] = true; - set = function (it, metadata) { - if (objectHas(it, STATE)) throw new TypeError(OBJECT_ALREADY_INITIALIZED); - metadata.facade = it; - createNonEnumerableProperty(it, STATE, metadata); - return metadata; - }; - get = function (it) { - return objectHas(it, STATE) ? it[STATE] : {}; - }; - has = function (it) { - return objectHas(it, STATE); - }; -} - -module.exports = { - set: set, - get: get, - has: has, - enforce: enforce, - getterFor: getterFor -}; - -},{"../internals/create-non-enumerable-property":81,"../internals/global":102,"../internals/has":103,"../internals/hidden-keys":104,"../internals/is-object":113,"../internals/native-weak-map":120,"../internals/shared-key":142,"../internals/shared-store":143}],110:[function(require,module,exports){ -var wellKnownSymbol = require('../internals/well-known-symbol'); -var Iterators = require('../internals/iterators'); - -var ITERATOR = wellKnownSymbol('iterator'); -var ArrayPrototype = Array.prototype; - -// check on default Array iterator -module.exports = function (it) { - return it !== undefined && (Iterators.Array === it || ArrayPrototype[ITERATOR] === it); -}; - -},{"../internals/iterators":118,"../internals/well-known-symbol":159}],111:[function(require,module,exports){ -var classof = require('../internals/classof-raw'); - -// `IsArray` abstract operation -// https://tc39.es/ecma262/#sec-isarray -// eslint-disable-next-line es/no-array-isarray -- safe -module.exports = Array.isArray || function isArray(arg) { - return classof(arg) == 'Array'; -}; - -},{"../internals/classof-raw":77}],112:[function(require,module,exports){ -var fails = require('../internals/fails'); - -var replacement = /#|\.prototype\./; - -var isForced = function (feature, detection) { - var value = data[normalize(feature)]; - return value == POLYFILL ? true - : value == NATIVE ? false - : typeof detection == 'function' ? fails(detection) - : !!detection; -}; - -var normalize = isForced.normalize = function (string) { - return String(string).replace(replacement, '.').toLowerCase(); -}; - -var data = isForced.data = {}; -var NATIVE = isForced.NATIVE = 'N'; -var POLYFILL = isForced.POLYFILL = 'P'; - -module.exports = isForced; - -},{"../internals/fails":98}],113:[function(require,module,exports){ -module.exports = function (it) { - return typeof it === 'object' ? it !== null : typeof it === 'function'; -}; - -},{}],114:[function(require,module,exports){ -module.exports = true; - -},{}],115:[function(require,module,exports){ -var getBuiltIn = require('../internals/get-built-in'); -var USE_SYMBOL_AS_UID = require('../internals/use-symbol-as-uid'); - -module.exports = USE_SYMBOL_AS_UID ? function (it) { - return typeof it == 'symbol'; -} : function (it) { - var $Symbol = getBuiltIn('Symbol'); - return typeof $Symbol == 'function' && Object(it) instanceof $Symbol; -}; - -},{"../internals/get-built-in":100,"../internals/use-symbol-as-uid":157}],116:[function(require,module,exports){ -var anObject = require('../internals/an-object'); - -module.exports = function (iterator) { - var returnMethod = iterator['return']; - if (returnMethod !== undefined) { - return anObject(returnMethod.call(iterator)).value; - } -}; - -},{"../internals/an-object":64}],117:[function(require,module,exports){ -'use strict'; -var fails = require('../internals/fails'); -var getPrototypeOf = require('../internals/object-get-prototype-of'); -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var has = require('../internals/has'); -var wellKnownSymbol = require('../internals/well-known-symbol'); -var IS_PURE = require('../internals/is-pure'); - -var ITERATOR = wellKnownSymbol('iterator'); -var BUGGY_SAFARI_ITERATORS = false; - -var returnThis = function () { return this; }; - -// `%IteratorPrototype%` object -// https://tc39.es/ecma262/#sec-%iteratorprototype%-object -var IteratorPrototype, PrototypeOfArrayIteratorPrototype, arrayIterator; - -/* eslint-disable es/no-array-prototype-keys -- safe */ -if ([].keys) { - arrayIterator = [].keys(); - // Safari 8 has buggy iterators w/o `next` - if (!('next' in arrayIterator)) BUGGY_SAFARI_ITERATORS = true; - else { - PrototypeOfArrayIteratorPrototype = getPrototypeOf(getPrototypeOf(arrayIterator)); - if (PrototypeOfArrayIteratorPrototype !== Object.prototype) IteratorPrototype = PrototypeOfArrayIteratorPrototype; - } -} - -var NEW_ITERATOR_PROTOTYPE = IteratorPrototype == undefined || fails(function () { - var test = {}; - // FF44- legacy iterators case - return IteratorPrototype[ITERATOR].call(test) !== test; -}); - -if (NEW_ITERATOR_PROTOTYPE) IteratorPrototype = {}; - -// `%IteratorPrototype%[@@iterator]()` method -// https://tc39.es/ecma262/#sec-%iteratorprototype%-@@iterator -if ((!IS_PURE || NEW_ITERATOR_PROTOTYPE) && !has(IteratorPrototype, ITERATOR)) { - createNonEnumerableProperty(IteratorPrototype, ITERATOR, returnThis); -} - -module.exports = { - IteratorPrototype: IteratorPrototype, - BUGGY_SAFARI_ITERATORS: BUGGY_SAFARI_ITERATORS -}; - -},{"../internals/create-non-enumerable-property":81,"../internals/fails":98,"../internals/has":103,"../internals/is-pure":114,"../internals/object-get-prototype-of":129,"../internals/well-known-symbol":159}],118:[function(require,module,exports){ -arguments[4][104][0].apply(exports,arguments) -},{"dup":104}],119:[function(require,module,exports){ -/* eslint-disable es/no-symbol -- required for testing */ -var V8_VERSION = require('../internals/engine-v8-version'); -var fails = require('../internals/fails'); - -// eslint-disable-next-line es/no-object-getownpropertysymbols -- required for testing -module.exports = !!Object.getOwnPropertySymbols && !fails(function () { - var symbol = Symbol(); - // Chrome 38 Symbol has incorrect toString conversion - // `get-own-property-symbols` polyfill symbols converted to object are not Symbol instances - return !String(symbol) || !(Object(symbol) instanceof Symbol) || - // Chrome 38-40 symbols are not inherited from DOM collections prototypes to instances - !Symbol.sham && V8_VERSION && V8_VERSION < 41; -}); - -},{"../internals/engine-v8-version":93,"../internals/fails":98}],120:[function(require,module,exports){ -var global = require('../internals/global'); -var inspectSource = require('../internals/inspect-source'); - -var WeakMap = global.WeakMap; - -module.exports = typeof WeakMap === 'function' && /native code/.test(inspectSource(WeakMap)); - -},{"../internals/global":102,"../internals/inspect-source":108}],121:[function(require,module,exports){ -var global = require('../internals/global'); -var toString = require('../internals/to-string'); -var trim = require('../internals/string-trim').trim; -var whitespaces = require('../internals/whitespaces'); - -var $parseInt = global.parseInt; -var hex = /^[+-]?0[Xx]/; -var FORCED = $parseInt(whitespaces + '08') !== 8 || $parseInt(whitespaces + '0x16') !== 22; - -// `parseInt` method -// https://tc39.es/ecma262/#sec-parseint-string-radix -module.exports = FORCED ? function parseInt(string, radix) { - var S = trim(toString(string)); - return $parseInt(S, (radix >>> 0) || (hex.test(S) ? 16 : 10)); -} : $parseInt; - -},{"../internals/global":102,"../internals/string-trim":146,"../internals/to-string":155,"../internals/whitespaces":160}],122:[function(require,module,exports){ -/* global ActiveXObject -- old IE, WSH */ -var anObject = require('../internals/an-object'); -var defineProperties = require('../internals/object-define-properties'); -var enumBugKeys = require('../internals/enum-bug-keys'); -var hiddenKeys = require('../internals/hidden-keys'); -var html = require('../internals/html'); -var documentCreateElement = require('../internals/document-create-element'); -var sharedKey = require('../internals/shared-key'); - -var GT = '>'; -var LT = '<'; -var PROTOTYPE = 'prototype'; -var SCRIPT = 'script'; -var IE_PROTO = sharedKey('IE_PROTO'); - -var EmptyConstructor = function () { /* empty */ }; - -var scriptTag = function (content) { - return LT + SCRIPT + GT + content + LT + '/' + SCRIPT + GT; -}; - -// Create object with fake `null` prototype: use ActiveX Object with cleared prototype -var NullProtoObjectViaActiveX = function (activeXDocument) { - activeXDocument.write(scriptTag('')); - activeXDocument.close(); - var temp = activeXDocument.parentWindow.Object; - activeXDocument = null; // avoid memory leak - return temp; -}; - -// Create object with fake `null` prototype: use iframe Object with cleared prototype -var NullProtoObjectViaIFrame = function () { - // Thrash, waste and sodomy: IE GC bug - var iframe = documentCreateElement('iframe'); - var JS = 'java' + SCRIPT + ':'; - var iframeDocument; - if (iframe.style) { - iframe.style.display = 'none'; - html.appendChild(iframe); - // https://github.com/zloirock/core-js/issues/475 - iframe.src = String(JS); - iframeDocument = iframe.contentWindow.document; - iframeDocument.open(); - iframeDocument.write(scriptTag('document.F=Object')); - iframeDocument.close(); - return iframeDocument.F; - } -}; - -// Check for document.domain and active x support -// No need to use active x approach when document.domain is not set -// see https://github.com/es-shims/es5-shim/issues/150 -// variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346 -// avoid IE GC bug -var activeXDocument; -var NullProtoObject = function () { - try { - activeXDocument = new ActiveXObject('htmlfile'); - } catch (error) { /* ignore */ } - NullProtoObject = document.domain && activeXDocument ? - NullProtoObjectViaActiveX(activeXDocument) : // old IE - NullProtoObjectViaIFrame() || - NullProtoObjectViaActiveX(activeXDocument); // WSH - var length = enumBugKeys.length; - while (length--) delete NullProtoObject[PROTOTYPE][enumBugKeys[length]]; - return NullProtoObject(); -}; - -hiddenKeys[IE_PROTO] = true; - -// `Object.create` method -// https://tc39.es/ecma262/#sec-object.create -module.exports = Object.create || function create(O, Properties) { - var result; - if (O !== null) { - EmptyConstructor[PROTOTYPE] = anObject(O); - result = new EmptyConstructor(); - EmptyConstructor[PROTOTYPE] = null; - // add "__proto__" for Object.getPrototypeOf polyfill - result[IE_PROTO] = O; - } else result = NullProtoObject(); - return Properties === undefined ? result : defineProperties(result, Properties); -}; - -},{"../internals/an-object":64,"../internals/document-create-element":87,"../internals/enum-bug-keys":96,"../internals/hidden-keys":104,"../internals/html":105,"../internals/object-define-properties":123,"../internals/shared-key":142}],123:[function(require,module,exports){ -var DESCRIPTORS = require('../internals/descriptors'); -var definePropertyModule = require('../internals/object-define-property'); -var anObject = require('../internals/an-object'); -var objectKeys = require('../internals/object-keys'); - -// `Object.defineProperties` method -// https://tc39.es/ecma262/#sec-object.defineproperties -// eslint-disable-next-line es/no-object-defineproperties -- safe -module.exports = DESCRIPTORS ? Object.defineProperties : function defineProperties(O, Properties) { - anObject(O); - var keys = objectKeys(Properties); - var length = keys.length; - var index = 0; - var key; - while (length > index) definePropertyModule.f(O, key = keys[index++], Properties[key]); - return O; -}; - -},{"../internals/an-object":64,"../internals/descriptors":86,"../internals/object-define-property":124,"../internals/object-keys":131}],124:[function(require,module,exports){ -var DESCRIPTORS = require('../internals/descriptors'); -var IE8_DOM_DEFINE = require('../internals/ie8-dom-define'); -var anObject = require('../internals/an-object'); -var toPropertyKey = require('../internals/to-property-key'); - -// eslint-disable-next-line es/no-object-defineproperty -- safe -var $defineProperty = Object.defineProperty; - -// `Object.defineProperty` method -// https://tc39.es/ecma262/#sec-object.defineproperty -exports.f = DESCRIPTORS ? $defineProperty : function defineProperty(O, P, Attributes) { - anObject(O); - P = toPropertyKey(P); - anObject(Attributes); - if (IE8_DOM_DEFINE) try { - return $defineProperty(O, P, Attributes); - } catch (error) { /* empty */ } - if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported'); - if ('value' in Attributes) O[P] = Attributes.value; - return O; -}; - -},{"../internals/an-object":64,"../internals/descriptors":86,"../internals/ie8-dom-define":106,"../internals/to-property-key":153}],125:[function(require,module,exports){ -var DESCRIPTORS = require('../internals/descriptors'); -var propertyIsEnumerableModule = require('../internals/object-property-is-enumerable'); -var createPropertyDescriptor = require('../internals/create-property-descriptor'); -var toIndexedObject = require('../internals/to-indexed-object'); -var toPropertyKey = require('../internals/to-property-key'); -var has = require('../internals/has'); -var IE8_DOM_DEFINE = require('../internals/ie8-dom-define'); - -// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe -var $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; - -// `Object.getOwnPropertyDescriptor` method -// https://tc39.es/ecma262/#sec-object.getownpropertydescriptor -exports.f = DESCRIPTORS ? $getOwnPropertyDescriptor : function getOwnPropertyDescriptor(O, P) { - O = toIndexedObject(O); - P = toPropertyKey(P); - if (IE8_DOM_DEFINE) try { - return $getOwnPropertyDescriptor(O, P); - } catch (error) { /* empty */ } - if (has(O, P)) return createPropertyDescriptor(!propertyIsEnumerableModule.f.call(O, P), O[P]); -}; - -},{"../internals/create-property-descriptor":82,"../internals/descriptors":86,"../internals/has":103,"../internals/ie8-dom-define":106,"../internals/object-property-is-enumerable":132,"../internals/to-indexed-object":148,"../internals/to-property-key":153}],126:[function(require,module,exports){ -/* eslint-disable es/no-object-getownpropertynames -- safe */ -var toIndexedObject = require('../internals/to-indexed-object'); -var $getOwnPropertyNames = require('../internals/object-get-own-property-names').f; - -var toString = {}.toString; - -var windowNames = typeof window == 'object' && window && Object.getOwnPropertyNames - ? Object.getOwnPropertyNames(window) : []; - -var getWindowNames = function (it) { - try { - return $getOwnPropertyNames(it); - } catch (error) { - return windowNames.slice(); - } -}; - -// fallback for IE11 buggy Object.getOwnPropertyNames with iframe and window -module.exports.f = function getOwnPropertyNames(it) { - return windowNames && toString.call(it) == '[object Window]' - ? getWindowNames(it) - : $getOwnPropertyNames(toIndexedObject(it)); -}; - -},{"../internals/object-get-own-property-names":127,"../internals/to-indexed-object":148}],127:[function(require,module,exports){ -var internalObjectKeys = require('../internals/object-keys-internal'); -var enumBugKeys = require('../internals/enum-bug-keys'); - -var hiddenKeys = enumBugKeys.concat('length', 'prototype'); - -// `Object.getOwnPropertyNames` method -// https://tc39.es/ecma262/#sec-object.getownpropertynames -// eslint-disable-next-line es/no-object-getownpropertynames -- safe -exports.f = Object.getOwnPropertyNames || function getOwnPropertyNames(O) { - return internalObjectKeys(O, hiddenKeys); -}; - -},{"../internals/enum-bug-keys":96,"../internals/object-keys-internal":130}],128:[function(require,module,exports){ -// eslint-disable-next-line es/no-object-getownpropertysymbols -- safe -exports.f = Object.getOwnPropertySymbols; - -},{}],129:[function(require,module,exports){ -var has = require('../internals/has'); -var toObject = require('../internals/to-object'); -var sharedKey = require('../internals/shared-key'); -var CORRECT_PROTOTYPE_GETTER = require('../internals/correct-prototype-getter'); - -var IE_PROTO = sharedKey('IE_PROTO'); -var ObjectPrototype = Object.prototype; - -// `Object.getPrototypeOf` method -// https://tc39.es/ecma262/#sec-object.getprototypeof -// eslint-disable-next-line es/no-object-getprototypeof -- safe -module.exports = CORRECT_PROTOTYPE_GETTER ? Object.getPrototypeOf : function (O) { - O = toObject(O); - if (has(O, IE_PROTO)) return O[IE_PROTO]; - if (typeof O.constructor == 'function' && O instanceof O.constructor) { - return O.constructor.prototype; - } return O instanceof Object ? ObjectPrototype : null; -}; - -},{"../internals/correct-prototype-getter":79,"../internals/has":103,"../internals/shared-key":142,"../internals/to-object":151}],130:[function(require,module,exports){ -var has = require('../internals/has'); -var toIndexedObject = require('../internals/to-indexed-object'); -var indexOf = require('../internals/array-includes').indexOf; -var hiddenKeys = require('../internals/hidden-keys'); - -module.exports = function (object, names) { - var O = toIndexedObject(object); - var i = 0; - var result = []; - var key; - for (key in O) !has(hiddenKeys, key) && has(O, key) && result.push(key); - // Don't enum bug & hidden keys - while (names.length > i) if (has(O, key = names[i++])) { - ~indexOf(result, key) || result.push(key); - } - return result; -}; - -},{"../internals/array-includes":67,"../internals/has":103,"../internals/hidden-keys":104,"../internals/to-indexed-object":148}],131:[function(require,module,exports){ -var internalObjectKeys = require('../internals/object-keys-internal'); -var enumBugKeys = require('../internals/enum-bug-keys'); - -// `Object.keys` method -// https://tc39.es/ecma262/#sec-object.keys -// eslint-disable-next-line es/no-object-keys -- safe -module.exports = Object.keys || function keys(O) { - return internalObjectKeys(O, enumBugKeys); -}; - -},{"../internals/enum-bug-keys":96,"../internals/object-keys-internal":130}],132:[function(require,module,exports){ -'use strict'; -var $propertyIsEnumerable = {}.propertyIsEnumerable; -// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe -var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; - -// Nashorn ~ JDK8 bug -var NASHORN_BUG = getOwnPropertyDescriptor && !$propertyIsEnumerable.call({ 1: 2 }, 1); - -// `Object.prototype.propertyIsEnumerable` method implementation -// https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable -exports.f = NASHORN_BUG ? function propertyIsEnumerable(V) { - var descriptor = getOwnPropertyDescriptor(this, V); - return !!descriptor && descriptor.enumerable; -} : $propertyIsEnumerable; - -},{}],133:[function(require,module,exports){ -/* eslint-disable no-proto -- safe */ -var anObject = require('../internals/an-object'); -var aPossiblePrototype = require('../internals/a-possible-prototype'); - -// `Object.setPrototypeOf` method -// https://tc39.es/ecma262/#sec-object.setprototypeof -// Works with __proto__ only. Old v8 can't work with null proto objects. -// eslint-disable-next-line es/no-object-setprototypeof -- safe -module.exports = Object.setPrototypeOf || ('__proto__' in {} ? function () { - var CORRECT_SETTER = false; - var test = {}; - var setter; - try { - // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe - setter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set; - setter.call(test, []); - CORRECT_SETTER = test instanceof Array; - } catch (error) { /* empty */ } - return function setPrototypeOf(O, proto) { - anObject(O); - aPossiblePrototype(proto); - if (CORRECT_SETTER) setter.call(O, proto); - else O.__proto__ = proto; - return O; - }; -}() : undefined); - -},{"../internals/a-possible-prototype":62,"../internals/an-object":64}],134:[function(require,module,exports){ -'use strict'; -var TO_STRING_TAG_SUPPORT = require('../internals/to-string-tag-support'); -var classof = require('../internals/classof'); - -// `Object.prototype.toString` method implementation -// https://tc39.es/ecma262/#sec-object.prototype.tostring -module.exports = TO_STRING_TAG_SUPPORT ? {}.toString : function toString() { - return '[object ' + classof(this) + ']'; -}; - -},{"../internals/classof":78,"../internals/to-string-tag-support":154}],135:[function(require,module,exports){ -var isObject = require('../internals/is-object'); - -// `OrdinaryToPrimitive` abstract operation -// https://tc39.es/ecma262/#sec-ordinarytoprimitive -module.exports = function (input, pref) { - var fn, val; - if (pref === 'string' && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val; - if (typeof (fn = input.valueOf) == 'function' && !isObject(val = fn.call(input))) return val; - if (pref !== 'string' && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val; - throw TypeError("Can't convert object to primitive value"); -}; - -},{"../internals/is-object":113}],136:[function(require,module,exports){ -arguments[4][104][0].apply(exports,arguments) -},{"dup":104}],137:[function(require,module,exports){ -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); - -module.exports = function (target, key, value, options) { - if (options && options.enumerable) target[key] = value; - else createNonEnumerableProperty(target, key, value); -}; - -},{"../internals/create-non-enumerable-property":81}],138:[function(require,module,exports){ -'use strict'; -var anObject = require('../internals/an-object'); - -// `RegExp.prototype.flags` getter implementation -// https://tc39.es/ecma262/#sec-get-regexp.prototype.flags -module.exports = function () { - var that = anObject(this); - var result = ''; - if (that.global) result += 'g'; - if (that.ignoreCase) result += 'i'; - if (that.multiline) result += 'm'; - if (that.dotAll) result += 's'; - if (that.unicode) result += 'u'; - if (that.sticky) result += 'y'; - return result; -}; - -},{"../internals/an-object":64}],139:[function(require,module,exports){ -// `RequireObjectCoercible` abstract operation -// https://tc39.es/ecma262/#sec-requireobjectcoercible -module.exports = function (it) { - if (it == undefined) throw TypeError("Can't call method on " + it); - return it; -}; - -},{}],140:[function(require,module,exports){ -var global = require('../internals/global'); - -module.exports = function (key, value) { - try { - // eslint-disable-next-line es/no-object-defineproperty -- safe - Object.defineProperty(global, key, { value: value, configurable: true, writable: true }); - } catch (error) { - global[key] = value; - } return value; -}; - -},{"../internals/global":102}],141:[function(require,module,exports){ -var TO_STRING_TAG_SUPPORT = require('../internals/to-string-tag-support'); -var defineProperty = require('../internals/object-define-property').f; -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var has = require('../internals/has'); -var toString = require('../internals/object-to-string'); -var wellKnownSymbol = require('../internals/well-known-symbol'); - -var TO_STRING_TAG = wellKnownSymbol('toStringTag'); - -module.exports = function (it, TAG, STATIC, SET_METHOD) { - if (it) { - var target = STATIC ? it : it.prototype; - if (!has(target, TO_STRING_TAG)) { - defineProperty(target, TO_STRING_TAG, { configurable: true, value: TAG }); - } - if (SET_METHOD && !TO_STRING_TAG_SUPPORT) { - createNonEnumerableProperty(target, 'toString', toString); - } - } -}; - -},{"../internals/create-non-enumerable-property":81,"../internals/has":103,"../internals/object-define-property":124,"../internals/object-to-string":134,"../internals/to-string-tag-support":154,"../internals/well-known-symbol":159}],142:[function(require,module,exports){ -var shared = require('../internals/shared'); -var uid = require('../internals/uid'); - -var keys = shared('keys'); - -module.exports = function (key) { - return keys[key] || (keys[key] = uid(key)); -}; - -},{"../internals/shared":144,"../internals/uid":156}],143:[function(require,module,exports){ -var global = require('../internals/global'); -var setGlobal = require('../internals/set-global'); - -var SHARED = '__core-js_shared__'; -var store = global[SHARED] || setGlobal(SHARED, {}); - -module.exports = store; - -},{"../internals/global":102,"../internals/set-global":140}],144:[function(require,module,exports){ -var IS_PURE = require('../internals/is-pure'); -var store = require('../internals/shared-store'); - -(module.exports = function (key, value) { - return store[key] || (store[key] = value !== undefined ? value : {}); -})('versions', []).push({ - version: '3.16.0', - mode: IS_PURE ? 'pure' : 'global', - copyright: '© 2021 Denis Pushkarev (zloirock.ru)' -}); - -},{"../internals/is-pure":114,"../internals/shared-store":143}],145:[function(require,module,exports){ -var toInteger = require('../internals/to-integer'); -var toString = require('../internals/to-string'); -var requireObjectCoercible = require('../internals/require-object-coercible'); - -// `String.prototype.codePointAt` methods implementation -var createMethod = function (CONVERT_TO_STRING) { - return function ($this, pos) { - var S = toString(requireObjectCoercible($this)); - var position = toInteger(pos); - var size = S.length; - var first, second; - if (position < 0 || position >= size) return CONVERT_TO_STRING ? '' : undefined; - first = S.charCodeAt(position); - return first < 0xD800 || first > 0xDBFF || position + 1 === size - || (second = S.charCodeAt(position + 1)) < 0xDC00 || second > 0xDFFF - ? CONVERT_TO_STRING ? S.charAt(position) : first - : CONVERT_TO_STRING ? S.slice(position, position + 2) : (first - 0xD800 << 10) + (second - 0xDC00) + 0x10000; - }; -}; - -module.exports = { - // `String.prototype.codePointAt` method - // https://tc39.es/ecma262/#sec-string.prototype.codepointat - codeAt: createMethod(false), - // `String.prototype.at` method - // https://github.com/mathiasbynens/String.prototype.at - charAt: createMethod(true) -}; - -},{"../internals/require-object-coercible":139,"../internals/to-integer":149,"../internals/to-string":155}],146:[function(require,module,exports){ -var requireObjectCoercible = require('../internals/require-object-coercible'); -var toString = require('../internals/to-string'); -var whitespaces = require('../internals/whitespaces'); - -var whitespace = '[' + whitespaces + ']'; -var ltrim = RegExp('^' + whitespace + whitespace + '*'); -var rtrim = RegExp(whitespace + whitespace + '*$'); - -// `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation -var createMethod = function (TYPE) { - return function ($this) { - var string = toString(requireObjectCoercible($this)); - if (TYPE & 1) string = string.replace(ltrim, ''); - if (TYPE & 2) string = string.replace(rtrim, ''); - return string; - }; -}; - -module.exports = { - // `String.prototype.{ trimLeft, trimStart }` methods - // https://tc39.es/ecma262/#sec-string.prototype.trimstart - start: createMethod(1), - // `String.prototype.{ trimRight, trimEnd }` methods - // https://tc39.es/ecma262/#sec-string.prototype.trimend - end: createMethod(2), - // `String.prototype.trim` method - // https://tc39.es/ecma262/#sec-string.prototype.trim - trim: createMethod(3) -}; - -},{"../internals/require-object-coercible":139,"../internals/to-string":155,"../internals/whitespaces":160}],147:[function(require,module,exports){ -var toInteger = require('../internals/to-integer'); - -var max = Math.max; -var min = Math.min; - -// Helper for a popular repeating case of the spec: -// Let integer be ? ToInteger(index). -// If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length). -module.exports = function (index, length) { - var integer = toInteger(index); - return integer < 0 ? max(integer + length, 0) : min(integer, length); -}; - -},{"../internals/to-integer":149}],148:[function(require,module,exports){ -// toObject with fallback for non-array-like ES3 strings -var IndexedObject = require('../internals/indexed-object'); -var requireObjectCoercible = require('../internals/require-object-coercible'); - -module.exports = function (it) { - return IndexedObject(requireObjectCoercible(it)); -}; - -},{"../internals/indexed-object":107,"../internals/require-object-coercible":139}],149:[function(require,module,exports){ -var ceil = Math.ceil; -var floor = Math.floor; - -// `ToInteger` abstract operation -// https://tc39.es/ecma262/#sec-tointeger -module.exports = function (argument) { - return isNaN(argument = +argument) ? 0 : (argument > 0 ? floor : ceil)(argument); -}; - -},{}],150:[function(require,module,exports){ -var toInteger = require('../internals/to-integer'); - -var min = Math.min; - -// `ToLength` abstract operation -// https://tc39.es/ecma262/#sec-tolength -module.exports = function (argument) { - return argument > 0 ? min(toInteger(argument), 0x1FFFFFFFFFFFFF) : 0; // 2 ** 53 - 1 == 9007199254740991 -}; - -},{"../internals/to-integer":149}],151:[function(require,module,exports){ -var requireObjectCoercible = require('../internals/require-object-coercible'); - -// `ToObject` abstract operation -// https://tc39.es/ecma262/#sec-toobject -module.exports = function (argument) { - return Object(requireObjectCoercible(argument)); -}; - -},{"../internals/require-object-coercible":139}],152:[function(require,module,exports){ -var isObject = require('../internals/is-object'); -var isSymbol = require('../internals/is-symbol'); -var ordinaryToPrimitive = require('../internals/ordinary-to-primitive'); -var wellKnownSymbol = require('../internals/well-known-symbol'); - -var TO_PRIMITIVE = wellKnownSymbol('toPrimitive'); - -// `ToPrimitive` abstract operation -// https://tc39.es/ecma262/#sec-toprimitive -module.exports = function (input, pref) { - if (!isObject(input) || isSymbol(input)) return input; - var exoticToPrim = input[TO_PRIMITIVE]; - var result; - if (exoticToPrim !== undefined) { - if (pref === undefined) pref = 'default'; - result = exoticToPrim.call(input, pref); - if (!isObject(result) || isSymbol(result)) return result; - throw TypeError("Can't convert object to primitive value"); - } - if (pref === undefined) pref = 'number'; - return ordinaryToPrimitive(input, pref); -}; - -},{"../internals/is-object":113,"../internals/is-symbol":115,"../internals/ordinary-to-primitive":135,"../internals/well-known-symbol":159}],153:[function(require,module,exports){ -var toPrimitive = require('../internals/to-primitive'); -var isSymbol = require('../internals/is-symbol'); - -// `ToPropertyKey` abstract operation -// https://tc39.es/ecma262/#sec-topropertykey -module.exports = function (argument) { - var key = toPrimitive(argument, 'string'); - return isSymbol(key) ? key : String(key); -}; - -},{"../internals/is-symbol":115,"../internals/to-primitive":152}],154:[function(require,module,exports){ -var wellKnownSymbol = require('../internals/well-known-symbol'); - -var TO_STRING_TAG = wellKnownSymbol('toStringTag'); -var test = {}; - -test[TO_STRING_TAG] = 'z'; - -module.exports = String(test) === '[object z]'; - -},{"../internals/well-known-symbol":159}],155:[function(require,module,exports){ -var isSymbol = require('../internals/is-symbol'); - -module.exports = function (argument) { - if (isSymbol(argument)) throw TypeError('Cannot convert a Symbol value to a string'); - return String(argument); -}; - -},{"../internals/is-symbol":115}],156:[function(require,module,exports){ -var id = 0; -var postfix = Math.random(); - -module.exports = function (key) { - return 'Symbol(' + String(key === undefined ? '' : key) + ')_' + (++id + postfix).toString(36); -}; - -},{}],157:[function(require,module,exports){ -/* eslint-disable es/no-symbol -- required for testing */ -var NATIVE_SYMBOL = require('../internals/native-symbol'); - -module.exports = NATIVE_SYMBOL - && !Symbol.sham - && typeof Symbol.iterator == 'symbol'; - -},{"../internals/native-symbol":119}],158:[function(require,module,exports){ -var wellKnownSymbol = require('../internals/well-known-symbol'); - -exports.f = wellKnownSymbol; - -},{"../internals/well-known-symbol":159}],159:[function(require,module,exports){ -var global = require('../internals/global'); -var shared = require('../internals/shared'); -var has = require('../internals/has'); -var uid = require('../internals/uid'); -var NATIVE_SYMBOL = require('../internals/native-symbol'); -var USE_SYMBOL_AS_UID = require('../internals/use-symbol-as-uid'); - -var WellKnownSymbolsStore = shared('wks'); -var Symbol = global.Symbol; -var createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol : Symbol && Symbol.withoutSetter || uid; - -module.exports = function (name) { - if (!has(WellKnownSymbolsStore, name) || !(NATIVE_SYMBOL || typeof WellKnownSymbolsStore[name] == 'string')) { - if (NATIVE_SYMBOL && has(Symbol, name)) { - WellKnownSymbolsStore[name] = Symbol[name]; - } else { - WellKnownSymbolsStore[name] = createWellKnownSymbol('Symbol.' + name); - } - } return WellKnownSymbolsStore[name]; -}; - -},{"../internals/global":102,"../internals/has":103,"../internals/native-symbol":119,"../internals/shared":144,"../internals/uid":156,"../internals/use-symbol-as-uid":157}],160:[function(require,module,exports){ -// a string of all valid unicode whitespaces -module.exports = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002' + - '\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF'; - -},{}],161:[function(require,module,exports){ -'use strict'; -var $ = require('../internals/export'); -var fails = require('../internals/fails'); -var isArray = require('../internals/is-array'); -var isObject = require('../internals/is-object'); -var toObject = require('../internals/to-object'); -var toLength = require('../internals/to-length'); -var createProperty = require('../internals/create-property'); -var arraySpeciesCreate = require('../internals/array-species-create'); -var arrayMethodHasSpeciesSupport = require('../internals/array-method-has-species-support'); -var wellKnownSymbol = require('../internals/well-known-symbol'); -var V8_VERSION = require('../internals/engine-v8-version'); - -var IS_CONCAT_SPREADABLE = wellKnownSymbol('isConcatSpreadable'); -var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF; -var MAXIMUM_ALLOWED_INDEX_EXCEEDED = 'Maximum allowed index exceeded'; - -// We can't use this feature detection in V8 since it causes -// deoptimization and serious performance degradation -// https://github.com/zloirock/core-js/issues/679 -var IS_CONCAT_SPREADABLE_SUPPORT = V8_VERSION >= 51 || !fails(function () { - var array = []; - array[IS_CONCAT_SPREADABLE] = false; - return array.concat()[0] !== array; -}); - -var SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('concat'); - -var isConcatSpreadable = function (O) { - if (!isObject(O)) return false; - var spreadable = O[IS_CONCAT_SPREADABLE]; - return spreadable !== undefined ? !!spreadable : isArray(O); -}; - -var FORCED = !IS_CONCAT_SPREADABLE_SUPPORT || !SPECIES_SUPPORT; - -// `Array.prototype.concat` method -// https://tc39.es/ecma262/#sec-array.prototype.concat -// with adding support of @@isConcatSpreadable and @@species -$({ target: 'Array', proto: true, forced: FORCED }, { - // eslint-disable-next-line no-unused-vars -- required for `.length` - concat: function concat(arg) { - var O = toObject(this); - var A = arraySpeciesCreate(O, 0); - var n = 0; - var i, k, length, len, E; - for (i = -1, length = arguments.length; i < length; i++) { - E = i === -1 ? O : arguments[i]; - if (isConcatSpreadable(E)) { - len = toLength(E.length); - if (n + len > MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED); - for (k = 0; k < len; k++, n++) if (k in E) createProperty(A, n, E[k]); - } else { - if (n >= MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED); - createProperty(A, n++, E); - } - } - A.length = n; - return A; - } -}); - -},{"../internals/array-method-has-species-support":69,"../internals/array-species-create":74,"../internals/create-property":83,"../internals/engine-v8-version":93,"../internals/export":97,"../internals/fails":98,"../internals/is-array":111,"../internals/is-object":113,"../internals/to-length":150,"../internals/to-object":151,"../internals/well-known-symbol":159}],162:[function(require,module,exports){ -'use strict'; -var $ = require('../internals/export'); -var forEach = require('../internals/array-for-each'); - -// `Array.prototype.forEach` method -// https://tc39.es/ecma262/#sec-array.prototype.foreach -// eslint-disable-next-line es/no-array-prototype-foreach -- safe -$({ target: 'Array', proto: true, forced: [].forEach != forEach }, { - forEach: forEach -}); - -},{"../internals/array-for-each":65,"../internals/export":97}],163:[function(require,module,exports){ -var $ = require('../internals/export'); -var from = require('../internals/array-from'); -var checkCorrectnessOfIteration = require('../internals/check-correctness-of-iteration'); - -var INCORRECT_ITERATION = !checkCorrectnessOfIteration(function (iterable) { - // eslint-disable-next-line es/no-array-from -- required for testing - Array.from(iterable); -}); - -// `Array.from` method -// https://tc39.es/ecma262/#sec-array.from -$({ target: 'Array', stat: true, forced: INCORRECT_ITERATION }, { - from: from -}); - -},{"../internals/array-from":66,"../internals/check-correctness-of-iteration":76,"../internals/export":97}],164:[function(require,module,exports){ -'use strict'; -/* eslint-disable es/no-array-prototype-indexof -- required for testing */ -var $ = require('../internals/export'); -var $indexOf = require('../internals/array-includes').indexOf; -var arrayMethodIsStrict = require('../internals/array-method-is-strict'); - -var nativeIndexOf = [].indexOf; - -var NEGATIVE_ZERO = !!nativeIndexOf && 1 / [1].indexOf(1, -0) < 0; -var STRICT_METHOD = arrayMethodIsStrict('indexOf'); - -// `Array.prototype.indexOf` method -// https://tc39.es/ecma262/#sec-array.prototype.indexof -$({ target: 'Array', proto: true, forced: NEGATIVE_ZERO || !STRICT_METHOD }, { - indexOf: function indexOf(searchElement /* , fromIndex = 0 */) { - return NEGATIVE_ZERO - // convert -0 to +0 - ? nativeIndexOf.apply(this, arguments) || 0 - : $indexOf(this, searchElement, arguments.length > 1 ? arguments[1] : undefined); - } -}); - -},{"../internals/array-includes":67,"../internals/array-method-is-strict":70,"../internals/export":97}],165:[function(require,module,exports){ -var $ = require('../internals/export'); -var isArray = require('../internals/is-array'); - -// `Array.isArray` method -// https://tc39.es/ecma262/#sec-array.isarray -$({ target: 'Array', stat: true }, { - isArray: isArray -}); - -},{"../internals/export":97,"../internals/is-array":111}],166:[function(require,module,exports){ -'use strict'; -var toIndexedObject = require('../internals/to-indexed-object'); -var addToUnscopables = require('../internals/add-to-unscopables'); -var Iterators = require('../internals/iterators'); -var InternalStateModule = require('../internals/internal-state'); -var defineIterator = require('../internals/define-iterator'); - -var ARRAY_ITERATOR = 'Array Iterator'; -var setInternalState = InternalStateModule.set; -var getInternalState = InternalStateModule.getterFor(ARRAY_ITERATOR); - -// `Array.prototype.entries` method -// https://tc39.es/ecma262/#sec-array.prototype.entries -// `Array.prototype.keys` method -// https://tc39.es/ecma262/#sec-array.prototype.keys -// `Array.prototype.values` method -// https://tc39.es/ecma262/#sec-array.prototype.values -// `Array.prototype[@@iterator]` method -// https://tc39.es/ecma262/#sec-array.prototype-@@iterator -// `CreateArrayIterator` internal method -// https://tc39.es/ecma262/#sec-createarrayiterator -module.exports = defineIterator(Array, 'Array', function (iterated, kind) { - setInternalState(this, { - type: ARRAY_ITERATOR, - target: toIndexedObject(iterated), // target - index: 0, // next index - kind: kind // kind - }); -// `%ArrayIteratorPrototype%.next` method -// https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next -}, function () { - var state = getInternalState(this); - var target = state.target; - var kind = state.kind; - var index = state.index++; - if (!target || index >= target.length) { - state.target = undefined; - return { value: undefined, done: true }; - } - if (kind == 'keys') return { value: index, done: false }; - if (kind == 'values') return { value: target[index], done: false }; - return { value: [index, target[index]], done: false }; -}, 'values'); - -// argumentsList[@@iterator] is %ArrayProto_values% -// https://tc39.es/ecma262/#sec-createunmappedargumentsobject -// https://tc39.es/ecma262/#sec-createmappedargumentsobject -Iterators.Arguments = Iterators.Array; - -// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables -addToUnscopables('keys'); -addToUnscopables('values'); -addToUnscopables('entries'); - -},{"../internals/add-to-unscopables":63,"../internals/define-iterator":84,"../internals/internal-state":109,"../internals/iterators":118,"../internals/to-indexed-object":148}],167:[function(require,module,exports){ -'use strict'; -var $ = require('../internals/export'); -var $map = require('../internals/array-iteration').map; -var arrayMethodHasSpeciesSupport = require('../internals/array-method-has-species-support'); - -var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('map'); - -// `Array.prototype.map` method -// https://tc39.es/ecma262/#sec-array.prototype.map -// with adding support of @@species -$({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, { - map: function map(callbackfn /* , thisArg */) { - return $map(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined); - } -}); - -},{"../internals/array-iteration":68,"../internals/array-method-has-species-support":69,"../internals/export":97}],168:[function(require,module,exports){ -'use strict'; -var $ = require('../internals/export'); -var $reduce = require('../internals/array-reduce').left; -var arrayMethodIsStrict = require('../internals/array-method-is-strict'); -var CHROME_VERSION = require('../internals/engine-v8-version'); -var IS_NODE = require('../internals/engine-is-node'); - -var STRICT_METHOD = arrayMethodIsStrict('reduce'); -// Chrome 80-82 has a critical bug -// https://bugs.chromium.org/p/chromium/issues/detail?id=1049982 -var CHROME_BUG = !IS_NODE && CHROME_VERSION > 79 && CHROME_VERSION < 83; - -// `Array.prototype.reduce` method -// https://tc39.es/ecma262/#sec-array.prototype.reduce -$({ target: 'Array', proto: true, forced: !STRICT_METHOD || CHROME_BUG }, { - reduce: function reduce(callbackfn /* , initialValue */) { - return $reduce(this, callbackfn, arguments.length, arguments.length > 1 ? arguments[1] : undefined); - } -}); - -},{"../internals/array-method-is-strict":70,"../internals/array-reduce":71,"../internals/engine-is-node":91,"../internals/engine-v8-version":93,"../internals/export":97}],169:[function(require,module,exports){ -'use strict'; -var $ = require('../internals/export'); -var isObject = require('../internals/is-object'); -var isArray = require('../internals/is-array'); -var toAbsoluteIndex = require('../internals/to-absolute-index'); -var toLength = require('../internals/to-length'); -var toIndexedObject = require('../internals/to-indexed-object'); -var createProperty = require('../internals/create-property'); -var wellKnownSymbol = require('../internals/well-known-symbol'); -var arrayMethodHasSpeciesSupport = require('../internals/array-method-has-species-support'); - -var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('slice'); - -var SPECIES = wellKnownSymbol('species'); -var nativeSlice = [].slice; -var max = Math.max; - -// `Array.prototype.slice` method -// https://tc39.es/ecma262/#sec-array.prototype.slice -// fallback for not array-like ES3 strings and DOM objects -$({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, { - slice: function slice(start, end) { - var O = toIndexedObject(this); - var length = toLength(O.length); - var k = toAbsoluteIndex(start, length); - var fin = toAbsoluteIndex(end === undefined ? length : end, length); - // inline `ArraySpeciesCreate` for usage native `Array#slice` where it's possible - var Constructor, result, n; - if (isArray(O)) { - Constructor = O.constructor; - // cross-realm fallback - if (typeof Constructor == 'function' && (Constructor === Array || isArray(Constructor.prototype))) { - Constructor = undefined; - } else if (isObject(Constructor)) { - Constructor = Constructor[SPECIES]; - if (Constructor === null) Constructor = undefined; - } - if (Constructor === Array || Constructor === undefined) { - return nativeSlice.call(O, k, fin); - } - } - result = new (Constructor === undefined ? Array : Constructor)(max(fin - k, 0)); - for (n = 0; k < fin; k++, n++) if (k in O) createProperty(result, n, O[k]); - result.length = n; - return result; - } -}); - -},{"../internals/array-method-has-species-support":69,"../internals/create-property":83,"../internals/export":97,"../internals/is-array":111,"../internals/is-object":113,"../internals/to-absolute-index":147,"../internals/to-indexed-object":148,"../internals/to-length":150,"../internals/well-known-symbol":159}],170:[function(require,module,exports){ -'use strict'; -var $ = require('../internals/export'); -var aFunction = require('../internals/a-function'); -var toObject = require('../internals/to-object'); -var toLength = require('../internals/to-length'); -var toString = require('../internals/to-string'); -var fails = require('../internals/fails'); -var internalSort = require('../internals/array-sort'); -var arrayMethodIsStrict = require('../internals/array-method-is-strict'); -var FF = require('../internals/engine-ff-version'); -var IE_OR_EDGE = require('../internals/engine-is-ie-or-edge'); -var V8 = require('../internals/engine-v8-version'); -var WEBKIT = require('../internals/engine-webkit-version'); - -var test = []; -var nativeSort = test.sort; - -// IE8- -var FAILS_ON_UNDEFINED = fails(function () { - test.sort(undefined); -}); -// V8 bug -var FAILS_ON_NULL = fails(function () { - test.sort(null); -}); -// Old WebKit -var STRICT_METHOD = arrayMethodIsStrict('sort'); - -var STABLE_SORT = !fails(function () { - // feature detection can be too slow, so check engines versions - if (V8) return V8 < 70; - if (FF && FF > 3) return; - if (IE_OR_EDGE) return true; - if (WEBKIT) return WEBKIT < 603; - - var result = ''; - var code, chr, value, index; - - // generate an array with more 512 elements (Chakra and old V8 fails only in this case) - for (code = 65; code < 76; code++) { - chr = String.fromCharCode(code); - - switch (code) { - case 66: case 69: case 70: case 72: value = 3; break; - case 68: case 71: value = 4; break; - default: value = 2; - } - - for (index = 0; index < 47; index++) { - test.push({ k: chr + index, v: value }); - } - } - - test.sort(function (a, b) { return b.v - a.v; }); - - for (index = 0; index < test.length; index++) { - chr = test[index].k.charAt(0); - if (result.charAt(result.length - 1) !== chr) result += chr; - } - - return result !== 'DGBEFHACIJK'; -}); - -var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT; - -var getSortCompare = function (comparefn) { - return function (x, y) { - if (y === undefined) return -1; - if (x === undefined) return 1; - if (comparefn !== undefined) return +comparefn(x, y) || 0; - return toString(x) > toString(y) ? 1 : -1; - }; -}; - -// `Array.prototype.sort` method -// https://tc39.es/ecma262/#sec-array.prototype.sort -$({ target: 'Array', proto: true, forced: FORCED }, { - sort: function sort(comparefn) { - if (comparefn !== undefined) aFunction(comparefn); - - var array = toObject(this); - - if (STABLE_SORT) return comparefn === undefined ? nativeSort.call(array) : nativeSort.call(array, comparefn); - - var items = []; - var arrayLength = toLength(array.length); - var itemsLength, index; - - for (index = 0; index < arrayLength; index++) { - if (index in array) items.push(array[index]); - } - - items = internalSort(items, getSortCompare(comparefn)); - itemsLength = items.length; - index = 0; - - while (index < itemsLength) array[index] = items[index++]; - while (index < arrayLength) delete array[index++]; - - return array; - } -}); - -},{"../internals/a-function":61,"../internals/array-method-is-strict":70,"../internals/array-sort":72,"../internals/engine-ff-version":89,"../internals/engine-is-ie-or-edge":90,"../internals/engine-v8-version":93,"../internals/engine-webkit-version":94,"../internals/export":97,"../internals/fails":98,"../internals/to-length":150,"../internals/to-object":151,"../internals/to-string":155}],171:[function(require,module,exports){ -var global = require('../internals/global'); -var setToStringTag = require('../internals/set-to-string-tag'); - -// JSON[@@toStringTag] property -// https://tc39.es/ecma262/#sec-json-@@tostringtag -setToStringTag(global.JSON, 'JSON', true); - -},{"../internals/global":102,"../internals/set-to-string-tag":141}],172:[function(require,module,exports){ -// empty - -},{}],173:[function(require,module,exports){ -var $ = require('../internals/export'); -var DESCRIPTORS = require('../internals/descriptors'); -var create = require('../internals/object-create'); - -// `Object.create` method -// https://tc39.es/ecma262/#sec-object.create -$({ target: 'Object', stat: true, sham: !DESCRIPTORS }, { - create: create -}); - -},{"../internals/descriptors":86,"../internals/export":97,"../internals/object-create":122}],174:[function(require,module,exports){ -var $ = require('../internals/export'); -var DESCRIPTORS = require('../internals/descriptors'); -var objectDefinePropertyModile = require('../internals/object-define-property'); - -// `Object.defineProperty` method -// https://tc39.es/ecma262/#sec-object.defineproperty -$({ target: 'Object', stat: true, forced: !DESCRIPTORS, sham: !DESCRIPTORS }, { - defineProperty: objectDefinePropertyModile.f -}); - -},{"../internals/descriptors":86,"../internals/export":97,"../internals/object-define-property":124}],175:[function(require,module,exports){ -arguments[4][172][0].apply(exports,arguments) -},{"dup":172}],176:[function(require,module,exports){ -var $ = require('../internals/export'); -var parseIntImplementation = require('../internals/number-parse-int'); - -// `parseInt` method -// https://tc39.es/ecma262/#sec-parseint-string-radix -$({ global: true, forced: parseInt != parseIntImplementation }, { - parseInt: parseIntImplementation -}); - -},{"../internals/export":97,"../internals/number-parse-int":121}],177:[function(require,module,exports){ -arguments[4][172][0].apply(exports,arguments) -},{"dup":172}],178:[function(require,module,exports){ -arguments[4][172][0].apply(exports,arguments) -},{"dup":172}],179:[function(require,module,exports){ -'use strict'; -var charAt = require('../internals/string-multibyte').charAt; -var toString = require('../internals/to-string'); -var InternalStateModule = require('../internals/internal-state'); -var defineIterator = require('../internals/define-iterator'); - -var STRING_ITERATOR = 'String Iterator'; -var setInternalState = InternalStateModule.set; -var getInternalState = InternalStateModule.getterFor(STRING_ITERATOR); - -// `String.prototype[@@iterator]` method -// https://tc39.es/ecma262/#sec-string.prototype-@@iterator -defineIterator(String, 'String', function (iterated) { - setInternalState(this, { - type: STRING_ITERATOR, - string: toString(iterated), - index: 0 - }); -// `%StringIteratorPrototype%.next` method -// https://tc39.es/ecma262/#sec-%stringiteratorprototype%.next -}, function next() { - var state = getInternalState(this); - var string = state.string; - var index = state.index; - var point; - if (index >= string.length) return { value: undefined, done: true }; - point = charAt(string, index); - state.index += point.length; - return { value: point, done: false }; -}); - -},{"../internals/define-iterator":84,"../internals/internal-state":109,"../internals/string-multibyte":145,"../internals/to-string":155}],180:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.asyncIterator` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.asynciterator -defineWellKnownSymbol('asyncIterator'); - -},{"../internals/define-well-known-symbol":85}],181:[function(require,module,exports){ -arguments[4][172][0].apply(exports,arguments) -},{"dup":172}],182:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.hasInstance` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.hasinstance -defineWellKnownSymbol('hasInstance'); - -},{"../internals/define-well-known-symbol":85}],183:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.isConcatSpreadable` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.isconcatspreadable -defineWellKnownSymbol('isConcatSpreadable'); - -},{"../internals/define-well-known-symbol":85}],184:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.iterator` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.iterator -defineWellKnownSymbol('iterator'); - -},{"../internals/define-well-known-symbol":85}],185:[function(require,module,exports){ -'use strict'; -var $ = require('../internals/export'); -var global = require('../internals/global'); -var getBuiltIn = require('../internals/get-built-in'); -var IS_PURE = require('../internals/is-pure'); -var DESCRIPTORS = require('../internals/descriptors'); -var NATIVE_SYMBOL = require('../internals/native-symbol'); -var fails = require('../internals/fails'); -var has = require('../internals/has'); -var isArray = require('../internals/is-array'); -var isObject = require('../internals/is-object'); -var isSymbol = require('../internals/is-symbol'); -var anObject = require('../internals/an-object'); -var toObject = require('../internals/to-object'); -var toIndexedObject = require('../internals/to-indexed-object'); -var toPropertyKey = require('../internals/to-property-key'); -var $toString = require('../internals/to-string'); -var createPropertyDescriptor = require('../internals/create-property-descriptor'); -var nativeObjectCreate = require('../internals/object-create'); -var objectKeys = require('../internals/object-keys'); -var getOwnPropertyNamesModule = require('../internals/object-get-own-property-names'); -var getOwnPropertyNamesExternal = require('../internals/object-get-own-property-names-external'); -var getOwnPropertySymbolsModule = require('../internals/object-get-own-property-symbols'); -var getOwnPropertyDescriptorModule = require('../internals/object-get-own-property-descriptor'); -var definePropertyModule = require('../internals/object-define-property'); -var propertyIsEnumerableModule = require('../internals/object-property-is-enumerable'); -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var redefine = require('../internals/redefine'); -var shared = require('../internals/shared'); -var sharedKey = require('../internals/shared-key'); -var hiddenKeys = require('../internals/hidden-keys'); -var uid = require('../internals/uid'); -var wellKnownSymbol = require('../internals/well-known-symbol'); -var wrappedWellKnownSymbolModule = require('../internals/well-known-symbol-wrapped'); -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); -var setToStringTag = require('../internals/set-to-string-tag'); -var InternalStateModule = require('../internals/internal-state'); -var $forEach = require('../internals/array-iteration').forEach; - -var HIDDEN = sharedKey('hidden'); -var SYMBOL = 'Symbol'; -var PROTOTYPE = 'prototype'; -var TO_PRIMITIVE = wellKnownSymbol('toPrimitive'); -var setInternalState = InternalStateModule.set; -var getInternalState = InternalStateModule.getterFor(SYMBOL); -var ObjectPrototype = Object[PROTOTYPE]; -var $Symbol = global.Symbol; -var $stringify = getBuiltIn('JSON', 'stringify'); -var nativeGetOwnPropertyDescriptor = getOwnPropertyDescriptorModule.f; -var nativeDefineProperty = definePropertyModule.f; -var nativeGetOwnPropertyNames = getOwnPropertyNamesExternal.f; -var nativePropertyIsEnumerable = propertyIsEnumerableModule.f; -var AllSymbols = shared('symbols'); -var ObjectPrototypeSymbols = shared('op-symbols'); -var StringToSymbolRegistry = shared('string-to-symbol-registry'); -var SymbolToStringRegistry = shared('symbol-to-string-registry'); -var WellKnownSymbolsStore = shared('wks'); -var QObject = global.QObject; -// Don't use setters in Qt Script, https://github.com/zloirock/core-js/issues/173 -var USE_SETTER = !QObject || !QObject[PROTOTYPE] || !QObject[PROTOTYPE].findChild; - -// fallback for old Android, https://code.google.com/p/v8/issues/detail?id=687 -var setSymbolDescriptor = DESCRIPTORS && fails(function () { - return nativeObjectCreate(nativeDefineProperty({}, 'a', { - get: function () { return nativeDefineProperty(this, 'a', { value: 7 }).a; } - })).a != 7; -}) ? function (O, P, Attributes) { - var ObjectPrototypeDescriptor = nativeGetOwnPropertyDescriptor(ObjectPrototype, P); - if (ObjectPrototypeDescriptor) delete ObjectPrototype[P]; - nativeDefineProperty(O, P, Attributes); - if (ObjectPrototypeDescriptor && O !== ObjectPrototype) { - nativeDefineProperty(ObjectPrototype, P, ObjectPrototypeDescriptor); - } -} : nativeDefineProperty; - -var wrap = function (tag, description) { - var symbol = AllSymbols[tag] = nativeObjectCreate($Symbol[PROTOTYPE]); - setInternalState(symbol, { - type: SYMBOL, - tag: tag, - description: description - }); - if (!DESCRIPTORS) symbol.description = description; - return symbol; -}; - -var $defineProperty = function defineProperty(O, P, Attributes) { - if (O === ObjectPrototype) $defineProperty(ObjectPrototypeSymbols, P, Attributes); - anObject(O); - var key = toPropertyKey(P); - anObject(Attributes); - if (has(AllSymbols, key)) { - if (!Attributes.enumerable) { - if (!has(O, HIDDEN)) nativeDefineProperty(O, HIDDEN, createPropertyDescriptor(1, {})); - O[HIDDEN][key] = true; - } else { - if (has(O, HIDDEN) && O[HIDDEN][key]) O[HIDDEN][key] = false; - Attributes = nativeObjectCreate(Attributes, { enumerable: createPropertyDescriptor(0, false) }); - } return setSymbolDescriptor(O, key, Attributes); - } return nativeDefineProperty(O, key, Attributes); -}; - -var $defineProperties = function defineProperties(O, Properties) { - anObject(O); - var properties = toIndexedObject(Properties); - var keys = objectKeys(properties).concat($getOwnPropertySymbols(properties)); - $forEach(keys, function (key) { - if (!DESCRIPTORS || $propertyIsEnumerable.call(properties, key)) $defineProperty(O, key, properties[key]); - }); - return O; -}; - -var $create = function create(O, Properties) { - return Properties === undefined ? nativeObjectCreate(O) : $defineProperties(nativeObjectCreate(O), Properties); -}; - -var $propertyIsEnumerable = function propertyIsEnumerable(V) { - var P = toPropertyKey(V); - var enumerable = nativePropertyIsEnumerable.call(this, P); - if (this === ObjectPrototype && has(AllSymbols, P) && !has(ObjectPrototypeSymbols, P)) return false; - return enumerable || !has(this, P) || !has(AllSymbols, P) || has(this, HIDDEN) && this[HIDDEN][P] ? enumerable : true; -}; - -var $getOwnPropertyDescriptor = function getOwnPropertyDescriptor(O, P) { - var it = toIndexedObject(O); - var key = toPropertyKey(P); - if (it === ObjectPrototype && has(AllSymbols, key) && !has(ObjectPrototypeSymbols, key)) return; - var descriptor = nativeGetOwnPropertyDescriptor(it, key); - if (descriptor && has(AllSymbols, key) && !(has(it, HIDDEN) && it[HIDDEN][key])) { - descriptor.enumerable = true; - } - return descriptor; -}; - -var $getOwnPropertyNames = function getOwnPropertyNames(O) { - var names = nativeGetOwnPropertyNames(toIndexedObject(O)); - var result = []; - $forEach(names, function (key) { - if (!has(AllSymbols, key) && !has(hiddenKeys, key)) result.push(key); - }); - return result; -}; - -var $getOwnPropertySymbols = function getOwnPropertySymbols(O) { - var IS_OBJECT_PROTOTYPE = O === ObjectPrototype; - var names = nativeGetOwnPropertyNames(IS_OBJECT_PROTOTYPE ? ObjectPrototypeSymbols : toIndexedObject(O)); - var result = []; - $forEach(names, function (key) { - if (has(AllSymbols, key) && (!IS_OBJECT_PROTOTYPE || has(ObjectPrototype, key))) { - result.push(AllSymbols[key]); - } - }); - return result; -}; - -// `Symbol` constructor -// https://tc39.es/ecma262/#sec-symbol-constructor -if (!NATIVE_SYMBOL) { - $Symbol = function Symbol() { - if (this instanceof $Symbol) throw TypeError('Symbol is not a constructor'); - var description = !arguments.length || arguments[0] === undefined ? undefined : $toString(arguments[0]); - var tag = uid(description); - var setter = function (value) { - if (this === ObjectPrototype) setter.call(ObjectPrototypeSymbols, value); - if (has(this, HIDDEN) && has(this[HIDDEN], tag)) this[HIDDEN][tag] = false; - setSymbolDescriptor(this, tag, createPropertyDescriptor(1, value)); - }; - if (DESCRIPTORS && USE_SETTER) setSymbolDescriptor(ObjectPrototype, tag, { configurable: true, set: setter }); - return wrap(tag, description); - }; - - redefine($Symbol[PROTOTYPE], 'toString', function toString() { - return getInternalState(this).tag; - }); - - redefine($Symbol, 'withoutSetter', function (description) { - return wrap(uid(description), description); - }); - - propertyIsEnumerableModule.f = $propertyIsEnumerable; - definePropertyModule.f = $defineProperty; - getOwnPropertyDescriptorModule.f = $getOwnPropertyDescriptor; - getOwnPropertyNamesModule.f = getOwnPropertyNamesExternal.f = $getOwnPropertyNames; - getOwnPropertySymbolsModule.f = $getOwnPropertySymbols; - - wrappedWellKnownSymbolModule.f = function (name) { - return wrap(wellKnownSymbol(name), name); - }; - - if (DESCRIPTORS) { - // https://github.com/tc39/proposal-Symbol-description - nativeDefineProperty($Symbol[PROTOTYPE], 'description', { - configurable: true, - get: function description() { - return getInternalState(this).description; - } - }); - if (!IS_PURE) { - redefine(ObjectPrototype, 'propertyIsEnumerable', $propertyIsEnumerable, { unsafe: true }); - } - } -} - -$({ global: true, wrap: true, forced: !NATIVE_SYMBOL, sham: !NATIVE_SYMBOL }, { - Symbol: $Symbol -}); - -$forEach(objectKeys(WellKnownSymbolsStore), function (name) { - defineWellKnownSymbol(name); -}); - -$({ target: SYMBOL, stat: true, forced: !NATIVE_SYMBOL }, { - // `Symbol.for` method - // https://tc39.es/ecma262/#sec-symbol.for - 'for': function (key) { - var string = $toString(key); - if (has(StringToSymbolRegistry, string)) return StringToSymbolRegistry[string]; - var symbol = $Symbol(string); - StringToSymbolRegistry[string] = symbol; - SymbolToStringRegistry[symbol] = string; - return symbol; - }, - // `Symbol.keyFor` method - // https://tc39.es/ecma262/#sec-symbol.keyfor - keyFor: function keyFor(sym) { - if (!isSymbol(sym)) throw TypeError(sym + ' is not a symbol'); - if (has(SymbolToStringRegistry, sym)) return SymbolToStringRegistry[sym]; - }, - useSetter: function () { USE_SETTER = true; }, - useSimple: function () { USE_SETTER = false; } -}); - -$({ target: 'Object', stat: true, forced: !NATIVE_SYMBOL, sham: !DESCRIPTORS }, { - // `Object.create` method - // https://tc39.es/ecma262/#sec-object.create - create: $create, - // `Object.defineProperty` method - // https://tc39.es/ecma262/#sec-object.defineproperty - defineProperty: $defineProperty, - // `Object.defineProperties` method - // https://tc39.es/ecma262/#sec-object.defineproperties - defineProperties: $defineProperties, - // `Object.getOwnPropertyDescriptor` method - // https://tc39.es/ecma262/#sec-object.getownpropertydescriptors - getOwnPropertyDescriptor: $getOwnPropertyDescriptor -}); - -$({ target: 'Object', stat: true, forced: !NATIVE_SYMBOL }, { - // `Object.getOwnPropertyNames` method - // https://tc39.es/ecma262/#sec-object.getownpropertynames - getOwnPropertyNames: $getOwnPropertyNames, - // `Object.getOwnPropertySymbols` method - // https://tc39.es/ecma262/#sec-object.getownpropertysymbols - getOwnPropertySymbols: $getOwnPropertySymbols -}); - -// Chrome 38 and 39 `Object.getOwnPropertySymbols` fails on primitives -// https://bugs.chromium.org/p/v8/issues/detail?id=3443 -$({ target: 'Object', stat: true, forced: fails(function () { getOwnPropertySymbolsModule.f(1); }) }, { - getOwnPropertySymbols: function getOwnPropertySymbols(it) { - return getOwnPropertySymbolsModule.f(toObject(it)); - } -}); - -// `JSON.stringify` method behavior with symbols -// https://tc39.es/ecma262/#sec-json.stringify -if ($stringify) { - var FORCED_JSON_STRINGIFY = !NATIVE_SYMBOL || fails(function () { - var symbol = $Symbol(); - // MS Edge converts symbol values to JSON as {} - return $stringify([symbol]) != '[null]' - // WebKit converts symbol values to JSON as null - || $stringify({ a: symbol }) != '{}' - // V8 throws on boxed symbols - || $stringify(Object(symbol)) != '{}'; - }); - - $({ target: 'JSON', stat: true, forced: FORCED_JSON_STRINGIFY }, { - // eslint-disable-next-line no-unused-vars -- required for `.length` - stringify: function stringify(it, replacer, space) { - var args = [it]; - var index = 1; - var $replacer; - while (arguments.length > index) args.push(arguments[index++]); - $replacer = replacer; - if (!isObject(replacer) && it === undefined || isSymbol(it)) return; // IE8 returns string on undefined - if (!isArray(replacer)) replacer = function (key, value) { - if (typeof $replacer == 'function') value = $replacer.call(this, key, value); - if (!isSymbol(value)) return value; - }; - args[1] = replacer; - return $stringify.apply(null, args); - } - }); -} - -// `Symbol.prototype[@@toPrimitive]` method -// https://tc39.es/ecma262/#sec-symbol.prototype-@@toprimitive -if (!$Symbol[PROTOTYPE][TO_PRIMITIVE]) { - createNonEnumerableProperty($Symbol[PROTOTYPE], TO_PRIMITIVE, $Symbol[PROTOTYPE].valueOf); -} -// `Symbol.prototype[@@toStringTag]` property -// https://tc39.es/ecma262/#sec-symbol.prototype-@@tostringtag -setToStringTag($Symbol, SYMBOL); - -hiddenKeys[HIDDEN] = true; - -},{"../internals/an-object":64,"../internals/array-iteration":68,"../internals/create-non-enumerable-property":81,"../internals/create-property-descriptor":82,"../internals/define-well-known-symbol":85,"../internals/descriptors":86,"../internals/export":97,"../internals/fails":98,"../internals/get-built-in":100,"../internals/global":102,"../internals/has":103,"../internals/hidden-keys":104,"../internals/internal-state":109,"../internals/is-array":111,"../internals/is-object":113,"../internals/is-pure":114,"../internals/is-symbol":115,"../internals/native-symbol":119,"../internals/object-create":122,"../internals/object-define-property":124,"../internals/object-get-own-property-descriptor":125,"../internals/object-get-own-property-names":127,"../internals/object-get-own-property-names-external":126,"../internals/object-get-own-property-symbols":128,"../internals/object-keys":131,"../internals/object-property-is-enumerable":132,"../internals/redefine":137,"../internals/set-to-string-tag":141,"../internals/shared":144,"../internals/shared-key":142,"../internals/to-indexed-object":148,"../internals/to-object":151,"../internals/to-property-key":153,"../internals/to-string":155,"../internals/uid":156,"../internals/well-known-symbol":159,"../internals/well-known-symbol-wrapped":158}],186:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.matchAll` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.matchall -defineWellKnownSymbol('matchAll'); - -},{"../internals/define-well-known-symbol":85}],187:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.match` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.match -defineWellKnownSymbol('match'); - -},{"../internals/define-well-known-symbol":85}],188:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.replace` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.replace -defineWellKnownSymbol('replace'); - -},{"../internals/define-well-known-symbol":85}],189:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.search` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.search -defineWellKnownSymbol('search'); - -},{"../internals/define-well-known-symbol":85}],190:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.species` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.species -defineWellKnownSymbol('species'); - -},{"../internals/define-well-known-symbol":85}],191:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.split` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.split -defineWellKnownSymbol('split'); - -},{"../internals/define-well-known-symbol":85}],192:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.toPrimitive` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.toprimitive -defineWellKnownSymbol('toPrimitive'); - -},{"../internals/define-well-known-symbol":85}],193:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.toStringTag` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.tostringtag -defineWellKnownSymbol('toStringTag'); - -},{"../internals/define-well-known-symbol":85}],194:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.unscopables` well-known symbol -// https://tc39.es/ecma262/#sec-symbol.unscopables -defineWellKnownSymbol('unscopables'); - -},{"../internals/define-well-known-symbol":85}],195:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.asyncDispose` well-known symbol -// https://github.com/tc39/proposal-using-statement -defineWellKnownSymbol('asyncDispose'); - -},{"../internals/define-well-known-symbol":85}],196:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.dispose` well-known symbol -// https://github.com/tc39/proposal-using-statement -defineWellKnownSymbol('dispose'); - -},{"../internals/define-well-known-symbol":85}],197:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.matcher` well-known symbol -// https://github.com/tc39/proposal-pattern-matching -defineWellKnownSymbol('matcher'); - -},{"../internals/define-well-known-symbol":85}],198:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.metadata` well-known symbol -// https://github.com/tc39/proposal-decorators -defineWellKnownSymbol('metadata'); - -},{"../internals/define-well-known-symbol":85}],199:[function(require,module,exports){ -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.observable` well-known symbol -// https://github.com/tc39/proposal-observable -defineWellKnownSymbol('observable'); - -},{"../internals/define-well-known-symbol":85}],200:[function(require,module,exports){ -// TODO: remove from `core-js@4` -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -// `Symbol.patternMatch` well-known symbol -// https://github.com/tc39/proposal-pattern-matching -defineWellKnownSymbol('patternMatch'); - -},{"../internals/define-well-known-symbol":85}],201:[function(require,module,exports){ -// TODO: remove from `core-js@4` -var defineWellKnownSymbol = require('../internals/define-well-known-symbol'); - -defineWellKnownSymbol('replaceAll'); - -},{"../internals/define-well-known-symbol":85}],202:[function(require,module,exports){ -require('./es.array.iterator'); -var DOMIterables = require('../internals/dom-iterables'); -var global = require('../internals/global'); -var classof = require('../internals/classof'); -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var Iterators = require('../internals/iterators'); -var wellKnownSymbol = require('../internals/well-known-symbol'); - -var TO_STRING_TAG = wellKnownSymbol('toStringTag'); - -for (var COLLECTION_NAME in DOMIterables) { - var Collection = global[COLLECTION_NAME]; - var CollectionPrototype = Collection && Collection.prototype; - if (CollectionPrototype && classof(CollectionPrototype) !== TO_STRING_TAG) { - createNonEnumerableProperty(CollectionPrototype, TO_STRING_TAG, COLLECTION_NAME); - } - Iterators[COLLECTION_NAME] = Iterators.Array; -} - -},{"../internals/classof":78,"../internals/create-non-enumerable-property":81,"../internals/dom-iterables":88,"../internals/global":102,"../internals/iterators":118,"../internals/well-known-symbol":159,"./es.array.iterator":166}],203:[function(require,module,exports){ -arguments[4][56][0].apply(exports,arguments) -},{"../../es/array/from":35,"dup":56}],204:[function(require,module,exports){ -arguments[4][57][0].apply(exports,arguments) -},{"../../es/array/is-array":36,"dup":57}],205:[function(require,module,exports){ -var parent = require('../../../es/array/virtual/for-each'); - -module.exports = parent; - -},{"../../../es/array/virtual/for-each":38}],206:[function(require,module,exports){ -var parent = require('../../es/instance/concat'); - -module.exports = parent; - -},{"../../es/instance/concat":44}],207:[function(require,module,exports){ -var parent = require('../../es/instance/flags'); - -module.exports = parent; - -},{"../../es/instance/flags":45}],208:[function(require,module,exports){ -require('../../modules/web.dom-collections.iterator'); -var forEach = require('../array/virtual/for-each'); -var classof = require('../../internals/classof'); -var ArrayPrototype = Array.prototype; - -var DOMIterables = { - DOMTokenList: true, - NodeList: true -}; - -module.exports = function (it) { - var own = it.forEach; - return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.forEach) - // eslint-disable-next-line no-prototype-builtins -- safe - || DOMIterables.hasOwnProperty(classof(it)) ? forEach : own; -}; - -},{"../../internals/classof":78,"../../modules/web.dom-collections.iterator":202,"../array/virtual/for-each":205}],209:[function(require,module,exports){ -var parent = require('../../es/instance/index-of'); - -module.exports = parent; - -},{"../../es/instance/index-of":46}],210:[function(require,module,exports){ -var parent = require('../../es/instance/map'); - -module.exports = parent; - -},{"../../es/instance/map":47}],211:[function(require,module,exports){ -var parent = require('../../es/instance/reduce'); - -module.exports = parent; - -},{"../../es/instance/reduce":48}],212:[function(require,module,exports){ -arguments[4][59][0].apply(exports,arguments) -},{"../../es/instance/slice":49,"dup":59}],213:[function(require,module,exports){ -var parent = require('../../es/instance/sort'); - -module.exports = parent; - -},{"../../es/instance/sort":50}],214:[function(require,module,exports){ -var parent = require('../../es/object/create'); - -module.exports = parent; - -},{"../../es/object/create":51}],215:[function(require,module,exports){ -var parent = require('../../es/object/define-property'); - -module.exports = parent; - -},{"../../es/object/define-property":52}],216:[function(require,module,exports){ -var parent = require('../es/parse-int'); - -module.exports = parent; - -},{"../es/parse-int":53}],217:[function(require,module,exports){ -var parent = require('../../es/symbol'); -require('../../modules/web.dom-collections.iterator'); - -module.exports = parent; - -},{"../../es/symbol":55,"../../modules/web.dom-collections.iterator":202}],218:[function(require,module,exports){ -module.exports = [ - { - 'name': 'C', - 'alias': 'Other', - 'isBmpLast': true, - 'bmp': '\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u0380-\u0383\u038B\u038D\u03A2\u0530\u0557\u0558\u058B\u058C\u0590\u05C8-\u05CF\u05EB-\u05EE\u05F5-\u0605\u061C\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB\u07FC\u082E\u082F\u083F\u085C\u085D\u085F\u086B-\u086F\u088F-\u0897\u08E2\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A77-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0BFF\u0C0D\u0C11\u0C29\u0C3A\u0C3B\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B\u0C5C\u0C5E\u0C5F\u0C64\u0C65\u0C70-\u0C76\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDC\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D50-\u0D53\u0D64\u0D65\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F6\u13F7\u13FE\u13FF\u169D-\u169F\u16F9-\u16FF\u1716-\u171E\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180E\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE\u1AAF\u1ACF-\u1AFF\u1B4D-\u1B4F\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C89-\u1C8F\u1CBB\u1CBC\u1CC8-\u1CCF\u1CFB-\u1CFF\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20C1-\u20CF\u20F1-\u20FF\u218C-\u218F\u2427-\u243F\u244B-\u245F\u2B74\u2B75\u2B96\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E5E-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u3130\u318F\u31E4-\u31EF\u321F\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA6F8-\uA6FF\uA7CB-\uA7CF\uA7D2\uA7D4\uA7DA-\uA7F1\uA82D-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C6-\uA8CD\uA8DA-\uA8DF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB6C-\uAB6F\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC3-\uFBD2\uFD90\uFD91\uFDC8-\uFDCE\uFDD0-\uFDEF\uFE1A-\uFE1F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF', - 'astral': '\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDCFF\uDD03-\uDD06\uDD34-\uDD36\uDD8F\uDD9D-\uDD9F\uDDA1-\uDDCF\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEFC-\uDEFF\uDF24-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDFC4-\uDFC7\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDD6E\uDD7B\uDD8B\uDD93\uDD96\uDDA2\uDDB2\uDDBA\uDDBD-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDF7F\uDF86\uDFB1\uDFBB-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56\uDC9F-\uDCA6\uDCB0-\uDCDF\uDCF3\uDCF6-\uDCFA\uDD1C-\uDD1E\uDD3A-\uDD3E\uDD40-\uDD7F\uDDB8-\uDDBB\uDDD0\uDDD1\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE49-\uDE4F\uDE59-\uDE5F\uDEA0-\uDEBF\uDEE7-\uDEEA\uDEF7-\uDEFF\uDF36-\uDF38\uDF56\uDF57\uDF73-\uDF77\uDF92-\uDF98\uDF9D-\uDFA8\uDFB0-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCF9\uDD28-\uDD2F\uDD3A-\uDE5F\uDE7F\uDEAA\uDEAE\uDEAF\uDEB2-\uDEFF\uDF28-\uDF2F\uDF5A-\uDF6F\uDF8A-\uDFAF\uDFCC-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC4E-\uDC51\uDC76-\uDC7E\uDCBD\uDCC3-\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD48-\uDD4F\uDD77-\uDD7F\uDDE0\uDDF5-\uDDFF\uDE12\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEAA-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC5C\uDC62-\uDC7F\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDDE-\uDDFF\uDE45-\uDE4F\uDE5A-\uDE5F\uDE6D-\uDE7F\uDEBA-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF47-\uDFFF]|\uD806[\uDC3C-\uDC9F\uDCF3-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD47-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE5-\uDDFF\uDE48-\uDE4F\uDEA3-\uDEAF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC46-\uDC4F\uDC6D-\uDC6F\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF9-\uDFAF\uDFB1-\uDFBF\uDFF2-\uDFFE]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F\uDC75-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80E-\uD810\uD812-\uD819\uD824-\uD82A\uD82D\uD82E\uD830-\uD832\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDBFF][\uDC00-\uDFFF]|\uD80B[\uDC00-\uDF8F\uDFF3-\uDFFF]|\uD80D[\uDC2F-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDE6D\uDEBF\uDECA-\uDECF\uDEEE\uDEEF\uDEF6-\uDEFF\uDF46-\uDF4F\uDF5A\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE9B-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82B[\uDC00-\uDFEF\uDFF4\uDFFC\uDFFF]|\uD82C[\uDD23-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A\uDC9B\uDCA0-\uDFFF]|\uD833[\uDC00-\uDEFF\uDF2E\uDF2F\uDF47-\uDF4F\uDFC4-\uDFFF]|\uD834[\uDCF6-\uDCFF\uDD27\uDD28\uDD73-\uDD7A\uDDEB-\uDDFF\uDE46-\uDEDF\uDEF4-\uDEFF\uDF57-\uDF5F\uDF79-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDFCC\uDFCD]|\uD836[\uDE8C-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD837[\uDC00-\uDEFF\uDF1F-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD50-\uDE8F\uDEAF-\uDEBF\uDEFA-\uDEFE\uDF00-\uDFFF]|\uD839[\uDC00-\uDFDF\uDFE7\uDFEC\uDFEF\uDFFF]|\uD83A[\uDCC5\uDCC6\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDD5D\uDD60-\uDFFF]|\uD83B[\uDC00-\uDC70\uDCB5-\uDD00\uDD3E-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDEEF\uDEF2-\uDFFF]|\uD83C[\uDC2C-\uDC2F\uDC94-\uDC9F\uDCAF\uDCB0\uDCC0\uDCD0\uDCF6-\uDCFF\uDDAE-\uDDE5\uDE03-\uDE0F\uDE3C-\uDE3F\uDE49-\uDE4F\uDE52-\uDE5F\uDE66-\uDEFF]|\uD83D[\uDED8-\uDEDC\uDEED-\uDEEF\uDEFD-\uDEFF\uDF74-\uDF7F\uDFD9-\uDFDF\uDFEC-\uDFEF\uDFF1-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE\uDCAF\uDCB2-\uDCFF\uDE54-\uDE5F\uDE6E\uDE6F\uDE75-\uDE77\uDE7D-\uDE7F\uDE87-\uDE8F\uDEAD-\uDEAF\uDEBB-\uDEBF\uDEC6-\uDECF\uDEDA-\uDEDF\uDEE8-\uDEEF\uDEF7-\uDEFF\uDF93\uDFCB-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEE0-\uDEFF]|\uD86D[\uDF39-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00-\uDCFF\uDDF0-\uDFFF]' - }, - { - 'name': 'Cc', - 'alias': 'Control', - 'bmp': '\0-\x1F\x7F-\x9F' - }, - { - 'name': 'Cf', - 'alias': 'Format', - 'bmp': '\xAD\u0600-\u0605\u061C\u06DD\u070F\u0890\u0891\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB', - 'astral': '\uD804[\uDCBD\uDCCD]|\uD80D[\uDC30-\uDC38]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]' - }, - { - 'name': 'Cn', - 'alias': 'Unassigned', - 'bmp': '\u0378\u0379\u0380-\u0383\u038B\u038D\u03A2\u0530\u0557\u0558\u058B\u058C\u0590\u05C8-\u05CF\u05EB-\u05EE\u05F5-\u05FF\u070E\u074B\u074C\u07B2-\u07BF\u07FB\u07FC\u082E\u082F\u083F\u085C\u085D\u085F\u086B-\u086F\u088F\u0892-\u0897\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A77-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0BFF\u0C0D\u0C11\u0C29\u0C3A\u0C3B\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B\u0C5C\u0C5E\u0C5F\u0C64\u0C65\u0C70-\u0C76\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDC\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D50-\u0D53\u0D64\u0D65\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F6\u13F7\u13FE\u13FF\u169D-\u169F\u16F9-\u16FF\u1716-\u171E\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE\u1AAF\u1ACF-\u1AFF\u1B4D-\u1B4F\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C89-\u1C8F\u1CBB\u1CBC\u1CC8-\u1CCF\u1CFB-\u1CFF\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u2065\u2072\u2073\u208F\u209D-\u209F\u20C1-\u20CF\u20F1-\u20FF\u218C-\u218F\u2427-\u243F\u244B-\u245F\u2B74\u2B75\u2B96\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E5E-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u3130\u318F\u31E4-\u31EF\u321F\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA6F8-\uA6FF\uA7CB-\uA7CF\uA7D2\uA7D4\uA7DA-\uA7F1\uA82D-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C6-\uA8CD\uA8DA-\uA8DF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB6C-\uAB6F\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC3-\uFBD2\uFD90\uFD91\uFDC8-\uFDCE\uFDD0-\uFDEF\uFE1A-\uFE1F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD\uFEFE\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFF8\uFFFE\uFFFF', - 'astral': '\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDCFF\uDD03-\uDD06\uDD34-\uDD36\uDD8F\uDD9D-\uDD9F\uDDA1-\uDDCF\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEFC-\uDEFF\uDF24-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDFC4-\uDFC7\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDD6E\uDD7B\uDD8B\uDD93\uDD96\uDDA2\uDDB2\uDDBA\uDDBD-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDF7F\uDF86\uDFB1\uDFBB-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56\uDC9F-\uDCA6\uDCB0-\uDCDF\uDCF3\uDCF6-\uDCFA\uDD1C-\uDD1E\uDD3A-\uDD3E\uDD40-\uDD7F\uDDB8-\uDDBB\uDDD0\uDDD1\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE49-\uDE4F\uDE59-\uDE5F\uDEA0-\uDEBF\uDEE7-\uDEEA\uDEF7-\uDEFF\uDF36-\uDF38\uDF56\uDF57\uDF73-\uDF77\uDF92-\uDF98\uDF9D-\uDFA8\uDFB0-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCF9\uDD28-\uDD2F\uDD3A-\uDE5F\uDE7F\uDEAA\uDEAE\uDEAF\uDEB2-\uDEFF\uDF28-\uDF2F\uDF5A-\uDF6F\uDF8A-\uDFAF\uDFCC-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC4E-\uDC51\uDC76-\uDC7E\uDCC3-\uDCCC\uDCCE\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD48-\uDD4F\uDD77-\uDD7F\uDDE0\uDDF5-\uDDFF\uDE12\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEAA-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC5C\uDC62-\uDC7F\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDDE-\uDDFF\uDE45-\uDE4F\uDE5A-\uDE5F\uDE6D-\uDE7F\uDEBA-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF47-\uDFFF]|\uD806[\uDC3C-\uDC9F\uDCF3-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD47-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE5-\uDDFF\uDE48-\uDE4F\uDEA3-\uDEAF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC46-\uDC4F\uDC6D-\uDC6F\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF9-\uDFAF\uDFB1-\uDFBF\uDFF2-\uDFFE]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F\uDC75-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80E-\uD810\uD812-\uD819\uD824-\uD82A\uD82D\uD82E\uD830-\uD832\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDB7F][\uDC00-\uDFFF]|\uD80B[\uDC00-\uDF8F\uDFF3-\uDFFF]|\uD80D[\uDC2F\uDC39-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDE6D\uDEBF\uDECA-\uDECF\uDEEE\uDEEF\uDEF6-\uDEFF\uDF46-\uDF4F\uDF5A\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE9B-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82B[\uDC00-\uDFEF\uDFF4\uDFFC\uDFFF]|\uD82C[\uDD23-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A\uDC9B\uDCA4-\uDFFF]|\uD833[\uDC00-\uDEFF\uDF2E\uDF2F\uDF47-\uDF4F\uDFC4-\uDFFF]|\uD834[\uDCF6-\uDCFF\uDD27\uDD28\uDDEB-\uDDFF\uDE46-\uDEDF\uDEF4-\uDEFF\uDF57-\uDF5F\uDF79-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDFCC\uDFCD]|\uD836[\uDE8C-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD837[\uDC00-\uDEFF\uDF1F-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD50-\uDE8F\uDEAF-\uDEBF\uDEFA-\uDEFE\uDF00-\uDFFF]|\uD839[\uDC00-\uDFDF\uDFE7\uDFEC\uDFEF\uDFFF]|\uD83A[\uDCC5\uDCC6\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDD5D\uDD60-\uDFFF]|\uD83B[\uDC00-\uDC70\uDCB5-\uDD00\uDD3E-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDEEF\uDEF2-\uDFFF]|\uD83C[\uDC2C-\uDC2F\uDC94-\uDC9F\uDCAF\uDCB0\uDCC0\uDCD0\uDCF6-\uDCFF\uDDAE-\uDDE5\uDE03-\uDE0F\uDE3C-\uDE3F\uDE49-\uDE4F\uDE52-\uDE5F\uDE66-\uDEFF]|\uD83D[\uDED8-\uDEDC\uDEED-\uDEEF\uDEFD-\uDEFF\uDF74-\uDF7F\uDFD9-\uDFDF\uDFEC-\uDFEF\uDFF1-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE\uDCAF\uDCB2-\uDCFF\uDE54-\uDE5F\uDE6E\uDE6F\uDE75-\uDE77\uDE7D-\uDE7F\uDE87-\uDE8F\uDEAD-\uDEAF\uDEBB-\uDEBF\uDEC6-\uDECF\uDEDA-\uDEDF\uDEE8-\uDEEF\uDEF7-\uDEFF\uDF93\uDFCB-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEE0-\uDEFF]|\uD86D[\uDF39-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00\uDC02-\uDC1F\uDC80-\uDCFF\uDDF0-\uDFFF]|[\uDBBF\uDBFF][\uDFFE\uDFFF]' - }, - { - 'name': 'Co', - 'alias': 'Private_Use', - 'bmp': '\uE000-\uF8FF', - 'astral': '[\uDB80-\uDBBE\uDBC0-\uDBFE][\uDC00-\uDFFF]|[\uDBBF\uDBFF][\uDC00-\uDFFD]' - }, - { - 'name': 'Cs', - 'alias': 'Surrogate', - 'bmp': '\uD800-\uDFFF' - }, - { - 'name': 'L', - 'alias': 'Letter', - 'bmp': 'A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', - 'astral': '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF1E]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]' - }, - { - 'name': 'LC', - 'alias': 'Cased_Letter', - 'bmp': 'A-Za-z\xB5\xC0-\xD6\xD8-\xF6\xF8-\u01BA\u01BC-\u01BF\u01C4-\u0293\u0295-\u02AF\u0370-\u0373\u0376\u0377\u037B-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0560-\u0588\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FD-\u10FF\u13A0-\u13F5\u13F8-\u13FD\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2134\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C7B\u2C7E-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA640-\uA66D\uA680-\uA69B\uA722-\uA76F\uA771-\uA787\uA78B-\uA78E\uA790-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F5\uA7F6\uA7FA\uAB30-\uAB5A\uAB60-\uAB68\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF21-\uFF3A\uFF41-\uFF5A', - 'astral': '\uD801[\uDC00-\uDC4F\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC]|\uD803[\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD806[\uDCA0-\uDCDF]|\uD81B[\uDE40-\uDE7F]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF09\uDF0B-\uDF1E]|\uD83A[\uDD00-\uDD43]' - }, - { - 'name': 'Ll', - 'alias': 'Lowercase_Letter', - 'bmp': 'a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD0-\u1FD3\u1FD6\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u210A\u210E\u210F\u2113\u212F\u2134\u2139\u213C\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5F\u2C61\u2C65\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7BB\uA7BD\uA7BF\uA7C1\uA7C3\uA7C8\uA7CA\uA7D1\uA7D3\uA7D5\uA7D7\uA7D9\uA7F6\uA7FA\uAB30-\uAB5A\uAB60-\uAB68\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A', - 'astral': '\uD801[\uDC28-\uDC4F\uDCD8-\uDCFB\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC]|\uD803[\uDCC0-\uDCF2]|\uD806[\uDCC0-\uDCDF]|\uD81B[\uDE60-\uDE7F]|\uD835[\uDC1A-\uDC33\uDC4E-\uDC54\uDC56-\uDC67\uDC82-\uDC9B\uDCB6-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDCEA-\uDD03\uDD1E-\uDD37\uDD52-\uDD6B\uDD86-\uDD9F\uDDBA-\uDDD3\uDDEE-\uDE07\uDE22-\uDE3B\uDE56-\uDE6F\uDE8A-\uDEA5\uDEC2-\uDEDA\uDEDC-\uDEE1\uDEFC-\uDF14\uDF16-\uDF1B\uDF36-\uDF4E\uDF50-\uDF55\uDF70-\uDF88\uDF8A-\uDF8F\uDFAA-\uDFC2\uDFC4-\uDFC9\uDFCB]|\uD837[\uDF00-\uDF09\uDF0B-\uDF1E]|\uD83A[\uDD22-\uDD43]' - }, - { - 'name': 'Lm', - 'alias': 'Modifier_Letter', - 'bmp': '\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5\u06E6\u07F4\u07F5\u07FA\u081A\u0824\u0828\u08C9\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C\uA69D\uA717-\uA71F\uA770\uA788\uA7F2-\uA7F4\uA7F8\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3\uAAF4\uAB5C-\uAB5F\uAB69\uFF70\uFF9E\uFF9F', - 'astral': '\uD801[\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD81A[\uDF40-\uDF43]|\uD81B[\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD838[\uDD37-\uDD3D]|\uD83A\uDD4B' - }, - { - 'name': 'Lo', - 'alias': 'Other_Letter', - 'bmp': '\xAA\xBA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C8\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E45\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4C\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', - 'astral': '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC50-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF4A\uDF50]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD837\uDF0A|\uD838[\uDD00-\uDD2C\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]' - }, - { - 'name': 'Lt', - 'alias': 'Titlecase_Letter', - 'bmp': '\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC' - }, - { - 'name': 'Lu', - 'alias': 'Uppercase_Letter', - 'bmp': 'A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178\u0179\u017B\u017D\u0181\u0182\u0184\u0186\u0187\u0189-\u018B\u018E-\u0191\u0193\u0194\u0196-\u0198\u019C\u019D\u019F\u01A0\u01A2\u01A4\u01A6\u01A7\u01A9\u01AC\u01AE\u01AF\u01B1-\u01B3\u01B5\u01B7\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A\u023B\u023D\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E\u213F\u2145\u2183\u2C00-\u2C2F\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uA7BA\uA7BC\uA7BE\uA7C0\uA7C2\uA7C4-\uA7C7\uA7C9\uA7D0\uA7D6\uA7D8\uA7F5\uFF21-\uFF3A', - 'astral': '\uD801[\uDC00-\uDC27\uDCB0-\uDCD3\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95]|\uD803[\uDC80-\uDCB2]|\uD806[\uDCA0-\uDCBF]|\uD81B[\uDE40-\uDE5F]|\uD835[\uDC00-\uDC19\uDC34-\uDC4D\uDC68-\uDC81\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB5\uDCD0-\uDCE9\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD38\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD6C-\uDD85\uDDA0-\uDDB9\uDDD4-\uDDED\uDE08-\uDE21\uDE3C-\uDE55\uDE70-\uDE89\uDEA8-\uDEC0\uDEE2-\uDEFA\uDF1C-\uDF34\uDF56-\uDF6E\uDF90-\uDFA8\uDFCA]|\uD83A[\uDD00-\uDD21]' - }, - { - 'name': 'M', - 'alias': 'Mark', - 'bmp': '\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C04\u0C3C\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D81-\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA82C\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F', - 'astral': '\uD800[\uDDFD\uDEE0\uDF76-\uDF7A]|\uD802[\uDE01-\uDE03\uDE05\uDE06\uDE0C-\uDE0F\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD803[\uDD24-\uDD27\uDEAB\uDEAC\uDF46-\uDF50\uDF82-\uDF85]|\uD804[\uDC00-\uDC02\uDC38-\uDC46\uDC70\uDC73\uDC74\uDC7F-\uDC82\uDCB0-\uDCBA\uDCC2\uDD00-\uDD02\uDD27-\uDD34\uDD45\uDD46\uDD73\uDD80-\uDD82\uDDB3-\uDDC0\uDDC9-\uDDCC\uDDCE\uDDCF\uDE2C-\uDE37\uDE3E\uDEDF-\uDEEA\uDF00-\uDF03\uDF3B\uDF3C\uDF3E-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF62\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC35-\uDC46\uDC5E\uDCB0-\uDCC3\uDDAF-\uDDB5\uDDB8-\uDDC0\uDDDC\uDDDD\uDE30-\uDE40\uDEAB-\uDEB7\uDF1D-\uDF2B]|\uD806[\uDC2C-\uDC3A\uDD30-\uDD35\uDD37\uDD38\uDD3B-\uDD3E\uDD40\uDD42\uDD43\uDDD1-\uDDD7\uDDDA-\uDDE0\uDDE4\uDE01-\uDE0A\uDE33-\uDE39\uDE3B-\uDE3E\uDE47\uDE51-\uDE5B\uDE8A-\uDE99]|\uD807[\uDC2F-\uDC36\uDC38-\uDC3F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD31-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD45\uDD47\uDD8A-\uDD8E\uDD90\uDD91\uDD93-\uDD97\uDEF3-\uDEF6]|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDF4F\uDF51-\uDF87\uDF8F-\uDF92\uDFE4\uDFF0\uDFF1]|\uD82F[\uDC9D\uDC9E]|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A\uDD30-\uDD36\uDEAE\uDEEC-\uDEEF]|\uD83A[\uDCD0-\uDCD6\uDD44-\uDD4A]|\uDB40[\uDD00-\uDDEF]' - }, - { - 'name': 'Mc', - 'alias': 'Spacing_Mark', - 'bmp': '\u0903\u093B\u093E-\u0940\u0949-\u094C\u094E\u094F\u0982\u0983\u09BE-\u09C0\u09C7\u09C8\u09CB\u09CC\u09D7\u0A03\u0A3E-\u0A40\u0A83\u0ABE-\u0AC0\u0AC9\u0ACB\u0ACC\u0B02\u0B03\u0B3E\u0B40\u0B47\u0B48\u0B4B\u0B4C\u0B57\u0BBE\u0BBF\u0BC1\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD7\u0C01-\u0C03\u0C41-\u0C44\u0C82\u0C83\u0CBE\u0CC0-\u0CC4\u0CC7\u0CC8\u0CCA\u0CCB\u0CD5\u0CD6\u0D02\u0D03\u0D3E-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D57\u0D82\u0D83\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DF2\u0DF3\u0F3E\u0F3F\u0F7F\u102B\u102C\u1031\u1038\u103B\u103C\u1056\u1057\u1062-\u1064\u1067-\u106D\u1083\u1084\u1087-\u108C\u108F\u109A-\u109C\u1715\u1734\u17B6\u17BE-\u17C5\u17C7\u17C8\u1923-\u1926\u1929-\u192B\u1930\u1931\u1933-\u1938\u1A19\u1A1A\u1A55\u1A57\u1A61\u1A63\u1A64\u1A6D-\u1A72\u1B04\u1B35\u1B3B\u1B3D-\u1B41\u1B43\u1B44\u1B82\u1BA1\u1BA6\u1BA7\u1BAA\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2\u1BF3\u1C24-\u1C2B\u1C34\u1C35\u1CE1\u1CF7\u302E\u302F\uA823\uA824\uA827\uA880\uA881\uA8B4-\uA8C3\uA952\uA953\uA983\uA9B4\uA9B5\uA9BA\uA9BB\uA9BE-\uA9C0\uAA2F\uAA30\uAA33\uAA34\uAA4D\uAA7B\uAA7D\uAAEB\uAAEE\uAAEF\uAAF5\uABE3\uABE4\uABE6\uABE7\uABE9\uABEA\uABEC', - 'astral': '\uD804[\uDC00\uDC02\uDC82\uDCB0-\uDCB2\uDCB7\uDCB8\uDD2C\uDD45\uDD46\uDD82\uDDB3-\uDDB5\uDDBF\uDDC0\uDDCE\uDE2C-\uDE2E\uDE32\uDE33\uDE35\uDEE0-\uDEE2\uDF02\uDF03\uDF3E\uDF3F\uDF41-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF62\uDF63]|\uD805[\uDC35-\uDC37\uDC40\uDC41\uDC45\uDCB0-\uDCB2\uDCB9\uDCBB-\uDCBE\uDCC1\uDDAF-\uDDB1\uDDB8-\uDDBB\uDDBE\uDE30-\uDE32\uDE3B\uDE3C\uDE3E\uDEAC\uDEAE\uDEAF\uDEB6\uDF20\uDF21\uDF26]|\uD806[\uDC2C-\uDC2E\uDC38\uDD30-\uDD35\uDD37\uDD38\uDD3D\uDD40\uDD42\uDDD1-\uDDD3\uDDDC-\uDDDF\uDDE4\uDE39\uDE57\uDE58\uDE97]|\uD807[\uDC2F\uDC3E\uDCA9\uDCB1\uDCB4\uDD8A-\uDD8E\uDD93\uDD94\uDD96\uDEF5\uDEF6]|\uD81B[\uDF51-\uDF87\uDFF0\uDFF1]|\uD834[\uDD65\uDD66\uDD6D-\uDD72]' - }, - { - 'name': 'Me', - 'alias': 'Enclosing_Mark', - 'bmp': '\u0488\u0489\u1ABE\u20DD-\u20E0\u20E2-\u20E4\uA670-\uA672' - }, - { - 'name': 'Mn', - 'alias': 'Nonspacing_Mark', - 'bmp': '\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1ABF-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA82C\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F', - 'astral': '\uD800[\uDDFD\uDEE0\uDF76-\uDF7A]|\uD802[\uDE01-\uDE03\uDE05\uDE06\uDE0C-\uDE0F\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD803[\uDD24-\uDD27\uDEAB\uDEAC\uDF46-\uDF50\uDF82-\uDF85]|\uD804[\uDC01\uDC38-\uDC46\uDC70\uDC73\uDC74\uDC7F-\uDC81\uDCB3-\uDCB6\uDCB9\uDCBA\uDCC2\uDD00-\uDD02\uDD27-\uDD2B\uDD2D-\uDD34\uDD73\uDD80\uDD81\uDDB6-\uDDBE\uDDC9-\uDDCC\uDDCF\uDE2F-\uDE31\uDE34\uDE36\uDE37\uDE3E\uDEDF\uDEE3-\uDEEA\uDF00\uDF01\uDF3B\uDF3C\uDF40\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC38-\uDC3F\uDC42-\uDC44\uDC46\uDC5E\uDCB3-\uDCB8\uDCBA\uDCBF\uDCC0\uDCC2\uDCC3\uDDB2-\uDDB5\uDDBC\uDDBD\uDDBF\uDDC0\uDDDC\uDDDD\uDE33-\uDE3A\uDE3D\uDE3F\uDE40\uDEAB\uDEAD\uDEB0-\uDEB5\uDEB7\uDF1D-\uDF1F\uDF22-\uDF25\uDF27-\uDF2B]|\uD806[\uDC2F-\uDC37\uDC39\uDC3A\uDD3B\uDD3C\uDD3E\uDD43\uDDD4-\uDDD7\uDDDA\uDDDB\uDDE0\uDE01-\uDE0A\uDE33-\uDE38\uDE3B-\uDE3E\uDE47\uDE51-\uDE56\uDE59-\uDE5B\uDE8A-\uDE96\uDE98\uDE99]|\uD807[\uDC30-\uDC36\uDC38-\uDC3D\uDC3F\uDC92-\uDCA7\uDCAA-\uDCB0\uDCB2\uDCB3\uDCB5\uDCB6\uDD31-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD45\uDD47\uDD90\uDD91\uDD95\uDD97\uDEF3\uDEF4]|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDF4F\uDF8F-\uDF92\uDFE4]|\uD82F[\uDC9D\uDC9E]|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD67-\uDD69\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A\uDD30-\uDD36\uDEAE\uDEEC-\uDEEF]|\uD83A[\uDCD0-\uDCD6\uDD44-\uDD4A]|\uDB40[\uDD00-\uDDEF]' - }, - { - 'name': 'N', - 'alias': 'Number', - 'bmp': '0-9\xB2\xB3\xB9\xBC-\xBE\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F\u0C78-\u0C7E\u0CE6-\u0CEF\u0D58-\u0D5E\u0D66-\u0D78\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19', - 'astral': '\uD800[\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDEE1-\uDEFB\uDF20-\uDF23\uDF41\uDF4A\uDFD1-\uDFD5]|\uD801[\uDCA0-\uDCA9]|\uD802[\uDC58-\uDC5F\uDC79-\uDC7F\uDCA7-\uDCAF\uDCFB-\uDCFF\uDD16-\uDD1B\uDDBC\uDDBD\uDDC0-\uDDCF\uDDD2-\uDDFF\uDE40-\uDE48\uDE7D\uDE7E\uDE9D-\uDE9F\uDEEB-\uDEEF\uDF58-\uDF5F\uDF78-\uDF7F\uDFA9-\uDFAF]|\uD803[\uDCFA-\uDCFF\uDD30-\uDD39\uDE60-\uDE7E\uDF1D-\uDF26\uDF51-\uDF54\uDFC5-\uDFCB]|\uD804[\uDC52-\uDC6F\uDCF0-\uDCF9\uDD36-\uDD3F\uDDD0-\uDDD9\uDDE1-\uDDF4\uDEF0-\uDEF9]|\uD805[\uDC50-\uDC59\uDCD0-\uDCD9\uDE50-\uDE59\uDEC0-\uDEC9\uDF30-\uDF3B]|\uD806[\uDCE0-\uDCF2\uDD50-\uDD59]|\uD807[\uDC50-\uDC6C\uDD50-\uDD59\uDDA0-\uDDA9\uDFC0-\uDFD4]|\uD809[\uDC00-\uDC6E]|\uD81A[\uDE60-\uDE69\uDEC0-\uDEC9\uDF50-\uDF59\uDF5B-\uDF61]|\uD81B[\uDE80-\uDE96]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDFCE-\uDFFF]|\uD838[\uDD40-\uDD49\uDEF0-\uDEF9]|\uD83A[\uDCC7-\uDCCF\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]' - }, - { - 'name': 'Nd', - 'alias': 'Decimal_Number', - 'bmp': '0-9\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29\u1040-\u1049\u1090-\u1099\u17E0-\u17E9\u1810-\u1819\u1946-\u194F\u19D0-\u19D9\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\uA620-\uA629\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19', - 'astral': '\uD801[\uDCA0-\uDCA9]|\uD803[\uDD30-\uDD39]|\uD804[\uDC66-\uDC6F\uDCF0-\uDCF9\uDD36-\uDD3F\uDDD0-\uDDD9\uDEF0-\uDEF9]|\uD805[\uDC50-\uDC59\uDCD0-\uDCD9\uDE50-\uDE59\uDEC0-\uDEC9\uDF30-\uDF39]|\uD806[\uDCE0-\uDCE9\uDD50-\uDD59]|\uD807[\uDC50-\uDC59\uDD50-\uDD59\uDDA0-\uDDA9]|\uD81A[\uDE60-\uDE69\uDEC0-\uDEC9\uDF50-\uDF59]|\uD835[\uDFCE-\uDFFF]|\uD838[\uDD40-\uDD49\uDEF0-\uDEF9]|\uD83A[\uDD50-\uDD59]|\uD83E[\uDFF0-\uDFF9]' - }, - { - 'name': 'Nl', - 'alias': 'Letter_Number', - 'bmp': '\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF', - 'astral': '\uD800[\uDD40-\uDD74\uDF41\uDF4A\uDFD1-\uDFD5]|\uD809[\uDC00-\uDC6E]' - }, - { - 'name': 'No', - 'alias': 'Other_Number', - 'bmp': '\xB2\xB3\xB9\xBC-\xBE\u09F4-\u09F9\u0B72-\u0B77\u0BF0-\u0BF2\u0C78-\u0C7E\u0D58-\u0D5E\u0D70-\u0D78\u0F2A-\u0F33\u1369-\u137C\u17F0-\u17F9\u19DA\u2070\u2074-\u2079\u2080-\u2089\u2150-\u215F\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA830-\uA835', - 'astral': '\uD800[\uDD07-\uDD33\uDD75-\uDD78\uDD8A\uDD8B\uDEE1-\uDEFB\uDF20-\uDF23]|\uD802[\uDC58-\uDC5F\uDC79-\uDC7F\uDCA7-\uDCAF\uDCFB-\uDCFF\uDD16-\uDD1B\uDDBC\uDDBD\uDDC0-\uDDCF\uDDD2-\uDDFF\uDE40-\uDE48\uDE7D\uDE7E\uDE9D-\uDE9F\uDEEB-\uDEEF\uDF58-\uDF5F\uDF78-\uDF7F\uDFA9-\uDFAF]|\uD803[\uDCFA-\uDCFF\uDE60-\uDE7E\uDF1D-\uDF26\uDF51-\uDF54\uDFC5-\uDFCB]|\uD804[\uDC52-\uDC65\uDDE1-\uDDF4]|\uD805[\uDF3A\uDF3B]|\uD806[\uDCEA-\uDCF2]|\uD807[\uDC5A-\uDC6C\uDFC0-\uDFD4]|\uD81A[\uDF5B-\uDF61]|\uD81B[\uDE80-\uDE96]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD83A[\uDCC7-\uDCCF]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D]|\uD83C[\uDD00-\uDD0C]' - }, - { - 'name': 'P', - 'alias': 'Punctuation', - 'bmp': '!-#%-\\*,-\\/:;\\?@\\[-\\]_\\{\\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061D-\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1B7D\u1B7E\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4F\u2E52-\u2E5D\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65', - 'astral': '\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDEAD\uDF55-\uDF59\uDF86-\uDF89]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5A\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDEB9\uDF3C-\uDF3E]|\uD806[\uDC3B\uDD44-\uDD46\uDDE2\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8\uDFFF]|\uD809[\uDC70-\uDC74]|\uD80B[\uDFF1\uDFF2]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A\uDFE2]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]' - }, - { - 'name': 'Pc', - 'alias': 'Connector_Punctuation', - 'bmp': '_\u203F\u2040\u2054\uFE33\uFE34\uFE4D-\uFE4F\uFF3F' - }, - { - 'name': 'Pd', - 'alias': 'Dash_Punctuation', - 'bmp': '\\-\u058A\u05BE\u1400\u1806\u2010-\u2015\u2E17\u2E1A\u2E3A\u2E3B\u2E40\u2E5D\u301C\u3030\u30A0\uFE31\uFE32\uFE58\uFE63\uFF0D', - 'astral': '\uD803\uDEAD' - }, - { - 'name': 'Pe', - 'alias': 'Close_Punctuation', - 'bmp': '\\)\\]\\}\u0F3B\u0F3D\u169C\u2046\u207E\u208E\u2309\u230B\u232A\u2769\u276B\u276D\u276F\u2771\u2773\u2775\u27C6\u27E7\u27E9\u27EB\u27ED\u27EF\u2984\u2986\u2988\u298A\u298C\u298E\u2990\u2992\u2994\u2996\u2998\u29D9\u29DB\u29FD\u2E23\u2E25\u2E27\u2E29\u2E56\u2E58\u2E5A\u2E5C\u3009\u300B\u300D\u300F\u3011\u3015\u3017\u3019\u301B\u301E\u301F\uFD3E\uFE18\uFE36\uFE38\uFE3A\uFE3C\uFE3E\uFE40\uFE42\uFE44\uFE48\uFE5A\uFE5C\uFE5E\uFF09\uFF3D\uFF5D\uFF60\uFF63' - }, - { - 'name': 'Pf', - 'alias': 'Final_Punctuation', - 'bmp': '\xBB\u2019\u201D\u203A\u2E03\u2E05\u2E0A\u2E0D\u2E1D\u2E21' - }, - { - 'name': 'Pi', - 'alias': 'Initial_Punctuation', - 'bmp': '\xAB\u2018\u201B\u201C\u201F\u2039\u2E02\u2E04\u2E09\u2E0C\u2E1C\u2E20' - }, - { - 'name': 'Po', - 'alias': 'Other_Punctuation', - 'bmp': '!-#%-\'\\*,\\.\\/:;\\?@\\\xA1\xA7\xB6\xB7\xBF\u037E\u0387\u055A-\u055F\u0589\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061D-\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u166E\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u1805\u1807-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1B7D\u1B7E\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2016\u2017\u2020-\u2027\u2030-\u2038\u203B-\u203E\u2041-\u2043\u2047-\u2051\u2053\u2055-\u205E\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00\u2E01\u2E06-\u2E08\u2E0B\u2E0E-\u2E16\u2E18\u2E19\u2E1B\u2E1E\u2E1F\u2E2A-\u2E2E\u2E30-\u2E39\u2E3C-\u2E3F\u2E41\u2E43-\u2E4F\u2E52-\u2E54\u3001-\u3003\u303D\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFE10-\uFE16\uFE19\uFE30\uFE45\uFE46\uFE49-\uFE4C\uFE50-\uFE52\uFE54-\uFE57\uFE5F-\uFE61\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF07\uFF0A\uFF0C\uFF0E\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3C\uFF61\uFF64\uFF65', - 'astral': '\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59\uDF86-\uDF89]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5A\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDEB9\uDF3C-\uDF3E]|\uD806[\uDC3B\uDD44-\uDD46\uDDE2\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8\uDFFF]|\uD809[\uDC70-\uDC74]|\uD80B[\uDFF1\uDFF2]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A\uDFE2]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]' - }, - { - 'name': 'Ps', - 'alias': 'Open_Punctuation', - 'bmp': '\\(\\[\\{\u0F3A\u0F3C\u169B\u201A\u201E\u2045\u207D\u208D\u2308\u230A\u2329\u2768\u276A\u276C\u276E\u2770\u2772\u2774\u27C5\u27E6\u27E8\u27EA\u27EC\u27EE\u2983\u2985\u2987\u2989\u298B\u298D\u298F\u2991\u2993\u2995\u2997\u29D8\u29DA\u29FC\u2E22\u2E24\u2E26\u2E28\u2E42\u2E55\u2E57\u2E59\u2E5B\u3008\u300A\u300C\u300E\u3010\u3014\u3016\u3018\u301A\u301D\uFD3F\uFE17\uFE35\uFE37\uFE39\uFE3B\uFE3D\uFE3F\uFE41\uFE43\uFE47\uFE59\uFE5B\uFE5D\uFF08\uFF3B\uFF5B\uFF5F\uFF62' - }, - { - 'name': 'S', - 'alias': 'Symbol', - 'bmp': '\\$\\+<->\\^`\\|~\xA2-\xA6\xA8\xA9\xAC\xAE-\xB1\xB4\xB8\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0384\u0385\u03F6\u0482\u058D-\u058F\u0606-\u0608\u060B\u060E\u060F\u06DE\u06E9\u06FD\u06FE\u07F6\u07FE\u07FF\u0888\u09F2\u09F3\u09FA\u09FB\u0AF1\u0B70\u0BF3-\u0BFA\u0C7F\u0D4F\u0D79\u0E3F\u0F01-\u0F03\u0F13\u0F15-\u0F17\u0F1A-\u0F1F\u0F34\u0F36\u0F38\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE\u0FCF\u0FD5-\u0FD8\u109E\u109F\u1390-\u1399\u166D\u17DB\u1940\u19DE-\u19FF\u1B61-\u1B6A\u1B74-\u1B7C\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2044\u2052\u207A-\u207C\u208A-\u208C\u20A0-\u20C0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u218A\u218B\u2190-\u2307\u230C-\u2328\u232B-\u2426\u2440-\u244A\u249C-\u24E9\u2500-\u2767\u2794-\u27C4\u27C7-\u27E5\u27F0-\u2982\u2999-\u29D7\u29DC-\u29FB\u29FE-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2E50\u2E51\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3004\u3012\u3013\u3020\u3036\u3037\u303E\u303F\u309B\u309C\u3190\u3191\u3196-\u319F\u31C0-\u31E3\u3200-\u321E\u322A-\u3247\u3250\u3260-\u327F\u328A-\u32B0\u32C0-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA700-\uA716\uA720\uA721\uA789\uA78A\uA828-\uA82B\uA836-\uA839\uAA77-\uAA79\uAB5B\uAB6A\uAB6B\uFB29\uFBB2-\uFBC2\uFD40-\uFD4F\uFDCF\uFDFC-\uFDFF\uFE62\uFE64-\uFE66\uFE69\uFF04\uFF0B\uFF1C-\uFF1E\uFF3E\uFF40\uFF5C\uFF5E\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFFC\uFFFD', - 'astral': '\uD800[\uDD37-\uDD3F\uDD79-\uDD89\uDD8C-\uDD8E\uDD90-\uDD9C\uDDA0\uDDD0-\uDDFC]|\uD802[\uDC77\uDC78\uDEC8]|\uD805\uDF3F|\uD807[\uDFD5-\uDFF1]|\uD81A[\uDF3C-\uDF3F\uDF45]|\uD82F\uDC9C|\uD833[\uDF50-\uDFC3]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD64\uDD6A-\uDD6C\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDEA\uDE00-\uDE41\uDE45\uDF00-\uDF56]|\uD835[\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85\uDE86]|\uD838[\uDD4F\uDEFF]|\uD83B[\uDCAC\uDCB0\uDD2E\uDEF0\uDEF1]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD0D-\uDDAD\uDDE6-\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFF]|\uD83D[\uDC00-\uDED7\uDEDD-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF73\uDF80-\uDFD8\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDE53\uDE60-\uDE6D\uDE70-\uDE74\uDE78-\uDE7C\uDE80-\uDE86\uDE90-\uDEAC\uDEB0-\uDEBA\uDEC0-\uDEC5\uDED0-\uDED9\uDEE0-\uDEE7\uDEF0-\uDEF6\uDF00-\uDF92\uDF94-\uDFCA]' - }, - { - 'name': 'Sc', - 'alias': 'Currency_Symbol', - 'bmp': '\\$\xA2-\xA5\u058F\u060B\u07FE\u07FF\u09F2\u09F3\u09FB\u0AF1\u0BF9\u0E3F\u17DB\u20A0-\u20C0\uA838\uFDFC\uFE69\uFF04\uFFE0\uFFE1\uFFE5\uFFE6', - 'astral': '\uD807[\uDFDD-\uDFE0]|\uD838\uDEFF|\uD83B\uDCB0' - }, - { - 'name': 'Sk', - 'alias': 'Modifier_Symbol', - 'bmp': '\\^`\xA8\xAF\xB4\xB8\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0384\u0385\u0888\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u309B\u309C\uA700-\uA716\uA720\uA721\uA789\uA78A\uAB5B\uAB6A\uAB6B\uFBB2-\uFBC2\uFF3E\uFF40\uFFE3', - 'astral': '\uD83C[\uDFFB-\uDFFF]' - }, - { - 'name': 'Sm', - 'alias': 'Math_Symbol', - 'bmp': '\\+<->\\|~\xAC\xB1\xD7\xF7\u03F6\u0606-\u0608\u2044\u2052\u207A-\u207C\u208A-\u208C\u2118\u2140-\u2144\u214B\u2190-\u2194\u219A\u219B\u21A0\u21A3\u21A6\u21AE\u21CE\u21CF\u21D2\u21D4\u21F4-\u22FF\u2320\u2321\u237C\u239B-\u23B3\u23DC-\u23E1\u25B7\u25C1\u25F8-\u25FF\u266F\u27C0-\u27C4\u27C7-\u27E5\u27F0-\u27FF\u2900-\u2982\u2999-\u29D7\u29DC-\u29FB\u29FE-\u2AFF\u2B30-\u2B44\u2B47-\u2B4C\uFB29\uFE62\uFE64-\uFE66\uFF0B\uFF1C-\uFF1E\uFF5C\uFF5E\uFFE2\uFFE9-\uFFEC', - 'astral': '\uD835[\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3]|\uD83B[\uDEF0\uDEF1]' - }, - { - 'name': 'So', - 'alias': 'Other_Symbol', - 'bmp': '\xA6\xA9\xAE\xB0\u0482\u058D\u058E\u060E\u060F\u06DE\u06E9\u06FD\u06FE\u07F6\u09FA\u0B70\u0BF3-\u0BF8\u0BFA\u0C7F\u0D4F\u0D79\u0F01-\u0F03\u0F13\u0F15-\u0F17\u0F1A-\u0F1F\u0F34\u0F36\u0F38\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE\u0FCF\u0FD5-\u0FD8\u109E\u109F\u1390-\u1399\u166D\u1940\u19DE-\u19FF\u1B61-\u1B6A\u1B74-\u1B7C\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116\u2117\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u214A\u214C\u214D\u214F\u218A\u218B\u2195-\u2199\u219C-\u219F\u21A1\u21A2\u21A4\u21A5\u21A7-\u21AD\u21AF-\u21CD\u21D0\u21D1\u21D3\u21D5-\u21F3\u2300-\u2307\u230C-\u231F\u2322-\u2328\u232B-\u237B\u237D-\u239A\u23B4-\u23DB\u23E2-\u2426\u2440-\u244A\u249C-\u24E9\u2500-\u25B6\u25B8-\u25C0\u25C2-\u25F7\u2600-\u266E\u2670-\u2767\u2794-\u27BF\u2800-\u28FF\u2B00-\u2B2F\u2B45\u2B46\u2B4D-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2E50\u2E51\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3004\u3012\u3013\u3020\u3036\u3037\u303E\u303F\u3190\u3191\u3196-\u319F\u31C0-\u31E3\u3200-\u321E\u322A-\u3247\u3250\u3260-\u327F\u328A-\u32B0\u32C0-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA828-\uA82B\uA836\uA837\uA839\uAA77-\uAA79\uFD40-\uFD4F\uFDCF\uFDFD-\uFDFF\uFFE4\uFFE8\uFFED\uFFEE\uFFFC\uFFFD', - 'astral': '\uD800[\uDD37-\uDD3F\uDD79-\uDD89\uDD8C-\uDD8E\uDD90-\uDD9C\uDDA0\uDDD0-\uDDFC]|\uD802[\uDC77\uDC78\uDEC8]|\uD805\uDF3F|\uD807[\uDFD5-\uDFDC\uDFE1-\uDFF1]|\uD81A[\uDF3C-\uDF3F\uDF45]|\uD82F\uDC9C|\uD833[\uDF50-\uDFC3]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD64\uDD6A-\uDD6C\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDEA\uDE00-\uDE41\uDE45\uDF00-\uDF56]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85\uDE86]|\uD838\uDD4F|\uD83B[\uDCAC\uDD2E]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD0D-\uDDAD\uDDE6-\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFA]|\uD83D[\uDC00-\uDED7\uDEDD-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF73\uDF80-\uDFD8\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDE53\uDE60-\uDE6D\uDE70-\uDE74\uDE78-\uDE7C\uDE80-\uDE86\uDE90-\uDEAC\uDEB0-\uDEBA\uDEC0-\uDEC5\uDED0-\uDED9\uDEE0-\uDEE7\uDEF0-\uDEF6\uDF00-\uDF92\uDF94-\uDFCA]' - }, - { - 'name': 'Z', - 'alias': 'Separator', - 'bmp': ' \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000' - }, - { - 'name': 'Zl', - 'alias': 'Line_Separator', - 'bmp': '\u2028' - }, - { - 'name': 'Zp', - 'alias': 'Paragraph_Separator', - 'bmp': '\u2029' - }, - { - 'name': 'Zs', - 'alias': 'Space_Separator', - 'bmp': ' \xA0\u1680\u2000-\u200A\u202F\u205F\u3000' - } -]; - -},{}],219:[function(require,module,exports){ -module.exports = [ - { - 'name': 'ASCII', - 'bmp': '\0-\x7F' - }, - { - 'name': 'Alphabetic', - 'bmp': 'A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0345\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05B0-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05EF-\u05F2\u0610-\u061A\u0620-\u0657\u0659-\u065F\u066E-\u06D3\u06D5-\u06DC\u06E1-\u06E8\u06ED-\u06EF\u06FA-\u06FC\u06FF\u0710-\u073F\u074D-\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0817\u081A-\u082C\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u08D4-\u08DF\u08E3-\u08E9\u08F0-\u093B\u093D-\u094C\u094E-\u0950\u0955-\u0963\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD-\u09C4\u09C7\u09C8\u09CB\u09CC\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09F0\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A42\u0A47\u0A48\u0A4B\u0A4C\u0A51\u0A59-\u0A5C\u0A5E\u0A70-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD-\u0AC5\u0AC7-\u0AC9\u0ACB\u0ACC\u0AD0\u0AE0-\u0AE3\u0AF9-\u0AFC\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D-\u0B44\u0B47\u0B48\u0B4B\u0B4C\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD0\u0BD7\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4C\u0C55\u0C56\u0C58-\u0C5A\u0C5D\u0C60-\u0C63\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCC\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0-\u0CE3\u0CF1\u0CF2\u0D00-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4C\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D7A-\u0D7F\u0D81-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E46\u0E4D\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0ECD\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F71-\u0F81\u0F88-\u0F97\u0F99-\u0FBC\u1000-\u1036\u1038\u103B-\u103F\u1050-\u108F\u109A-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1713\u171F-\u1733\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17B3\u17B6-\u17C8\u17D7\u17DC\u1820-\u1878\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u1938\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A1B\u1A20-\u1A5E\u1A61-\u1A74\u1AA7\u1ABF\u1AC0\u1ACC-\u1ACE\u1B00-\u1B33\u1B35-\u1B43\u1B45-\u1B4C\u1B80-\u1BA9\u1BAC-\u1BAF\u1BBA-\u1BE5\u1BE7-\u1BF1\u1C00-\u1C36\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1DE7-\u1DF4\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u24B6-\u24E9\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA674-\uA67B\uA67F-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA805\uA807-\uA827\uA840-\uA873\uA880-\uA8C3\uA8C5\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FF\uA90A-\uA92A\uA930-\uA952\uA960-\uA97C\uA980-\uA9B2\uA9B4-\uA9BF\uA9CF\uA9E0-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA60-\uAA76\uAA7A-\uAABE\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF5\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABEA\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', - 'astral': '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD27\uDE80-\uDEA9\uDEAB\uDEAC\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC00-\uDC45\uDC71-\uDC75\uDC82-\uDCB8\uDCC2\uDCD0-\uDCE8\uDD00-\uDD32\uDD44-\uDD47\uDD50-\uDD72\uDD76\uDD80-\uDDBF\uDDC1-\uDDC4\uDDCE\uDDCF\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE34\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEE8\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D-\uDF44\uDF47\uDF48\uDF4B\uDF4C\uDF50\uDF57\uDF5D-\uDF63]|\uD805[\uDC00-\uDC41\uDC43-\uDC45\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCC1\uDCC4\uDCC5\uDCC7\uDD80-\uDDB5\uDDB8-\uDDBE\uDDD8-\uDDDD\uDE00-\uDE3E\uDE40\uDE44\uDE80-\uDEB5\uDEB8\uDF00-\uDF1A\uDF1D-\uDF2A\uDF40-\uDF46]|\uD806[\uDC00-\uDC38\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD35\uDD37\uDD38\uDD3B\uDD3C\uDD3F-\uDD42\uDDA0-\uDDA7\uDDAA-\uDDD7\uDDDA-\uDDDF\uDDE1\uDDE3\uDDE4\uDE00-\uDE32\uDE35-\uDE3E\uDE50-\uDE97\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC3E\uDC40\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD41\uDD43\uDD46\uDD47\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD8E\uDD90\uDD91\uDD93-\uDD96\uDD98\uDEE0-\uDEF6\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF4F-\uDF87\uDF8F-\uDF9F\uDFE0\uDFE1\uDFE3\uDFF0\uDFF1]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9E]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF1E]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD47\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD30-\uDD49\uDD50-\uDD69\uDD70-\uDD89]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]' - }, - { - 'name': 'Any', - 'isBmpLast': true, - 'bmp': '\0-\uFFFF', - 'astral': '[\uD800-\uDBFF][\uDC00-\uDFFF]' - }, - { - 'name': 'Default_Ignorable_Code_Point', - 'bmp': '\xAD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180B-\u180F\u200B-\u200F\u202A-\u202E\u2060-\u206F\u3164\uFE00-\uFE0F\uFEFF\uFFA0\uFFF0-\uFFF8', - 'astral': '\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|[\uDB40-\uDB43][\uDC00-\uDFFF]' - }, - { - 'name': 'Lowercase', - 'bmp': 'a-z\xAA\xB5\xBA\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02B8\u02C0\u02C1\u02E0-\u02E4\u0345\u0371\u0373\u0377\u037A-\u037D\u0390\u03AC-\u03CE\u03D0\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1DBF\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD0-\u1FD3\u1FD6\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u2071\u207F\u2090-\u209C\u210A\u210E\u210F\u2113\u212F\u2134\u2139\u213C\u213D\u2146-\u2149\u214E\u2170-\u217F\u2184\u24D0-\u24E9\u2C30-\u2C5F\u2C61\u2C65\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73\u2C74\u2C76-\u2C7D\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B-\uA69D\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7BB\uA7BD\uA7BF\uA7C1\uA7C3\uA7C8\uA7CA\uA7D1\uA7D3\uA7D5\uA7D7\uA7D9\uA7F6\uA7F8-\uA7FA\uAB30-\uAB5A\uAB5C-\uAB68\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A', - 'astral': '\uD801[\uDC28-\uDC4F\uDCD8-\uDCFB\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC]|\uD803[\uDCC0-\uDCF2]|\uD806[\uDCC0-\uDCDF]|\uD81B[\uDE60-\uDE7F]|\uD835[\uDC1A-\uDC33\uDC4E-\uDC54\uDC56-\uDC67\uDC82-\uDC9B\uDCB6-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDCEA-\uDD03\uDD1E-\uDD37\uDD52-\uDD6B\uDD86-\uDD9F\uDDBA-\uDDD3\uDDEE-\uDE07\uDE22-\uDE3B\uDE56-\uDE6F\uDE8A-\uDEA5\uDEC2-\uDEDA\uDEDC-\uDEE1\uDEFC-\uDF14\uDF16-\uDF1B\uDF36-\uDF4E\uDF50-\uDF55\uDF70-\uDF88\uDF8A-\uDF8F\uDFAA-\uDFC2\uDFC4-\uDFC9\uDFCB]|\uD837[\uDF00-\uDF09\uDF0B-\uDF1E]|\uD83A[\uDD22-\uDD43]' - }, - { - 'name': 'Noncharacter_Code_Point', - 'bmp': '\uFDD0-\uFDEF\uFFFE\uFFFF', - 'astral': '[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]' - }, - { - 'name': 'Uppercase', - 'bmp': 'A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178\u0179\u017B\u017D\u0181\u0182\u0184\u0186\u0187\u0189-\u018B\u018E-\u0191\u0193\u0194\u0196-\u0198\u019C\u019D\u019F\u01A0\u01A2\u01A4\u01A6\u01A7\u01A9\u01AC\u01AE\u01AF\u01B1-\u01B3\u01B5\u01B7\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A\u023B\u023D\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E\u213F\u2145\u2160-\u216F\u2183\u24B6-\u24CF\u2C00-\u2C2F\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uA7BA\uA7BC\uA7BE\uA7C0\uA7C2\uA7C4-\uA7C7\uA7C9\uA7D0\uA7D6\uA7D8\uA7F5\uFF21-\uFF3A', - 'astral': '\uD801[\uDC00-\uDC27\uDCB0-\uDCD3\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95]|\uD803[\uDC80-\uDCB2]|\uD806[\uDCA0-\uDCBF]|\uD81B[\uDE40-\uDE5F]|\uD835[\uDC00-\uDC19\uDC34-\uDC4D\uDC68-\uDC81\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB5\uDCD0-\uDCE9\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD38\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD6C-\uDD85\uDDA0-\uDDB9\uDDD4-\uDDED\uDE08-\uDE21\uDE3C-\uDE55\uDE70-\uDE89\uDEA8-\uDEC0\uDEE2-\uDEFA\uDF1C-\uDF34\uDF56-\uDF6E\uDF90-\uDFA8\uDFCA]|\uD83A[\uDD00-\uDD21]|\uD83C[\uDD30-\uDD49\uDD50-\uDD69\uDD70-\uDD89]' - }, - { - 'name': 'White_Space', - 'bmp': '\t-\r \x85\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000' - } -]; - -},{}],220:[function(require,module,exports){ -module.exports = [ - { - 'name': 'Adlam', - 'astral': '\uD83A[\uDD00-\uDD4B\uDD50-\uDD59\uDD5E\uDD5F]' - }, - { - 'name': 'Ahom', - 'astral': '\uD805[\uDF00-\uDF1A\uDF1D-\uDF2B\uDF30-\uDF46]' - }, - { - 'name': 'Anatolian_Hieroglyphs', - 'astral': '\uD811[\uDC00-\uDE46]' - }, - { - 'name': 'Arabic', - 'bmp': '\u0600-\u0604\u0606-\u060B\u060D-\u061A\u061C-\u061E\u0620-\u063F\u0641-\u064A\u0656-\u066F\u0671-\u06DC\u06DE-\u06FF\u0750-\u077F\u0870-\u088E\u0890\u0891\u0898-\u08E1\u08E3-\u08FF\uFB50-\uFBC2\uFBD3-\uFD3D\uFD40-\uFD8F\uFD92-\uFDC7\uFDCF\uFDF0-\uFDFF\uFE70-\uFE74\uFE76-\uFEFC', - 'astral': '\uD803[\uDE60-\uDE7E]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB\uDEF0\uDEF1]' - }, - { - 'name': 'Armenian', - 'bmp': '\u0531-\u0556\u0559-\u058A\u058D-\u058F\uFB13-\uFB17' - }, - { - 'name': 'Avestan', - 'astral': '\uD802[\uDF00-\uDF35\uDF39-\uDF3F]' - }, - { - 'name': 'Balinese', - 'bmp': '\u1B00-\u1B4C\u1B50-\u1B7E' - }, - { - 'name': 'Bamum', - 'bmp': '\uA6A0-\uA6F7', - 'astral': '\uD81A[\uDC00-\uDE38]' - }, - { - 'name': 'Bassa_Vah', - 'astral': '\uD81A[\uDED0-\uDEED\uDEF0-\uDEF5]' - }, - { - 'name': 'Batak', - 'bmp': '\u1BC0-\u1BF3\u1BFC-\u1BFF' - }, - { - 'name': 'Bengali', - 'bmp': '\u0980-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09FE' - }, - { - 'name': 'Bhaiksuki', - 'astral': '\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC45\uDC50-\uDC6C]' - }, - { - 'name': 'Bopomofo', - 'bmp': '\u02EA\u02EB\u3105-\u312F\u31A0-\u31BF' - }, - { - 'name': 'Brahmi', - 'astral': '\uD804[\uDC00-\uDC4D\uDC52-\uDC75\uDC7F]' - }, - { - 'name': 'Braille', - 'bmp': '\u2800-\u28FF' - }, - { - 'name': 'Buginese', - 'bmp': '\u1A00-\u1A1B\u1A1E\u1A1F' - }, - { - 'name': 'Buhid', - 'bmp': '\u1740-\u1753' - }, - { - 'name': 'Canadian_Aboriginal', - 'bmp': '\u1400-\u167F\u18B0-\u18F5', - 'astral': '\uD806[\uDEB0-\uDEBF]' - }, - { - 'name': 'Carian', - 'astral': '\uD800[\uDEA0-\uDED0]' - }, - { - 'name': 'Caucasian_Albanian', - 'astral': '\uD801[\uDD30-\uDD63\uDD6F]' - }, - { - 'name': 'Chakma', - 'astral': '\uD804[\uDD00-\uDD34\uDD36-\uDD47]' - }, - { - 'name': 'Cham', - 'bmp': '\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F' - }, - { - 'name': 'Cherokee', - 'bmp': '\u13A0-\u13F5\u13F8-\u13FD\uAB70-\uABBF' - }, - { - 'name': 'Chorasmian', - 'astral': '\uD803[\uDFB0-\uDFCB]' - }, - { - 'name': 'Common', - 'bmp': '\0-@\\[-`\\{-\xA9\xAB-\xB9\xBB-\xBF\xD7\xF7\u02B9-\u02DF\u02E5-\u02E9\u02EC-\u02FF\u0374\u037E\u0385\u0387\u0605\u060C\u061B\u061F\u0640\u06DD\u08E2\u0964\u0965\u0E3F\u0FD5-\u0FD8\u10FB\u16EB-\u16ED\u1735\u1736\u1802\u1803\u1805\u1CD3\u1CE1\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5-\u1CF7\u1CFA\u2000-\u200B\u200E-\u2064\u2066-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20C0\u2100-\u2125\u2127-\u2129\u212C-\u2131\u2133-\u214D\u214F-\u215F\u2189-\u218B\u2190-\u2426\u2440-\u244A\u2460-\u27FF\u2900-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2E00-\u2E5D\u2FF0-\u2FFB\u3000-\u3004\u3006\u3008-\u3020\u3030-\u3037\u303C-\u303F\u309B\u309C\u30A0\u30FB\u30FC\u3190-\u319F\u31C0-\u31E3\u3220-\u325F\u327F-\u32CF\u32FF\u3358-\u33FF\u4DC0-\u4DFF\uA700-\uA721\uA788-\uA78A\uA830-\uA839\uA92E\uA9CF\uAB5B\uAB6A\uAB6B\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFF70\uFF9E\uFF9F\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD', - 'astral': '\uD800[\uDD00-\uDD02\uDD07-\uDD33\uDD37-\uDD3F\uDD90-\uDD9C\uDDD0-\uDDFC\uDEE1-\uDEFB]|\uD82F[\uDCA0-\uDCA3]|\uD833[\uDF50-\uDFC3]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD66\uDD6A-\uDD7A\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDEA\uDEE0-\uDEF3\uDF00-\uDF56\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDFCB\uDFCE-\uDFFF]|\uD83B[\uDC71-\uDCB4\uDD01-\uDD3D]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD00-\uDDAD\uDDE6-\uDDFF\uDE01\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFF]|\uD83D[\uDC00-\uDED7\uDEDD-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF73\uDF80-\uDFD8\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDE53\uDE60-\uDE6D\uDE70-\uDE74\uDE78-\uDE7C\uDE80-\uDE86\uDE90-\uDEAC\uDEB0-\uDEBA\uDEC0-\uDEC5\uDED0-\uDED9\uDEE0-\uDEE7\uDEF0-\uDEF6\uDF00-\uDF92\uDF94-\uDFCA\uDFF0-\uDFF9]|\uDB40[\uDC01\uDC20-\uDC7F]' - }, - { - 'name': 'Coptic', - 'bmp': '\u03E2-\u03EF\u2C80-\u2CF3\u2CF9-\u2CFF' - }, - { - 'name': 'Cuneiform', - 'astral': '\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC70-\uDC74\uDC80-\uDD43]' - }, - { - 'name': 'Cypriot', - 'astral': '\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F]' - }, - { - 'name': 'Cypro_Minoan', - 'astral': '\uD80B[\uDF90-\uDFF2]' - }, - { - 'name': 'Cyrillic', - 'bmp': '\u0400-\u0484\u0487-\u052F\u1C80-\u1C88\u1D2B\u1D78\u2DE0-\u2DFF\uA640-\uA69F\uFE2E\uFE2F' - }, - { - 'name': 'Deseret', - 'astral': '\uD801[\uDC00-\uDC4F]' - }, - { - 'name': 'Devanagari', - 'bmp': '\u0900-\u0950\u0955-\u0963\u0966-\u097F\uA8E0-\uA8FF' - }, - { - 'name': 'Dives_Akuru', - 'astral': '\uD806[\uDD00-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD35\uDD37\uDD38\uDD3B-\uDD46\uDD50-\uDD59]' - }, - { - 'name': 'Dogra', - 'astral': '\uD806[\uDC00-\uDC3B]' - }, - { - 'name': 'Duployan', - 'astral': '\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9C-\uDC9F]' - }, - { - 'name': 'Egyptian_Hieroglyphs', - 'astral': '\uD80C[\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E\uDC30-\uDC38]' - }, - { - 'name': 'Elbasan', - 'astral': '\uD801[\uDD00-\uDD27]' - }, - { - 'name': 'Elymaic', - 'astral': '\uD803[\uDFE0-\uDFF6]' - }, - { - 'name': 'Ethiopic', - 'bmp': '\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u137C\u1380-\u1399\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E', - 'astral': '\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]' - }, - { - 'name': 'Georgian', - 'bmp': '\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u10FF\u1C90-\u1CBA\u1CBD-\u1CBF\u2D00-\u2D25\u2D27\u2D2D' - }, - { - 'name': 'Glagolitic', - 'bmp': '\u2C00-\u2C5F', - 'astral': '\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]' - }, - { - 'name': 'Gothic', - 'astral': '\uD800[\uDF30-\uDF4A]' - }, - { - 'name': 'Grantha', - 'astral': '\uD804[\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]' - }, - { - 'name': 'Greek', - 'bmp': '\u0370-\u0373\u0375-\u0377\u037A-\u037D\u037F\u0384\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03E1\u03F0-\u03FF\u1D26-\u1D2A\u1D5D-\u1D61\u1D66-\u1D6A\u1DBF\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FC4\u1FC6-\u1FD3\u1FD6-\u1FDB\u1FDD-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFE\u2126\uAB65', - 'astral': '\uD800[\uDD40-\uDD8E\uDDA0]|\uD834[\uDE00-\uDE45]' - }, - { - 'name': 'Gujarati', - 'bmp': '\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AF1\u0AF9-\u0AFF' - }, - { - 'name': 'Gunjala_Gondi', - 'astral': '\uD807[\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD8E\uDD90\uDD91\uDD93-\uDD98\uDDA0-\uDDA9]' - }, - { - 'name': 'Gurmukhi', - 'bmp': '\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A76' - }, - { - 'name': 'Han', - 'bmp': '\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u3005\u3007\u3021-\u3029\u3038-\u303B\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFA6D\uFA70-\uFAD9', - 'astral': '\uD81B[\uDFE2\uDFE3\uDFF0\uDFF1]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]' - }, - { - 'name': 'Hangul', - 'bmp': '\u1100-\u11FF\u302E\u302F\u3131-\u318E\u3200-\u321E\u3260-\u327E\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC' - }, - { - 'name': 'Hanifi_Rohingya', - 'astral': '\uD803[\uDD00-\uDD27\uDD30-\uDD39]' - }, - { - 'name': 'Hanunoo', - 'bmp': '\u1720-\u1734' - }, - { - 'name': 'Hatran', - 'astral': '\uD802[\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDCFF]' - }, - { - 'name': 'Hebrew', - 'bmp': '\u0591-\u05C7\u05D0-\u05EA\u05EF-\u05F4\uFB1D-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFB4F' - }, - { - 'name': 'Hiragana', - 'bmp': '\u3041-\u3096\u309D-\u309F', - 'astral': '\uD82C[\uDC01-\uDD1F\uDD50-\uDD52]|\uD83C\uDE00' - }, - { - 'name': 'Imperial_Aramaic', - 'astral': '\uD802[\uDC40-\uDC55\uDC57-\uDC5F]' - }, - { - 'name': 'Inherited', - 'bmp': '\u0300-\u036F\u0485\u0486\u064B-\u0655\u0670\u0951-\u0954\u1AB0-\u1ACE\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u200C\u200D\u20D0-\u20F0\u302A-\u302D\u3099\u309A\uFE00-\uFE0F\uFE20-\uFE2D', - 'astral': '\uD800[\uDDFD\uDEE0]|\uD804\uDF3B|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD67-\uDD69\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD]|\uDB40[\uDD00-\uDDEF]' - }, - { - 'name': 'Inscriptional_Pahlavi', - 'astral': '\uD802[\uDF60-\uDF72\uDF78-\uDF7F]' - }, - { - 'name': 'Inscriptional_Parthian', - 'astral': '\uD802[\uDF40-\uDF55\uDF58-\uDF5F]' - }, - { - 'name': 'Javanese', - 'bmp': '\uA980-\uA9CD\uA9D0-\uA9D9\uA9DE\uA9DF' - }, - { - 'name': 'Kaithi', - 'astral': '\uD804[\uDC80-\uDCC2\uDCCD]' - }, - { - 'name': 'Kannada', - 'bmp': '\u0C80-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2' - }, - { - 'name': 'Katakana', - 'bmp': '\u30A1-\u30FA\u30FD-\u30FF\u31F0-\u31FF\u32D0-\u32FE\u3300-\u3357\uFF66-\uFF6F\uFF71-\uFF9D', - 'astral': '\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00\uDD20-\uDD22\uDD64-\uDD67]' - }, - { - 'name': 'Kayah_Li', - 'bmp': '\uA900-\uA92D\uA92F' - }, - { - 'name': 'Kharoshthi', - 'astral': '\uD802[\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE38-\uDE3A\uDE3F-\uDE48\uDE50-\uDE58]' - }, - { - 'name': 'Khitan_Small_Script', - 'astral': '\uD81B\uDFE4|\uD822[\uDF00-\uDFFF]|\uD823[\uDC00-\uDCD5]' - }, - { - 'name': 'Khmer', - 'bmp': '\u1780-\u17DD\u17E0-\u17E9\u17F0-\u17F9\u19E0-\u19FF' - }, - { - 'name': 'Khojki', - 'astral': '\uD804[\uDE00-\uDE11\uDE13-\uDE3E]' - }, - { - 'name': 'Khudawadi', - 'astral': '\uD804[\uDEB0-\uDEEA\uDEF0-\uDEF9]' - }, - { - 'name': 'Lao', - 'bmp': '\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF' - }, - { - 'name': 'Latin', - 'bmp': 'A-Za-z\xAA\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02B8\u02E0-\u02E4\u1D00-\u1D25\u1D2C-\u1D5C\u1D62-\u1D65\u1D6B-\u1D77\u1D79-\u1DBE\u1E00-\u1EFF\u2071\u207F\u2090-\u209C\u212A\u212B\u2132\u214E\u2160-\u2188\u2C60-\u2C7F\uA722-\uA787\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA7FF\uAB30-\uAB5A\uAB5C-\uAB64\uAB66-\uAB69\uFB00-\uFB06\uFF21-\uFF3A\uFF41-\uFF5A', - 'astral': '\uD801[\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD837[\uDF00-\uDF1E]' - }, - { - 'name': 'Lepcha', - 'bmp': '\u1C00-\u1C37\u1C3B-\u1C49\u1C4D-\u1C4F' - }, - { - 'name': 'Limbu', - 'bmp': '\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F' - }, - { - 'name': 'Linear_A', - 'astral': '\uD801[\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]' - }, - { - 'name': 'Linear_B', - 'astral': '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA]' - }, - { - 'name': 'Lisu', - 'bmp': '\uA4D0-\uA4FF', - 'astral': '\uD807\uDFB0' - }, - { - 'name': 'Lycian', - 'astral': '\uD800[\uDE80-\uDE9C]' - }, - { - 'name': 'Lydian', - 'astral': '\uD802[\uDD20-\uDD39\uDD3F]' - }, - { - 'name': 'Mahajani', - 'astral': '\uD804[\uDD50-\uDD76]' - }, - { - 'name': 'Makasar', - 'astral': '\uD807[\uDEE0-\uDEF8]' - }, - { - 'name': 'Malayalam', - 'bmp': '\u0D00-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4F\u0D54-\u0D63\u0D66-\u0D7F' - }, - { - 'name': 'Mandaic', - 'bmp': '\u0840-\u085B\u085E' - }, - { - 'name': 'Manichaean', - 'astral': '\uD802[\uDEC0-\uDEE6\uDEEB-\uDEF6]' - }, - { - 'name': 'Marchen', - 'astral': '\uD807[\uDC70-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6]' - }, - { - 'name': 'Masaram_Gondi', - 'astral': '\uD807[\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59]' - }, - { - 'name': 'Medefaidrin', - 'astral': '\uD81B[\uDE40-\uDE9A]' - }, - { - 'name': 'Meetei_Mayek', - 'bmp': '\uAAE0-\uAAF6\uABC0-\uABED\uABF0-\uABF9' - }, - { - 'name': 'Mende_Kikakui', - 'astral': '\uD83A[\uDC00-\uDCC4\uDCC7-\uDCD6]' - }, - { - 'name': 'Meroitic_Cursive', - 'astral': '\uD802[\uDDA0-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDDFF]' - }, - { - 'name': 'Meroitic_Hieroglyphs', - 'astral': '\uD802[\uDD80-\uDD9F]' - }, - { - 'name': 'Miao', - 'astral': '\uD81B[\uDF00-\uDF4A\uDF4F-\uDF87\uDF8F-\uDF9F]' - }, - { - 'name': 'Modi', - 'astral': '\uD805[\uDE00-\uDE44\uDE50-\uDE59]' - }, - { - 'name': 'Mongolian', - 'bmp': '\u1800\u1801\u1804\u1806-\u1819\u1820-\u1878\u1880-\u18AA', - 'astral': '\uD805[\uDE60-\uDE6C]' - }, - { - 'name': 'Mro', - 'astral': '\uD81A[\uDE40-\uDE5E\uDE60-\uDE69\uDE6E\uDE6F]' - }, - { - 'name': 'Multani', - 'astral': '\uD804[\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA9]' - }, - { - 'name': 'Myanmar', - 'bmp': '\u1000-\u109F\uA9E0-\uA9FE\uAA60-\uAA7F' - }, - { - 'name': 'Nabataean', - 'astral': '\uD802[\uDC80-\uDC9E\uDCA7-\uDCAF]' - }, - { - 'name': 'Nandinagari', - 'astral': '\uD806[\uDDA0-\uDDA7\uDDAA-\uDDD7\uDDDA-\uDDE4]' - }, - { - 'name': 'New_Tai_Lue', - 'bmp': '\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u19DE\u19DF' - }, - { - 'name': 'Newa', - 'astral': '\uD805[\uDC00-\uDC5B\uDC5D-\uDC61]' - }, - { - 'name': 'Nko', - 'bmp': '\u07C0-\u07FA\u07FD-\u07FF' - }, - { - 'name': 'Nushu', - 'astral': '\uD81B\uDFE1|\uD82C[\uDD70-\uDEFB]' - }, - { - 'name': 'Nyiakeng_Puachue_Hmong', - 'astral': '\uD838[\uDD00-\uDD2C\uDD30-\uDD3D\uDD40-\uDD49\uDD4E\uDD4F]' - }, - { - 'name': 'Ogham', - 'bmp': '\u1680-\u169C' - }, - { - 'name': 'Ol_Chiki', - 'bmp': '\u1C50-\u1C7F' - }, - { - 'name': 'Old_Hungarian', - 'astral': '\uD803[\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDCFF]' - }, - { - 'name': 'Old_Italic', - 'astral': '\uD800[\uDF00-\uDF23\uDF2D-\uDF2F]' - }, - { - 'name': 'Old_North_Arabian', - 'astral': '\uD802[\uDE80-\uDE9F]' - }, - { - 'name': 'Old_Permic', - 'astral': '\uD800[\uDF50-\uDF7A]' - }, - { - 'name': 'Old_Persian', - 'astral': '\uD800[\uDFA0-\uDFC3\uDFC8-\uDFD5]' - }, - { - 'name': 'Old_Sogdian', - 'astral': '\uD803[\uDF00-\uDF27]' - }, - { - 'name': 'Old_South_Arabian', - 'astral': '\uD802[\uDE60-\uDE7F]' - }, - { - 'name': 'Old_Turkic', - 'astral': '\uD803[\uDC00-\uDC48]' - }, - { - 'name': 'Old_Uyghur', - 'astral': '\uD803[\uDF70-\uDF89]' - }, - { - 'name': 'Oriya', - 'bmp': '\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B77' - }, - { - 'name': 'Osage', - 'astral': '\uD801[\uDCB0-\uDCD3\uDCD8-\uDCFB]' - }, - { - 'name': 'Osmanya', - 'astral': '\uD801[\uDC80-\uDC9D\uDCA0-\uDCA9]' - }, - { - 'name': 'Pahawh_Hmong', - 'astral': '\uD81A[\uDF00-\uDF45\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]' - }, - { - 'name': 'Palmyrene', - 'astral': '\uD802[\uDC60-\uDC7F]' - }, - { - 'name': 'Pau_Cin_Hau', - 'astral': '\uD806[\uDEC0-\uDEF8]' - }, - { - 'name': 'Phags_Pa', - 'bmp': '\uA840-\uA877' - }, - { - 'name': 'Phoenician', - 'astral': '\uD802[\uDD00-\uDD1B\uDD1F]' - }, - { - 'name': 'Psalter_Pahlavi', - 'astral': '\uD802[\uDF80-\uDF91\uDF99-\uDF9C\uDFA9-\uDFAF]' - }, - { - 'name': 'Rejang', - 'bmp': '\uA930-\uA953\uA95F' - }, - { - 'name': 'Runic', - 'bmp': '\u16A0-\u16EA\u16EE-\u16F8' - }, - { - 'name': 'Samaritan', - 'bmp': '\u0800-\u082D\u0830-\u083E' - }, - { - 'name': 'Saurashtra', - 'bmp': '\uA880-\uA8C5\uA8CE-\uA8D9' - }, - { - 'name': 'Sharada', - 'astral': '\uD804[\uDD80-\uDDDF]' - }, - { - 'name': 'Shavian', - 'astral': '\uD801[\uDC50-\uDC7F]' - }, - { - 'name': 'Siddham', - 'astral': '\uD805[\uDD80-\uDDB5\uDDB8-\uDDDD]' - }, - { - 'name': 'SignWriting', - 'astral': '\uD836[\uDC00-\uDE8B\uDE9B-\uDE9F\uDEA1-\uDEAF]' - }, - { - 'name': 'Sinhala', - 'bmp': '\u0D81-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4', - 'astral': '\uD804[\uDDE1-\uDDF4]' - }, - { - 'name': 'Sogdian', - 'astral': '\uD803[\uDF30-\uDF59]' - }, - { - 'name': 'Sora_Sompeng', - 'astral': '\uD804[\uDCD0-\uDCE8\uDCF0-\uDCF9]' - }, - { - 'name': 'Soyombo', - 'astral': '\uD806[\uDE50-\uDEA2]' - }, - { - 'name': 'Sundanese', - 'bmp': '\u1B80-\u1BBF\u1CC0-\u1CC7' - }, - { - 'name': 'Syloti_Nagri', - 'bmp': '\uA800-\uA82C' - }, - { - 'name': 'Syriac', - 'bmp': '\u0700-\u070D\u070F-\u074A\u074D-\u074F\u0860-\u086A' - }, - { - 'name': 'Tagalog', - 'bmp': '\u1700-\u1715\u171F' - }, - { - 'name': 'Tagbanwa', - 'bmp': '\u1760-\u176C\u176E-\u1770\u1772\u1773' - }, - { - 'name': 'Tai_Le', - 'bmp': '\u1950-\u196D\u1970-\u1974' - }, - { - 'name': 'Tai_Tham', - 'bmp': '\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD' - }, - { - 'name': 'Tai_Viet', - 'bmp': '\uAA80-\uAAC2\uAADB-\uAADF' - }, - { - 'name': 'Takri', - 'astral': '\uD805[\uDE80-\uDEB9\uDEC0-\uDEC9]' - }, - { - 'name': 'Tamil', - 'bmp': '\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BFA', - 'astral': '\uD807[\uDFC0-\uDFF1\uDFFF]' - }, - { - 'name': 'Tangsa', - 'astral': '\uD81A[\uDE70-\uDEBE\uDEC0-\uDEC9]' - }, - { - 'name': 'Tangut', - 'astral': '\uD81B\uDFE0|[\uD81C-\uD820][\uDC00-\uDFFF]|\uD821[\uDC00-\uDFF7]|\uD822[\uDC00-\uDEFF]|\uD823[\uDD00-\uDD08]' - }, - { - 'name': 'Telugu', - 'bmp': '\u0C00-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3C-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C5D\u0C60-\u0C63\u0C66-\u0C6F\u0C77-\u0C7F' - }, - { - 'name': 'Thaana', - 'bmp': '\u0780-\u07B1' - }, - { - 'name': 'Thai', - 'bmp': '\u0E01-\u0E3A\u0E40-\u0E5B' - }, - { - 'name': 'Tibetan', - 'bmp': '\u0F00-\u0F47\u0F49-\u0F6C\u0F71-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u0FD9\u0FDA' - }, - { - 'name': 'Tifinagh', - 'bmp': '\u2D30-\u2D67\u2D6F\u2D70\u2D7F' - }, - { - 'name': 'Tirhuta', - 'astral': '\uD805[\uDC80-\uDCC7\uDCD0-\uDCD9]' - }, - { - 'name': 'Toto', - 'astral': '\uD838[\uDE90-\uDEAE]' - }, - { - 'name': 'Ugaritic', - 'astral': '\uD800[\uDF80-\uDF9D\uDF9F]' - }, - { - 'name': 'Vai', - 'bmp': '\uA500-\uA62B' - }, - { - 'name': 'Vithkuqi', - 'astral': '\uD801[\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC]' - }, - { - 'name': 'Wancho', - 'astral': '\uD838[\uDEC0-\uDEF9\uDEFF]' - }, - { - 'name': 'Warang_Citi', - 'astral': '\uD806[\uDCA0-\uDCF2\uDCFF]' - }, - { - 'name': 'Yezidi', - 'astral': '\uD803[\uDE80-\uDEA9\uDEAB-\uDEAD\uDEB0\uDEB1]' - }, - { - 'name': 'Yi', - 'bmp': '\uA000-\uA48C\uA490-\uA4C6' - }, - { - 'name': 'Zanabazar_Square', - 'astral': '\uD806[\uDE00-\uDE47]' - } -]; - -},{}]},{},[7])(7) -}); + }, + { + '../../internals/classof': 78, + '../../modules/web.dom-collections.iterator': 202, + '../array/virtual/for-each': 205, + }, + ], + 209: [ + function (require, module, exports) { + var parent = require('../../es/instance/index-of') + + module.exports = parent + }, + { '../../es/instance/index-of': 46 }, + ], + 210: [ + function (require, module, exports) { + var parent = require('../../es/instance/map') + + module.exports = parent + }, + { '../../es/instance/map': 47 }, + ], + 211: [ + function (require, module, exports) { + var parent = require('../../es/instance/reduce') + + module.exports = parent + }, + { '../../es/instance/reduce': 48 }, + ], + 212: [ + function (require, module, exports) { + arguments[4][59][0].apply(exports, arguments) + }, + { '../../es/instance/slice': 49, dup: 59 }, + ], + 213: [ + function (require, module, exports) { + var parent = require('../../es/instance/sort') + + module.exports = parent + }, + { '../../es/instance/sort': 50 }, + ], + 214: [ + function (require, module, exports) { + var parent = require('../../es/object/create') + + module.exports = parent + }, + { '../../es/object/create': 51 }, + ], + 215: [ + function (require, module, exports) { + var parent = require('../../es/object/define-property') + + module.exports = parent + }, + { '../../es/object/define-property': 52 }, + ], + 216: [ + function (require, module, exports) { + var parent = require('../es/parse-int') + + module.exports = parent + }, + { '../es/parse-int': 53 }, + ], + 217: [ + function (require, module, exports) { + var parent = require('../../es/symbol') + require('../../modules/web.dom-collections.iterator') + + module.exports = parent + }, + { '../../es/symbol': 55, '../../modules/web.dom-collections.iterator': 202 }, + ], + 218: [ + function (require, module, exports) { + module.exports = [ + { + name: 'C', + alias: 'Other', + isBmpLast: true, + bmp: '\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u0380-\u0383\u038B\u038D\u03A2\u0530\u0557\u0558\u058B\u058C\u0590\u05C8-\u05CF\u05EB-\u05EE\u05F5-\u0605\u061C\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB\u07FC\u082E\u082F\u083F\u085C\u085D\u085F\u086B-\u086F\u088F-\u0897\u08E2\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A77-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0BFF\u0C0D\u0C11\u0C29\u0C3A\u0C3B\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B\u0C5C\u0C5E\u0C5F\u0C64\u0C65\u0C70-\u0C76\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDC\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D50-\u0D53\u0D64\u0D65\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F6\u13F7\u13FE\u13FF\u169D-\u169F\u16F9-\u16FF\u1716-\u171E\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180E\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE\u1AAF\u1ACF-\u1AFF\u1B4D-\u1B4F\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C89-\u1C8F\u1CBB\u1CBC\u1CC8-\u1CCF\u1CFB-\u1CFF\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20C1-\u20CF\u20F1-\u20FF\u218C-\u218F\u2427-\u243F\u244B-\u245F\u2B74\u2B75\u2B96\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E5E-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u3130\u318F\u31E4-\u31EF\u321F\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA6F8-\uA6FF\uA7CB-\uA7CF\uA7D2\uA7D4\uA7DA-\uA7F1\uA82D-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C6-\uA8CD\uA8DA-\uA8DF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB6C-\uAB6F\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC3-\uFBD2\uFD90\uFD91\uFDC8-\uFDCE\uFDD0-\uFDEF\uFE1A-\uFE1F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF', + astral: + '\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDCFF\uDD03-\uDD06\uDD34-\uDD36\uDD8F\uDD9D-\uDD9F\uDDA1-\uDDCF\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEFC-\uDEFF\uDF24-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDFC4-\uDFC7\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDD6E\uDD7B\uDD8B\uDD93\uDD96\uDDA2\uDDB2\uDDBA\uDDBD-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDF7F\uDF86\uDFB1\uDFBB-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56\uDC9F-\uDCA6\uDCB0-\uDCDF\uDCF3\uDCF6-\uDCFA\uDD1C-\uDD1E\uDD3A-\uDD3E\uDD40-\uDD7F\uDDB8-\uDDBB\uDDD0\uDDD1\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE49-\uDE4F\uDE59-\uDE5F\uDEA0-\uDEBF\uDEE7-\uDEEA\uDEF7-\uDEFF\uDF36-\uDF38\uDF56\uDF57\uDF73-\uDF77\uDF92-\uDF98\uDF9D-\uDFA8\uDFB0-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCF9\uDD28-\uDD2F\uDD3A-\uDE5F\uDE7F\uDEAA\uDEAE\uDEAF\uDEB2-\uDEFF\uDF28-\uDF2F\uDF5A-\uDF6F\uDF8A-\uDFAF\uDFCC-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC4E-\uDC51\uDC76-\uDC7E\uDCBD\uDCC3-\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD48-\uDD4F\uDD77-\uDD7F\uDDE0\uDDF5-\uDDFF\uDE12\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEAA-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC5C\uDC62-\uDC7F\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDDE-\uDDFF\uDE45-\uDE4F\uDE5A-\uDE5F\uDE6D-\uDE7F\uDEBA-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF47-\uDFFF]|\uD806[\uDC3C-\uDC9F\uDCF3-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD47-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE5-\uDDFF\uDE48-\uDE4F\uDEA3-\uDEAF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC46-\uDC4F\uDC6D-\uDC6F\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF9-\uDFAF\uDFB1-\uDFBF\uDFF2-\uDFFE]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F\uDC75-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80E-\uD810\uD812-\uD819\uD824-\uD82A\uD82D\uD82E\uD830-\uD832\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDBFF][\uDC00-\uDFFF]|\uD80B[\uDC00-\uDF8F\uDFF3-\uDFFF]|\uD80D[\uDC2F-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDE6D\uDEBF\uDECA-\uDECF\uDEEE\uDEEF\uDEF6-\uDEFF\uDF46-\uDF4F\uDF5A\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE9B-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82B[\uDC00-\uDFEF\uDFF4\uDFFC\uDFFF]|\uD82C[\uDD23-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A\uDC9B\uDCA0-\uDFFF]|\uD833[\uDC00-\uDEFF\uDF2E\uDF2F\uDF47-\uDF4F\uDFC4-\uDFFF]|\uD834[\uDCF6-\uDCFF\uDD27\uDD28\uDD73-\uDD7A\uDDEB-\uDDFF\uDE46-\uDEDF\uDEF4-\uDEFF\uDF57-\uDF5F\uDF79-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDFCC\uDFCD]|\uD836[\uDE8C-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD837[\uDC00-\uDEFF\uDF1F-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD50-\uDE8F\uDEAF-\uDEBF\uDEFA-\uDEFE\uDF00-\uDFFF]|\uD839[\uDC00-\uDFDF\uDFE7\uDFEC\uDFEF\uDFFF]|\uD83A[\uDCC5\uDCC6\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDD5D\uDD60-\uDFFF]|\uD83B[\uDC00-\uDC70\uDCB5-\uDD00\uDD3E-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDEEF\uDEF2-\uDFFF]|\uD83C[\uDC2C-\uDC2F\uDC94-\uDC9F\uDCAF\uDCB0\uDCC0\uDCD0\uDCF6-\uDCFF\uDDAE-\uDDE5\uDE03-\uDE0F\uDE3C-\uDE3F\uDE49-\uDE4F\uDE52-\uDE5F\uDE66-\uDEFF]|\uD83D[\uDED8-\uDEDC\uDEED-\uDEEF\uDEFD-\uDEFF\uDF74-\uDF7F\uDFD9-\uDFDF\uDFEC-\uDFEF\uDFF1-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE\uDCAF\uDCB2-\uDCFF\uDE54-\uDE5F\uDE6E\uDE6F\uDE75-\uDE77\uDE7D-\uDE7F\uDE87-\uDE8F\uDEAD-\uDEAF\uDEBB-\uDEBF\uDEC6-\uDECF\uDEDA-\uDEDF\uDEE8-\uDEEF\uDEF7-\uDEFF\uDF93\uDFCB-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEE0-\uDEFF]|\uD86D[\uDF39-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00-\uDCFF\uDDF0-\uDFFF]', + }, + { + name: 'Cc', + alias: 'Control', + bmp: '\0-\x1F\x7F-\x9F', + }, + { + name: 'Cf', + alias: 'Format', + bmp: '\xAD\u0600-\u0605\u061C\u06DD\u070F\u0890\u0891\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB', + astral: + '\uD804[\uDCBD\uDCCD]|\uD80D[\uDC30-\uDC38]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]', + }, + { + name: 'Cn', + alias: 'Unassigned', + bmp: '\u0378\u0379\u0380-\u0383\u038B\u038D\u03A2\u0530\u0557\u0558\u058B\u058C\u0590\u05C8-\u05CF\u05EB-\u05EE\u05F5-\u05FF\u070E\u074B\u074C\u07B2-\u07BF\u07FB\u07FC\u082E\u082F\u083F\u085C\u085D\u085F\u086B-\u086F\u088F\u0892-\u0897\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A77-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0BFF\u0C0D\u0C11\u0C29\u0C3A\u0C3B\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B\u0C5C\u0C5E\u0C5F\u0C64\u0C65\u0C70-\u0C76\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDC\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D50-\u0D53\u0D64\u0D65\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F6\u13F7\u13FE\u13FF\u169D-\u169F\u16F9-\u16FF\u1716-\u171E\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE\u1AAF\u1ACF-\u1AFF\u1B4D-\u1B4F\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C89-\u1C8F\u1CBB\u1CBC\u1CC8-\u1CCF\u1CFB-\u1CFF\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u2065\u2072\u2073\u208F\u209D-\u209F\u20C1-\u20CF\u20F1-\u20FF\u218C-\u218F\u2427-\u243F\u244B-\u245F\u2B74\u2B75\u2B96\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E5E-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u3130\u318F\u31E4-\u31EF\u321F\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA6F8-\uA6FF\uA7CB-\uA7CF\uA7D2\uA7D4\uA7DA-\uA7F1\uA82D-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C6-\uA8CD\uA8DA-\uA8DF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB6C-\uAB6F\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC3-\uFBD2\uFD90\uFD91\uFDC8-\uFDCE\uFDD0-\uFDEF\uFE1A-\uFE1F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD\uFEFE\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFF8\uFFFE\uFFFF', + astral: + '\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDCFF\uDD03-\uDD06\uDD34-\uDD36\uDD8F\uDD9D-\uDD9F\uDDA1-\uDDCF\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEFC-\uDEFF\uDF24-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDFC4-\uDFC7\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDD6E\uDD7B\uDD8B\uDD93\uDD96\uDDA2\uDDB2\uDDBA\uDDBD-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDF7F\uDF86\uDFB1\uDFBB-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56\uDC9F-\uDCA6\uDCB0-\uDCDF\uDCF3\uDCF6-\uDCFA\uDD1C-\uDD1E\uDD3A-\uDD3E\uDD40-\uDD7F\uDDB8-\uDDBB\uDDD0\uDDD1\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE49-\uDE4F\uDE59-\uDE5F\uDEA0-\uDEBF\uDEE7-\uDEEA\uDEF7-\uDEFF\uDF36-\uDF38\uDF56\uDF57\uDF73-\uDF77\uDF92-\uDF98\uDF9D-\uDFA8\uDFB0-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCF9\uDD28-\uDD2F\uDD3A-\uDE5F\uDE7F\uDEAA\uDEAE\uDEAF\uDEB2-\uDEFF\uDF28-\uDF2F\uDF5A-\uDF6F\uDF8A-\uDFAF\uDFCC-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC4E-\uDC51\uDC76-\uDC7E\uDCC3-\uDCCC\uDCCE\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD48-\uDD4F\uDD77-\uDD7F\uDDE0\uDDF5-\uDDFF\uDE12\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEAA-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC5C\uDC62-\uDC7F\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDDE-\uDDFF\uDE45-\uDE4F\uDE5A-\uDE5F\uDE6D-\uDE7F\uDEBA-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF47-\uDFFF]|\uD806[\uDC3C-\uDC9F\uDCF3-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD47-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE5-\uDDFF\uDE48-\uDE4F\uDEA3-\uDEAF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC46-\uDC4F\uDC6D-\uDC6F\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF9-\uDFAF\uDFB1-\uDFBF\uDFF2-\uDFFE]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F\uDC75-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80E-\uD810\uD812-\uD819\uD824-\uD82A\uD82D\uD82E\uD830-\uD832\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDB7F][\uDC00-\uDFFF]|\uD80B[\uDC00-\uDF8F\uDFF3-\uDFFF]|\uD80D[\uDC2F\uDC39-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDE6D\uDEBF\uDECA-\uDECF\uDEEE\uDEEF\uDEF6-\uDEFF\uDF46-\uDF4F\uDF5A\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE9B-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82B[\uDC00-\uDFEF\uDFF4\uDFFC\uDFFF]|\uD82C[\uDD23-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A\uDC9B\uDCA4-\uDFFF]|\uD833[\uDC00-\uDEFF\uDF2E\uDF2F\uDF47-\uDF4F\uDFC4-\uDFFF]|\uD834[\uDCF6-\uDCFF\uDD27\uDD28\uDDEB-\uDDFF\uDE46-\uDEDF\uDEF4-\uDEFF\uDF57-\uDF5F\uDF79-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDFCC\uDFCD]|\uD836[\uDE8C-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD837[\uDC00-\uDEFF\uDF1F-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD50-\uDE8F\uDEAF-\uDEBF\uDEFA-\uDEFE\uDF00-\uDFFF]|\uD839[\uDC00-\uDFDF\uDFE7\uDFEC\uDFEF\uDFFF]|\uD83A[\uDCC5\uDCC6\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDD5D\uDD60-\uDFFF]|\uD83B[\uDC00-\uDC70\uDCB5-\uDD00\uDD3E-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDEEF\uDEF2-\uDFFF]|\uD83C[\uDC2C-\uDC2F\uDC94-\uDC9F\uDCAF\uDCB0\uDCC0\uDCD0\uDCF6-\uDCFF\uDDAE-\uDDE5\uDE03-\uDE0F\uDE3C-\uDE3F\uDE49-\uDE4F\uDE52-\uDE5F\uDE66-\uDEFF]|\uD83D[\uDED8-\uDEDC\uDEED-\uDEEF\uDEFD-\uDEFF\uDF74-\uDF7F\uDFD9-\uDFDF\uDFEC-\uDFEF\uDFF1-\uDFFF]|\uD83E[\uDC0C-\uDC0F\uDC48-\uDC4F\uDC5A-\uDC5F\uDC88-\uDC8F\uDCAE\uDCAF\uDCB2-\uDCFF\uDE54-\uDE5F\uDE6E\uDE6F\uDE75-\uDE77\uDE7D-\uDE7F\uDE87-\uDE8F\uDEAD-\uDEAF\uDEBB-\uDEBF\uDEC6-\uDECF\uDEDA-\uDEDF\uDEE8-\uDEEF\uDEF7-\uDEFF\uDF93\uDFCB-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEE0-\uDEFF]|\uD86D[\uDF39-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00\uDC02-\uDC1F\uDC80-\uDCFF\uDDF0-\uDFFF]|[\uDBBF\uDBFF][\uDFFE\uDFFF]', + }, + { + name: 'Co', + alias: 'Private_Use', + bmp: '\uE000-\uF8FF', + astral: '[\uDB80-\uDBBE\uDBC0-\uDBFE][\uDC00-\uDFFF]|[\uDBBF\uDBFF][\uDC00-\uDFFD]', + }, + { + name: 'Cs', + alias: 'Surrogate', + bmp: '\uD800-\uDFFF', + }, + { + name: 'L', + alias: 'Letter', + bmp: 'A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', + astral: + '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF1E]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]', + }, + { + name: 'LC', + alias: 'Cased_Letter', + bmp: 'A-Za-z\xB5\xC0-\xD6\xD8-\xF6\xF8-\u01BA\u01BC-\u01BF\u01C4-\u0293\u0295-\u02AF\u0370-\u0373\u0376\u0377\u037B-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0560-\u0588\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FD-\u10FF\u13A0-\u13F5\u13F8-\u13FD\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2134\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C7B\u2C7E-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA640-\uA66D\uA680-\uA69B\uA722-\uA76F\uA771-\uA787\uA78B-\uA78E\uA790-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F5\uA7F6\uA7FA\uAB30-\uAB5A\uAB60-\uAB68\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF21-\uFF3A\uFF41-\uFF5A', + astral: + '\uD801[\uDC00-\uDC4F\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC]|\uD803[\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD806[\uDCA0-\uDCDF]|\uD81B[\uDE40-\uDE7F]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF09\uDF0B-\uDF1E]|\uD83A[\uDD00-\uDD43]', + }, + { + name: 'Ll', + alias: 'Lowercase_Letter', + bmp: 'a-z\xB5\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02AF\u0371\u0373\u0377\u037B-\u037D\u0390\u03AC-\u03CE\u03D0\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1D2B\u1D6B-\u1D77\u1D79-\u1D9A\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD0-\u1FD3\u1FD6\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u210A\u210E\u210F\u2113\u212F\u2134\u2139\u213C\u213D\u2146-\u2149\u214E\u2184\u2C30-\u2C5F\u2C61\u2C65\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73\u2C74\u2C76-\u2C7B\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F\uA771-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7BB\uA7BD\uA7BF\uA7C1\uA7C3\uA7C8\uA7CA\uA7D1\uA7D3\uA7D5\uA7D7\uA7D9\uA7F6\uA7FA\uAB30-\uAB5A\uAB60-\uAB68\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A', + astral: + '\uD801[\uDC28-\uDC4F\uDCD8-\uDCFB\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC]|\uD803[\uDCC0-\uDCF2]|\uD806[\uDCC0-\uDCDF]|\uD81B[\uDE60-\uDE7F]|\uD835[\uDC1A-\uDC33\uDC4E-\uDC54\uDC56-\uDC67\uDC82-\uDC9B\uDCB6-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDCEA-\uDD03\uDD1E-\uDD37\uDD52-\uDD6B\uDD86-\uDD9F\uDDBA-\uDDD3\uDDEE-\uDE07\uDE22-\uDE3B\uDE56-\uDE6F\uDE8A-\uDEA5\uDEC2-\uDEDA\uDEDC-\uDEE1\uDEFC-\uDF14\uDF16-\uDF1B\uDF36-\uDF4E\uDF50-\uDF55\uDF70-\uDF88\uDF8A-\uDF8F\uDFAA-\uDFC2\uDFC4-\uDFC9\uDFCB]|\uD837[\uDF00-\uDF09\uDF0B-\uDF1E]|\uD83A[\uDD22-\uDD43]', + }, + { + name: 'Lm', + alias: 'Modifier_Letter', + bmp: '\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0374\u037A\u0559\u0640\u06E5\u06E6\u07F4\u07F5\u07FA\u081A\u0824\u0828\u08C9\u0971\u0E46\u0EC6\u10FC\u17D7\u1843\u1AA7\u1C78-\u1C7D\u1D2C-\u1D6A\u1D78\u1D9B-\u1DBF\u2071\u207F\u2090-\u209C\u2C7C\u2C7D\u2D6F\u2E2F\u3005\u3031-\u3035\u303B\u309D\u309E\u30FC-\u30FE\uA015\uA4F8-\uA4FD\uA60C\uA67F\uA69C\uA69D\uA717-\uA71F\uA770\uA788\uA7F2-\uA7F4\uA7F8\uA7F9\uA9CF\uA9E6\uAA70\uAADD\uAAF3\uAAF4\uAB5C-\uAB5F\uAB69\uFF70\uFF9E\uFF9F', + astral: + '\uD801[\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD81A[\uDF40-\uDF43]|\uD81B[\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD838[\uDD37-\uDD3D]|\uD83A\uDD4B', + }, + { + name: 'Lo', + alias: 'Other_Letter', + bmp: '\xAA\xBA\u01BB\u01C0-\u01C3\u0294\u05D0-\u05EA\u05EF-\u05F2\u0620-\u063F\u0641-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u0800-\u0815\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C8\u0904-\u0939\u093D\u0950\u0958-\u0961\u0972-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E45\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17DC\u1820-\u1842\u1844-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1B05-\u1B33\u1B45-\u1B4C\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C77\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u2135-\u2138\u2D30-\u2D67\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3006\u303C\u3041-\u3096\u309F\u30A1-\u30FA\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA014\uA016-\uA48C\uA4D0-\uA4F7\uA500-\uA60B\uA610-\uA61F\uA62A\uA62B\uA66E\uA6A0-\uA6E5\uA78F\uA7F7\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9E0-\uA9E4\uA9E7-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA6F\uAA71-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB\uAADC\uAAE0-\uAAEA\uAAF2\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF66-\uFF6F\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', + astral: + '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC50-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF4A\uDF50]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD837\uDF0A|\uD838[\uDD00-\uDD2C\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]', + }, + { + name: 'Lt', + alias: 'Titlecase_Letter', + bmp: '\u01C5\u01C8\u01CB\u01F2\u1F88-\u1F8F\u1F98-\u1F9F\u1FA8-\u1FAF\u1FBC\u1FCC\u1FFC', + }, + { + name: 'Lu', + alias: 'Uppercase_Letter', + bmp: 'A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178\u0179\u017B\u017D\u0181\u0182\u0184\u0186\u0187\u0189-\u018B\u018E-\u0191\u0193\u0194\u0196-\u0198\u019C\u019D\u019F\u01A0\u01A2\u01A4\u01A6\u01A7\u01A9\u01AC\u01AE\u01AF\u01B1-\u01B3\u01B5\u01B7\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A\u023B\u023D\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E\u213F\u2145\u2183\u2C00-\u2C2F\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uA7BA\uA7BC\uA7BE\uA7C0\uA7C2\uA7C4-\uA7C7\uA7C9\uA7D0\uA7D6\uA7D8\uA7F5\uFF21-\uFF3A', + astral: + '\uD801[\uDC00-\uDC27\uDCB0-\uDCD3\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95]|\uD803[\uDC80-\uDCB2]|\uD806[\uDCA0-\uDCBF]|\uD81B[\uDE40-\uDE5F]|\uD835[\uDC00-\uDC19\uDC34-\uDC4D\uDC68-\uDC81\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB5\uDCD0-\uDCE9\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD38\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD6C-\uDD85\uDDA0-\uDDB9\uDDD4-\uDDED\uDE08-\uDE21\uDE3C-\uDE55\uDE70-\uDE89\uDEA8-\uDEC0\uDEE2-\uDEFA\uDF1C-\uDF34\uDF56-\uDF6E\uDF90-\uDFA8\uDFCA]|\uD83A[\uDD00-\uDD21]', + }, + { + name: 'M', + alias: 'Mark', + bmp: '\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C04\u0C3C\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D81-\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ACE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA82C\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F', + astral: + '\uD800[\uDDFD\uDEE0\uDF76-\uDF7A]|\uD802[\uDE01-\uDE03\uDE05\uDE06\uDE0C-\uDE0F\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD803[\uDD24-\uDD27\uDEAB\uDEAC\uDF46-\uDF50\uDF82-\uDF85]|\uD804[\uDC00-\uDC02\uDC38-\uDC46\uDC70\uDC73\uDC74\uDC7F-\uDC82\uDCB0-\uDCBA\uDCC2\uDD00-\uDD02\uDD27-\uDD34\uDD45\uDD46\uDD73\uDD80-\uDD82\uDDB3-\uDDC0\uDDC9-\uDDCC\uDDCE\uDDCF\uDE2C-\uDE37\uDE3E\uDEDF-\uDEEA\uDF00-\uDF03\uDF3B\uDF3C\uDF3E-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF62\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC35-\uDC46\uDC5E\uDCB0-\uDCC3\uDDAF-\uDDB5\uDDB8-\uDDC0\uDDDC\uDDDD\uDE30-\uDE40\uDEAB-\uDEB7\uDF1D-\uDF2B]|\uD806[\uDC2C-\uDC3A\uDD30-\uDD35\uDD37\uDD38\uDD3B-\uDD3E\uDD40\uDD42\uDD43\uDDD1-\uDDD7\uDDDA-\uDDE0\uDDE4\uDE01-\uDE0A\uDE33-\uDE39\uDE3B-\uDE3E\uDE47\uDE51-\uDE5B\uDE8A-\uDE99]|\uD807[\uDC2F-\uDC36\uDC38-\uDC3F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD31-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD45\uDD47\uDD8A-\uDD8E\uDD90\uDD91\uDD93-\uDD97\uDEF3-\uDEF6]|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDF4F\uDF51-\uDF87\uDF8F-\uDF92\uDFE4\uDFF0\uDFF1]|\uD82F[\uDC9D\uDC9E]|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A\uDD30-\uDD36\uDEAE\uDEEC-\uDEEF]|\uD83A[\uDCD0-\uDCD6\uDD44-\uDD4A]|\uDB40[\uDD00-\uDDEF]', + }, + { + name: 'Mc', + alias: 'Spacing_Mark', + bmp: '\u0903\u093B\u093E-\u0940\u0949-\u094C\u094E\u094F\u0982\u0983\u09BE-\u09C0\u09C7\u09C8\u09CB\u09CC\u09D7\u0A03\u0A3E-\u0A40\u0A83\u0ABE-\u0AC0\u0AC9\u0ACB\u0ACC\u0B02\u0B03\u0B3E\u0B40\u0B47\u0B48\u0B4B\u0B4C\u0B57\u0BBE\u0BBF\u0BC1\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD7\u0C01-\u0C03\u0C41-\u0C44\u0C82\u0C83\u0CBE\u0CC0-\u0CC4\u0CC7\u0CC8\u0CCA\u0CCB\u0CD5\u0CD6\u0D02\u0D03\u0D3E-\u0D40\u0D46-\u0D48\u0D4A-\u0D4C\u0D57\u0D82\u0D83\u0DCF-\u0DD1\u0DD8-\u0DDF\u0DF2\u0DF3\u0F3E\u0F3F\u0F7F\u102B\u102C\u1031\u1038\u103B\u103C\u1056\u1057\u1062-\u1064\u1067-\u106D\u1083\u1084\u1087-\u108C\u108F\u109A-\u109C\u1715\u1734\u17B6\u17BE-\u17C5\u17C7\u17C8\u1923-\u1926\u1929-\u192B\u1930\u1931\u1933-\u1938\u1A19\u1A1A\u1A55\u1A57\u1A61\u1A63\u1A64\u1A6D-\u1A72\u1B04\u1B35\u1B3B\u1B3D-\u1B41\u1B43\u1B44\u1B82\u1BA1\u1BA6\u1BA7\u1BAA\u1BE7\u1BEA-\u1BEC\u1BEE\u1BF2\u1BF3\u1C24-\u1C2B\u1C34\u1C35\u1CE1\u1CF7\u302E\u302F\uA823\uA824\uA827\uA880\uA881\uA8B4-\uA8C3\uA952\uA953\uA983\uA9B4\uA9B5\uA9BA\uA9BB\uA9BE-\uA9C0\uAA2F\uAA30\uAA33\uAA34\uAA4D\uAA7B\uAA7D\uAAEB\uAAEE\uAAEF\uAAF5\uABE3\uABE4\uABE6\uABE7\uABE9\uABEA\uABEC', + astral: + '\uD804[\uDC00\uDC02\uDC82\uDCB0-\uDCB2\uDCB7\uDCB8\uDD2C\uDD45\uDD46\uDD82\uDDB3-\uDDB5\uDDBF\uDDC0\uDDCE\uDE2C-\uDE2E\uDE32\uDE33\uDE35\uDEE0-\uDEE2\uDF02\uDF03\uDF3E\uDF3F\uDF41-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF62\uDF63]|\uD805[\uDC35-\uDC37\uDC40\uDC41\uDC45\uDCB0-\uDCB2\uDCB9\uDCBB-\uDCBE\uDCC1\uDDAF-\uDDB1\uDDB8-\uDDBB\uDDBE\uDE30-\uDE32\uDE3B\uDE3C\uDE3E\uDEAC\uDEAE\uDEAF\uDEB6\uDF20\uDF21\uDF26]|\uD806[\uDC2C-\uDC2E\uDC38\uDD30-\uDD35\uDD37\uDD38\uDD3D\uDD40\uDD42\uDDD1-\uDDD3\uDDDC-\uDDDF\uDDE4\uDE39\uDE57\uDE58\uDE97]|\uD807[\uDC2F\uDC3E\uDCA9\uDCB1\uDCB4\uDD8A-\uDD8E\uDD93\uDD94\uDD96\uDEF5\uDEF6]|\uD81B[\uDF51-\uDF87\uDFF0\uDFF1]|\uD834[\uDD65\uDD66\uDD6D-\uDD72]', + }, + { + name: 'Me', + alias: 'Enclosing_Mark', + bmp: '\u0488\u0489\u1ABE\u20DD-\u20E0\u20E2-\u20E4\uA670-\uA672', + }, + { + name: 'Mn', + alias: 'Nonspacing_Mark', + bmp: '\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u0898-\u089F\u08CA-\u08E1\u08E3-\u0902\u093A\u093C\u0941-\u0948\u094D\u0951-\u0957\u0962\u0963\u0981\u09BC\u09C1-\u09C4\u09CD\u09E2\u09E3\u09FE\u0A01\u0A02\u0A3C\u0A41\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81\u0A82\u0ABC\u0AC1-\u0AC5\u0AC7\u0AC8\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01\u0B3C\u0B3F\u0B41-\u0B44\u0B4D\u0B55\u0B56\u0B62\u0B63\u0B82\u0BC0\u0BCD\u0C00\u0C04\u0C3C\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81\u0CBC\u0CBF\u0CC6\u0CCC\u0CCD\u0CE2\u0CE3\u0D00\u0D01\u0D3B\u0D3C\u0D41-\u0D44\u0D4D\u0D62\u0D63\u0D81\u0DCA\u0DD2-\u0DD4\u0DD6\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039\u103A\u103D\u103E\u1058\u1059\u105E-\u1060\u1071-\u1074\u1082\u1085\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732\u1733\u1752\u1753\u1772\u1773\u17B4\u17B5\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u180F\u1885\u1886\u18A9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193B\u1A17\u1A18\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1ABF-\u1ACE\u1B00-\u1B03\u1B34\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80\u1B81\u1BA2-\u1BA5\u1BA8\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8\u1BE9\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u3099\u309A\uA66F\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA825\uA826\uA82C\uA8C4\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC\uA9BD\uA9E5\uAA29-\uAA2E\uAA31\uAA32\uAA35\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEC\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F', + astral: + '\uD800[\uDDFD\uDEE0\uDF76-\uDF7A]|\uD802[\uDE01-\uDE03\uDE05\uDE06\uDE0C-\uDE0F\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD803[\uDD24-\uDD27\uDEAB\uDEAC\uDF46-\uDF50\uDF82-\uDF85]|\uD804[\uDC01\uDC38-\uDC46\uDC70\uDC73\uDC74\uDC7F-\uDC81\uDCB3-\uDCB6\uDCB9\uDCBA\uDCC2\uDD00-\uDD02\uDD27-\uDD2B\uDD2D-\uDD34\uDD73\uDD80\uDD81\uDDB6-\uDDBE\uDDC9-\uDDCC\uDDCF\uDE2F-\uDE31\uDE34\uDE36\uDE37\uDE3E\uDEDF\uDEE3-\uDEEA\uDF00\uDF01\uDF3B\uDF3C\uDF40\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC38-\uDC3F\uDC42-\uDC44\uDC46\uDC5E\uDCB3-\uDCB8\uDCBA\uDCBF\uDCC0\uDCC2\uDCC3\uDDB2-\uDDB5\uDDBC\uDDBD\uDDBF\uDDC0\uDDDC\uDDDD\uDE33-\uDE3A\uDE3D\uDE3F\uDE40\uDEAB\uDEAD\uDEB0-\uDEB5\uDEB7\uDF1D-\uDF1F\uDF22-\uDF25\uDF27-\uDF2B]|\uD806[\uDC2F-\uDC37\uDC39\uDC3A\uDD3B\uDD3C\uDD3E\uDD43\uDDD4-\uDDD7\uDDDA\uDDDB\uDDE0\uDE01-\uDE0A\uDE33-\uDE38\uDE3B-\uDE3E\uDE47\uDE51-\uDE56\uDE59-\uDE5B\uDE8A-\uDE96\uDE98\uDE99]|\uD807[\uDC30-\uDC36\uDC38-\uDC3D\uDC3F\uDC92-\uDCA7\uDCAA-\uDCB0\uDCB2\uDCB3\uDCB5\uDCB6\uDD31-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD45\uDD47\uDD90\uDD91\uDD95\uDD97\uDEF3\uDEF4]|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDF4F\uDF8F-\uDF92\uDFE4]|\uD82F[\uDC9D\uDC9E]|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD67-\uDD69\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A\uDD30-\uDD36\uDEAE\uDEEC-\uDEEF]|\uD83A[\uDCD0-\uDCD6\uDD44-\uDD4A]|\uDB40[\uDD00-\uDDEF]', + }, + { + name: 'N', + alias: 'Number', + bmp: '0-9\xB2\xB3\xB9\xBC-\xBE\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F\u0C78-\u0C7E\u0CE6-\u0CEF\u0D58-\u0D5E\u0D66-\u0D78\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19', + astral: + '\uD800[\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDEE1-\uDEFB\uDF20-\uDF23\uDF41\uDF4A\uDFD1-\uDFD5]|\uD801[\uDCA0-\uDCA9]|\uD802[\uDC58-\uDC5F\uDC79-\uDC7F\uDCA7-\uDCAF\uDCFB-\uDCFF\uDD16-\uDD1B\uDDBC\uDDBD\uDDC0-\uDDCF\uDDD2-\uDDFF\uDE40-\uDE48\uDE7D\uDE7E\uDE9D-\uDE9F\uDEEB-\uDEEF\uDF58-\uDF5F\uDF78-\uDF7F\uDFA9-\uDFAF]|\uD803[\uDCFA-\uDCFF\uDD30-\uDD39\uDE60-\uDE7E\uDF1D-\uDF26\uDF51-\uDF54\uDFC5-\uDFCB]|\uD804[\uDC52-\uDC6F\uDCF0-\uDCF9\uDD36-\uDD3F\uDDD0-\uDDD9\uDDE1-\uDDF4\uDEF0-\uDEF9]|\uD805[\uDC50-\uDC59\uDCD0-\uDCD9\uDE50-\uDE59\uDEC0-\uDEC9\uDF30-\uDF3B]|\uD806[\uDCE0-\uDCF2\uDD50-\uDD59]|\uD807[\uDC50-\uDC6C\uDD50-\uDD59\uDDA0-\uDDA9\uDFC0-\uDFD4]|\uD809[\uDC00-\uDC6E]|\uD81A[\uDE60-\uDE69\uDEC0-\uDEC9\uDF50-\uDF59\uDF5B-\uDF61]|\uD81B[\uDE80-\uDE96]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDFCE-\uDFFF]|\uD838[\uDD40-\uDD49\uDEF0-\uDEF9]|\uD83A[\uDCC7-\uDCCF\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]', + }, + { + name: 'Nd', + alias: 'Decimal_Number', + bmp: '0-9\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE6-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29\u1040-\u1049\u1090-\u1099\u17E0-\u17E9\u1810-\u1819\u1946-\u194F\u19D0-\u19D9\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\uA620-\uA629\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19', + astral: + '\uD801[\uDCA0-\uDCA9]|\uD803[\uDD30-\uDD39]|\uD804[\uDC66-\uDC6F\uDCF0-\uDCF9\uDD36-\uDD3F\uDDD0-\uDDD9\uDEF0-\uDEF9]|\uD805[\uDC50-\uDC59\uDCD0-\uDCD9\uDE50-\uDE59\uDEC0-\uDEC9\uDF30-\uDF39]|\uD806[\uDCE0-\uDCE9\uDD50-\uDD59]|\uD807[\uDC50-\uDC59\uDD50-\uDD59\uDDA0-\uDDA9]|\uD81A[\uDE60-\uDE69\uDEC0-\uDEC9\uDF50-\uDF59]|\uD835[\uDFCE-\uDFFF]|\uD838[\uDD40-\uDD49\uDEF0-\uDEF9]|\uD83A[\uDD50-\uDD59]|\uD83E[\uDFF0-\uDFF9]', + }, + { + name: 'Nl', + alias: 'Letter_Number', + bmp: '\u16EE-\u16F0\u2160-\u2182\u2185-\u2188\u3007\u3021-\u3029\u3038-\u303A\uA6E6-\uA6EF', + astral: '\uD800[\uDD40-\uDD74\uDF41\uDF4A\uDFD1-\uDFD5]|\uD809[\uDC00-\uDC6E]', + }, + { + name: 'No', + alias: 'Other_Number', + bmp: '\xB2\xB3\xB9\xBC-\xBE\u09F4-\u09F9\u0B72-\u0B77\u0BF0-\u0BF2\u0C78-\u0C7E\u0D58-\u0D5E\u0D70-\u0D78\u0F2A-\u0F33\u1369-\u137C\u17F0-\u17F9\u19DA\u2070\u2074-\u2079\u2080-\u2089\u2150-\u215F\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA830-\uA835', + astral: + '\uD800[\uDD07-\uDD33\uDD75-\uDD78\uDD8A\uDD8B\uDEE1-\uDEFB\uDF20-\uDF23]|\uD802[\uDC58-\uDC5F\uDC79-\uDC7F\uDCA7-\uDCAF\uDCFB-\uDCFF\uDD16-\uDD1B\uDDBC\uDDBD\uDDC0-\uDDCF\uDDD2-\uDDFF\uDE40-\uDE48\uDE7D\uDE7E\uDE9D-\uDE9F\uDEEB-\uDEEF\uDF58-\uDF5F\uDF78-\uDF7F\uDFA9-\uDFAF]|\uD803[\uDCFA-\uDCFF\uDE60-\uDE7E\uDF1D-\uDF26\uDF51-\uDF54\uDFC5-\uDFCB]|\uD804[\uDC52-\uDC65\uDDE1-\uDDF4]|\uD805[\uDF3A\uDF3B]|\uD806[\uDCEA-\uDCF2]|\uD807[\uDC5A-\uDC6C\uDFC0-\uDFD4]|\uD81A[\uDF5B-\uDF61]|\uD81B[\uDE80-\uDE96]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD83A[\uDCC7-\uDCCF]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D]|\uD83C[\uDD00-\uDD0C]', + }, + { + name: 'P', + alias: 'Punctuation', + bmp: '!-#%-\\*,-\\/:;\\?@\\[-\\]_\\{\\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061D-\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1B7D\u1B7E\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4F\u2E52-\u2E5D\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65', + astral: + '\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDEAD\uDF55-\uDF59\uDF86-\uDF89]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5A\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDEB9\uDF3C-\uDF3E]|\uD806[\uDC3B\uDD44-\uDD46\uDDE2\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8\uDFFF]|\uD809[\uDC70-\uDC74]|\uD80B[\uDFF1\uDFF2]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A\uDFE2]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]', + }, + { + name: 'Pc', + alias: 'Connector_Punctuation', + bmp: '_\u203F\u2040\u2054\uFE33\uFE34\uFE4D-\uFE4F\uFF3F', + }, + { + name: 'Pd', + alias: 'Dash_Punctuation', + bmp: '\\-\u058A\u05BE\u1400\u1806\u2010-\u2015\u2E17\u2E1A\u2E3A\u2E3B\u2E40\u2E5D\u301C\u3030\u30A0\uFE31\uFE32\uFE58\uFE63\uFF0D', + astral: '\uD803\uDEAD', + }, + { + name: 'Pe', + alias: 'Close_Punctuation', + bmp: '\\)\\]\\}\u0F3B\u0F3D\u169C\u2046\u207E\u208E\u2309\u230B\u232A\u2769\u276B\u276D\u276F\u2771\u2773\u2775\u27C6\u27E7\u27E9\u27EB\u27ED\u27EF\u2984\u2986\u2988\u298A\u298C\u298E\u2990\u2992\u2994\u2996\u2998\u29D9\u29DB\u29FD\u2E23\u2E25\u2E27\u2E29\u2E56\u2E58\u2E5A\u2E5C\u3009\u300B\u300D\u300F\u3011\u3015\u3017\u3019\u301B\u301E\u301F\uFD3E\uFE18\uFE36\uFE38\uFE3A\uFE3C\uFE3E\uFE40\uFE42\uFE44\uFE48\uFE5A\uFE5C\uFE5E\uFF09\uFF3D\uFF5D\uFF60\uFF63', + }, + { + name: 'Pf', + alias: 'Final_Punctuation', + bmp: '\xBB\u2019\u201D\u203A\u2E03\u2E05\u2E0A\u2E0D\u2E1D\u2E21', + }, + { + name: 'Pi', + alias: 'Initial_Punctuation', + bmp: '\xAB\u2018\u201B\u201C\u201F\u2039\u2E02\u2E04\u2E09\u2E0C\u2E1C\u2E20', + }, + { + name: 'Po', + alias: 'Other_Punctuation', + bmp: "!-#%-'\\*,\\.\\/:;\\?@\\\xA1\xA7\xB6\xB7\xBF\u037E\u0387\u055A-\u055F\u0589\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061D-\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u166E\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u1805\u1807-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1B7D\u1B7E\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2016\u2017\u2020-\u2027\u2030-\u2038\u203B-\u203E\u2041-\u2043\u2047-\u2051\u2053\u2055-\u205E\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00\u2E01\u2E06-\u2E08\u2E0B\u2E0E-\u2E16\u2E18\u2E19\u2E1B\u2E1E\u2E1F\u2E2A-\u2E2E\u2E30-\u2E39\u2E3C-\u2E3F\u2E41\u2E43-\u2E4F\u2E52-\u2E54\u3001-\u3003\u303D\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFE10-\uFE16\uFE19\uFE30\uFE45\uFE46\uFE49-\uFE4C\uFE50-\uFE52\uFE54-\uFE57\uFE5F-\uFE61\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF07\uFF0A\uFF0C\uFF0E\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3C\uFF61\uFF64\uFF65", + astral: + '\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59\uDF86-\uDF89]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5A\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDEB9\uDF3C-\uDF3E]|\uD806[\uDC3B\uDD44-\uDD46\uDDE2\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8\uDFFF]|\uD809[\uDC70-\uDC74]|\uD80B[\uDFF1\uDFF2]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A\uDFE2]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]', + }, + { + name: 'Ps', + alias: 'Open_Punctuation', + bmp: '\\(\\[\\{\u0F3A\u0F3C\u169B\u201A\u201E\u2045\u207D\u208D\u2308\u230A\u2329\u2768\u276A\u276C\u276E\u2770\u2772\u2774\u27C5\u27E6\u27E8\u27EA\u27EC\u27EE\u2983\u2985\u2987\u2989\u298B\u298D\u298F\u2991\u2993\u2995\u2997\u29D8\u29DA\u29FC\u2E22\u2E24\u2E26\u2E28\u2E42\u2E55\u2E57\u2E59\u2E5B\u3008\u300A\u300C\u300E\u3010\u3014\u3016\u3018\u301A\u301D\uFD3F\uFE17\uFE35\uFE37\uFE39\uFE3B\uFE3D\uFE3F\uFE41\uFE43\uFE47\uFE59\uFE5B\uFE5D\uFF08\uFF3B\uFF5B\uFF5F\uFF62', + }, + { + name: 'S', + alias: 'Symbol', + bmp: '\\$\\+<->\\^`\\|~\xA2-\xA6\xA8\xA9\xAC\xAE-\xB1\xB4\xB8\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0384\u0385\u03F6\u0482\u058D-\u058F\u0606-\u0608\u060B\u060E\u060F\u06DE\u06E9\u06FD\u06FE\u07F6\u07FE\u07FF\u0888\u09F2\u09F3\u09FA\u09FB\u0AF1\u0B70\u0BF3-\u0BFA\u0C7F\u0D4F\u0D79\u0E3F\u0F01-\u0F03\u0F13\u0F15-\u0F17\u0F1A-\u0F1F\u0F34\u0F36\u0F38\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE\u0FCF\u0FD5-\u0FD8\u109E\u109F\u1390-\u1399\u166D\u17DB\u1940\u19DE-\u19FF\u1B61-\u1B6A\u1B74-\u1B7C\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2044\u2052\u207A-\u207C\u208A-\u208C\u20A0-\u20C0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u218A\u218B\u2190-\u2307\u230C-\u2328\u232B-\u2426\u2440-\u244A\u249C-\u24E9\u2500-\u2767\u2794-\u27C4\u27C7-\u27E5\u27F0-\u2982\u2999-\u29D7\u29DC-\u29FB\u29FE-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2E50\u2E51\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3004\u3012\u3013\u3020\u3036\u3037\u303E\u303F\u309B\u309C\u3190\u3191\u3196-\u319F\u31C0-\u31E3\u3200-\u321E\u322A-\u3247\u3250\u3260-\u327F\u328A-\u32B0\u32C0-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA700-\uA716\uA720\uA721\uA789\uA78A\uA828-\uA82B\uA836-\uA839\uAA77-\uAA79\uAB5B\uAB6A\uAB6B\uFB29\uFBB2-\uFBC2\uFD40-\uFD4F\uFDCF\uFDFC-\uFDFF\uFE62\uFE64-\uFE66\uFE69\uFF04\uFF0B\uFF1C-\uFF1E\uFF3E\uFF40\uFF5C\uFF5E\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFFC\uFFFD', + astral: + '\uD800[\uDD37-\uDD3F\uDD79-\uDD89\uDD8C-\uDD8E\uDD90-\uDD9C\uDDA0\uDDD0-\uDDFC]|\uD802[\uDC77\uDC78\uDEC8]|\uD805\uDF3F|\uD807[\uDFD5-\uDFF1]|\uD81A[\uDF3C-\uDF3F\uDF45]|\uD82F\uDC9C|\uD833[\uDF50-\uDFC3]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD64\uDD6A-\uDD6C\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDEA\uDE00-\uDE41\uDE45\uDF00-\uDF56]|\uD835[\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85\uDE86]|\uD838[\uDD4F\uDEFF]|\uD83B[\uDCAC\uDCB0\uDD2E\uDEF0\uDEF1]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD0D-\uDDAD\uDDE6-\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFF]|\uD83D[\uDC00-\uDED7\uDEDD-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF73\uDF80-\uDFD8\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDE53\uDE60-\uDE6D\uDE70-\uDE74\uDE78-\uDE7C\uDE80-\uDE86\uDE90-\uDEAC\uDEB0-\uDEBA\uDEC0-\uDEC5\uDED0-\uDED9\uDEE0-\uDEE7\uDEF0-\uDEF6\uDF00-\uDF92\uDF94-\uDFCA]', + }, + { + name: 'Sc', + alias: 'Currency_Symbol', + bmp: '\\$\xA2-\xA5\u058F\u060B\u07FE\u07FF\u09F2\u09F3\u09FB\u0AF1\u0BF9\u0E3F\u17DB\u20A0-\u20C0\uA838\uFDFC\uFE69\uFF04\uFFE0\uFFE1\uFFE5\uFFE6', + astral: '\uD807[\uDFDD-\uDFE0]|\uD838\uDEFF|\uD83B\uDCB0', + }, + { + name: 'Sk', + alias: 'Modifier_Symbol', + bmp: '\\^`\xA8\xAF\xB4\xB8\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0384\u0385\u0888\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u309B\u309C\uA700-\uA716\uA720\uA721\uA789\uA78A\uAB5B\uAB6A\uAB6B\uFBB2-\uFBC2\uFF3E\uFF40\uFFE3', + astral: '\uD83C[\uDFFB-\uDFFF]', + }, + { + name: 'Sm', + alias: 'Math_Symbol', + bmp: '\\+<->\\|~\xAC\xB1\xD7\xF7\u03F6\u0606-\u0608\u2044\u2052\u207A-\u207C\u208A-\u208C\u2118\u2140-\u2144\u214B\u2190-\u2194\u219A\u219B\u21A0\u21A3\u21A6\u21AE\u21CE\u21CF\u21D2\u21D4\u21F4-\u22FF\u2320\u2321\u237C\u239B-\u23B3\u23DC-\u23E1\u25B7\u25C1\u25F8-\u25FF\u266F\u27C0-\u27C4\u27C7-\u27E5\u27F0-\u27FF\u2900-\u2982\u2999-\u29D7\u29DC-\u29FB\u29FE-\u2AFF\u2B30-\u2B44\u2B47-\u2B4C\uFB29\uFE62\uFE64-\uFE66\uFF0B\uFF1C-\uFF1E\uFF5C\uFF5E\uFFE2\uFFE9-\uFFEC', + astral: '\uD835[\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3]|\uD83B[\uDEF0\uDEF1]', + }, + { + name: 'So', + alias: 'Other_Symbol', + bmp: '\xA6\xA9\xAE\xB0\u0482\u058D\u058E\u060E\u060F\u06DE\u06E9\u06FD\u06FE\u07F6\u09FA\u0B70\u0BF3-\u0BF8\u0BFA\u0C7F\u0D4F\u0D79\u0F01-\u0F03\u0F13\u0F15-\u0F17\u0F1A-\u0F1F\u0F34\u0F36\u0F38\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE\u0FCF\u0FD5-\u0FD8\u109E\u109F\u1390-\u1399\u166D\u1940\u19DE-\u19FF\u1B61-\u1B6A\u1B74-\u1B7C\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116\u2117\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u214A\u214C\u214D\u214F\u218A\u218B\u2195-\u2199\u219C-\u219F\u21A1\u21A2\u21A4\u21A5\u21A7-\u21AD\u21AF-\u21CD\u21D0\u21D1\u21D3\u21D5-\u21F3\u2300-\u2307\u230C-\u231F\u2322-\u2328\u232B-\u237B\u237D-\u239A\u23B4-\u23DB\u23E2-\u2426\u2440-\u244A\u249C-\u24E9\u2500-\u25B6\u25B8-\u25C0\u25C2-\u25F7\u2600-\u266E\u2670-\u2767\u2794-\u27BF\u2800-\u28FF\u2B00-\u2B2F\u2B45\u2B46\u2B4D-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2E50\u2E51\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3004\u3012\u3013\u3020\u3036\u3037\u303E\u303F\u3190\u3191\u3196-\u319F\u31C0-\u31E3\u3200-\u321E\u322A-\u3247\u3250\u3260-\u327F\u328A-\u32B0\u32C0-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA828-\uA82B\uA836\uA837\uA839\uAA77-\uAA79\uFD40-\uFD4F\uFDCF\uFDFD-\uFDFF\uFFE4\uFFE8\uFFED\uFFEE\uFFFC\uFFFD', + astral: + '\uD800[\uDD37-\uDD3F\uDD79-\uDD89\uDD8C-\uDD8E\uDD90-\uDD9C\uDDA0\uDDD0-\uDDFC]|\uD802[\uDC77\uDC78\uDEC8]|\uD805\uDF3F|\uD807[\uDFD5-\uDFDC\uDFE1-\uDFF1]|\uD81A[\uDF3C-\uDF3F\uDF45]|\uD82F\uDC9C|\uD833[\uDF50-\uDFC3]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD64\uDD6A-\uDD6C\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDEA\uDE00-\uDE41\uDE45\uDF00-\uDF56]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85\uDE86]|\uD838\uDD4F|\uD83B[\uDCAC\uDD2E]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD0D-\uDDAD\uDDE6-\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFA]|\uD83D[\uDC00-\uDED7\uDEDD-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF73\uDF80-\uDFD8\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDE53\uDE60-\uDE6D\uDE70-\uDE74\uDE78-\uDE7C\uDE80-\uDE86\uDE90-\uDEAC\uDEB0-\uDEBA\uDEC0-\uDEC5\uDED0-\uDED9\uDEE0-\uDEE7\uDEF0-\uDEF6\uDF00-\uDF92\uDF94-\uDFCA]', + }, + { + name: 'Z', + alias: 'Separator', + bmp: ' \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000', + }, + { + name: 'Zl', + alias: 'Line_Separator', + bmp: '\u2028', + }, + { + name: 'Zp', + alias: 'Paragraph_Separator', + bmp: '\u2029', + }, + { + name: 'Zs', + alias: 'Space_Separator', + bmp: ' \xA0\u1680\u2000-\u200A\u202F\u205F\u3000', + }, + ] + }, + {}, + ], + 219: [ + function (require, module, exports) { + module.exports = [ + { + name: 'ASCII', + bmp: '\0-\x7F', + }, + { + name: 'Alphabetic', + bmp: 'A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0345\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05B0-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05EF-\u05F2\u0610-\u061A\u0620-\u0657\u0659-\u065F\u066E-\u06D3\u06D5-\u06DC\u06E1-\u06E8\u06ED-\u06EF\u06FA-\u06FC\u06FF\u0710-\u073F\u074D-\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0817\u081A-\u082C\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u08D4-\u08DF\u08E3-\u08E9\u08F0-\u093B\u093D-\u094C\u094E-\u0950\u0955-\u0963\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD-\u09C4\u09C7\u09C8\u09CB\u09CC\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09F0\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A42\u0A47\u0A48\u0A4B\u0A4C\u0A51\u0A59-\u0A5C\u0A5E\u0A70-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD-\u0AC5\u0AC7-\u0AC9\u0ACB\u0ACC\u0AD0\u0AE0-\u0AE3\u0AF9-\u0AFC\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D-\u0B44\u0B47\u0B48\u0B4B\u0B4C\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD0\u0BD7\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4C\u0C55\u0C56\u0C58-\u0C5A\u0C5D\u0C60-\u0C63\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCC\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0-\u0CE3\u0CF1\u0CF2\u0D00-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4C\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D7A-\u0D7F\u0D81-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E46\u0E4D\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0ECD\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F71-\u0F81\u0F88-\u0F97\u0F99-\u0FBC\u1000-\u1036\u1038\u103B-\u103F\u1050-\u108F\u109A-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1713\u171F-\u1733\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17B3\u17B6-\u17C8\u17D7\u17DC\u1820-\u1878\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u1938\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A1B\u1A20-\u1A5E\u1A61-\u1A74\u1AA7\u1ABF\u1AC0\u1ACC-\u1ACE\u1B00-\u1B33\u1B35-\u1B43\u1B45-\u1B4C\u1B80-\u1BA9\u1BAC-\u1BAF\u1BBA-\u1BE5\u1BE7-\u1BF1\u1C00-\u1C36\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1DE7-\u1DF4\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u24B6-\u24E9\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA674-\uA67B\uA67F-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA805\uA807-\uA827\uA840-\uA873\uA880-\uA8C3\uA8C5\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FF\uA90A-\uA92A\uA930-\uA952\uA960-\uA97C\uA980-\uA9B2\uA9B4-\uA9BF\uA9CF\uA9E0-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA60-\uAA76\uAA7A-\uAABE\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF5\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABEA\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', + astral: + '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD27\uDE80-\uDEA9\uDEAB\uDEAC\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC00-\uDC45\uDC71-\uDC75\uDC82-\uDCB8\uDCC2\uDCD0-\uDCE8\uDD00-\uDD32\uDD44-\uDD47\uDD50-\uDD72\uDD76\uDD80-\uDDBF\uDDC1-\uDDC4\uDDCE\uDDCF\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE34\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEE8\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D-\uDF44\uDF47\uDF48\uDF4B\uDF4C\uDF50\uDF57\uDF5D-\uDF63]|\uD805[\uDC00-\uDC41\uDC43-\uDC45\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCC1\uDCC4\uDCC5\uDCC7\uDD80-\uDDB5\uDDB8-\uDDBE\uDDD8-\uDDDD\uDE00-\uDE3E\uDE40\uDE44\uDE80-\uDEB5\uDEB8\uDF00-\uDF1A\uDF1D-\uDF2A\uDF40-\uDF46]|\uD806[\uDC00-\uDC38\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD35\uDD37\uDD38\uDD3B\uDD3C\uDD3F-\uDD42\uDDA0-\uDDA7\uDDAA-\uDDD7\uDDDA-\uDDDF\uDDE1\uDDE3\uDDE4\uDE00-\uDE32\uDE35-\uDE3E\uDE50-\uDE97\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC3E\uDC40\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD41\uDD43\uDD46\uDD47\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD8E\uDD90\uDD91\uDD93-\uDD96\uDD98\uDEE0-\uDEF6\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF4F-\uDF87\uDF8F-\uDF9F\uDFE0\uDFE1\uDFE3\uDFF0\uDFF1]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9E]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF1E]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD47\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD30-\uDD49\uDD50-\uDD69\uDD70-\uDD89]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]', + }, + { + name: 'Any', + isBmpLast: true, + bmp: '\0-\uFFFF', + astral: '[\uD800-\uDBFF][\uDC00-\uDFFF]', + }, + { + name: 'Default_Ignorable_Code_Point', + bmp: '\xAD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180B-\u180F\u200B-\u200F\u202A-\u202E\u2060-\u206F\u3164\uFE00-\uFE0F\uFEFF\uFFA0\uFFF0-\uFFF8', + astral: '\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|[\uDB40-\uDB43][\uDC00-\uDFFF]', + }, + { + name: 'Lowercase', + bmp: 'a-z\xAA\xB5\xBA\xDF-\xF6\xF8-\xFF\u0101\u0103\u0105\u0107\u0109\u010B\u010D\u010F\u0111\u0113\u0115\u0117\u0119\u011B\u011D\u011F\u0121\u0123\u0125\u0127\u0129\u012B\u012D\u012F\u0131\u0133\u0135\u0137\u0138\u013A\u013C\u013E\u0140\u0142\u0144\u0146\u0148\u0149\u014B\u014D\u014F\u0151\u0153\u0155\u0157\u0159\u015B\u015D\u015F\u0161\u0163\u0165\u0167\u0169\u016B\u016D\u016F\u0171\u0173\u0175\u0177\u017A\u017C\u017E-\u0180\u0183\u0185\u0188\u018C\u018D\u0192\u0195\u0199-\u019B\u019E\u01A1\u01A3\u01A5\u01A8\u01AA\u01AB\u01AD\u01B0\u01B4\u01B6\u01B9\u01BA\u01BD-\u01BF\u01C6\u01C9\u01CC\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8\u01DA\u01DC\u01DD\u01DF\u01E1\u01E3\u01E5\u01E7\u01E9\u01EB\u01ED\u01EF\u01F0\u01F3\u01F5\u01F9\u01FB\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B\u020D\u020F\u0211\u0213\u0215\u0217\u0219\u021B\u021D\u021F\u0221\u0223\u0225\u0227\u0229\u022B\u022D\u022F\u0231\u0233-\u0239\u023C\u023F\u0240\u0242\u0247\u0249\u024B\u024D\u024F-\u0293\u0295-\u02B8\u02C0\u02C1\u02E0-\u02E4\u0345\u0371\u0373\u0377\u037A-\u037D\u0390\u03AC-\u03CE\u03D0\u03D1\u03D5-\u03D7\u03D9\u03DB\u03DD\u03DF\u03E1\u03E3\u03E5\u03E7\u03E9\u03EB\u03ED\u03EF-\u03F3\u03F5\u03F8\u03FB\u03FC\u0430-\u045F\u0461\u0463\u0465\u0467\u0469\u046B\u046D\u046F\u0471\u0473\u0475\u0477\u0479\u047B\u047D\u047F\u0481\u048B\u048D\u048F\u0491\u0493\u0495\u0497\u0499\u049B\u049D\u049F\u04A1\u04A3\u04A5\u04A7\u04A9\u04AB\u04AD\u04AF\u04B1\u04B3\u04B5\u04B7\u04B9\u04BB\u04BD\u04BF\u04C2\u04C4\u04C6\u04C8\u04CA\u04CC\u04CE\u04CF\u04D1\u04D3\u04D5\u04D7\u04D9\u04DB\u04DD\u04DF\u04E1\u04E3\u04E5\u04E7\u04E9\u04EB\u04ED\u04EF\u04F1\u04F3\u04F5\u04F7\u04F9\u04FB\u04FD\u04FF\u0501\u0503\u0505\u0507\u0509\u050B\u050D\u050F\u0511\u0513\u0515\u0517\u0519\u051B\u051D\u051F\u0521\u0523\u0525\u0527\u0529\u052B\u052D\u052F\u0560-\u0588\u10D0-\u10FA\u10FD-\u10FF\u13F8-\u13FD\u1C80-\u1C88\u1D00-\u1DBF\u1E01\u1E03\u1E05\u1E07\u1E09\u1E0B\u1E0D\u1E0F\u1E11\u1E13\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E1F\u1E21\u1E23\u1E25\u1E27\u1E29\u1E2B\u1E2D\u1E2F\u1E31\u1E33\u1E35\u1E37\u1E39\u1E3B\u1E3D\u1E3F\u1E41\u1E43\u1E45\u1E47\u1E49\u1E4B\u1E4D\u1E4F\u1E51\u1E53\u1E55\u1E57\u1E59\u1E5B\u1E5D\u1E5F\u1E61\u1E63\u1E65\u1E67\u1E69\u1E6B\u1E6D\u1E6F\u1E71\u1E73\u1E75\u1E77\u1E79\u1E7B\u1E7D\u1E7F\u1E81\u1E83\u1E85\u1E87\u1E89\u1E8B\u1E8D\u1E8F\u1E91\u1E93\u1E95-\u1E9D\u1E9F\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u1EF3\u1EF5\u1EF7\u1EF9\u1EFB\u1EFD\u1EFF-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB0-\u1FB4\u1FB6\u1FB7\u1FBE\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD0-\u1FD3\u1FD6\u1FD7\u1FE0-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u2071\u207F\u2090-\u209C\u210A\u210E\u210F\u2113\u212F\u2134\u2139\u213C\u213D\u2146-\u2149\u214E\u2170-\u217F\u2184\u24D0-\u24E9\u2C30-\u2C5F\u2C61\u2C65\u2C66\u2C68\u2C6A\u2C6C\u2C71\u2C73\u2C74\u2C76-\u2C7D\u2C81\u2C83\u2C85\u2C87\u2C89\u2C8B\u2C8D\u2C8F\u2C91\u2C93\u2C95\u2C97\u2C99\u2C9B\u2C9D\u2C9F\u2CA1\u2CA3\u2CA5\u2CA7\u2CA9\u2CAB\u2CAD\u2CAF\u2CB1\u2CB3\u2CB5\u2CB7\u2CB9\u2CBB\u2CBD\u2CBF\u2CC1\u2CC3\u2CC5\u2CC7\u2CC9\u2CCB\u2CCD\u2CCF\u2CD1\u2CD3\u2CD5\u2CD7\u2CD9\u2CDB\u2CDD\u2CDF\u2CE1\u2CE3\u2CE4\u2CEC\u2CEE\u2CF3\u2D00-\u2D25\u2D27\u2D2D\uA641\uA643\uA645\uA647\uA649\uA64B\uA64D\uA64F\uA651\uA653\uA655\uA657\uA659\uA65B\uA65D\uA65F\uA661\uA663\uA665\uA667\uA669\uA66B\uA66D\uA681\uA683\uA685\uA687\uA689\uA68B\uA68D\uA68F\uA691\uA693\uA695\uA697\uA699\uA69B-\uA69D\uA723\uA725\uA727\uA729\uA72B\uA72D\uA72F-\uA731\uA733\uA735\uA737\uA739\uA73B\uA73D\uA73F\uA741\uA743\uA745\uA747\uA749\uA74B\uA74D\uA74F\uA751\uA753\uA755\uA757\uA759\uA75B\uA75D\uA75F\uA761\uA763\uA765\uA767\uA769\uA76B\uA76D\uA76F-\uA778\uA77A\uA77C\uA77F\uA781\uA783\uA785\uA787\uA78C\uA78E\uA791\uA793-\uA795\uA797\uA799\uA79B\uA79D\uA79F\uA7A1\uA7A3\uA7A5\uA7A7\uA7A9\uA7AF\uA7B5\uA7B7\uA7B9\uA7BB\uA7BD\uA7BF\uA7C1\uA7C3\uA7C8\uA7CA\uA7D1\uA7D3\uA7D5\uA7D7\uA7D9\uA7F6\uA7F8-\uA7FA\uAB30-\uAB5A\uAB5C-\uAB68\uAB70-\uABBF\uFB00-\uFB06\uFB13-\uFB17\uFF41-\uFF5A', + astral: + '\uD801[\uDC28-\uDC4F\uDCD8-\uDCFB\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC]|\uD803[\uDCC0-\uDCF2]|\uD806[\uDCC0-\uDCDF]|\uD81B[\uDE60-\uDE7F]|\uD835[\uDC1A-\uDC33\uDC4E-\uDC54\uDC56-\uDC67\uDC82-\uDC9B\uDCB6-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDCEA-\uDD03\uDD1E-\uDD37\uDD52-\uDD6B\uDD86-\uDD9F\uDDBA-\uDDD3\uDDEE-\uDE07\uDE22-\uDE3B\uDE56-\uDE6F\uDE8A-\uDEA5\uDEC2-\uDEDA\uDEDC-\uDEE1\uDEFC-\uDF14\uDF16-\uDF1B\uDF36-\uDF4E\uDF50-\uDF55\uDF70-\uDF88\uDF8A-\uDF8F\uDFAA-\uDFC2\uDFC4-\uDFC9\uDFCB]|\uD837[\uDF00-\uDF09\uDF0B-\uDF1E]|\uD83A[\uDD22-\uDD43]', + }, + { + name: 'Noncharacter_Code_Point', + bmp: '\uFDD0-\uFDEF\uFFFE\uFFFF', + astral: + '[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]', + }, + { + name: 'Uppercase', + bmp: 'A-Z\xC0-\xD6\xD8-\xDE\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178\u0179\u017B\u017D\u0181\u0182\u0184\u0186\u0187\u0189-\u018B\u018E-\u0191\u0193\u0194\u0196-\u0198\u019C\u019D\u019F\u01A0\u01A2\u01A4\u01A6\u01A7\u01A9\u01AC\u01AE\u01AF\u01B1-\u01B3\u01B5\u01B7\u01B8\u01BC\u01C4\u01C7\u01CA\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F1\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A\u023B\u023D\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E\u038F\u0391-\u03A1\u03A3-\u03AB\u03CF\u03D2-\u03D4\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F4\u03F7\u03F9\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u10A0-\u10C5\u10C7\u10CD\u13A0-\u13F5\u1C90-\u1CBA\u1CBD-\u1CBF\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1FB8-\u1FBB\u1FC8-\u1FCB\u1FD8-\u1FDB\u1FE8-\u1FEC\u1FF8-\u1FFB\u2102\u2107\u210B-\u210D\u2110-\u2112\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u2130-\u2133\u213E\u213F\u2145\u2160-\u216F\u2183\u24B6-\u24CF\u2C00-\u2C2F\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AE\uA7B0-\uA7B4\uA7B6\uA7B8\uA7BA\uA7BC\uA7BE\uA7C0\uA7C2\uA7C4-\uA7C7\uA7C9\uA7D0\uA7D6\uA7D8\uA7F5\uFF21-\uFF3A', + astral: + '\uD801[\uDC00-\uDC27\uDCB0-\uDCD3\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95]|\uD803[\uDC80-\uDCB2]|\uD806[\uDCA0-\uDCBF]|\uD81B[\uDE40-\uDE5F]|\uD835[\uDC00-\uDC19\uDC34-\uDC4D\uDC68-\uDC81\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB5\uDCD0-\uDCE9\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD38\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD6C-\uDD85\uDDA0-\uDDB9\uDDD4-\uDDED\uDE08-\uDE21\uDE3C-\uDE55\uDE70-\uDE89\uDEA8-\uDEC0\uDEE2-\uDEFA\uDF1C-\uDF34\uDF56-\uDF6E\uDF90-\uDFA8\uDFCA]|\uD83A[\uDD00-\uDD21]|\uD83C[\uDD30-\uDD49\uDD50-\uDD69\uDD70-\uDD89]', + }, + { + name: 'White_Space', + bmp: '\t-\r \x85\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000', + }, + ] + }, + {}, + ], + 220: [ + function (require, module, exports) { + module.exports = [ + { + name: 'Adlam', + astral: '\uD83A[\uDD00-\uDD4B\uDD50-\uDD59\uDD5E\uDD5F]', + }, + { + name: 'Ahom', + astral: '\uD805[\uDF00-\uDF1A\uDF1D-\uDF2B\uDF30-\uDF46]', + }, + { + name: 'Anatolian_Hieroglyphs', + astral: '\uD811[\uDC00-\uDE46]', + }, + { + name: 'Arabic', + bmp: '\u0600-\u0604\u0606-\u060B\u060D-\u061A\u061C-\u061E\u0620-\u063F\u0641-\u064A\u0656-\u066F\u0671-\u06DC\u06DE-\u06FF\u0750-\u077F\u0870-\u088E\u0890\u0891\u0898-\u08E1\u08E3-\u08FF\uFB50-\uFBC2\uFBD3-\uFD3D\uFD40-\uFD8F\uFD92-\uFDC7\uFDCF\uFDF0-\uFDFF\uFE70-\uFE74\uFE76-\uFEFC', + astral: + '\uD803[\uDE60-\uDE7E]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB\uDEF0\uDEF1]', + }, + { + name: 'Armenian', + bmp: '\u0531-\u0556\u0559-\u058A\u058D-\u058F\uFB13-\uFB17', + }, + { + name: 'Avestan', + astral: '\uD802[\uDF00-\uDF35\uDF39-\uDF3F]', + }, + { + name: 'Balinese', + bmp: '\u1B00-\u1B4C\u1B50-\u1B7E', + }, + { + name: 'Bamum', + bmp: '\uA6A0-\uA6F7', + astral: '\uD81A[\uDC00-\uDE38]', + }, + { + name: 'Bassa_Vah', + astral: '\uD81A[\uDED0-\uDEED\uDEF0-\uDEF5]', + }, + { + name: 'Batak', + bmp: '\u1BC0-\u1BF3\u1BFC-\u1BFF', + }, + { + name: 'Bengali', + bmp: '\u0980-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09FE', + }, + { + name: 'Bhaiksuki', + astral: '\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC45\uDC50-\uDC6C]', + }, + { + name: 'Bopomofo', + bmp: '\u02EA\u02EB\u3105-\u312F\u31A0-\u31BF', + }, + { + name: 'Brahmi', + astral: '\uD804[\uDC00-\uDC4D\uDC52-\uDC75\uDC7F]', + }, + { + name: 'Braille', + bmp: '\u2800-\u28FF', + }, + { + name: 'Buginese', + bmp: '\u1A00-\u1A1B\u1A1E\u1A1F', + }, + { + name: 'Buhid', + bmp: '\u1740-\u1753', + }, + { + name: 'Canadian_Aboriginal', + bmp: '\u1400-\u167F\u18B0-\u18F5', + astral: '\uD806[\uDEB0-\uDEBF]', + }, + { + name: 'Carian', + astral: '\uD800[\uDEA0-\uDED0]', + }, + { + name: 'Caucasian_Albanian', + astral: '\uD801[\uDD30-\uDD63\uDD6F]', + }, + { + name: 'Chakma', + astral: '\uD804[\uDD00-\uDD34\uDD36-\uDD47]', + }, + { + name: 'Cham', + bmp: '\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F', + }, + { + name: 'Cherokee', + bmp: '\u13A0-\u13F5\u13F8-\u13FD\uAB70-\uABBF', + }, + { + name: 'Chorasmian', + astral: '\uD803[\uDFB0-\uDFCB]', + }, + { + name: 'Common', + bmp: '\0-@\\[-`\\{-\xA9\xAB-\xB9\xBB-\xBF\xD7\xF7\u02B9-\u02DF\u02E5-\u02E9\u02EC-\u02FF\u0374\u037E\u0385\u0387\u0605\u060C\u061B\u061F\u0640\u06DD\u08E2\u0964\u0965\u0E3F\u0FD5-\u0FD8\u10FB\u16EB-\u16ED\u1735\u1736\u1802\u1803\u1805\u1CD3\u1CE1\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5-\u1CF7\u1CFA\u2000-\u200B\u200E-\u2064\u2066-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20C0\u2100-\u2125\u2127-\u2129\u212C-\u2131\u2133-\u214D\u214F-\u215F\u2189-\u218B\u2190-\u2426\u2440-\u244A\u2460-\u27FF\u2900-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2E00-\u2E5D\u2FF0-\u2FFB\u3000-\u3004\u3006\u3008-\u3020\u3030-\u3037\u303C-\u303F\u309B\u309C\u30A0\u30FB\u30FC\u3190-\u319F\u31C0-\u31E3\u3220-\u325F\u327F-\u32CF\u32FF\u3358-\u33FF\u4DC0-\u4DFF\uA700-\uA721\uA788-\uA78A\uA830-\uA839\uA92E\uA9CF\uAB5B\uAB6A\uAB6B\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFF70\uFF9E\uFF9F\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD', + astral: + '\uD800[\uDD00-\uDD02\uDD07-\uDD33\uDD37-\uDD3F\uDD90-\uDD9C\uDDD0-\uDDFC\uDEE1-\uDEFB]|\uD82F[\uDCA0-\uDCA3]|\uD833[\uDF50-\uDFC3]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD66\uDD6A-\uDD7A\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDEA\uDEE0-\uDEF3\uDF00-\uDF56\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDFCB\uDFCE-\uDFFF]|\uD83B[\uDC71-\uDCB4\uDD01-\uDD3D]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD00-\uDDAD\uDDE6-\uDDFF\uDE01\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFF]|\uD83D[\uDC00-\uDED7\uDEDD-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF73\uDF80-\uDFD8\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDE53\uDE60-\uDE6D\uDE70-\uDE74\uDE78-\uDE7C\uDE80-\uDE86\uDE90-\uDEAC\uDEB0-\uDEBA\uDEC0-\uDEC5\uDED0-\uDED9\uDEE0-\uDEE7\uDEF0-\uDEF6\uDF00-\uDF92\uDF94-\uDFCA\uDFF0-\uDFF9]|\uDB40[\uDC01\uDC20-\uDC7F]', + }, + { + name: 'Coptic', + bmp: '\u03E2-\u03EF\u2C80-\u2CF3\u2CF9-\u2CFF', + }, + { + name: 'Cuneiform', + astral: '\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC70-\uDC74\uDC80-\uDD43]', + }, + { + name: 'Cypriot', + astral: '\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F]', + }, + { + name: 'Cypro_Minoan', + astral: '\uD80B[\uDF90-\uDFF2]', + }, + { + name: 'Cyrillic', + bmp: '\u0400-\u0484\u0487-\u052F\u1C80-\u1C88\u1D2B\u1D78\u2DE0-\u2DFF\uA640-\uA69F\uFE2E\uFE2F', + }, + { + name: 'Deseret', + astral: '\uD801[\uDC00-\uDC4F]', + }, + { + name: 'Devanagari', + bmp: '\u0900-\u0950\u0955-\u0963\u0966-\u097F\uA8E0-\uA8FF', + }, + { + name: 'Dives_Akuru', + astral: + '\uD806[\uDD00-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD35\uDD37\uDD38\uDD3B-\uDD46\uDD50-\uDD59]', + }, + { + name: 'Dogra', + astral: '\uD806[\uDC00-\uDC3B]', + }, + { + name: 'Duployan', + astral: '\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9C-\uDC9F]', + }, + { + name: 'Egyptian_Hieroglyphs', + astral: '\uD80C[\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E\uDC30-\uDC38]', + }, + { + name: 'Elbasan', + astral: '\uD801[\uDD00-\uDD27]', + }, + { + name: 'Elymaic', + astral: '\uD803[\uDFE0-\uDFF6]', + }, + { + name: 'Ethiopic', + bmp: '\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u137C\u1380-\u1399\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E', + astral: '\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]', + }, + { + name: 'Georgian', + bmp: '\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u10FF\u1C90-\u1CBA\u1CBD-\u1CBF\u2D00-\u2D25\u2D27\u2D2D', + }, + { + name: 'Glagolitic', + bmp: '\u2C00-\u2C5F', + astral: '\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]', + }, + { + name: 'Gothic', + astral: '\uD800[\uDF30-\uDF4A]', + }, + { + name: 'Grantha', + astral: + '\uD804[\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]', + }, + { + name: 'Greek', + bmp: '\u0370-\u0373\u0375-\u0377\u037A-\u037D\u037F\u0384\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03E1\u03F0-\u03FF\u1D26-\u1D2A\u1D5D-\u1D61\u1D66-\u1D6A\u1DBF\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FC4\u1FC6-\u1FD3\u1FD6-\u1FDB\u1FDD-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFE\u2126\uAB65', + astral: '\uD800[\uDD40-\uDD8E\uDDA0]|\uD834[\uDE00-\uDE45]', + }, + { + name: 'Gujarati', + bmp: '\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AF1\u0AF9-\u0AFF', + }, + { + name: 'Gunjala_Gondi', + astral: '\uD807[\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD8E\uDD90\uDD91\uDD93-\uDD98\uDDA0-\uDDA9]', + }, + { + name: 'Gurmukhi', + bmp: '\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A76', + }, + { + name: 'Han', + bmp: '\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u3005\u3007\u3021-\u3029\u3038-\u303B\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFA6D\uFA70-\uFAD9', + astral: + '\uD81B[\uDFE2\uDFE3\uDFF0\uDFF1]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A]', + }, + { + name: 'Hangul', + bmp: '\u1100-\u11FF\u302E\u302F\u3131-\u318E\u3200-\u321E\u3260-\u327E\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', + }, + { + name: 'Hanifi_Rohingya', + astral: '\uD803[\uDD00-\uDD27\uDD30-\uDD39]', + }, + { + name: 'Hanunoo', + bmp: '\u1720-\u1734', + }, + { + name: 'Hatran', + astral: '\uD802[\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDCFF]', + }, + { + name: 'Hebrew', + bmp: '\u0591-\u05C7\u05D0-\u05EA\u05EF-\u05F4\uFB1D-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFB4F', + }, + { + name: 'Hiragana', + bmp: '\u3041-\u3096\u309D-\u309F', + astral: '\uD82C[\uDC01-\uDD1F\uDD50-\uDD52]|\uD83C\uDE00', + }, + { + name: 'Imperial_Aramaic', + astral: '\uD802[\uDC40-\uDC55\uDC57-\uDC5F]', + }, + { + name: 'Inherited', + bmp: '\u0300-\u036F\u0485\u0486\u064B-\u0655\u0670\u0951-\u0954\u1AB0-\u1ACE\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DFF\u200C\u200D\u20D0-\u20F0\u302A-\u302D\u3099\u309A\uFE00-\uFE0F\uFE20-\uFE2D', + astral: + '\uD800[\uDDFD\uDEE0]|\uD804\uDF3B|\uD833[\uDF00-\uDF2D\uDF30-\uDF46]|\uD834[\uDD67-\uDD69\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD]|\uDB40[\uDD00-\uDDEF]', + }, + { + name: 'Inscriptional_Pahlavi', + astral: '\uD802[\uDF60-\uDF72\uDF78-\uDF7F]', + }, + { + name: 'Inscriptional_Parthian', + astral: '\uD802[\uDF40-\uDF55\uDF58-\uDF5F]', + }, + { + name: 'Javanese', + bmp: '\uA980-\uA9CD\uA9D0-\uA9D9\uA9DE\uA9DF', + }, + { + name: 'Kaithi', + astral: '\uD804[\uDC80-\uDCC2\uDCCD]', + }, + { + name: 'Kannada', + bmp: '\u0C80-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDD\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2', + }, + { + name: 'Katakana', + bmp: '\u30A1-\u30FA\u30FD-\u30FF\u31F0-\u31FF\u32D0-\u32FE\u3300-\u3357\uFF66-\uFF6F\uFF71-\uFF9D', + astral: '\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00\uDD20-\uDD22\uDD64-\uDD67]', + }, + { + name: 'Kayah_Li', + bmp: '\uA900-\uA92D\uA92F', + }, + { + name: 'Kharoshthi', + astral: + '\uD802[\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE38-\uDE3A\uDE3F-\uDE48\uDE50-\uDE58]', + }, + { + name: 'Khitan_Small_Script', + astral: '\uD81B\uDFE4|\uD822[\uDF00-\uDFFF]|\uD823[\uDC00-\uDCD5]', + }, + { + name: 'Khmer', + bmp: '\u1780-\u17DD\u17E0-\u17E9\u17F0-\u17F9\u19E0-\u19FF', + }, + { + name: 'Khojki', + astral: '\uD804[\uDE00-\uDE11\uDE13-\uDE3E]', + }, + { + name: 'Khudawadi', + astral: '\uD804[\uDEB0-\uDEEA\uDEF0-\uDEF9]', + }, + { + name: 'Lao', + bmp: '\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF', + }, + { + name: 'Latin', + bmp: 'A-Za-z\xAA\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02B8\u02E0-\u02E4\u1D00-\u1D25\u1D2C-\u1D5C\u1D62-\u1D65\u1D6B-\u1D77\u1D79-\u1DBE\u1E00-\u1EFF\u2071\u207F\u2090-\u209C\u212A\u212B\u2132\u214E\u2160-\u2188\u2C60-\u2C7F\uA722-\uA787\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA7FF\uAB30-\uAB5A\uAB5C-\uAB64\uAB66-\uAB69\uFB00-\uFB06\uFF21-\uFF3A\uFF41-\uFF5A', + astral: '\uD801[\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD837[\uDF00-\uDF1E]', + }, + { + name: 'Lepcha', + bmp: '\u1C00-\u1C37\u1C3B-\u1C49\u1C4D-\u1C4F', + }, + { + name: 'Limbu', + bmp: '\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F', + }, + { + name: 'Linear_A', + astral: '\uD801[\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]', + }, + { + name: 'Linear_B', + astral: + '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA]', + }, + { + name: 'Lisu', + bmp: '\uA4D0-\uA4FF', + astral: '\uD807\uDFB0', + }, + { + name: 'Lycian', + astral: '\uD800[\uDE80-\uDE9C]', + }, + { + name: 'Lydian', + astral: '\uD802[\uDD20-\uDD39\uDD3F]', + }, + { + name: 'Mahajani', + astral: '\uD804[\uDD50-\uDD76]', + }, + { + name: 'Makasar', + astral: '\uD807[\uDEE0-\uDEF8]', + }, + { + name: 'Malayalam', + bmp: '\u0D00-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4F\u0D54-\u0D63\u0D66-\u0D7F', + }, + { + name: 'Mandaic', + bmp: '\u0840-\u085B\u085E', + }, + { + name: 'Manichaean', + astral: '\uD802[\uDEC0-\uDEE6\uDEEB-\uDEF6]', + }, + { + name: 'Marchen', + astral: '\uD807[\uDC70-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6]', + }, + { + name: 'Masaram_Gondi', + astral: '\uD807[\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59]', + }, + { + name: 'Medefaidrin', + astral: '\uD81B[\uDE40-\uDE9A]', + }, + { + name: 'Meetei_Mayek', + bmp: '\uAAE0-\uAAF6\uABC0-\uABED\uABF0-\uABF9', + }, + { + name: 'Mende_Kikakui', + astral: '\uD83A[\uDC00-\uDCC4\uDCC7-\uDCD6]', + }, + { + name: 'Meroitic_Cursive', + astral: '\uD802[\uDDA0-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDDFF]', + }, + { + name: 'Meroitic_Hieroglyphs', + astral: '\uD802[\uDD80-\uDD9F]', + }, + { + name: 'Miao', + astral: '\uD81B[\uDF00-\uDF4A\uDF4F-\uDF87\uDF8F-\uDF9F]', + }, + { + name: 'Modi', + astral: '\uD805[\uDE00-\uDE44\uDE50-\uDE59]', + }, + { + name: 'Mongolian', + bmp: '\u1800\u1801\u1804\u1806-\u1819\u1820-\u1878\u1880-\u18AA', + astral: '\uD805[\uDE60-\uDE6C]', + }, + { + name: 'Mro', + astral: '\uD81A[\uDE40-\uDE5E\uDE60-\uDE69\uDE6E\uDE6F]', + }, + { + name: 'Multani', + astral: '\uD804[\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA9]', + }, + { + name: 'Myanmar', + bmp: '\u1000-\u109F\uA9E0-\uA9FE\uAA60-\uAA7F', + }, + { + name: 'Nabataean', + astral: '\uD802[\uDC80-\uDC9E\uDCA7-\uDCAF]', + }, + { + name: 'Nandinagari', + astral: '\uD806[\uDDA0-\uDDA7\uDDAA-\uDDD7\uDDDA-\uDDE4]', + }, + { + name: 'New_Tai_Lue', + bmp: '\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u19DE\u19DF', + }, + { + name: 'Newa', + astral: '\uD805[\uDC00-\uDC5B\uDC5D-\uDC61]', + }, + { + name: 'Nko', + bmp: '\u07C0-\u07FA\u07FD-\u07FF', + }, + { + name: 'Nushu', + astral: '\uD81B\uDFE1|\uD82C[\uDD70-\uDEFB]', + }, + { + name: 'Nyiakeng_Puachue_Hmong', + astral: '\uD838[\uDD00-\uDD2C\uDD30-\uDD3D\uDD40-\uDD49\uDD4E\uDD4F]', + }, + { + name: 'Ogham', + bmp: '\u1680-\u169C', + }, + { + name: 'Ol_Chiki', + bmp: '\u1C50-\u1C7F', + }, + { + name: 'Old_Hungarian', + astral: '\uD803[\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDCFF]', + }, + { + name: 'Old_Italic', + astral: '\uD800[\uDF00-\uDF23\uDF2D-\uDF2F]', + }, + { + name: 'Old_North_Arabian', + astral: '\uD802[\uDE80-\uDE9F]', + }, + { + name: 'Old_Permic', + astral: '\uD800[\uDF50-\uDF7A]', + }, + { + name: 'Old_Persian', + astral: '\uD800[\uDFA0-\uDFC3\uDFC8-\uDFD5]', + }, + { + name: 'Old_Sogdian', + astral: '\uD803[\uDF00-\uDF27]', + }, + { + name: 'Old_South_Arabian', + astral: '\uD802[\uDE60-\uDE7F]', + }, + { + name: 'Old_Turkic', + astral: '\uD803[\uDC00-\uDC48]', + }, + { + name: 'Old_Uyghur', + astral: '\uD803[\uDF70-\uDF89]', + }, + { + name: 'Oriya', + bmp: '\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B55-\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B77', + }, + { + name: 'Osage', + astral: '\uD801[\uDCB0-\uDCD3\uDCD8-\uDCFB]', + }, + { + name: 'Osmanya', + astral: '\uD801[\uDC80-\uDC9D\uDCA0-\uDCA9]', + }, + { + name: 'Pahawh_Hmong', + astral: '\uD81A[\uDF00-\uDF45\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]', + }, + { + name: 'Palmyrene', + astral: '\uD802[\uDC60-\uDC7F]', + }, + { + name: 'Pau_Cin_Hau', + astral: '\uD806[\uDEC0-\uDEF8]', + }, + { + name: 'Phags_Pa', + bmp: '\uA840-\uA877', + }, + { + name: 'Phoenician', + astral: '\uD802[\uDD00-\uDD1B\uDD1F]', + }, + { + name: 'Psalter_Pahlavi', + astral: '\uD802[\uDF80-\uDF91\uDF99-\uDF9C\uDFA9-\uDFAF]', + }, + { + name: 'Rejang', + bmp: '\uA930-\uA953\uA95F', + }, + { + name: 'Runic', + bmp: '\u16A0-\u16EA\u16EE-\u16F8', + }, + { + name: 'Samaritan', + bmp: '\u0800-\u082D\u0830-\u083E', + }, + { + name: 'Saurashtra', + bmp: '\uA880-\uA8C5\uA8CE-\uA8D9', + }, + { + name: 'Sharada', + astral: '\uD804[\uDD80-\uDDDF]', + }, + { + name: 'Shavian', + astral: '\uD801[\uDC50-\uDC7F]', + }, + { + name: 'Siddham', + astral: '\uD805[\uDD80-\uDDB5\uDDB8-\uDDDD]', + }, + { + name: 'SignWriting', + astral: '\uD836[\uDC00-\uDE8B\uDE9B-\uDE9F\uDEA1-\uDEAF]', + }, + { + name: 'Sinhala', + bmp: '\u0D81-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4', + astral: '\uD804[\uDDE1-\uDDF4]', + }, + { + name: 'Sogdian', + astral: '\uD803[\uDF30-\uDF59]', + }, + { + name: 'Sora_Sompeng', + astral: '\uD804[\uDCD0-\uDCE8\uDCF0-\uDCF9]', + }, + { + name: 'Soyombo', + astral: '\uD806[\uDE50-\uDEA2]', + }, + { + name: 'Sundanese', + bmp: '\u1B80-\u1BBF\u1CC0-\u1CC7', + }, + { + name: 'Syloti_Nagri', + bmp: '\uA800-\uA82C', + }, + { + name: 'Syriac', + bmp: '\u0700-\u070D\u070F-\u074A\u074D-\u074F\u0860-\u086A', + }, + { + name: 'Tagalog', + bmp: '\u1700-\u1715\u171F', + }, + { + name: 'Tagbanwa', + bmp: '\u1760-\u176C\u176E-\u1770\u1772\u1773', + }, + { + name: 'Tai_Le', + bmp: '\u1950-\u196D\u1970-\u1974', + }, + { + name: 'Tai_Tham', + bmp: '\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD', + }, + { + name: 'Tai_Viet', + bmp: '\uAA80-\uAAC2\uAADB-\uAADF', + }, + { + name: 'Takri', + astral: '\uD805[\uDE80-\uDEB9\uDEC0-\uDEC9]', + }, + { + name: 'Tamil', + bmp: '\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BFA', + astral: '\uD807[\uDFC0-\uDFF1\uDFFF]', + }, + { + name: 'Tangsa', + astral: '\uD81A[\uDE70-\uDEBE\uDEC0-\uDEC9]', + }, + { + name: 'Tangut', + astral: + '\uD81B\uDFE0|[\uD81C-\uD820][\uDC00-\uDFFF]|\uD821[\uDC00-\uDFF7]|\uD822[\uDC00-\uDEFF]|\uD823[\uDD00-\uDD08]', + }, + { + name: 'Telugu', + bmp: '\u0C00-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3C-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C5D\u0C60-\u0C63\u0C66-\u0C6F\u0C77-\u0C7F', + }, + { + name: 'Thaana', + bmp: '\u0780-\u07B1', + }, + { + name: 'Thai', + bmp: '\u0E01-\u0E3A\u0E40-\u0E5B', + }, + { + name: 'Tibetan', + bmp: '\u0F00-\u0F47\u0F49-\u0F6C\u0F71-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u0FD9\u0FDA', + }, + { + name: 'Tifinagh', + bmp: '\u2D30-\u2D67\u2D6F\u2D70\u2D7F', + }, + { + name: 'Tirhuta', + astral: '\uD805[\uDC80-\uDCC7\uDCD0-\uDCD9]', + }, + { + name: 'Toto', + astral: '\uD838[\uDE90-\uDEAE]', + }, + { + name: 'Ugaritic', + astral: '\uD800[\uDF80-\uDF9D\uDF9F]', + }, + { + name: 'Vai', + bmp: '\uA500-\uA62B', + }, + { + name: 'Vithkuqi', + astral: + '\uD801[\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC]', + }, + { + name: 'Wancho', + astral: '\uD838[\uDEC0-\uDEF9\uDEFF]', + }, + { + name: 'Warang_Citi', + astral: '\uD806[\uDCA0-\uDCF2\uDCFF]', + }, + { + name: 'Yezidi', + astral: '\uD803[\uDE80-\uDEA9\uDEAB-\uDEAD\uDEB0\uDEB1]', + }, + { + name: 'Yi', + bmp: '\uA000-\uA48C\uA490-\uA4C6', + }, + { + name: 'Zanabazar_Square', + astral: '\uD806[\uDE00-\uDE47]', + }, + ] + }, + {}, + ], + }, + {}, + [7] + )(7) +}) diff --git a/module/actor/actor-sheet.js b/module/actor/actor-sheet.js index d37c5d08c..38f4bde12 100755 --- a/module/actor/actor-sheet.js +++ b/module/actor/actor-sheet.js @@ -17,1927 +17,1927 @@ import SplitDREditor from './splitdr-editor.js' * @extends {ActorSheet} */ export class GurpsActorSheet extends ActorSheet { - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['gurps', 'sheet', 'actor'], - width: 800, - height: 800, - tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], - scrollY: [ - '.gurpsactorsheet #advantages #reactions #melee #ranged #skills #spells #equipmentcarried #equipmentother #notes', - ], - dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], - }) - } - - /* -------------------------------------------- */ - - /** @override */ - get template() { - if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' - return 'systems/gurps/templates/actor/actor-sheet-gcs.hbs' - } - - /* -------------------------------------------- */ - - async close(options = {}) { - await super.close(options) - GURPS.ClearLastActor(this.actor) - } - - // Hack to keep sheet from flashing during multiple DB updates - async _render(...args) { - //console.log("IgnoreRender: " + this.object?.ignoreRender) - if (!!this.object?.ignoreRender) return - await super._render(...args) - } - - update(data, options) { - super.update(data, options) - } - - /** @override */ - getData() { - const sheetData = super.getData() - sheetData.olddata = sheetData.data - sheetData.data = this.actor.system - sheetData.ranges = GURPS.rangeObject.ranges - sheetData.useCI = GURPS.ConditionalInjury.isInUse() - sheetData.conditionalEffectsTable = GURPS.ConditionalInjury.conditionalEffectsTable() - GURPS.SetLastActor(this.actor) - sheetData.eqtsummary = this.actor.system.eqtsummary - sheetData.navigateVisible = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_SHEET_NAVIGATION) - sheetData.isGM = game.user.isGM - sheetData._id = sheetData.olddata._id - sheetData.effects = this.actor.getEmbeddedCollection('ActiveEffect').contents - sheetData.useQN = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_USE_QUINTESSENCE) - - return sheetData - } - - /* -------------------------------------------- */ - - /** - * @inheritdoc - * @param {JQuery} html - */ - activateListeners(html) { - super.activateListeners(html) - - html.find('.gurpsactorsheet').click(ev => { - this._onfocus(ev) - }) - - html - .parent('.window-content') - .siblings('.window-header') - .click(ev => { - this._onfocus(ev) - }) - - // Click on a navigation-link to scroll the sheet to that section. - html.find('.navigation-link').click(this._onNavigate.bind(this)) - - // Enable some fields to be a targeted roll without an OTF. - html.find('.rollable').click(this._onClickRoll.bind(this)) - - // Wire events to all OTFs on the sheet. - GurpsWiring.hookupAllEvents(html) - - // Allow OTFs on this actor sheet to be draggable. - html.find('[data-otf]').each((_, li) => { - li.setAttribute('draggable', true) - li.addEventListener('dragstart', ev => { - let display = '' - if (!!ev.currentTarget.dataset.action) display = ev.currentTarget.innerText - return ev.dataTransfer.setData( - 'text/plain', - JSON.stringify({ - otf: li.getAttribute('data-otf'), - actor: this.actor.id, - encodedAction: ev.currentTarget.dataset.action, - displayname: display, - }) - ) - }) - }) - - // open the split DR dialog - html.find('.dr button[data-key]').on('click', this._onClickSplit.bind(this)) - - if (!isConfigurationAllowed(this.actor)) return // Only allow "owners" to be able to edit the sheet, but anyone can roll from the sheet - - this._createHeaderMenus(html) - this._createEquipmentItemMenus(html) - - // if not doing automatic encumbrance calculations, allow a click on the Encumbrance table to set the current value. - if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ENCUMBRANCE)) { - html.find('.enc').click(this._onClickEnc.bind(this)) - } - - // allow items in these lists to be draggable - // TODO provide feedback about where the cursor is and whether you can drop it there. - this.makelistdrag(html, '.condmoddraggable', 'condmod') - this.makelistdrag(html, '.reactdraggable', 'reactions') - this.makelistdrag(html, '.eqtdraggable', 'equipment') - this.makelistdrag(html, '.adsdraggable', 'ads') - this.makelistdrag(html, '.skldraggable', 'skills') - this.makelistdrag(html, '.spldraggable', 'spells') - this.makelistdrag(html, '.notedraggable', 'note') - this.makelistdrag(html, '.meleedraggable', 'melee') - this.makelistdrag(html, '.rangeddraggable', 'ranged') - - html.find('[data-operation="share-portrait"]').click(ev => { - ev.preventDefault() - let image = this.actor.system.fullimage ?? this.actor.img - const ip = new ImagePopout(image, { - title: this.actor.name, - shareable: true, - entity: this.actor, - }) - - // Display the image popout - ip.render(true) - }) - - // Stop ENTER key in a Resource Tracker (HP, FP, others) from doing anything. - // This prevents the inadvertant triggering of the inc/dec buttons. - html.find('.spinner details summary input').keypress(ev => { - if (ev.key === 'Enter') ev.preventDefault() - }) - - // Handle resource tracker "+" button. - html.find('button[data-operation="resource-inc"]').click(async ev => { - ev.preventDefault() - let parent = $(ev.currentTarget).closest('[data-gurps-resource]') - let path = parent.attr('data-gurps-resource') - - let tracker = getProperty(this.actor.system, path) - let value = (+tracker.value || 0) + (ev.shiftKey ? 5 : 1) - if (isNaN(value)) value = tracker.max || 0 - - let json = `{ "data.${path}.value": ${value} }` - this.actor.update(JSON.parse(json)) - }) - - // Handle resource tracker "-" button. - html.find('button[data-operation="resource-dec"]').click(ev => { - ev.preventDefault() - let parent = $(ev.currentTarget).closest('[data-gurps-resource]') - let path = parent.attr('data-gurps-resource') - - let tracker = getProperty(this.actor.system, path) - let value = (tracker.value || 0) - (ev.shiftKey ? 5 : 1) - if (isNaN(value)) value = tracker.max || 0 - - let json = `{ "data.${path}.value": ${value} }` - this.actor.update(JSON.parse(json)) - }) - - // Handle resource tracker "reset" button. - html.find('button[data-operation="resource-reset"]').click(ev => { - ev.preventDefault() - let parent = $(ev.currentTarget).closest('[data-gurps-resource]') - let path = parent.attr('data-gurps-resource') - - let tracker = getProperty(this.actor.system, path) - let value = !!tracker.isDamageTracker ? tracker.min || 0 : tracker.max || 0 - - let json = `{ "data.${path}.value": ${value} }` - this.actor.update(JSON.parse(json)) - }) - - // allow a click on the 'edit' icon to open the resource tracker editor. - html.find('.tracked-resource .header.with-editor').click(this.editTracker.bind(this)) - - // START CONDITIONAL INJURY - - const formatCIEmpty = val => (val === null ? '' : val) - - const updateActorWithChangedSeverity = changedSeverity => { - console.log('updateActorWithChangedSeverity') - this.actor.update({ - 'data.conditionalinjury.injury.severity': formatCIEmpty(changedSeverity), - 'data.conditionalinjury.injury.daystoheal': formatCIEmpty(CI.daysToHealForSeverity(changedSeverity)), - }) - } - - html.find('button[data-operation="ci-severity-inc"]').click(async ev => { - ev.preventDefault() - updateActorWithChangedSeverity(CI.incrementSeverity(this.actor.system.conditionalinjury.injury.severity)) - }) - - html.find('button[data-operation="ci-severity-dec"]').click(ev => { - ev.preventDefault() - updateActorWithChangedSeverity(CI.decrementSeverity(this.actor.system.conditionalinjury.injury.severity)) - }) - - const updateActorWithChangedDaysToHeal = changedDaysToHeal => { - console.log('updateActorWithChangedDaysToHeal') - this.actor.update({ - 'data.conditionalinjury.injury.severity': formatCIEmpty(CI.severityForDaysToHeal(changedDaysToHeal)), - 'data.conditionalinjury.injury.daystoheal': formatCIEmpty(changedDaysToHeal), - }) - } - - html.find('button[data-operation="ci-days-inc"]').click(async ev => { - ev.preventDefault() - updateActorWithChangedDaysToHeal( - CI.incrementDaysToHeal(this.actor.system.conditionalinjury.injury.daystoheal, ev.shiftKey ? 5 : 1) - ) - }) - - html.find('button[data-operation="ci-days-dec"]').click(ev => { - ev.preventDefault() - updateActorWithChangedDaysToHeal( - CI.decrementDaysToHeal(this.actor.system.conditionalinjury.injury.daystoheal, ev.shiftKey ? 5 : 1) - ) - }) - - html.find('button[data-operation="ci-reset"]').click(ev => { - ev.preventDefault() - updateActorWithChangedSeverity(null) - }) - - html.find('input[data-operation="ci-severity-set"]').change(ev => { - ev.preventDefault() - console.log('change severity', ev.target.value) - updateActorWithChangedSeverity(CI.setSeverity(ev.target.value)) - }) - - // TODO after this event resolves, the severity field briefly flashes with the correct value but then reverts to what was there before the change - html.find('input[data-operation="ci-days-set"]').change(ev => { - ev.preventDefault() - console.log('change days', ev.target.value) - updateActorWithChangedDaysToHeal(CI.setDaysToHeal(ev.target.value)) - }) - - // END CONDITIONAL INJURY - - // If using the "enhanced" inputs for trackers, enable the ribbon popup. - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_ENHANCED_INPUT)) { - // On Focus, initialize the ribbon popup and show it. - html.find('.spinner details summary input').focus(ev => { - let details = ev.currentTarget.closest('details') - - if (!details.open) { - let parent = ev.currentTarget.closest('[data-gurps-resource]') - let path = $(parent).attr('data-gurps-resource') - let tracker = getProperty(this.actor.system, path) - - let restoreButton = $(details).find('button.restore') - restoreButton.attr('data-value', `${tracker.value}`) - restoreButton.text(tracker.value) - } - details.open = true - }) - - // Update the actor's data, set the restore button to the new value, - // and close the popup. - html.find('.spinner details summary input').focusout(ev => { - ev.preventDefault() - // set the restore button to the new value of the input field - let details = ev.currentTarget.closest('details') - let input = $(details).find('input') - let newValue = input.val() - - let restoreButton = $(details).find('button.restore') - restoreButton.attr('data-value', newValue) - restoreButton.text(newValue) - - // update the actor's data to newValue - let parent = ev.currentTarget.closest('[data-gurps-resource]') - let path = $(parent).attr('data-gurps-resource') - let value = parseInt(newValue) - let json = `{ "data.${path}.value": ${value} }` - this.actor.update(JSON.parse(json)) - - details.open = false - }) - - // Prevent the popup from closing on a click. - html.find('.spinner details .popup > *').mousedown(ev => { - ev.preventDefault() - }) - - // On a click of the enhanced input popup, update the text input field, but do not update the actor's data. - html.find('button[data-operation="resource-update"]').click(ev => { - let dataValue = $(ev.currentTarget).attr('data-value') - let details = $(ev.currentTarget).closest('details') - let input = $(details).find('input') - let value = parseInt(input.val()) - - if (dataValue.charAt(0) === '-' || dataValue.charAt(0) === '+') { - value += parseInt(dataValue) - } else { - value = parseInt(dataValue) - } - - if (!isNaN(value)) { - input.val(value) - } - }) - } // end enhanced input - - // On mouseover any item with the class .tooltip-manager which also has a child (image) of class .tooltippic, - // display the tooltip in the correct position. - html.find('.tooltip.gga-manual').mouseover(ev => { - ev.preventDefault() - - let target = $(ev.currentTarget) - if (target.hasNoChildren) { - return - } - - let tooltip = target.children('.tooltiptext.gga-manual') - if (tooltip) { - tooltip.css({ visibility: 'visible' }) - } - tooltip = target.children('.tooltippic.gga-manual') - if (tooltip) { - tooltip.css({ visibility: 'visible' }) - } - }) - - // On mouseout, stop displaying the tooltip. - html.find('.tooltip.gga-manual').mouseout(ev => { - ev.preventDefault() - let target = $(ev.currentTarget) - if (target.hasNoChildren) { - return - } - - let tooltip = target.children('.tooltiptext.gga-manual') - if (tooltip) { - tooltip.css({ visibility: 'hidden' }) - } - tooltip = target.children('.tooltippic.gga-manual') - if (tooltip) { - tooltip.css({ visibility: 'hidden' }) - } - }) - - // Equipment === - - // On clicking the Equip column, toggle the equipped status of the item. - html.find('.changeequip').click(this._onClickEquip.bind(this)) - - // Simple trick, move 'contains' items into 'collapsed' and back. The html doesn't show 'collapsed'. - html.find('.expandcollapseicon').click(async ev => { - let actor = this.actor - let element = ev.currentTarget - let parent = $(element).closest('[data-key]') - let path = parent.attr('data-key') - actor.toggleExpand(path) - }) - - // On double-clicking an item, open its editor. - html.find('.dblclkedit').dblclick(async ev => { - ev.preventDefault() - let parent = $(ev.currentTarget).closest('[data-key]') - - let path = parent[0].dataset.key - let actor = this.actor - let obj = duplicate(getProperty(actor, path)) // must dup so difference can be detected when updated - if (!!obj.itemid) { - let item = this.actor.items.get(obj.itemid) - item.editingActor = this.actor - item.sheet.render(true) - return - } - - if (path.includes('equipment')) this.editEquipment(actor, path, obj) - if (path.includes('melee')) this.editMelee(actor, path, obj) - if (path.includes('ranged')) this.editRanged(actor, path, obj) - if (path.includes('ads')) this.editAds(actor, path, obj) - if (path.includes('skills')) this.editSkills(actor, path, obj) - if (path.includes('spells')) this.editSpells(actor, path, obj) - if (path.includes('notes')) this.editNotes(actor, path, obj) - }) - - html.find('.dblclkedit').on('drop', this.handleDblclickeditDrop.bind(this)) - - // On clicking equipment quantity increment, increase the amount. - html.find('button[data-operation="equipment-inc"]').click(async ev => { - ev.preventDefault() - let parent = $(ev.currentTarget).closest('[data-key]') - let path = parent.attr('data-key') - let eqt = getProperty(this.actor, path) - let value = parseInt(eqt.count) + (ev.shiftKey ? 5 : 1) - if (isNaN(value)) value = 0 - await this.actor.updateEqtCount(path, value) - }) - - html.find('button[data-operation="equipment-inc-uses"]').click(async ev => { - ev.preventDefault() - let parent = $(ev.currentTarget).closest('[data-key]') - let path = parent.attr('data-key') - let eqt = getProperty(this.actor, path) - let value = parseInt(eqt.uses) + (ev.shiftKey ? 5 : 1) - if (isNaN(value)) value = eqt.uses - await this.actor.update({ [path + '.uses']: value }) - }) - html.find('button[data-operation="equipment-dec-uses"]').click(async ev => { - ev.preventDefault() - let parent = $(ev.currentTarget).closest('[data-key]') - let path = parent.attr('data-key') - let eqt = getProperty(this.actor, path) - let value = parseInt(eqt.uses) - (ev.shiftKey ? 5 : 1) - if (isNaN(value)) value = eqt.uses - if (value < 0) value = 0 - await this.actor.update({ [path + '.uses']: value }) - }) - - // On clicking equipment quantity decrement, decrease the amount or remove from list. - html.find('button[data-operation="equipment-dec"]').click(async ev => { - ev.preventDefault() - let parent = $(ev.currentTarget).closest('[data-key]') - let path = parent.attr('data-key') - let actor = this.actor - let eqt = getProperty(actor, path) - if (eqt.count == 0) { - await Dialog.confirm({ - title: i18n('GURPS.removeItem'), - content: i18n_f('GURPS.confirmRemoveItem', { name: eqt.name }, 'Remove {name} from the Equipment List?'), - yes: () => actor.deleteEquipment(path), - }) - } else { - let value = parseInt(eqt.count) - (ev.shiftKey ? 5 : 1) - if (isNaN(value) || value < 0) value = 0 - await this.actor.updateEqtCount(path, value) - } - }) - - html.find('.addnoteicon').on('click', this._addNote.bind(this)) - - let notesMenuItems = [ - { - name: 'Edit', - icon: "", - callback: e => { - let path = e[0].dataset.key - let o = duplicate(GURPS.decode(this.actor, path)) - this.editNotes(this.actor, path, o) - }, - }, - { - name: 'Delete', - icon: "", - callback: e => { - GURPS.removeKey(this.actor, e[0].dataset.key) - }, - }, - ] - new ContextMenu(html, '.notesmenu', notesMenuItems) - - html.find('[data-onethird]').click(ev => { - let el = ev.currentTarget - let opt = el.dataset.onethird - let active = !!this.actor.system.conditions[opt] - this.actor.toggleEffectByName(opt, !active) - }) - - html.find('[data-onethird]').hover( - function() { - let opt = $(this).attr('data-onethird') - let msg = 'Disable ' + opt - if ($(this).hasClass('buttongrey')) msg = 'Enable ' + opt - $(this).append( - $( - `
    ${msg}
    ` - ) - ) - }, - function() { - $(this).find('div').last().remove() - } - ) - - html.find('#qnotes').dblclick(ex => { - let n = this.actor.system.additionalresources.qnotes || '' - n = n.replace(/
    /g, '\n') - let actor = this.actor - new Dialog({ - title: 'Edit Quick Note', - content: `Enter a Quick Note (a great place to put an On-the-Fly formula!):



    Examples: + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['gurps', 'sheet', 'actor'], + width: 800, + height: 800, + tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], + scrollY: [ + '.gurpsactorsheet #advantages #reactions #melee #ranged #skills #spells #equipmentcarried #equipmentother #notes', + ], + dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], + }) + } + + /* -------------------------------------------- */ + + /** @override */ + get template() { + if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' + return 'systems/gurps/templates/actor/actor-sheet-gcs.hbs' + } + + /* -------------------------------------------- */ + + async close(options = {}) { + await super.close(options) + GURPS.ClearLastActor(this.actor) + } + + // Hack to keep sheet from flashing during multiple DB updates + async _render(...args) { + //console.log("IgnoreRender: " + this.object?.ignoreRender) + if (!!this.object?.ignoreRender) return + await super._render(...args) + } + + update(data, options) { + super.update(data, options) + } + + /** @override */ + getData() { + const sheetData = super.getData() + sheetData.olddata = sheetData.data + sheetData.data = this.actor.system + sheetData.ranges = GURPS.rangeObject.ranges + sheetData.useCI = GURPS.ConditionalInjury.isInUse() + sheetData.conditionalEffectsTable = GURPS.ConditionalInjury.conditionalEffectsTable() + GURPS.SetLastActor(this.actor) + sheetData.eqtsummary = this.actor.system.eqtsummary + sheetData.navigateVisible = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_SHEET_NAVIGATION) + sheetData.isGM = game.user.isGM + sheetData._id = sheetData.olddata._id + sheetData.effects = this.actor.getEmbeddedCollection('ActiveEffect').contents + sheetData.useQN = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_USE_QUINTESSENCE) + + return sheetData + } + + /* -------------------------------------------- */ + + /** + * @inheritdoc + * @param {JQuery} html + */ + activateListeners(html) { + super.activateListeners(html) + + html.find('.gurpsactorsheet').click(ev => { + this._onfocus(ev) + }) + + html + .parent('.window-content') + .siblings('.window-header') + .click(ev => { + this._onfocus(ev) + }) + + // Click on a navigation-link to scroll the sheet to that section. + html.find('.navigation-link').click(this._onNavigate.bind(this)) + + // Enable some fields to be a targeted roll without an OTF. + html.find('.rollable').click(this._onClickRoll.bind(this)) + + // Wire events to all OTFs on the sheet. + GurpsWiring.hookupAllEvents(html) + + // Allow OTFs on this actor sheet to be draggable. + html.find('[data-otf]').each((_, li) => { + li.setAttribute('draggable', true) + li.addEventListener('dragstart', ev => { + let display = '' + if (!!ev.currentTarget.dataset.action) display = ev.currentTarget.innerText + return ev.dataTransfer.setData( + 'text/plain', + JSON.stringify({ + otf: li.getAttribute('data-otf'), + actor: this.actor.id, + encodedAction: ev.currentTarget.dataset.action, + displayname: display, + }) + ) + }) + }) + + // open the split DR dialog + html.find('.dr button[data-key]').on('click', this._onClickSplit.bind(this)) + + if (!isConfigurationAllowed(this.actor)) return // Only allow "owners" to be able to edit the sheet, but anyone can roll from the sheet + + this._createHeaderMenus(html) + this._createEquipmentItemMenus(html) + + // if not doing automatic encumbrance calculations, allow a click on the Encumbrance table to set the current value. + if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ENCUMBRANCE)) { + html.find('.enc').click(this._onClickEnc.bind(this)) + } + + // allow items in these lists to be draggable + // TODO provide feedback about where the cursor is and whether you can drop it there. + this.makelistdrag(html, '.condmoddraggable', 'condmod') + this.makelistdrag(html, '.reactdraggable', 'reactions') + this.makelistdrag(html, '.eqtdraggable', 'equipment') + this.makelistdrag(html, '.adsdraggable', 'ads') + this.makelistdrag(html, '.skldraggable', 'skills') + this.makelistdrag(html, '.spldraggable', 'spells') + this.makelistdrag(html, '.notedraggable', 'note') + this.makelistdrag(html, '.meleedraggable', 'melee') + this.makelistdrag(html, '.rangeddraggable', 'ranged') + + html.find('[data-operation="share-portrait"]').click(ev => { + ev.preventDefault() + let image = this.actor.system.fullimage ?? this.actor.img + const ip = new ImagePopout(image, { + title: this.actor.name, + shareable: true, + entity: this.actor, + }) + + // Display the image popout + ip.render(true) + }) + + // Stop ENTER key in a Resource Tracker (HP, FP, others) from doing anything. + // This prevents the inadvertant triggering of the inc/dec buttons. + html.find('.spinner details summary input').keypress(ev => { + if (ev.key === 'Enter') ev.preventDefault() + }) + + // Handle resource tracker "+" button. + html.find('button[data-operation="resource-inc"]').click(async ev => { + ev.preventDefault() + let parent = $(ev.currentTarget).closest('[data-gurps-resource]') + let path = parent.attr('data-gurps-resource') + + let tracker = getProperty(this.actor.system, path) + let value = (+tracker.value || 0) + (ev.shiftKey ? 5 : 1) + if (isNaN(value)) value = tracker.max || 0 + + let json = `{ "data.${path}.value": ${value} }` + this.actor.update(JSON.parse(json)) + }) + + // Handle resource tracker "-" button. + html.find('button[data-operation="resource-dec"]').click(ev => { + ev.preventDefault() + let parent = $(ev.currentTarget).closest('[data-gurps-resource]') + let path = parent.attr('data-gurps-resource') + + let tracker = getProperty(this.actor.system, path) + let value = (tracker.value || 0) - (ev.shiftKey ? 5 : 1) + if (isNaN(value)) value = tracker.max || 0 + + let json = `{ "data.${path}.value": ${value} }` + this.actor.update(JSON.parse(json)) + }) + + // Handle resource tracker "reset" button. + html.find('button[data-operation="resource-reset"]').click(ev => { + ev.preventDefault() + let parent = $(ev.currentTarget).closest('[data-gurps-resource]') + let path = parent.attr('data-gurps-resource') + + let tracker = getProperty(this.actor.system, path) + let value = !!tracker.isDamageTracker ? tracker.min || 0 : tracker.max || 0 + + let json = `{ "data.${path}.value": ${value} }` + this.actor.update(JSON.parse(json)) + }) + + // allow a click on the 'edit' icon to open the resource tracker editor. + html.find('.tracked-resource .header.with-editor').click(this.editTracker.bind(this)) + + // START CONDITIONAL INJURY + + const formatCIEmpty = val => (val === null ? '' : val) + + const updateActorWithChangedSeverity = changedSeverity => { + console.log('updateActorWithChangedSeverity') + this.actor.update({ + 'data.conditionalinjury.injury.severity': formatCIEmpty(changedSeverity), + 'data.conditionalinjury.injury.daystoheal': formatCIEmpty(CI.daysToHealForSeverity(changedSeverity)), + }) + } + + html.find('button[data-operation="ci-severity-inc"]').click(async ev => { + ev.preventDefault() + updateActorWithChangedSeverity(CI.incrementSeverity(this.actor.system.conditionalinjury.injury.severity)) + }) + + html.find('button[data-operation="ci-severity-dec"]').click(ev => { + ev.preventDefault() + updateActorWithChangedSeverity(CI.decrementSeverity(this.actor.system.conditionalinjury.injury.severity)) + }) + + const updateActorWithChangedDaysToHeal = changedDaysToHeal => { + console.log('updateActorWithChangedDaysToHeal') + this.actor.update({ + 'data.conditionalinjury.injury.severity': formatCIEmpty(CI.severityForDaysToHeal(changedDaysToHeal)), + 'data.conditionalinjury.injury.daystoheal': formatCIEmpty(changedDaysToHeal), + }) + } + + html.find('button[data-operation="ci-days-inc"]').click(async ev => { + ev.preventDefault() + updateActorWithChangedDaysToHeal( + CI.incrementDaysToHeal(this.actor.system.conditionalinjury.injury.daystoheal, ev.shiftKey ? 5 : 1) + ) + }) + + html.find('button[data-operation="ci-days-dec"]').click(ev => { + ev.preventDefault() + updateActorWithChangedDaysToHeal( + CI.decrementDaysToHeal(this.actor.system.conditionalinjury.injury.daystoheal, ev.shiftKey ? 5 : 1) + ) + }) + + html.find('button[data-operation="ci-reset"]').click(ev => { + ev.preventDefault() + updateActorWithChangedSeverity(null) + }) + + html.find('input[data-operation="ci-severity-set"]').change(ev => { + ev.preventDefault() + console.log('change severity', ev.target.value) + updateActorWithChangedSeverity(CI.setSeverity(ev.target.value)) + }) + + // TODO after this event resolves, the severity field briefly flashes with the correct value but then reverts to what was there before the change + html.find('input[data-operation="ci-days-set"]').change(ev => { + ev.preventDefault() + console.log('change days', ev.target.value) + updateActorWithChangedDaysToHeal(CI.setDaysToHeal(ev.target.value)) + }) + + // END CONDITIONAL INJURY + + // If using the "enhanced" inputs for trackers, enable the ribbon popup. + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_ENHANCED_INPUT)) { + // On Focus, initialize the ribbon popup and show it. + html.find('.spinner details summary input').focus(ev => { + let details = ev.currentTarget.closest('details') + + if (!details.open) { + let parent = ev.currentTarget.closest('[data-gurps-resource]') + let path = $(parent).attr('data-gurps-resource') + let tracker = getProperty(this.actor.system, path) + + let restoreButton = $(details).find('button.restore') + restoreButton.attr('data-value', `${tracker.value}`) + restoreButton.text(tracker.value) + } + details.open = true + }) + + // Update the actor's data, set the restore button to the new value, + // and close the popup. + html.find('.spinner details summary input').focusout(ev => { + ev.preventDefault() + // set the restore button to the new value of the input field + let details = ev.currentTarget.closest('details') + let input = $(details).find('input') + let newValue = input.val() + + let restoreButton = $(details).find('button.restore') + restoreButton.attr('data-value', newValue) + restoreButton.text(newValue) + + // update the actor's data to newValue + let parent = ev.currentTarget.closest('[data-gurps-resource]') + let path = $(parent).attr('data-gurps-resource') + let value = parseInt(newValue) + let json = `{ "data.${path}.value": ${value} }` + this.actor.update(JSON.parse(json)) + + details.open = false + }) + + // Prevent the popup from closing on a click. + html.find('.spinner details .popup > *').mousedown(ev => { + ev.preventDefault() + }) + + // On a click of the enhanced input popup, update the text input field, but do not update the actor's data. + html.find('button[data-operation="resource-update"]').click(ev => { + let dataValue = $(ev.currentTarget).attr('data-value') + let details = $(ev.currentTarget).closest('details') + let input = $(details).find('input') + let value = parseInt(input.val()) + + if (dataValue.charAt(0) === '-' || dataValue.charAt(0) === '+') { + value += parseInt(dataValue) + } else { + value = parseInt(dataValue) + } + + if (!isNaN(value)) { + input.val(value) + } + }) + } // end enhanced input + + // On mouseover any item with the class .tooltip-manager which also has a child (image) of class .tooltippic, + // display the tooltip in the correct position. + html.find('.tooltip.gga-manual').mouseover(ev => { + ev.preventDefault() + + let target = $(ev.currentTarget) + if (target.hasNoChildren) { + return + } + + let tooltip = target.children('.tooltiptext.gga-manual') + if (tooltip) { + tooltip.css({ visibility: 'visible' }) + } + tooltip = target.children('.tooltippic.gga-manual') + if (tooltip) { + tooltip.css({ visibility: 'visible' }) + } + }) + + // On mouseout, stop displaying the tooltip. + html.find('.tooltip.gga-manual').mouseout(ev => { + ev.preventDefault() + let target = $(ev.currentTarget) + if (target.hasNoChildren) { + return + } + + let tooltip = target.children('.tooltiptext.gga-manual') + if (tooltip) { + tooltip.css({ visibility: 'hidden' }) + } + tooltip = target.children('.tooltippic.gga-manual') + if (tooltip) { + tooltip.css({ visibility: 'hidden' }) + } + }) + + // Equipment === + + // On clicking the Equip column, toggle the equipped status of the item. + html.find('.changeequip').click(this._onClickEquip.bind(this)) + + // Simple trick, move 'contains' items into 'collapsed' and back. The html doesn't show 'collapsed'. + html.find('.expandcollapseicon').click(async ev => { + let actor = this.actor + let element = ev.currentTarget + let parent = $(element).closest('[data-key]') + let path = parent.attr('data-key') + actor.toggleExpand(path) + }) + + // On double-clicking an item, open its editor. + html.find('.dblclkedit').dblclick(async ev => { + ev.preventDefault() + let parent = $(ev.currentTarget).closest('[data-key]') + + let path = parent[0].dataset.key + let actor = this.actor + let obj = duplicate(getProperty(actor, path)) // must dup so difference can be detected when updated + if (!!obj.itemid) { + let item = this.actor.items.get(obj.itemid) + item.editingActor = this.actor + item.sheet.render(true) + return + } + + if (path.includes('equipment')) this.editEquipment(actor, path, obj) + if (path.includes('melee')) this.editMelee(actor, path, obj) + if (path.includes('ranged')) this.editRanged(actor, path, obj) + if (path.includes('ads')) this.editAds(actor, path, obj) + if (path.includes('skills')) this.editSkills(actor, path, obj) + if (path.includes('spells')) this.editSpells(actor, path, obj) + if (path.includes('notes')) this.editNotes(actor, path, obj) + }) + + html.find('.dblclkedit').on('drop', this.handleDblclickeditDrop.bind(this)) + + // On clicking equipment quantity increment, increase the amount. + html.find('button[data-operation="equipment-inc"]').click(async ev => { + ev.preventDefault() + let parent = $(ev.currentTarget).closest('[data-key]') + let path = parent.attr('data-key') + let eqt = getProperty(this.actor, path) + let value = parseInt(eqt.count) + (ev.shiftKey ? 5 : 1) + if (isNaN(value)) value = 0 + await this.actor.updateEqtCount(path, value) + }) + + html.find('button[data-operation="equipment-inc-uses"]').click(async ev => { + ev.preventDefault() + let parent = $(ev.currentTarget).closest('[data-key]') + let path = parent.attr('data-key') + let eqt = getProperty(this.actor, path) + let value = parseInt(eqt.uses) + (ev.shiftKey ? 5 : 1) + if (isNaN(value)) value = eqt.uses + await this.actor.update({ [path + '.uses']: value }) + }) + html.find('button[data-operation="equipment-dec-uses"]').click(async ev => { + ev.preventDefault() + let parent = $(ev.currentTarget).closest('[data-key]') + let path = parent.attr('data-key') + let eqt = getProperty(this.actor, path) + let value = parseInt(eqt.uses) - (ev.shiftKey ? 5 : 1) + if (isNaN(value)) value = eqt.uses + if (value < 0) value = 0 + await this.actor.update({ [path + '.uses']: value }) + }) + + // On clicking equipment quantity decrement, decrease the amount or remove from list. + html.find('button[data-operation="equipment-dec"]').click(async ev => { + ev.preventDefault() + let parent = $(ev.currentTarget).closest('[data-key]') + let path = parent.attr('data-key') + let actor = this.actor + let eqt = getProperty(actor, path) + if (eqt.count == 0) { + await Dialog.confirm({ + title: i18n('GURPS.removeItem'), + content: i18n_f('GURPS.confirmRemoveItem', { name: eqt.name }, 'Remove {name} from the Equipment List?'), + yes: () => actor.deleteEquipment(path), + }) + } else { + let value = parseInt(eqt.count) - (ev.shiftKey ? 5 : 1) + if (isNaN(value) || value < 0) value = 0 + await this.actor.updateEqtCount(path, value) + } + }) + + html.find('.addnoteicon').on('click', this._addNote.bind(this)) + + let notesMenuItems = [ + { + name: 'Edit', + icon: "", + callback: e => { + let path = e[0].dataset.key + let o = duplicate(GURPS.decode(this.actor, path)) + this.editNotes(this.actor, path, o) + }, + }, + { + name: 'Delete', + icon: "", + callback: e => { + GURPS.removeKey(this.actor, e[0].dataset.key) + }, + }, + ] + new ContextMenu(html, '.notesmenu', notesMenuItems) + + html.find('[data-onethird]').click(ev => { + let el = ev.currentTarget + let opt = el.dataset.onethird + let active = !!this.actor.system.conditions[opt] + this.actor.toggleEffectByName(opt, !active) + }) + + html.find('[data-onethird]').hover( + function () { + let opt = $(this).attr('data-onethird') + let msg = 'Disable ' + opt + if ($(this).hasClass('buttongrey')) msg = 'Enable ' + opt + $(this).append( + $( + `
    ${msg}
    ` + ) + ) + }, + function () { + $(this).find('div').last().remove() + } + ) + + html.find('#qnotes').dblclick(ex => { + let n = this.actor.system.additionalresources.qnotes || '' + n = n.replace(/
    /g, '\n') + let actor = this.actor + new Dialog({ + title: 'Edit Quick Note', + content: `Enter a Quick Note (a great place to put an On-the-Fly formula!):



    Examples:
    [+1 due to shield]
    [Dodge +3 retreat]
    [Dodge +2 Feverish Defense *Cost 1FP]`, - buttons: { - save: { - icon: '', - label: 'Save', - callback: html => { - const i = html[0].querySelector('#i') - actor.update({ 'data.additionalresources.qnotes': i.value.replace(/\n/g, '
    ') }) - }, - }, - }, - render: h => { - $(h).find('textarea').on('drop', this.dropFoundryLinks) - $(h).find('input').on('drop', this.dropFoundryLinks) - }, - }).render(true) - }) - - html.find('#qnotes').on('drop', this.handleQnoteDrop.bind(this)) - - html.find('#maneuver').on('change', ev => { - let target = $(ev.currentTarget) - this.actor.replaceManeuver(target.val()) - }) - - html.find('#posture').on('change', ev => { - let target = $(ev.currentTarget) - this.actor.replacePosture(target.val()) - }) - - html.find('#move-mode').on('change', ev => { - let target = $(ev.currentTarget) - this.actor.setMoveDefault(target.val()) - }) - - html.find('#open-modifier-popup').on('click', this._showActiveEffectsListPopup.bind(this)) - html.find('#edit-move-modes').on('click', this._showMoveModeEditorPopup.bind(this)) - - html.find('#addFirstResourceTracker').on('click', ev => this._addTracker()) - } - - _createHeaderMenus(html) { - // add the default menu items for all tables with a headermenu - let tables = html.find('.headermenu').closest('.gga-table') - for (const table of tables) { - let id = `#${table.id}` - let items = this.getMenuItems(id) - this._makeHeaderMenu($(table), '.headermenu', items, ClickAndContextMenu) - } - - let trackermenu = html.find('#combat-trackers') - this._makeHeaderMenu( - $(trackermenu[0]), - '.headermenu', - [ - { - name: i18n('GURPS.addTracker'), - icon: '', - callback: e => { - this._addTracker() - }, - }, - ], - ClickAndContextMenu - ) - } - - _createEquipmentItemMenus(html) { - let includeCollapsed = this instanceof GurpsActorEditorSheet - - let opts = [ - this._createMenu(i18n('GURPS.edit'), '', this._editEquipment.bind(this)), - this._createMenu( - i18n('GURPS.sortContentsAscending'), - '', - this._sortContentAscending.bind(this), - this._isSortable.bind(this, includeCollapsed) - ), - this._createMenu( - i18n('GURPS.sortContentsDescending'), - '', - this._sortContentDescending.bind(this), - this._isSortable.bind(this, includeCollapsed) - ), - this._createMenu(i18n('GURPS.delete'), '', this._deleteItem.bind(this)), - ] - - let movedown = this._createMenu( - i18n('GURPS.moveToOtherEquipment'), - '', - this._moveEquipment.bind(this, 'data.equipment.other') - ) - new ContextMenu(html, '.equipmenucarried', [movedown, ...opts], { eventName: 'contextmenu' }) - - let moveup = this._createMenu( - i18n('GURPS.moveToCarriedEquipment'), - '', - this._moveEquipment.bind(this, 'data.equipment.carried') - ) - new ContextMenu(html, '.equipmenuother', [moveup, ...opts], { eventName: 'contextmenu' }) - } - - _editEquipment(target) { - let path = target[0].dataset.key - let o = duplicate(GURPS.decode(this.actor, path)) - this.editEquipment(this.actor, path, o) - } - - _createMenu(label, icon, callback, condition = () => true) { - return { - name: label, - icon: icon, - callback: callback, - condition: condition, - } - } - - _deleteItem(target) { - let key = target[0].dataset.key - if (key.includes('.equipment.')) this.actor.deleteEquipment(key) - else GURPS.removeKey(this.actor, key) - } - - _sortContentAscending(target) { - this._sortContent(target[0].dataset.key, 'contains', false) - this._sortContent(target[0].dataset.key, 'collapsed', false) - } - - async _sortContent(parentpath, objkey, reverse) { - let key = parentpath + '.' + objkey - let list = getProperty(this.actor, key) - let t = parentpath + '.-=' + objkey - - await this.actor.update({ [t]: null }) // Delete the whole object - - let sortedobj = {} - let index = 0 - Object.values(list) - .sort((a, b) => (reverse ? b.name.localeCompare(a.name) : a.name.localeCompare(b.name))) - .forEach(o => GURPS.put(sortedobj, o, index++)) - await this.actor.update({ [key]: sortedobj }) - } - - _sortContentDescending(target) { - this._sortContent(target[0].dataset.key, 'contains', true) - this._sortContent(target[0].dataset.key, 'collapsed', true) - } - - _moveEquipment(list, target) { - let path = target[0].dataset.key - this.actor.moveEquipment(path, list) - } - - _hasContents(target) { - let path = target[0].dataset.key - let elements = $(target).siblings(`.desc[data-key="${path}.contains"]`) - return elements.length > 0 - } - - /** - * - * @param {*} target - * @returns true if the object is a container ... ie, it has a non-empty contains collection - */ - _isSortable(includeCollapsed, target) { - let path = target[0].dataset.key - let x = GURPS.decode(this.actor, path) - if (x?.contains && Object.keys(x.contains).length > 1) return true - if (includeCollapsed) return x?.collapsed && Object.keys(x.collapsed).length > 1 - return false - } - - getMenuItems(elementid) { - const map = { - '#ranged': [this.sortAscendingMenu('data.ranged'), this.sortDescendingMenu('data.ranged')], - '#melee': [this.sortAscendingMenu('data.melee'), this.sortDescendingMenu('data.melee')], - '#advantages': [this.sortAscendingMenu('data.ads'), this.sortDescendingMenu('data.ads')], - '#skills': [this.sortAscendingMenu('data.skills'), this.sortDescendingMenu('data.skills')], - '#spells': [this.sortAscendingMenu('data.spells'), this.sortDescendingMenu('data.spells')], - '#equipmentcarried': [ - this.addItemMenu( - i18n('GURPS.equipment'), - new Equipment(`${i18n('GURPS.equipment')}...`, true), - 'data.equipment.carried' - ), - this.sortAscendingMenu('data.equipment.carried'), - this.sortDescendingMenu('data.equipment.carried'), - ], - '#equipmentother': [ - this.addItemMenu( - i18n('GURPS.equipment'), - new Equipment(`${i18n('GURPS.equipment')}...`, true), - 'data.equipment.other' - ), - this.sortAscendingMenu('data.equipment.other'), - this.sortDescendingMenu('data.equipment.other'), - ], - } - return map[elementid] ?? [] - } - - addItemMenu(name, obj, path) { - return { - name: i18n_f('GURPS.editorAddItem', { name: name }, 'Add {name} at the end'), - icon: '', - callback: e => { - let o = GURPS.decode(this.actor, path) || {} - GURPS.put(o, duplicate(obj)) - this.actor.update({ [path]: o }) - }, - } - } - - makelistdrag(html, cls, type) { - html.find(cls).each((i, li) => { - li.setAttribute('draggable', true) - - li.addEventListener('dragstart', ev => { - let oldd = ev.dataTransfer.getData('text/plain') - let eqtkey = ev.currentTarget.dataset.key - let eqt = getProperty(this.actor, eqtkey) // FYI, may not actually be Equipment - - if (!eqt) return - if (!!eqt.eqtkey) { - eqtkey = eqt.eqtkey - eqt = GURPS.decode(this.actor, eqtkey) // Features added by equipment will point to the equipment - type = 'equipment' - } - - var itemData - if (!!eqt.itemid) { - itemData = this.actor.items.get(eqt.itemid) // We have to get it now, as the source of the drag, since the target may not be owned by us - let img = new Image() - img.src = itemData.img - const w = 50 - const h = 50 - const preview = DragDrop.createDragImage(img, w, h) - ev.dataTransfer.setDragImage(preview, 0, 0) - } - - let newd = { - actorid: this.actor.id, // may not be useful if this is an unlinked token - // actor: this.actor, // so send the actor, - isLinked: !this.actor.isToken, - type: type, - key: eqtkey, - itemid: eqt.itemid, - itemData: itemData, - } - if (!!oldd) mergeObject(newd, JSON.parse(oldd)) // May need to merge in OTF drag info - - let payload = JSON.stringify(newd) - //console.log(payload) - return ev.dataTransfer.setData('text/plain', payload) - }) - }) - } - - async _addNote(event) { - let parent = $(event.currentTarget).closest('.header') - let path = parent.attr('data-key') - let actor = this.actor - let list = duplicate(getProperty(actor, path)) - let obj = new Note('', true) - let dlgHtml = await renderTemplate('systems/gurps/templates/note-editor-popup.html', obj) - - let d = new Dialog( - { - title: 'Note Editor', - content: dlgHtml, - buttons: { - one: { - label: 'Create', - callback: async html => { - ;['notes', 'pageref', 'title'].forEach(a => (obj[a] = html.find(`.${a}`).val())) - let u = html.find('.save') // Should only find in Note (or equipment) - if (!!u) obj.save = u.is(':checked') - GURPS.put(list, obj) - await actor.update({ [path]: list }) - }, - }, - }, - default: 'one', - }, - { - width: 730, - popOut: true, - minimizable: false, - jQuery: true, - } - ) - d.render(true) - } - - async _addTracker(event) { - this.actor.addTracker() - } - - handleDblclickeditDrop(ev) { - let parent = $(ev.currentTarget).closest('[data-key]') - let path = parent[0].dataset.key - this.dropFoundryLinks(ev, path + '.notes') - } - - handleQnoteDrop(ev) { - this.dropFoundryLinks(ev, 'data.additionalresources.qnotes') - } - - dropFoundryLinks(ev, modelkey) { - if (!!ev.originalEvent) ev = ev.originalEvent - let dragData = JSON.parse(ev.dataTransfer.getData('text/plain')) - let add = '' - var n - if (dragData.type == 'JournalEntry') { - n = game.journal.get(dragData.id).name - } - if (dragData.type == 'Actor') { - n = game.actors.get(dragData.id).name - } - if (dragData.type == 'RollTable') { - n = game.tables.get(dragData.id).name - } - if (dragData.type == 'Item') { - n = game.items.get(dragData.id).name - } - if (!!n) add = ` [${dragData.type}[${dragData.id}]` + '{' + n + '}]' - - if (!!dragData.otf) { - let prefix = '' - if (!!dragData.displayname) { - let q = '"' - if (dragData.displayname.includes(q)) q = "'" - prefix = q + dragData.displayname + q - } - add = '[' + prefix + dragData.otf + ']' - } - if (!!dragData.bucket) { - add = '["Modifier Bucket"' - let sep = '' - dragData.bucket.forEach(otf => { - add += sep + '/r [' + otf + ']' - sep = '\\\\' - }) - add += ']' - } - - if (!!add) - if (!!modelkey) { - let t = getProperty(this.actor, modelkey) || '' - this.actor.update({ [modelkey]: t + (t ? ' ' : '') + add }) - } else { - let t = $(ev.currentTarget).val() - $(ev.currentTarget).val(t + (t ? ' ' : '') + add) - } - } - - /** - * - * @param {*} ev - */ - async editTracker(ev) { - ev.preventDefault() - - let path = $(ev.currentTarget).closest('[data-gurps-resource]').attr('data-gurps-resource') - let templates = ResourceTrackerManager.getAllTemplates() - if (!templates || templates.length == 0) templates = null - - let selectTracker = async function(html) { - let name = html.find('select option:selected').text().trim() - let template = templates.find(template => template.tracker.name === name) - await this.actor.applyTrackerTemplate(path, template) - } - - // show dialog asking if they want to apply a standard tracker, or edit this tracker - let buttons = { - edit: { - icon: '', - label: game.i18n.localize('GURPS.resourceEditTracker'), - callback: () => ResourceTrackerEditor.editForActor(this.actor, path), - }, - remove: { - icon: '', - label: game.i18n.localize('GURPS.resourceDeleteTracker'), - callback: async () => await this.actor.removeTracker(path), - }, - } - - if (!!templates) { - buttons.apply = { - icon: '', - label: game.i18n.localize('GURPS.resourceCopyTemplate'), - callback: selectTracker.bind(this), - } - } - - let d = new Dialog( - { - title: game.i18n.localize('GURPS.resourceUpdateTrackerSlot'), - content: await renderTemplate('systems/gurps/templates/actor/update-tracker.html', { templates: templates }), - buttons: buttons, - default: 'edit', - templates: templates, - }, - { width: 600 } - ) - d.render(true) - } - - async _showActiveEffectsListPopup(ev) { - ev.preventDefault() - new GurpsActiveEffectListSheet(this.actor).render(true) - } - - async _showMoveModeEditorPopup(ev) { - ev.preventDefault() - new MoveModeEditor(this.actor).render(true) - } - - async editEquipment(actor, path, obj) { - // NOTE: This code is duplicated above. Haven't refactored yet - obj.f_count = obj.count // Hack to get around The Furnace's "helpful" Handlebar helper {{count}} - let dlgHtml = await renderTemplate('systems/gurps/templates/equipment-editor-popup.html', obj) - - let d = new Dialog( - { - title: 'Equipment Editor', - content: dlgHtml, - buttons: { - one: { - label: 'Update', - callback: async html => { - ;['name', 'uses', 'maxuses', 'techlevel', 'notes', 'pageref'].forEach( - a => (obj[a] = html.find(`.${a}`).val()) - ) - ;['count', 'cost', 'weight'].forEach(a => (obj[a] = parseFloat(html.find(`.${a}`).val()))) - let u = html.find('.save') // Should only find in Note (or equipment) - if (!!u && obj.save != null) obj.save = u.is(':checked') // only set 'saved' if it was already defined - let v = html.find('.ignoreImportQty') // Should only find in equipment - if (!!v) obj.ignoreImportQty = v.is(':checked') - await actor.update({ [path]: obj }) - await actor.updateParentOf(path, false) - }, - }, - }, - render: h => { - $(h).find('textarea').on('drop', this.dropFoundryLinks) - $(h).find('input').on('drop', this.dropFoundryLinks) - }, - default: 'one', - }, - { - width: 530, - popOut: true, - minimizable: false, - jQuery: true, - } - ) - d.render(true) - } - - async editMelee(actor, path, obj) { - this.editItem( - actor, - path, - obj, - 'systems/gurps/templates/melee-editor-popup.html', - 'Melee Weapon Editor', - [ - 'name', - 'mode', - 'parry', - 'block', - 'damage', - 'reach', - 'st', - 'notes', - 'import', - 'checkotf', - 'duringotf', - 'passotf', - 'failotf', - ], - [] - ) - } - - async editRanged(actor, path, obj) { - this.editItem( - actor, - path, - obj, - 'systems/gurps/templates/ranged-editor-popup.html', - 'Ranged Weapon Editor', - [ - 'name', - 'mode', - 'range', - 'rof', - 'damage', - 'shots', - 'rcl', - 'st', - 'notes', - 'import', - 'checkotf', - 'duringotf', - 'passotf', - 'failotf', - ], - ['acc', 'bulk'] - ) - } - - async editAds(actor, path, obj) { - this.editItem( - actor, - path, - obj, - 'systems/gurps/templates/advantage-editor-popup.html', - 'Advantage / Disadvantage / Perk / Quirk Editor', - ['name', 'notes', 'pageref'], - ['points'] - ) - } - - async editSkills(actor, path, obj) { - this.editItem( - actor, - path, - obj, - 'systems/gurps/templates/skill-editor-popup.html', - 'Skill Editor', - ['name', 'import', 'relativelevel', 'pageref', 'notes', 'checkotf', 'duringotf', 'passotf', 'failotf'], - ['points'] - ) - } - - async editSpells(actor, path, obj) { - this.editItem( - actor, - path, - obj, - 'systems/gurps/templates/spell-editor-popup.html', - 'Spell Editor', - [ - 'name', - 'import', - 'difficulty', - 'pageref', - 'notes', - 'resist', - 'class', - 'cost', - 'maintain', - 'casttime', - 'duration', - 'college', - 'checkotf', - 'duringotf', - 'passotf', - 'failotf', - ], - ['points'] - ) - } - - async editNotes(actor, path, obj) { - this.editItem( - actor, - path, - obj, - 'systems/gurps/templates/note-editor-popup.html', - 'Note Editor', - ['pageref', 'notes', 'title'], - [], - 730 - ) - } - - async editItem(actor, path, obj, html, title, strprops, numprops, width = 560) { - let dlgHtml = await renderTemplate(html, obj) - let d = new Dialog( - { - title: title, - content: dlgHtml, - buttons: { - one: { - label: 'Update', - callback: async html => { - strprops.forEach(a => (obj[a] = html.find(`.${a}`).val())) - numprops.forEach(a => (obj[a] = parseFloat(html.find(`.${a}`).val()))) - - let u = html.find('.save') // Should only find in Note (or equipment) - if (!!u) obj.save = u.is(':checked') - actor.update({ [path]: obj }) - }, - }, - }, - render: h => { - $(h).find('textarea').on('drop', this.dropFoundryLinks) - $(h).find('input').on('drop', this.dropFoundryLinks) - }, - }, - { - width: width, - popOut: true, - minimizable: false, - jQuery: true, - } - ) - d.render(true) - } - - _makeHeaderMenu(html, cssclass, menuitems, eventname = 'contextmenu') { - new ContextMenu(html, cssclass, menuitems, { eventName: eventname }) - } - - sortAscendingMenu(key) { - return { - name: i18n('GURPS.sortAscending'), - icon: '', - callback: e => this.sortAscending(key), - } - } - - sortDescendingMenu(key) { - return { - name: i18n('GURPS.sortDescending'), - icon: '', - callback: e => this.sortDescending(key), - } - } - - async sortAscending(key) { - let i = key.lastIndexOf('.') - let parentpath = key.substring(0, i) - let objkey = key.substr(i + 1) - let object = GURPS.decode(this.actor, key) - let t = parentpath + '.-=' + objkey - await this.actor.update({ [t]: null }) // Delete the whole object - let sortedobj = {} - let index = 0 - Object.values(object) - .sort((a, b) => a.name.localeCompare(b.name)) - .forEach(o => GURPS.put(sortedobj, o, index++)) - await this.actor.update({ [key]: sortedobj }) - } - - async sortDescending(key) { - let i = key.lastIndexOf('.') - let parentpath = key.substring(0, i) - let objkey = key.substr(i + 1) - let object = GURPS.decode(this.actor, key) - let t = parentpath + '.-=' + objkey - await this.actor.update({ [t]: null }) // Delete the whole object - let sortedobj = {} - let index = 0 - Object.values(object) - .sort((a, b) => b.name.localeCompare(a.name)) - .forEach(o => GURPS.put(sortedobj, o, index++)) - await this.actor.update({ [key]: sortedobj }) - } - - /* -------------------------------------------- */ - - /** @override */ - async _onDrop(event) { - let dragData = JSON.parse(event.dataTransfer.getData('text/plain')) - - if (dragData.type === 'damageItem') this.actor.handleDamageDrop(dragData.payload) - if (dragData.type === 'Item') this.actor.handleItemDrop(dragData) - - this.handleDragFor(event, dragData, 'ranged', 'rangeddraggable') - this.handleDragFor(event, dragData, 'melee', 'meleedraggable') - this.handleDragFor(event, dragData, 'ads', 'adsdraggable') - this.handleDragFor(event, dragData, 'skills', 'skldraggable') - this.handleDragFor(event, dragData, 'spells', 'spldraggable') - this.handleDragFor(event, dragData, 'note', 'notedraggable') - this.handleDragFor(event, dragData, 'reactions', 'reactdraggable') - this.handleDragFor(event, dragData, 'condmod', 'condmoddraggable') - - if (dragData.type === 'equipment') { - if ((await this.actor.handleEquipmentDrop(dragData)) != false) return // handle external drag/drop - - // drag/drop in same character sheet - // Validate that the target is valid for the drop. - let dropTargetElements = $(event.target).closest('.eqtdraggable, .eqtdragtarget') - if (dropTargetElements?.length === 0) return - - // Get the target element. - let dropTarget = dropTargetElements[0] - - let targetkey = dropTarget.dataset.key - if (!!targetkey) { - let srckey = dragData.key - this.actor.moveEquipment(srckey, targetkey, event.shiftKey) - } - } - } - - // Non-equipment list drags - async handleDragFor(event, dragData, type, cls) { - if (dragData.type === type) { - // Validate that the target is valid for the drop. - let dropTargetElements = $(event.target).closest(`.${cls}`) - if (dropTargetElements?.length === 0) return - - // Get the target element. - let dropTarget = dropTargetElements[0] - - // Dropping an item into a container that already contains it does nothing; tell the user and bail. - let targetkey = dropTarget.dataset.key - if (!!targetkey) { - let sourceKey = dragData.key - if (sourceKey.includes(targetkey) || targetkey.includes(sourceKey)) { - ui.notifications.error(i18n('GURPS.dragSameContainer')) - return - } - - let object = GURPS.decode(this.actor, sourceKey) - - // Because we may be modifing the same list, we have to check the order of the keys and - // apply the operation that occurs later in the list, first (to keep the indexes the same). - let sourceTermsArray = sourceKey.split('.') - sourceTermsArray.splice(0, 2) // Remove the first two elements: data.xxxx - let targetTermsArray = targetkey.split('.') - targetTermsArray.splice(0, 2) - let max = Math.min(sourceTermsArray.length, targetTermsArray.length) - - let isSrcFirst = false - for (let i = 0; i < max; i++) { - // Could be a term like parseInt('contains') < parseInt('contains'), which in typical JS jankiness, reduces - // to NaN < NaN, which is false. - if (parseInt(sourceTermsArray[i]) < parseInt(targetTermsArray[i])) { - isSrcFirst = true - break - } - } - - let d = new Dialog({ - title: object.name, - content: `

    ${i18n('GURPS.dropResolve')}

    `, - buttons: { - one: { - icon: '', - label: `${i18n('GURPS.dropBefore')}`, - callback: async () => { - if (!isSrcFirst) { - await this._removeKey(sourceKey) - await this._insertBeforeKey(targetkey, object) - } else { - await this._insertBeforeKey(targetkey, object) - await this._removeKey(sourceKey) - } - }, - }, - two: { - icon: '', - label: `${i18n('GURPS.dropInside')}`, - callback: async () => { - let key = targetkey + '.contains.' + zeroFill(0) - if (!isSrcFirst) { - await this._removeKey(sourceKey) - await this._insertBeforeKey(key, object) - } else { - await this._insertBeforeKey(key, object) - await this._removeKey(sourceKey) - } - }, - }, - }, - default: 'one', - }) - d.render(true) - } - } - } - - async _insertBeforeKey(targetKey, element) { - // target key is the whole path, like 'data.melee.00001' - let components = targetKey.split('.') - - let index = parseInt(components.pop()) - let path = components.join('.') - - let object = GURPS.decode(this.actor, path) - let array = objectToArray(object) - - // Delete the whole object. - let last = components.pop() - let t = `${components.join('.')}.-=${last}` - await this.actor.internalUpdate({ [t]: null }) - - // Insert the element into the array. - array.splice(index, 0, element) - - // Convert back to an object - object = arrayToObject(array, 5) - - // update the actor - await this.actor.internalUpdate({ [path]: object }, { diff: false }) - } - - async _removeKey(sourceKey) { - // source key is the whole path, like 'data.melee.00001' - let components = sourceKey.split('.') - - let index = parseInt(components.pop()) - let path = components.join('.') - - let object = GURPS.decode(this.actor, path) - let array = objectToArray(object) - - // Delete the whole object. - let last = components.pop() - let t = `${components.join('.')}.-=${last}` - await this.actor.internalUpdate({ [t]: null }) - - // Remove the element from the array - array.splice(index, 1) - - // Convert back to an object - object = arrayToObject(array, 5) - - // update the actor - await this.actor.internalUpdate({ [path]: object }, { diff: false }) - } - - _onfocus(ev) { - ev.preventDefault() - GURPS.SetLastActor(this.actor) - } - - /** @override */ - setPosition(options = {}) { - const position = super.setPosition(options) - const sheetBody = this.element.find('.sheet-body') - if (!!position.height) { - const bodyHeight = position.height - 192 - sheetBody.css('height', bodyHeight) - } - return position - } - - get title() { - const t = this.actor.name - const sheet = this.actor.getFlag('core', 'sheetClass') - return sheet === 'gurps.GurpsActorEditorSheet' ? '**** Editing: ' + t + ' ****' : t - } - - _getHeaderButtons() { - let buttons = super._getHeaderButtons() - - // Token Configuration - if (this.options.editable && isConfigurationAllowed(this.actor)) { - buttons = this.getCustomHeaderButtons().concat(buttons) - } - return buttons - } - - /** - * Override this to change the buttons appended to the actor sheet title bar. - */ - getCustomHeaderButtons() { - const sheet = this.actor.getFlag('core', 'sheetClass') - const isEditor = sheet === 'gurps.GurpsActorEditorSheet' - const altsheet = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_ALT_SHEET) - - const isFull = sheet === undefined || sheet === 'gurps.GurpsActorSheet' - let b = [ - { - label: isFull ? altsheet : 'Full View', - class: 'toggle', - icon: 'fas fa-exchange-alt', - onclick: ev => this._onToggleSheet(ev, altsheet), - }, - ] - - if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_BLOCK_IMPORT) || game.user.isTrusted) - b.push({ - label: 'Import', - class: 'import', - icon: 'fas fa-file-import', - onclick: ev => this._onFileImport(ev), - }) - - if (!isEditor) { - b.push({ - label: 'Editor', - class: 'edit', - icon: 'fas fa-edit', - onclick: ev => this._onOpenEditor(ev), - }) - } - return b - } - - async _onFileImport(event) { - event.preventDefault() - this.actor.importCharacter() - } - - async _onToggleSheet(event, altsheet) { - event.preventDefault() - let newSheet = Object.values(CONFIG.Actor.sheetClasses['character']).filter(s => s.label == altsheet)[0].id - - const original = - this.actor.getFlag('core', 'sheetClass') || - Object.values(CONFIG.Actor.sheetClasses['character']).filter(s => s.default)[0].id - console.log('original: ' + original) - - if (original != 'gurps.GurpsActorSheet') newSheet = 'gurps.GurpsActorSheet' - if (event.shiftKey) - // Hold down the shift key for Simplified - newSheet = 'gurps.GurpsActorSimplifiedSheet' - if (game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.CONTROL)) - // Hold down the Ctrl key (Command on Mac) for Simplified - newSheet = 'gurps.GurpsActorNpcSheet' - - this.actor.openSheet(newSheet) - } - - async _onOpenEditor(event) { - event.preventDefault() - this.actor.openSheet('gurps.GurpsActorEditorSheet') - } - - async _onRightClickGurpslink(event) { - event.preventDefault() - event.stopImmediatePropagation() // Since this may occur in note or a list (which has its own RMB handler) - let el = event.currentTarget - let action = el.dataset.action - if (!!action) { - action = JSON.parse(atou(action)) - if (action.type === 'damage' || action.type === 'deriveddamage') { - GURPS.resolveDamageRoll(event, this.actor, action.orig, action.overridetxt, game.user.isGM, true) - } else { - GURPS.whisperOtfToOwner(action.orig, action.overridetxt, event, action, this.actor) // only offer blind rolls for things that can be blind, No need to offer blind roll if it is already blind - } - } - } - - async _onRightClickPdf(event) { - event.preventDefault() - let el = event.currentTarget - GURPS.whisperOtfToOwner('PDF:' + el.innerText, null, event, false, this.actor) - } - - async _onRightClickGmod(event) { - event.preventDefault() - let el = event.currentTarget - let n = el.dataset.name - let t = el.innerText - GURPS.whisperOtfToOwner(t + ' ' + n, null, event, false, this.actor) - } - - async _onRightClickOtf(event) { - event.preventDefault() - let el = event.currentTarget - let isDamageRoll = el.dataset.hasOwnProperty('damage') - let otf = event.currentTarget.dataset.otf - - if (isDamageRoll) { - GURPS.resolveDamageRoll(event, this.actor, otf, null, game.user.isGM) - } else { - GURPS.whisperOtfToOwner(event.currentTarget.dataset.otf, null, event, !isDamageRoll, this.actor) // Can't blind roll damages (yet) - } - } - - async _onClickRoll(event, targets) { - GURPS.handleRoll(event, this.actor, { targets: targets }) - } - - async _onClickSplit(event) { - let element = event.currentTarget - let key = element.dataset.key - new SplitDREditor(this.actor, key).render(true) - } - - async _onNavigate(event) { - let dataValue = $(event.currentTarget).attr('data-value') - let windowContent = event.currentTarget.closest('.window-content') - let target = windowContent.querySelector(`#${dataValue}`) - - // The '33' represents the height of the window title bar + a bit of margin - // TODO: we should really look this up and use the actual values as found in the DOM. - windowContent.scrollTop = target.offsetTop - 33 - - // add the glowing class to target AND to event.currentTarget, then remove it - $(target).addClass('glowing') - $(event.currentTarget).addClass('glowing') - - setTimeout(function() { - $(target).removeClass('glowing') - $(event.currentTarget).removeClass('glowing') - }, 2000) - } - - async _onClickEnc(ev) { - ev.preventDefault() - if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ENCUMBRANCE)) { - let element = ev.currentTarget - let key = element.dataset.key - ////////// - // Check for 'undefined' when clicking on Encumbrance Level 'header'. ~Stevil - if (key !== undefined) { - ////////// - let encs = this.actor.system.encumbrance - if (encs[key].current) return // already selected - for (let enckey in encs) { - let enc = encs[enckey] - let t = 'data.encumbrance.' + enckey + '.current' - if (key === enckey) { - await this.actor.update({ - [t]: true, - 'data.currentmove': parseInt(enc.move), - 'data.currentdodge': parseInt(enc.dodge), - }) - } else if (enc.current) { - await this.actor.update({ [t]: false }) - } - } - ////////// - } - ////////// - } else { - ui.notifications.warn( - "You cannot manually change the Encumbrance level. The 'Automatically calculate Encumbrance Level' setting is turned on." - ) - } - } - - async _onClickEquip(ev) { - ev.preventDefault() - let element = ev.currentTarget - let key = element.dataset.key - let eqt = duplicate(GURPS.decode(this.actor, key)) - eqt.equipped = !eqt.equipped - await this.actor.update({ [key]: eqt }) - await this.actor.updateItemAdditionsBasedOn(eqt, key) - let p = this.actor.getEquippedParry() - let b = this.actor.getEquippedBlock() - await this.actor.update({ - 'data.equippedparry': p, - 'data.equippedblock': b, - }) - this.actor._forceRender() - } - - deleteItemMenu(obj) { - return [ - { - name: 'Delete', - icon: "", - callback: e => { - let key = e[0].dataset.key - if (key.includes('.equipment.')) this.actor.deleteEquipment(key) - else GURPS.removeKey(this.actor, key) - }, - }, - ] - } - - /* -------------------------------------------- */ - - /** @override */ - _updateObject(event, formData) { - return super._updateObject(event, formData) - } + buttons: { + save: { + icon: '', + label: 'Save', + callback: html => { + const i = html[0].querySelector('#i') + actor.update({ 'data.additionalresources.qnotes': i.value.replace(/\n/g, '
    ') }) + }, + }, + }, + render: h => { + $(h).find('textarea').on('drop', this.dropFoundryLinks) + $(h).find('input').on('drop', this.dropFoundryLinks) + }, + }).render(true) + }) + + html.find('#qnotes').on('drop', this.handleQnoteDrop.bind(this)) + + html.find('#maneuver').on('change', ev => { + let target = $(ev.currentTarget) + this.actor.replaceManeuver(target.val()) + }) + + html.find('#posture').on('change', ev => { + let target = $(ev.currentTarget) + this.actor.replacePosture(target.val()) + }) + + html.find('#move-mode').on('change', ev => { + let target = $(ev.currentTarget) + this.actor.setMoveDefault(target.val()) + }) + + html.find('#open-modifier-popup').on('click', this._showActiveEffectsListPopup.bind(this)) + html.find('#edit-move-modes').on('click', this._showMoveModeEditorPopup.bind(this)) + + html.find('#addFirstResourceTracker').on('click', ev => this._addTracker()) + } + + _createHeaderMenus(html) { + // add the default menu items for all tables with a headermenu + let tables = html.find('.headermenu').closest('.gga-table') + for (const table of tables) { + let id = `#${table.id}` + let items = this.getMenuItems(id) + this._makeHeaderMenu($(table), '.headermenu', items, ClickAndContextMenu) + } + + let trackermenu = html.find('#combat-trackers') + this._makeHeaderMenu( + $(trackermenu[0]), + '.headermenu', + [ + { + name: i18n('GURPS.addTracker'), + icon: '', + callback: e => { + this._addTracker() + }, + }, + ], + ClickAndContextMenu + ) + } + + _createEquipmentItemMenus(html) { + let includeCollapsed = this instanceof GurpsActorEditorSheet + + let opts = [ + this._createMenu(i18n('GURPS.edit'), '', this._editEquipment.bind(this)), + this._createMenu( + i18n('GURPS.sortContentsAscending'), + '', + this._sortContentAscending.bind(this), + this._isSortable.bind(this, includeCollapsed) + ), + this._createMenu( + i18n('GURPS.sortContentsDescending'), + '', + this._sortContentDescending.bind(this), + this._isSortable.bind(this, includeCollapsed) + ), + this._createMenu(i18n('GURPS.delete'), '', this._deleteItem.bind(this)), + ] + + let movedown = this._createMenu( + i18n('GURPS.moveToOtherEquipment'), + '', + this._moveEquipment.bind(this, 'data.equipment.other') + ) + new ContextMenu(html, '.equipmenucarried', [movedown, ...opts], { eventName: 'contextmenu' }) + + let moveup = this._createMenu( + i18n('GURPS.moveToCarriedEquipment'), + '', + this._moveEquipment.bind(this, 'data.equipment.carried') + ) + new ContextMenu(html, '.equipmenuother', [moveup, ...opts], { eventName: 'contextmenu' }) + } + + _editEquipment(target) { + let path = target[0].dataset.key + let o = duplicate(GURPS.decode(this.actor, path)) + this.editEquipment(this.actor, path, o) + } + + _createMenu(label, icon, callback, condition = () => true) { + return { + name: label, + icon: icon, + callback: callback, + condition: condition, + } + } + + _deleteItem(target) { + let key = target[0].dataset.key + if (key.includes('.equipment.')) this.actor.deleteEquipment(key) + else GURPS.removeKey(this.actor, key) + } + + _sortContentAscending(target) { + this._sortContent(target[0].dataset.key, 'contains', false) + this._sortContent(target[0].dataset.key, 'collapsed', false) + } + + async _sortContent(parentpath, objkey, reverse) { + let key = parentpath + '.' + objkey + let list = getProperty(this.actor, key) + let t = parentpath + '.-=' + objkey + + await this.actor.update({ [t]: null }) // Delete the whole object + + let sortedobj = {} + let index = 0 + Object.values(list) + .sort((a, b) => (reverse ? b.name.localeCompare(a.name) : a.name.localeCompare(b.name))) + .forEach(o => GURPS.put(sortedobj, o, index++)) + await this.actor.update({ [key]: sortedobj }) + } + + _sortContentDescending(target) { + this._sortContent(target[0].dataset.key, 'contains', true) + this._sortContent(target[0].dataset.key, 'collapsed', true) + } + + _moveEquipment(list, target) { + let path = target[0].dataset.key + this.actor.moveEquipment(path, list) + } + + _hasContents(target) { + let path = target[0].dataset.key + let elements = $(target).siblings(`.desc[data-key="${path}.contains"]`) + return elements.length > 0 + } + + /** + * + * @param {*} target + * @returns true if the object is a container ... ie, it has a non-empty contains collection + */ + _isSortable(includeCollapsed, target) { + let path = target[0].dataset.key + let x = GURPS.decode(this.actor, path) + if (x?.contains && Object.keys(x.contains).length > 1) return true + if (includeCollapsed) return x?.collapsed && Object.keys(x.collapsed).length > 1 + return false + } + + getMenuItems(elementid) { + const map = { + '#ranged': [this.sortAscendingMenu('data.ranged'), this.sortDescendingMenu('data.ranged')], + '#melee': [this.sortAscendingMenu('data.melee'), this.sortDescendingMenu('data.melee')], + '#advantages': [this.sortAscendingMenu('data.ads'), this.sortDescendingMenu('data.ads')], + '#skills': [this.sortAscendingMenu('data.skills'), this.sortDescendingMenu('data.skills')], + '#spells': [this.sortAscendingMenu('data.spells'), this.sortDescendingMenu('data.spells')], + '#equipmentcarried': [ + this.addItemMenu( + i18n('GURPS.equipment'), + new Equipment(`${i18n('GURPS.equipment')}...`, true), + 'data.equipment.carried' + ), + this.sortAscendingMenu('data.equipment.carried'), + this.sortDescendingMenu('data.equipment.carried'), + ], + '#equipmentother': [ + this.addItemMenu( + i18n('GURPS.equipment'), + new Equipment(`${i18n('GURPS.equipment')}...`, true), + 'data.equipment.other' + ), + this.sortAscendingMenu('data.equipment.other'), + this.sortDescendingMenu('data.equipment.other'), + ], + } + return map[elementid] ?? [] + } + + addItemMenu(name, obj, path) { + return { + name: i18n_f('GURPS.editorAddItem', { name: name }, 'Add {name} at the end'), + icon: '', + callback: e => { + let o = GURPS.decode(this.actor, path) || {} + GURPS.put(o, duplicate(obj)) + this.actor.update({ [path]: o }) + }, + } + } + + makelistdrag(html, cls, type) { + html.find(cls).each((i, li) => { + li.setAttribute('draggable', true) + + li.addEventListener('dragstart', ev => { + let oldd = ev.dataTransfer.getData('text/plain') + let eqtkey = ev.currentTarget.dataset.key + let eqt = getProperty(this.actor, eqtkey) // FYI, may not actually be Equipment + + if (!eqt) return + if (!!eqt.eqtkey) { + eqtkey = eqt.eqtkey + eqt = GURPS.decode(this.actor, eqtkey) // Features added by equipment will point to the equipment + type = 'equipment' + } + + var itemData + if (!!eqt.itemid) { + itemData = this.actor.items.get(eqt.itemid) // We have to get it now, as the source of the drag, since the target may not be owned by us + let img = new Image() + img.src = itemData.img + const w = 50 + const h = 50 + const preview = DragDrop.createDragImage(img, w, h) + ev.dataTransfer.setDragImage(preview, 0, 0) + } + + let newd = { + actorid: this.actor.id, // may not be useful if this is an unlinked token + // actor: this.actor, // so send the actor, + isLinked: !this.actor.isToken, + type: type, + key: eqtkey, + itemid: eqt.itemid, + itemData: itemData, + } + if (!!oldd) mergeObject(newd, JSON.parse(oldd)) // May need to merge in OTF drag info + + let payload = JSON.stringify(newd) + //console.log(payload) + return ev.dataTransfer.setData('text/plain', payload) + }) + }) + } + + async _addNote(event) { + let parent = $(event.currentTarget).closest('.header') + let path = parent.attr('data-key') + let actor = this.actor + let list = duplicate(getProperty(actor, path)) + let obj = new Note('', true) + let dlgHtml = await renderTemplate('systems/gurps/templates/note-editor-popup.html', obj) + + let d = new Dialog( + { + title: 'Note Editor', + content: dlgHtml, + buttons: { + one: { + label: 'Create', + callback: async html => { + ;['notes', 'pageref', 'title'].forEach(a => (obj[a] = html.find(`.${a}`).val())) + let u = html.find('.save') // Should only find in Note (or equipment) + if (!!u) obj.save = u.is(':checked') + GURPS.put(list, obj) + await actor.update({ [path]: list }) + }, + }, + }, + default: 'one', + }, + { + width: 730, + popOut: true, + minimizable: false, + jQuery: true, + } + ) + d.render(true) + } + + async _addTracker(event) { + this.actor.addTracker() + } + + handleDblclickeditDrop(ev) { + let parent = $(ev.currentTarget).closest('[data-key]') + let path = parent[0].dataset.key + this.dropFoundryLinks(ev, path + '.notes') + } + + handleQnoteDrop(ev) { + this.dropFoundryLinks(ev, 'data.additionalresources.qnotes') + } + + dropFoundryLinks(ev, modelkey) { + if (!!ev.originalEvent) ev = ev.originalEvent + let dragData = JSON.parse(ev.dataTransfer.getData('text/plain')) + let add = '' + var n + if (dragData.type == 'JournalEntry') { + n = game.journal.get(dragData.id).name + } + if (dragData.type == 'Actor') { + n = game.actors.get(dragData.id).name + } + if (dragData.type == 'RollTable') { + n = game.tables.get(dragData.id).name + } + if (dragData.type == 'Item') { + n = game.items.get(dragData.id).name + } + if (!!n) add = ` [${dragData.type}[${dragData.id}]` + '{' + n + '}]' + + if (!!dragData.otf) { + let prefix = '' + if (!!dragData.displayname) { + let q = '"' + if (dragData.displayname.includes(q)) q = "'" + prefix = q + dragData.displayname + q + } + add = '[' + prefix + dragData.otf + ']' + } + if (!!dragData.bucket) { + add = '["Modifier Bucket"' + let sep = '' + dragData.bucket.forEach(otf => { + add += sep + '/r [' + otf + ']' + sep = '\\\\' + }) + add += ']' + } + + if (!!add) + if (!!modelkey) { + let t = getProperty(this.actor, modelkey) || '' + this.actor.update({ [modelkey]: t + (t ? ' ' : '') + add }) + } else { + let t = $(ev.currentTarget).val() + $(ev.currentTarget).val(t + (t ? ' ' : '') + add) + } + } + + /** + * + * @param {*} ev + */ + async editTracker(ev) { + ev.preventDefault() + + let path = $(ev.currentTarget).closest('[data-gurps-resource]').attr('data-gurps-resource') + let templates = ResourceTrackerManager.getAllTemplates() + if (!templates || templates.length == 0) templates = null + + let selectTracker = async function (html) { + let name = html.find('select option:selected').text().trim() + let template = templates.find(template => template.tracker.name === name) + await this.actor.applyTrackerTemplate(path, template) + } + + // show dialog asking if they want to apply a standard tracker, or edit this tracker + let buttons = { + edit: { + icon: '', + label: game.i18n.localize('GURPS.resourceEditTracker'), + callback: () => ResourceTrackerEditor.editForActor(this.actor, path), + }, + remove: { + icon: '', + label: game.i18n.localize('GURPS.resourceDeleteTracker'), + callback: async () => await this.actor.removeTracker(path), + }, + } + + if (!!templates) { + buttons.apply = { + icon: '', + label: game.i18n.localize('GURPS.resourceCopyTemplate'), + callback: selectTracker.bind(this), + } + } + + let d = new Dialog( + { + title: game.i18n.localize('GURPS.resourceUpdateTrackerSlot'), + content: await renderTemplate('systems/gurps/templates/actor/update-tracker.html', { templates: templates }), + buttons: buttons, + default: 'edit', + templates: templates, + }, + { width: 600 } + ) + d.render(true) + } + + async _showActiveEffectsListPopup(ev) { + ev.preventDefault() + new GurpsActiveEffectListSheet(this.actor).render(true) + } + + async _showMoveModeEditorPopup(ev) { + ev.preventDefault() + new MoveModeEditor(this.actor).render(true) + } + + async editEquipment(actor, path, obj) { + // NOTE: This code is duplicated above. Haven't refactored yet + obj.f_count = obj.count // Hack to get around The Furnace's "helpful" Handlebar helper {{count}} + let dlgHtml = await renderTemplate('systems/gurps/templates/equipment-editor-popup.html', obj) + + let d = new Dialog( + { + title: 'Equipment Editor', + content: dlgHtml, + buttons: { + one: { + label: 'Update', + callback: async html => { + ;['name', 'uses', 'maxuses', 'techlevel', 'notes', 'pageref'].forEach( + a => (obj[a] = html.find(`.${a}`).val()) + ) + ;['count', 'cost', 'weight'].forEach(a => (obj[a] = parseFloat(html.find(`.${a}`).val()))) + let u = html.find('.save') // Should only find in Note (or equipment) + if (!!u && obj.save != null) obj.save = u.is(':checked') // only set 'saved' if it was already defined + let v = html.find('.ignoreImportQty') // Should only find in equipment + if (!!v) obj.ignoreImportQty = v.is(':checked') + await actor.update({ [path]: obj }) + await actor.updateParentOf(path, false) + }, + }, + }, + render: h => { + $(h).find('textarea').on('drop', this.dropFoundryLinks) + $(h).find('input').on('drop', this.dropFoundryLinks) + }, + default: 'one', + }, + { + width: 530, + popOut: true, + minimizable: false, + jQuery: true, + } + ) + d.render(true) + } + + async editMelee(actor, path, obj) { + this.editItem( + actor, + path, + obj, + 'systems/gurps/templates/melee-editor-popup.html', + 'Melee Weapon Editor', + [ + 'name', + 'mode', + 'parry', + 'block', + 'damage', + 'reach', + 'st', + 'notes', + 'import', + 'checkotf', + 'duringotf', + 'passotf', + 'failotf', + ], + [] + ) + } + + async editRanged(actor, path, obj) { + this.editItem( + actor, + path, + obj, + 'systems/gurps/templates/ranged-editor-popup.html', + 'Ranged Weapon Editor', + [ + 'name', + 'mode', + 'range', + 'rof', + 'damage', + 'shots', + 'rcl', + 'st', + 'notes', + 'import', + 'checkotf', + 'duringotf', + 'passotf', + 'failotf', + ], + ['acc', 'bulk'] + ) + } + + async editAds(actor, path, obj) { + this.editItem( + actor, + path, + obj, + 'systems/gurps/templates/advantage-editor-popup.html', + 'Advantage / Disadvantage / Perk / Quirk Editor', + ['name', 'notes', 'pageref'], + ['points'] + ) + } + + async editSkills(actor, path, obj) { + this.editItem( + actor, + path, + obj, + 'systems/gurps/templates/skill-editor-popup.html', + 'Skill Editor', + ['name', 'import', 'relativelevel', 'pageref', 'notes', 'checkotf', 'duringotf', 'passotf', 'failotf'], + ['points'] + ) + } + + async editSpells(actor, path, obj) { + this.editItem( + actor, + path, + obj, + 'systems/gurps/templates/spell-editor-popup.html', + 'Spell Editor', + [ + 'name', + 'import', + 'difficulty', + 'pageref', + 'notes', + 'resist', + 'class', + 'cost', + 'maintain', + 'casttime', + 'duration', + 'college', + 'checkotf', + 'duringotf', + 'passotf', + 'failotf', + ], + ['points'] + ) + } + + async editNotes(actor, path, obj) { + this.editItem( + actor, + path, + obj, + 'systems/gurps/templates/note-editor-popup.html', + 'Note Editor', + ['pageref', 'notes', 'title'], + [], + 730 + ) + } + + async editItem(actor, path, obj, html, title, strprops, numprops, width = 560) { + let dlgHtml = await renderTemplate(html, obj) + let d = new Dialog( + { + title: title, + content: dlgHtml, + buttons: { + one: { + label: 'Update', + callback: async html => { + strprops.forEach(a => (obj[a] = html.find(`.${a}`).val())) + numprops.forEach(a => (obj[a] = parseFloat(html.find(`.${a}`).val()))) + + let u = html.find('.save') // Should only find in Note (or equipment) + if (!!u) obj.save = u.is(':checked') + actor.update({ [path]: obj }) + }, + }, + }, + render: h => { + $(h).find('textarea').on('drop', this.dropFoundryLinks) + $(h).find('input').on('drop', this.dropFoundryLinks) + }, + }, + { + width: width, + popOut: true, + minimizable: false, + jQuery: true, + } + ) + d.render(true) + } + + _makeHeaderMenu(html, cssclass, menuitems, eventname = 'contextmenu') { + new ContextMenu(html, cssclass, menuitems, { eventName: eventname }) + } + + sortAscendingMenu(key) { + return { + name: i18n('GURPS.sortAscending'), + icon: '', + callback: e => this.sortAscending(key), + } + } + + sortDescendingMenu(key) { + return { + name: i18n('GURPS.sortDescending'), + icon: '', + callback: e => this.sortDescending(key), + } + } + + async sortAscending(key) { + let i = key.lastIndexOf('.') + let parentpath = key.substring(0, i) + let objkey = key.substr(i + 1) + let object = GURPS.decode(this.actor, key) + let t = parentpath + '.-=' + objkey + await this.actor.update({ [t]: null }) // Delete the whole object + let sortedobj = {} + let index = 0 + Object.values(object) + .sort((a, b) => a.name.localeCompare(b.name)) + .forEach(o => GURPS.put(sortedobj, o, index++)) + await this.actor.update({ [key]: sortedobj }) + } + + async sortDescending(key) { + let i = key.lastIndexOf('.') + let parentpath = key.substring(0, i) + let objkey = key.substr(i + 1) + let object = GURPS.decode(this.actor, key) + let t = parentpath + '.-=' + objkey + await this.actor.update({ [t]: null }) // Delete the whole object + let sortedobj = {} + let index = 0 + Object.values(object) + .sort((a, b) => b.name.localeCompare(a.name)) + .forEach(o => GURPS.put(sortedobj, o, index++)) + await this.actor.update({ [key]: sortedobj }) + } + + /* -------------------------------------------- */ + + /** @override */ + async _onDrop(event) { + let dragData = JSON.parse(event.dataTransfer.getData('text/plain')) + + if (dragData.type === 'damageItem') this.actor.handleDamageDrop(dragData.payload) + if (dragData.type === 'Item') this.actor.handleItemDrop(dragData) + + this.handleDragFor(event, dragData, 'ranged', 'rangeddraggable') + this.handleDragFor(event, dragData, 'melee', 'meleedraggable') + this.handleDragFor(event, dragData, 'ads', 'adsdraggable') + this.handleDragFor(event, dragData, 'skills', 'skldraggable') + this.handleDragFor(event, dragData, 'spells', 'spldraggable') + this.handleDragFor(event, dragData, 'note', 'notedraggable') + this.handleDragFor(event, dragData, 'reactions', 'reactdraggable') + this.handleDragFor(event, dragData, 'condmod', 'condmoddraggable') + + if (dragData.type === 'equipment') { + if ((await this.actor.handleEquipmentDrop(dragData)) != false) return // handle external drag/drop + + // drag/drop in same character sheet + // Validate that the target is valid for the drop. + let dropTargetElements = $(event.target).closest('.eqtdraggable, .eqtdragtarget') + if (dropTargetElements?.length === 0) return + + // Get the target element. + let dropTarget = dropTargetElements[0] + + let targetkey = dropTarget.dataset.key + if (!!targetkey) { + let srckey = dragData.key + this.actor.moveEquipment(srckey, targetkey, event.shiftKey) + } + } + } + + // Non-equipment list drags + async handleDragFor(event, dragData, type, cls) { + if (dragData.type === type) { + // Validate that the target is valid for the drop. + let dropTargetElements = $(event.target).closest(`.${cls}`) + if (dropTargetElements?.length === 0) return + + // Get the target element. + let dropTarget = dropTargetElements[0] + + // Dropping an item into a container that already contains it does nothing; tell the user and bail. + let targetkey = dropTarget.dataset.key + if (!!targetkey) { + let sourceKey = dragData.key + if (sourceKey.includes(targetkey) || targetkey.includes(sourceKey)) { + ui.notifications.error(i18n('GURPS.dragSameContainer')) + return + } + + let object = GURPS.decode(this.actor, sourceKey) + + // Because we may be modifing the same list, we have to check the order of the keys and + // apply the operation that occurs later in the list, first (to keep the indexes the same). + let sourceTermsArray = sourceKey.split('.') + sourceTermsArray.splice(0, 2) // Remove the first two elements: data.xxxx + let targetTermsArray = targetkey.split('.') + targetTermsArray.splice(0, 2) + let max = Math.min(sourceTermsArray.length, targetTermsArray.length) + + let isSrcFirst = false + for (let i = 0; i < max; i++) { + // Could be a term like parseInt('contains') < parseInt('contains'), which in typical JS jankiness, reduces + // to NaN < NaN, which is false. + if (parseInt(sourceTermsArray[i]) < parseInt(targetTermsArray[i])) { + isSrcFirst = true + break + } + } + + let d = new Dialog({ + title: object.name, + content: `

    ${i18n('GURPS.dropResolve')}

    `, + buttons: { + one: { + icon: '', + label: `${i18n('GURPS.dropBefore')}`, + callback: async () => { + if (!isSrcFirst) { + await this._removeKey(sourceKey) + await this._insertBeforeKey(targetkey, object) + } else { + await this._insertBeforeKey(targetkey, object) + await this._removeKey(sourceKey) + } + }, + }, + two: { + icon: '', + label: `${i18n('GURPS.dropInside')}`, + callback: async () => { + let key = targetkey + '.contains.' + zeroFill(0) + if (!isSrcFirst) { + await this._removeKey(sourceKey) + await this._insertBeforeKey(key, object) + } else { + await this._insertBeforeKey(key, object) + await this._removeKey(sourceKey) + } + }, + }, + }, + default: 'one', + }) + d.render(true) + } + } + } + + async _insertBeforeKey(targetKey, element) { + // target key is the whole path, like 'data.melee.00001' + let components = targetKey.split('.') + + let index = parseInt(components.pop()) + let path = components.join('.') + + let object = GURPS.decode(this.actor, path) + let array = objectToArray(object) + + // Delete the whole object. + let last = components.pop() + let t = `${components.join('.')}.-=${last}` + await this.actor.internalUpdate({ [t]: null }) + + // Insert the element into the array. + array.splice(index, 0, element) + + // Convert back to an object + object = arrayToObject(array, 5) + + // update the actor + await this.actor.internalUpdate({ [path]: object }, { diff: false }) + } + + async _removeKey(sourceKey) { + // source key is the whole path, like 'data.melee.00001' + let components = sourceKey.split('.') + + let index = parseInt(components.pop()) + let path = components.join('.') + + let object = GURPS.decode(this.actor, path) + let array = objectToArray(object) + + // Delete the whole object. + let last = components.pop() + let t = `${components.join('.')}.-=${last}` + await this.actor.internalUpdate({ [t]: null }) + + // Remove the element from the array + array.splice(index, 1) + + // Convert back to an object + object = arrayToObject(array, 5) + + // update the actor + await this.actor.internalUpdate({ [path]: object }, { diff: false }) + } + + _onfocus(ev) { + ev.preventDefault() + GURPS.SetLastActor(this.actor) + } + + /** @override */ + setPosition(options = {}) { + const position = super.setPosition(options) + const sheetBody = this.element.find('.sheet-body') + if (!!position.height) { + const bodyHeight = position.height - 192 + sheetBody.css('height', bodyHeight) + } + return position + } + + get title() { + const t = this.actor.name + const sheet = this.actor.getFlag('core', 'sheetClass') + return sheet === 'gurps.GurpsActorEditorSheet' ? '**** Editing: ' + t + ' ****' : t + } + + _getHeaderButtons() { + let buttons = super._getHeaderButtons() + + // Token Configuration + if (this.options.editable && isConfigurationAllowed(this.actor)) { + buttons = this.getCustomHeaderButtons().concat(buttons) + } + return buttons + } + + /** + * Override this to change the buttons appended to the actor sheet title bar. + */ + getCustomHeaderButtons() { + const sheet = this.actor.getFlag('core', 'sheetClass') + const isEditor = sheet === 'gurps.GurpsActorEditorSheet' + const altsheet = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_ALT_SHEET) + + const isFull = sheet === undefined || sheet === 'gurps.GurpsActorSheet' + let b = [ + { + label: isFull ? altsheet : 'Full View', + class: 'toggle', + icon: 'fas fa-exchange-alt', + onclick: ev => this._onToggleSheet(ev, altsheet), + }, + ] + + if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_BLOCK_IMPORT) || game.user.isTrusted) + b.push({ + label: 'Import', + class: 'import', + icon: 'fas fa-file-import', + onclick: ev => this._onFileImport(ev), + }) + + if (!isEditor) { + b.push({ + label: 'Editor', + class: 'edit', + icon: 'fas fa-edit', + onclick: ev => this._onOpenEditor(ev), + }) + } + return b + } + + async _onFileImport(event) { + event.preventDefault() + this.actor.importCharacter() + } + + async _onToggleSheet(event, altsheet) { + event.preventDefault() + let newSheet = Object.values(CONFIG.Actor.sheetClasses['character']).filter(s => s.label == altsheet)[0].id + + const original = + this.actor.getFlag('core', 'sheetClass') || + Object.values(CONFIG.Actor.sheetClasses['character']).filter(s => s.default)[0].id + console.log('original: ' + original) + + if (original != 'gurps.GurpsActorSheet') newSheet = 'gurps.GurpsActorSheet' + if (event.shiftKey) + // Hold down the shift key for Simplified + newSheet = 'gurps.GurpsActorSimplifiedSheet' + if (game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.CONTROL)) + // Hold down the Ctrl key (Command on Mac) for Simplified + newSheet = 'gurps.GurpsActorNpcSheet' + + this.actor.openSheet(newSheet) + } + + async _onOpenEditor(event) { + event.preventDefault() + this.actor.openSheet('gurps.GurpsActorEditorSheet') + } + + async _onRightClickGurpslink(event) { + event.preventDefault() + event.stopImmediatePropagation() // Since this may occur in note or a list (which has its own RMB handler) + let el = event.currentTarget + let action = el.dataset.action + if (!!action) { + action = JSON.parse(atou(action)) + if (action.type === 'damage' || action.type === 'deriveddamage') { + GURPS.resolveDamageRoll(event, this.actor, action.orig, action.overridetxt, game.user.isGM, true) + } else { + GURPS.whisperOtfToOwner(action.orig, action.overridetxt, event, action, this.actor) // only offer blind rolls for things that can be blind, No need to offer blind roll if it is already blind + } + } + } + + async _onRightClickPdf(event) { + event.preventDefault() + let el = event.currentTarget + GURPS.whisperOtfToOwner('PDF:' + el.innerText, null, event, false, this.actor) + } + + async _onRightClickGmod(event) { + event.preventDefault() + let el = event.currentTarget + let n = el.dataset.name + let t = el.innerText + GURPS.whisperOtfToOwner(t + ' ' + n, null, event, false, this.actor) + } + + async _onRightClickOtf(event) { + event.preventDefault() + let el = event.currentTarget + let isDamageRoll = el.dataset.hasOwnProperty('damage') + let otf = event.currentTarget.dataset.otf + + if (isDamageRoll) { + GURPS.resolveDamageRoll(event, this.actor, otf, null, game.user.isGM) + } else { + GURPS.whisperOtfToOwner(event.currentTarget.dataset.otf, null, event, !isDamageRoll, this.actor) // Can't blind roll damages (yet) + } + } + + async _onClickRoll(event, targets) { + GURPS.handleRoll(event, this.actor, { targets: targets }) + } + + async _onClickSplit(event) { + let element = event.currentTarget + let key = element.dataset.key + new SplitDREditor(this.actor, key).render(true) + } + + async _onNavigate(event) { + let dataValue = $(event.currentTarget).attr('data-value') + let windowContent = event.currentTarget.closest('.window-content') + let target = windowContent.querySelector(`#${dataValue}`) + + // The '33' represents the height of the window title bar + a bit of margin + // TODO: we should really look this up and use the actual values as found in the DOM. + windowContent.scrollTop = target.offsetTop - 33 + + // add the glowing class to target AND to event.currentTarget, then remove it + $(target).addClass('glowing') + $(event.currentTarget).addClass('glowing') + + setTimeout(function () { + $(target).removeClass('glowing') + $(event.currentTarget).removeClass('glowing') + }, 2000) + } + + async _onClickEnc(ev) { + ev.preventDefault() + if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ENCUMBRANCE)) { + let element = ev.currentTarget + let key = element.dataset.key + ////////// + // Check for 'undefined' when clicking on Encumbrance Level 'header'. ~Stevil + if (key !== undefined) { + ////////// + let encs = this.actor.system.encumbrance + if (encs[key].current) return // already selected + for (let enckey in encs) { + let enc = encs[enckey] + let t = 'data.encumbrance.' + enckey + '.current' + if (key === enckey) { + await this.actor.update({ + [t]: true, + 'data.currentmove': parseInt(enc.move), + 'data.currentdodge': parseInt(enc.dodge), + }) + } else if (enc.current) { + await this.actor.update({ [t]: false }) + } + } + ////////// + } + ////////// + } else { + ui.notifications.warn( + "You cannot manually change the Encumbrance level. The 'Automatically calculate Encumbrance Level' setting is turned on." + ) + } + } + + async _onClickEquip(ev) { + ev.preventDefault() + let element = ev.currentTarget + let key = element.dataset.key + let eqt = duplicate(GURPS.decode(this.actor, key)) + eqt.equipped = !eqt.equipped + await this.actor.update({ [key]: eqt }) + await this.actor.updateItemAdditionsBasedOn(eqt, key) + let p = this.actor.getEquippedParry() + let b = this.actor.getEquippedBlock() + await this.actor.update({ + 'data.equippedparry': p, + 'data.equippedblock': b, + }) + this.actor._forceRender() + } + + deleteItemMenu(obj) { + return [ + { + name: 'Delete', + icon: "", + callback: e => { + let key = e[0].dataset.key + if (key.includes('.equipment.')) this.actor.deleteEquipment(key) + else GURPS.removeKey(this.actor, key) + }, + }, + ] + } + + /* -------------------------------------------- */ + + /** @override */ + _updateObject(event, formData) { + return super._updateObject(event, formData) + } } export class GurpsActorTabSheet extends GurpsActorSheet { - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['gurps', 'sheet', 'actor'], - width: 860, - height: 600, - tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], - dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], - }) - } - - /* -------------------------------------------- */ - - /** @override */ - get template() { - if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' - return 'systems/gurps/templates/actor/actor-tab-sheet.hbs' - } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['gurps', 'sheet', 'actor'], + width: 860, + height: 600, + tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], + dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], + }) + } + + /* -------------------------------------------- */ + + /** @override */ + get template() { + if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' + return 'systems/gurps/templates/actor/actor-tab-sheet.hbs' + } } export class GurpsActorCombatSheet extends GurpsActorSheet { - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['gurps', 'sheet', 'actor'], - width: 670, - height: 'auto', - tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], - dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], - }) - } - - /* -------------------------------------------- */ - - /** @override */ - get template() { - if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' - return 'systems/gurps/templates/actor/combat-sheet.hbs' - } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['gurps', 'sheet', 'actor'], + width: 670, + height: 'auto', + tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], + dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], + }) + } + + /* -------------------------------------------- */ + + /** @override */ + get template() { + if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' + return 'systems/gurps/templates/actor/combat-sheet.hbs' + } } Hooks.on('getGurpsActorEditorSheetHeaderButtons', sheet => { - if (sheet.actor.isEmptyActor()) { - ui.notifications.error('You are editing an EMPTY Actor!') - setTimeout( - () => - Dialog.prompt({ - title: 'Empty Actor', - content: - 'You are editing an EMPTY Actor!

    Either use the Import button to enter data, or delete this Actor and use the /mook chat command to create NPCs.

    Press Ok to open the Full View.', - label: 'Ok', - callback: async () => { - sheet.actor.openSheet('gurps.GurpsActorSheet') - }, - rejectClose: false, - }), - 500 - ) - } + if (sheet.actor.isEmptyActor()) { + ui.notifications.error('You are editing an EMPTY Actor!') + setTimeout( + () => + Dialog.prompt({ + title: 'Empty Actor', + content: + 'You are editing an EMPTY Actor!

    Either use the Import button to enter data, or delete this Actor and use the /mook chat command to create NPCs.

    Press Ok to open the Full View.', + label: 'Ok', + callback: async () => { + sheet.actor.openSheet('gurps.GurpsActorSheet') + }, + rejectClose: false, + }), + 500 + ) + } }) const ClickAndContextMenu = 'click contextmenu' export class GurpsActorEditorSheet extends GurpsActorSheet { - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['gurps', 'gurpsactorsheet', 'sheet', 'actor'], - scrollY: [ - '.gurpsactorsheet #advantages #reactions #melee #ranged #skills #spells #equipmentcarried #equipmentother #notes', - ], - width: 880, - height: 800, - tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], - dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], - }) - } - - /* -------------------------------------------- */ - - /** @override */ - get template() { - if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' - return 'systems/gurps/templates/actor/actor-sheet-gcs-editor.hbs' - } - - //TODO: Don't let user change the sheet in case of user having limited access and the sheet is left in editor mode by someone else - - getData() { - const sheetData = super.getData() - sheetData.isEditing = true - return sheetData - } - - makeDeleteMenu(html, cssclass, obj, eventname = 'contextmenu') { - new ContextMenu(html, cssclass, this.deleteItemMenu(obj), { eventName: eventname }) - } - - makeHeaderMenu(html, cssclass, name, obj, path, eventname = 'contextmenu') { - new ContextMenu(html, cssclass, [this.addItemMenu(name, obj, path)], { eventName: eventname }) - } - - activateListeners(html) { - super.activateListeners(html) - html.find('textarea').on('drop', this.dropFoundryLinks) - html.find('input').on('drop', this.dropFoundryLinks) - - html.find('#ignoreinputbodyplan').on('click', this._onClickIgnoreImportBodyPlan.bind(this)) - html.find('label[for="ignoreinputbodyplan"]').on('click', this._onClickIgnoreImportBodyPlan.bind(this)) - - html.find('#showflightmove').on('click', this._onClickShowFlightMove.bind(this)) - html.find('label[for="showflightmove"]').on('click', this._onClickShowFlightMove.bind(this)) - - html.find('#showflightmove').click(ev => { - ev.preventDefault() - let element = ev.currentTarget - let show = element.checked - this.actor.update({ 'data.additionalresources.showflightmove': show }) - }) - - this.makeDeleteMenu(html, '.hlmenu', new HitLocation('???'), ClickAndContextMenu) - this.makeDeleteMenu(html, '.reactmenu', new Reaction('+0', '???'), ClickAndContextMenu) - this.makeDeleteMenu(html, '.condmodmenu', new Modifier('+0', '???'), ClickAndContextMenu) - this.makeDeleteMenu(html, '.meleemenu', new Melee('???'), ClickAndContextMenu) - this.makeDeleteMenu(html, '.rangedmenu', new Ranged('???'), 'click context') - this.makeDeleteMenu(html, '.adsmenu', new Advantage('???'), ClickAndContextMenu) - this.makeDeleteMenu(html, '.skillmenu', new Skill('???'), ClickAndContextMenu) - this.makeDeleteMenu(html, '.spellmenu', new Spell('???'), ClickAndContextMenu) - this.makeDeleteMenu(html, '.notemenu', new Note('???', true), 'contextmenu') - - html.find('#body-plan').change(async e => { - let bodyplan = e.currentTarget.value - if (bodyplan !== this.actor.system.additionalresources.bodyplan) { - let hitlocationTable = hitlocationDictionary[bodyplan] - if (!hitlocationTable) { - ui.notifications.error(`Unsupported bodyplan value: ${bodyplan}`) - } else { - // Try to copy any DR values from hit locations that match - let hitlocations = {} - let oldlocations = this.actor.system.hitlocations || {} - let count = 0 - for (let loc in hitlocationTable) { - let hit = hitlocationTable[loc] - let originalLoc = Object.values(oldlocations).filter(it => it.where === loc) - let dr = originalLoc.length === 0 ? 0 : originalLoc[0]?.dr - let it = new HitLocation(loc, dr, hit.penalty, hit.roll) - GURPS.put(hitlocations, it, count++) - } - this.actor.ignoreRender = true - await this.actor.update({ - 'data.-=hitlocations': null, - 'data.additionalresources.bodyplan': bodyplan, - }) - await this.actor.update({ 'data.hitlocations': 0 }) // A hack. The delete above doesn't always get rid of the properties, so set it to Zero - this.actor.ignoreRender = false - await this.actor.update({ 'data.hitlocations': hitlocations }) - } - } - }) - } - - /** - * @override - * @param {*} elementid - * @returns - */ - getMenuItems(elementid) { - // returns an array of menuitems - let menu = super.getMenuItems(elementid) - - // add any additional items to the menu - switch (elementid) { - case '#location': - return [this.addItemMenu(i18n('GURPS.hitLocation'), new HitLocation('???'), 'data.hitlocations'), ...menu] - - case '#reactions': - return [ - this.addItemMenu(i18n('GURPS.reaction'), new Reaction('+0', i18n('GURPS.fromEllipses')), 'data.reactions'), - ...menu, - ] - - case '#conditionalmods': - return [ - this.addItemMenu( - i18n('GURPS.conditionalModifier'), - new Modifier('+0', i18n('GURPS.fromEllipses')), - 'data.conditionalmods' - ), - ...menu, - ] - - case '#melee': - return [ - this.addItemMenu(i18n('GURPS.meleeAttack'), new Ranged(`${i18n('GURPS.meleeAttack')}...`), 'data.melee'), - ...menu, - ] - - case '#ranged': - return [ - this.addItemMenu(i18n('GURPS.rangedAttack'), new Ranged(`${i18n('GURPS.rangedAttack')}...`), 'data.ranged'), - ...menu, - ] - - case '#advantages': - return [ - this.addItemMenu(i18n('GURPS.adDisadQuirkPerk'), new Advantage(`${i18n('GURPS.adDisad')}...`), 'data.ads'), - ...menu, - ] - - case '#skills': - return [this.addItemMenu(i18n('GURPS.skill'), new Skill(`${i18n('GURPS.skill')}...`), 'data.skills'), ...menu] - - case '#spells': - return [this.addItemMenu(i18n('GURPS.spell'), new Spell(`${i18n('GURPS.spell')}...`), 'data.spells'), ...menu] - - default: - return menu - } - } - - async _onClickIgnoreImportBodyPlan(ev) { - ev.preventDefault() - let current = this.actor.system.additionalresources.ignoreinputbodyplan - let ignore = !current - await this.actor.update({ 'data.additionalresources.ignoreinputbodyplan': ignore }) - } - - async _onClickShowFlightMove(ev) { - ev.preventDefault() - let current = this.actor.system.additionalresources.showflightmove - let show = !current - await this.actor.update({ 'data.additionalresources.showflightmove': show }) - } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['gurps', 'gurpsactorsheet', 'sheet', 'actor'], + scrollY: [ + '.gurpsactorsheet #advantages #reactions #melee #ranged #skills #spells #equipmentcarried #equipmentother #notes', + ], + width: 880, + height: 800, + tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], + dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], + }) + } + + /* -------------------------------------------- */ + + /** @override */ + get template() { + if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' + return 'systems/gurps/templates/actor/actor-sheet-gcs-editor.hbs' + } + + //TODO: Don't let user change the sheet in case of user having limited access and the sheet is left in editor mode by someone else + + getData() { + const sheetData = super.getData() + sheetData.isEditing = true + return sheetData + } + + makeDeleteMenu(html, cssclass, obj, eventname = 'contextmenu') { + new ContextMenu(html, cssclass, this.deleteItemMenu(obj), { eventName: eventname }) + } + + makeHeaderMenu(html, cssclass, name, obj, path, eventname = 'contextmenu') { + new ContextMenu(html, cssclass, [this.addItemMenu(name, obj, path)], { eventName: eventname }) + } + + activateListeners(html) { + super.activateListeners(html) + html.find('textarea').on('drop', this.dropFoundryLinks) + html.find('input').on('drop', this.dropFoundryLinks) + + html.find('#ignoreinputbodyplan').on('click', this._onClickIgnoreImportBodyPlan.bind(this)) + html.find('label[for="ignoreinputbodyplan"]').on('click', this._onClickIgnoreImportBodyPlan.bind(this)) + + html.find('#showflightmove').on('click', this._onClickShowFlightMove.bind(this)) + html.find('label[for="showflightmove"]').on('click', this._onClickShowFlightMove.bind(this)) + + html.find('#showflightmove').click(ev => { + ev.preventDefault() + let element = ev.currentTarget + let show = element.checked + this.actor.update({ 'data.additionalresources.showflightmove': show }) + }) + + this.makeDeleteMenu(html, '.hlmenu', new HitLocation('???'), ClickAndContextMenu) + this.makeDeleteMenu(html, '.reactmenu', new Reaction('+0', '???'), ClickAndContextMenu) + this.makeDeleteMenu(html, '.condmodmenu', new Modifier('+0', '???'), ClickAndContextMenu) + this.makeDeleteMenu(html, '.meleemenu', new Melee('???'), ClickAndContextMenu) + this.makeDeleteMenu(html, '.rangedmenu', new Ranged('???'), 'click context') + this.makeDeleteMenu(html, '.adsmenu', new Advantage('???'), ClickAndContextMenu) + this.makeDeleteMenu(html, '.skillmenu', new Skill('???'), ClickAndContextMenu) + this.makeDeleteMenu(html, '.spellmenu', new Spell('???'), ClickAndContextMenu) + this.makeDeleteMenu(html, '.notemenu', new Note('???', true), 'contextmenu') + + html.find('#body-plan').change(async e => { + let bodyplan = e.currentTarget.value + if (bodyplan !== this.actor.system.additionalresources.bodyplan) { + let hitlocationTable = hitlocationDictionary[bodyplan] + if (!hitlocationTable) { + ui.notifications.error(`Unsupported bodyplan value: ${bodyplan}`) + } else { + // Try to copy any DR values from hit locations that match + let hitlocations = {} + let oldlocations = this.actor.system.hitlocations || {} + let count = 0 + for (let loc in hitlocationTable) { + let hit = hitlocationTable[loc] + let originalLoc = Object.values(oldlocations).filter(it => it.where === loc) + let dr = originalLoc.length === 0 ? 0 : originalLoc[0]?.dr + let it = new HitLocation(loc, dr, hit.penalty, hit.roll) + GURPS.put(hitlocations, it, count++) + } + this.actor.ignoreRender = true + await this.actor.update({ + 'data.-=hitlocations': null, + 'data.additionalresources.bodyplan': bodyplan, + }) + await this.actor.update({ 'data.hitlocations': 0 }) // A hack. The delete above doesn't always get rid of the properties, so set it to Zero + this.actor.ignoreRender = false + await this.actor.update({ 'data.hitlocations': hitlocations }) + } + } + }) + } + + /** + * @override + * @param {*} elementid + * @returns + */ + getMenuItems(elementid) { + // returns an array of menuitems + let menu = super.getMenuItems(elementid) + + // add any additional items to the menu + switch (elementid) { + case '#location': + return [this.addItemMenu(i18n('GURPS.hitLocation'), new HitLocation('???'), 'data.hitlocations'), ...menu] + + case '#reactions': + return [ + this.addItemMenu(i18n('GURPS.reaction'), new Reaction('+0', i18n('GURPS.fromEllipses')), 'data.reactions'), + ...menu, + ] + + case '#conditionalmods': + return [ + this.addItemMenu( + i18n('GURPS.conditionalModifier'), + new Modifier('+0', i18n('GURPS.fromEllipses')), + 'data.conditionalmods' + ), + ...menu, + ] + + case '#melee': + return [ + this.addItemMenu(i18n('GURPS.meleeAttack'), new Ranged(`${i18n('GURPS.meleeAttack')}...`), 'data.melee'), + ...menu, + ] + + case '#ranged': + return [ + this.addItemMenu(i18n('GURPS.rangedAttack'), new Ranged(`${i18n('GURPS.rangedAttack')}...`), 'data.ranged'), + ...menu, + ] + + case '#advantages': + return [ + this.addItemMenu(i18n('GURPS.adDisadQuirkPerk'), new Advantage(`${i18n('GURPS.adDisad')}...`), 'data.ads'), + ...menu, + ] + + case '#skills': + return [this.addItemMenu(i18n('GURPS.skill'), new Skill(`${i18n('GURPS.skill')}...`), 'data.skills'), ...menu] + + case '#spells': + return [this.addItemMenu(i18n('GURPS.spell'), new Spell(`${i18n('GURPS.spell')}...`), 'data.spells'), ...menu] + + default: + return menu + } + } + + async _onClickIgnoreImportBodyPlan(ev) { + ev.preventDefault() + let current = this.actor.system.additionalresources.ignoreinputbodyplan + let ignore = !current + await this.actor.update({ 'data.additionalresources.ignoreinputbodyplan': ignore }) + } + + async _onClickShowFlightMove(ev) { + ev.preventDefault() + let current = this.actor.system.additionalresources.showflightmove + let show = !current + await this.actor.update({ 'data.additionalresources.showflightmove': show }) + } } export class GurpsActorSimplifiedSheet extends GurpsActorSheet { - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['gurps', 'sheet', 'actor'], - width: 820, - height: 900, - tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], - dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], - }) - } - - /* -------------------------------------------- */ - - /** @override */ - get template() { - if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' - return 'systems/gurps/templates/simplified.html' - } - - getData() { - const data = super.getData() - data.dodge = this.actor.getCurrentDodge() - data.defense = this.actor.getTorsoDr() - return data - } - - activateListeners(html) { - super.activateListeners(html) - html.find('.rollableicon').click(this._onClickRollableIcon.bind(this)) - } - - async _onClickRollableIcon(ev) { - ev.preventDefault() - let element = ev.currentTarget - let val = element.dataset.value - let parsed = parselink(val) - GURPS.performAction(parsed.action, this.actor, ev) - } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['gurps', 'sheet', 'actor'], + width: 820, + height: 900, + tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.sheet-body', initial: 'description' }], + dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], + }) + } + + /* -------------------------------------------- */ + + /** @override */ + get template() { + if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' + return 'systems/gurps/templates/simplified.html' + } + + getData() { + const data = super.getData() + data.dodge = this.actor.getCurrentDodge() + data.defense = this.actor.getTorsoDr() + return data + } + + activateListeners(html) { + super.activateListeners(html) + html.find('.rollableicon').click(this._onClickRollableIcon.bind(this)) + } + + async _onClickRollableIcon(ev) { + ev.preventDefault() + let element = ev.currentTarget + let val = element.dataset.value + let parsed = parselink(val) + GURPS.performAction(parsed.action, this.actor, ev) + } } export class GurpsActorNpcSheet extends GurpsActorSheet { - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['npc-sheet', 'sheet', 'actor'], - width: 750, - height: 450, - dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], - }) - } - - /* -------------------------------------------- */ - - /** @override */ - get template() { - if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' - return 'systems/gurps/templates/actor/npc-sheet-ci.hbs' - } - - getData() { - const data = super.getData() - data.currentdodge = this.actor.system.currentdodge - data.currentmove = this.actor.system.currentmove - data.defense = this.actor.getTorsoDr() - let p = this.actor.getEquippedParry() - // let b = this.actor.getEquippedBlock(); // Don't have a good way to display block yet - // if (b > 0) - // data.parryblock = p + "/" + b; - // else - data.parryblock = p - - return data - } - - activateListeners(html) { - super.activateListeners(html) - html.find('.npc-sheet').click(ev => { - this._onfocus(ev) - }) - html.find('.rollableicon').click(this._onClickRollableIcon.bind(this)) - } - - async _onClickRollableIcon(ev) { - ev.preventDefault() - let element = ev.currentTarget - let val = element.dataset.value - let parsed = parselink(val) - GURPS.performAction(parsed.action, this.actor, ev) - } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['npc-sheet', 'sheet', 'actor'], + width: 750, + height: 450, + dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], + }) + } + + /* -------------------------------------------- */ + + /** @override */ + get template() { + if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' + return 'systems/gurps/templates/actor/npc-sheet-ci.hbs' + } + + getData() { + const data = super.getData() + data.currentdodge = this.actor.system.currentdodge + data.currentmove = this.actor.system.currentmove + data.defense = this.actor.getTorsoDr() + let p = this.actor.getEquippedParry() + // let b = this.actor.getEquippedBlock(); // Don't have a good way to display block yet + // if (b > 0) + // data.parryblock = p + "/" + b; + // else + data.parryblock = p + + return data + } + + activateListeners(html) { + super.activateListeners(html) + html.find('.npc-sheet').click(ev => { + this._onfocus(ev) + }) + html.find('.rollableicon').click(this._onClickRollableIcon.bind(this)) + } + + async _onClickRollableIcon(ev) { + ev.preventDefault() + let element = ev.currentTarget + let val = element.dataset.value + let parsed = parselink(val) + GURPS.performAction(parsed.action, this.actor, ev) + } } export class GurpsInventorySheet extends GurpsActorSheet { - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['gurps', 'sheet', 'actor'], - width: 700, - height: 400, - tabs: [], - scrollY: [], - dragDrop: [{ dragSelector: 'item-list .item', dropSelector: null }], - }) - } - - /* -------------------------------------------- */ - - /** @override */ - get template() { - if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' - return 'systems/gurps/templates/inventory-sheet.html' - } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['gurps', 'sheet', 'actor'], + width: 700, + height: 400, + tabs: [], + scrollY: [], + dragDrop: [{ dragSelector: 'item-list .item', dropSelector: null }], + }) + } + + /* -------------------------------------------- */ + + /** @override */ + get template() { + if (!game.user.isGM && this.actor.limited) return 'systems/gurps/templates/actor/actor-sheet-gcs-limited.hbs' + return 'systems/gurps/templates/inventory-sheet.html' + } } diff --git a/module/actor/actor.js b/module/actor/actor.js index 79c55928e..3df95cfaf 100755 --- a/module/actor/actor.js +++ b/module/actor/actor.js @@ -1,18 +1,18 @@ 'use strict' import { - xmlTextToJson, - convertRollStringToArrayOfInt, - recurselist, - makeRegexPatternFrom, - i18n, - i18n_f, - splitArgs, - generateUniqueId, - objectToArray, - arrayToObject, - zeroFill, - arrayBuffertoBase64, + xmlTextToJson, + convertRollStringToArrayOfInt, + recurselist, + makeRegexPatternFrom, + i18n, + i18n_f, + splitArgs, + generateUniqueId, + objectToArray, + arrayToObject, + zeroFill, + arrayBuffertoBase64, } from '../../lib/utilities.js' import { parselink, COSTS_REGEX } from '../../lib/parselink.js' import { ResourceTrackerManager } from './resource-tracker-manager.js' @@ -21,4351 +21,4349 @@ import * as HitLocations from '../hitlocation/hitlocation.js' import * as settings from '../../lib/miscellaneous-settings.js' import { SemanticVersion } from '../../lib/semver.js' import { - MOVE_NONE, - MOVE_ONE, - MOVE_STEP, - MOVE_ONETHIRD, - MOVE_HALF, - MOVE_TWOTHIRDS, - PROPERTY_MOVEOVERRIDE_MANEUVER, - PROPERTY_MOVEOVERRIDE_POSTURE, + MOVE_NONE, + MOVE_ONE, + MOVE_STEP, + MOVE_ONETHIRD, + MOVE_HALF, + MOVE_TWOTHIRDS, + PROPERTY_MOVEOVERRIDE_MANEUVER, + PROPERTY_MOVEOVERRIDE_POSTURE, } from './maneuver.js' import { SmartImporter } from '../smart-importer.js' import { GurpsItem } from '../item.js' import GurpsToken from '../token.js' import { parseDecimalNumber } from '../../lib/parse-decimal-number/parse-decimal-number.js' import { - _Base, - Skill, - Spell, - Advantage, - Ranged, - Note, - Encumbrance, - Equipment, - Reaction, - Modifier, - Melee, - HitLocationEntry, - Language, + _Base, + Skill, + Spell, + Advantage, + Ranged, + Note, + Encumbrance, + Equipment, + Reaction, + Modifier, + Melee, + HitLocationEntry, + Language, } from './actor-components.js' import { multiplyDice } from '../utilities/damage-utils.js' // Ensure that ALL actors has the current version loaded into them (for migration purposes) -Hooks.on('createActor', async function(/** @type {Actor} */ actor) { - await actor.update({ "_stats.systemVersion": game.system.version }) +Hooks.on('createActor', async function (/** @type {Actor} */ actor) { + await actor.update({ '_stats.systemVersion': game.system.version }) }) export const MoveModes = { - Ground: 'GURPS.moveModeGround', - Air: 'GURPS.moveModeAir', - Water: 'GURPS.moveModeWater', - Space: 'GURPS.moveModeSpace', + Ground: 'GURPS.moveModeGround', + Air: 'GURPS.moveModeAir', + Water: 'GURPS.moveModeWater', + Space: 'GURPS.moveModeSpace', } export class GurpsActor extends Actor { - /** @override */ - getRollData() { - const data = super.getRollData() - return data - } - - /** - * @returns {GurpsActor} - */ - asGurpsActor() { - // @ts-ignore - return /** @type {GurpsActor} */ (this) - } - - // Return collection os Users that have ownership on this actor - getOwners() { - return game.users?.contents.filter(u => this.getUserLevel(u) >= CONST.DOCUMENT_PERMISSION_LEVELS.OWNER) - } - - // 0.8.x added steps necessary to switch sheets - /** - * @param {Application} newSheet - */ - async openSheet(newSheet) { - const sheet = this.sheet - if (!!sheet) { - await sheet.close() - this._sheet = null - delete this.apps[sheet.appId] - await this.setFlag('core', 'sheetClass', newSheet) - this.ignoreRender = false - this.sheet.render(true) - } - } - - prepareData() { - super.prepareData() - // By default, it does this: - // this.data.reset() - // this.prepareBaseData() - // this.prepareEmbeddedEntities() - // this.prepareDerivedData() - } - - prepareBaseData() { - super.prepareBaseData() - - this.system.conditions.posture = 'standing' - this.system.conditions.self = { modifiers: [] } - this.system.conditions.target = { modifiers: [] } - this.system.conditions.exhausted = false - this.system.conditions.reeling = false - - { - // Oh how I wish we had a typesafe model! - // I hate treating everything as "maybe its a number, maybe its a string...?!" - - let sizemod = this.system.traits?.sizemod.toString() || '+0' - if (sizemod.match(/^\d/g)) sizemod = `+${sizemod}` - if (sizemod !== '0' && sizemod !== '+0') { - this.system.conditions.target.modifiers.push( - i18n_f('GURPS.modifiersSize', { sm: sizemod }, '{sm} for Size Modifier') - ) - } - } - - let attributes = this.system.attributes - if (foundry.utils.getType(attributes.ST.import) === 'string') - this.system.attributes.ST.import = parseInt(attributes.ST.import) - } - - prepareEmbeddedEntities() { - // Calls this.applyActiveEffects() - super.prepareEmbeddedEntities() - } - - prepareDerivedData() { - super.prepareDerivedData() - - // Handle new move data -- if data.move exists, use the default value in that object to set the move - // value in the first entry of the encumbrance object. - if (this.system.encumbrance) { - let move = this.system.move - if (!move) { - let currentMove = this.system.encumbrance['00000'].move ?? this.system.basicmove.value - let value = { mode: MoveModes.Ground, basic: currentMove, default: true } - setProperty(this.system, 'move.00000', value) - move = this.system.move - } - - let current = Object.values(move).find(it => it.default) - if (current) { - // This is nonpersistent, derived values only. - this.system.encumbrance['00000'].move = current.basic - } - } - - this.calculateDerivedValues() - } - - // execute after every import. - async postImport() { - this.calculateDerivedValues() - - // Convoluted code to add Items (and features) into the equipment list - // @ts-ignore - let orig = /** @type {GurpsItem[]} */ (this.items.contents.slice().sort((a, b) => b.name.localeCompare(a.name))) // in case items are in the same list... add them alphabetically - /** - * @type {any[]} - */ - let good = [] - while (orig.length > 0) { - // We are trying to place 'parent' items before we place 'children' items - let left = [] - let atLeastOne = false - for (const i of orig) { - // @ts-ignore - if (!i.system.eqt.parentuuid || good.find(e => e.system.eqt.uuid == i.system.eqt.parentuuid)) { - atLeastOne = true - good.push(i) // Add items in 'parent' order... parents before children (so children can find parent when inserted into list) - } else left.push(i) - } - if (atLeastOne) orig = left - else { - // if unable to move at least one, just copy the rest and hope for the best - good = [...good, ...left] - orig = [] - } - } - for (const item of good) await this.addItemData(item.data) // re-add the item equipment and features - - await this.update({ "_stats.systemVersion": game.system.version }, { diff: false, render: false }) - // Set custom trackers based on templates. should be last because it may need other data to initialize... - await this.setResourceTrackers() - await this.syncLanguages() - } - - // Ensure Language Advantages conform to a standard (for Polygot module) - async syncLanguages() { - if (this.system.languages) { - let updated = false - let newads = { ...this.system.ads } - let langn = new RegExp('Language:?', 'i') - let langt = new RegExp(i18n('GURPS.language') + ':?', 'i') - recurselist(this.system.languages, (e, k, d) => { - let a = GURPS.findAdDisad(this, '*' + e.name) // is there an Adv including the same name - if (a) { - if (!a.name.match(langn) && !a.name.match(langt)) { - // GCA4/GCS style - a.name = i18n('GURPS.language') + ': ' + a.name - updated = true - } - } else { - // GCA5 style (Language without Adv) - let n = i18n('GURPS.language') + ': ' + e.name - if (e.spoken == e.written) - // If equal, then just report single level - n += ' (' + e.spoken + ')' - else if (!!e.spoken) - // Otherwise, report type and level (like GCA4) - n += ' (' + i18n('GURPS.spoken') + ') (' + e.spoken + ')' - else n += ' (' + i18n('GURPS.written') + ') (' + e.written + ')' - let a = new Advantage() - a.name = n - a.points = e.points - GURPS.put(newads, a) - updated = true - } - }) - if (updated) { - await this.update({ 'data.ads': newads }) - } - } - } - - // This will ensure that every characater at least starts with these new data values. actor-sheet.js may change them. - calculateDerivedValues() { - let saved = !!this.ignoreRender - this.ignoreRender = true - this._initializeStartingValues() - this._applyItemBonuses() - - // Must be done after bonuses, but before weights - this._calculateEncumbranceIssues() - - // Must be after bonuses and encumbrance effects on ST - this._recalcItemFeatures() - this._calculateRangedRanges() - - // Must be done at end - this._calculateWeights() - - let maneuver = this.effects.contents.find(it => it.data.flags?.core?.statusId === 'maneuver') - this.system.conditions.maneuver = !!maneuver ? maneuver.data.flags.gurps.name : 'undefined' - this.ignoreRender = saved - if (!saved) setTimeout(() => this._forceRender(), 500) - } - - // Initialize the attribute starting values/levels. The code is expecting 'value' or 'level' for many things, and instead of changing all of the GUIs and OTF logic - // we are just going to switch the rug out from underneath. "Import" data will be in the 'import' key and then we will calculate value/level when the actor is loaded. - _initializeStartingValues() { - const data = this.system - data.currentdodge = 0 // start at zero, and bonuses will add, and then they will be finalized later - if (!!data.equipment && !data.equipment.carried) data.equipment.carried = {} // data protection - if (!!data.equipment && !data.equipment.other) data.equipment.other = {} - - if (!data.migrationversion) return // Prior to v0.9.6, this did not exist - let v = /** @type {SemanticVersion} */ (SemanticVersion.fromString(data.migrationversion)) - - // Attributes need to have 'value' set because Foundry expects objs with value and max to be attributes (so we can't use currentvalue) - // Need to protect against data errors - for (const attr in data.attributes) { - if (typeof data.attributes[attr] === 'object' && data.attributes[attr] !== null) - if (isNaN(data.attributes[attr].import)) data.attributes[attr].value = 0 - else data.attributes[attr].value = parseInt(data.attributes[attr].import) - } - // After all of the attributes are copied over, apply tired to ST - // if (!!data.conditions.exhausted) - // data.attributes.ST.value = Math.ceil(parseInt(data.attributes.ST.value.toString()) / 2) - recurselist(data.skills, (e, k, d) => { - // @ts-ignore - if (!!e.import) e.level = parseInt(+e.import) - }) - recurselist(data.spells, (e, k, d) => { - // @ts-ignore - if (!!e.import) e.level = parseInt(+e.import) - }) - - // we don't really need to use recurselist for melee/ranged... but who knows, they may become hierarchical in the future - recurselist(data.melee, (e, k, d) => { - if (!!e.import) { - e.level = parseInt(e.import) - if (!isNaN(parseInt(e.parry))) { - // allows for '14f' and 'no' - let base = 3 + Math.floor(e.level / 2) - let bonus = parseInt(e.parry) - base - if (bonus != 0) { - e.parrybonus = (bonus > 0 ? '+' : '') + bonus - } - } - if (!isNaN(parseInt(e.block))) { - let base = 3 + Math.floor(e.level / 2) - let bonus = parseInt(e.block) - base - if (bonus != 0) { - e.blockbonus = (bonus > 0 ? '+' : '') + bonus - } - } - } else { - e.parrybonus = e.parry - e.blockbonus = e.block - } - }) - - recurselist(data.ranged, (e, k, d) => { - e.level = parseInt(e.import) - }) - - // Only prep hitlocation DRs from v0.9.7 or higher (we don't really need to use recurselist... but who knows, hitlocations may become hierarchical in the future) - if (!v.isLowerThan(settings.VERSION_097)) - recurselist(data.hitlocations, (e, k, d) => { - e.dr = e.import - }) - } - - _applyItemBonuses() { - let pi = (/** @type {string | undefined} */ n) => (!!n ? parseInt(n) : 0) - /** @type {string[]} */ - let gids = [] //only allow each global bonus to add once - const data = this.system - for (const item of this.items.contents) { - let itemData = GurpsItem.asGurpsItem(item).system - if (itemData.equipped && itemData.carried && !!itemData.bonuses && !gids.includes(itemData.globalid)) { - gids.push(itemData.globalid) - let bonuses = itemData.bonuses.split('\n') - for (let bonus of bonuses) { - let m = bonus.match(/\[(.*)\]/) - if (!!m) bonus = m[1] // remove extranious [ ] - let link = parselink(bonus) // ATM, we only support attribute and skill - if (!!link.action) { - // start OTF - recurselist(data.melee, (e, k, d) => { - e.level = pi(e.level) - if (link.action.type == 'attribute' && link.action.attrkey == 'DX') { - // All melee attack skills affected by DX - e.level += pi(link.action.mod) - if (!isNaN(parseInt(e.parry))) { - // handles '11f' - let m = (e.parry + '').match(/(\d+)(.*)/) - e.parry = 3 + Math.floor(e.level / 2) - if (!!e.parrybonus) e.parry += pi(e.parrybonus) - if (!!m) e.parry += m[2] - } - if (!isNaN(parseInt(e.block))) { - // handles 'no' - e.block = 3 + Math.floor(e.level / 2) - if (!!e.blockbonus) e.block += pi(e.blockbonus) - } - } - if (link.action.type == 'attack' && !!link.action.isMelee) { - if (e.name.match(makeRegexPatternFrom(link.action.name, false))) { - e.level += pi(link.action.mod) - if (!isNaN(parseInt(e.parry))) { - // handles '11f' - let m = (e.parry + '').match(/(\d+)(.*)/) - e.parry = 3 + Math.floor(e.level / 2) - if (!!e.parrybonus) e.parry += pi(e.parrybonus) - if (!!m) e.parry += m[2] - } - if (!isNaN(parseInt(e.block))) { - // handles 'no' - e.block = 3 + Math.floor(e.level / 2) - if (!!e.blockbonus) e.block += pi(e.blockbonus) - } - } - } - }) // end melee - recurselist(data.ranged, (e, k, d) => { - e.level = pi(e.level) - if (link.action.type == 'attribute' && link.action.attrkey == 'DX') e.level += pi(link.action.mod) - if (link.action.type == 'attack' && !!link.action.isRanged) { - if (e.name.match(makeRegexPatternFrom(link.action.name, false))) e.level += pi(link.action.mod) - } - }) // end ranged - recurselist(data.skills, (e, k, d) => { - e.level = pi(e.level) - if (link.action.type == 'attribute') { - // skills affected by attribute changes - if (e.relativelevel?.toUpperCase().startsWith(link.action.attrkey)) e.level += pi(link.action.mod) - } - if (link.action.type == 'skill-spell' && !link.action.isSpellOnly) { - if (e.name.match(makeRegexPatternFrom(link.action.name, false))) e.level += pi(link.action.mod) - } - }) // end skills - recurselist(data.spells, (e, k, d) => { - e.level = pi(e.level) - if (link.action.type == 'attribute') { - // spells affected by attribute changes - if (e.relativelevel?.toUpperCase().startsWith(link.action.attrkey)) e.level += pi(link.action.mod) - } - if (link.action.type == 'skill-spell' && !link.action.isSkillOnly) { - if (e.name.match(makeRegexPatternFrom(link.action.name, false))) e.level += pi(link.action.mod) - } - }) // end spells - if (link.action.type == 'attribute') { - let paths = link.action.path.split('.') - let last = paths.pop() - let data = this.system - if (paths.length > 0) data = getProperty(data, paths.join('.')) - // regular attributes have a path - else { - // only accept DODGE - if (link.action.attrkey != 'DODGE') break - } - data[last] = pi(data[last]) + pi(link.action.mod) // enforce that attribute is int - } // end attributes & Dodge - } // end OTF - - // parse bonus for other forms, DR+x? - m = bonus.match(/DR *([+-]\d+) *(.*)/) // DR+1 *Arms "Left Leg" ... - if (!!m) { - let delta = parseInt(m[1]) - let locpatterns = null - if (!!m[2]) { - let locs = splitArgs(m[2]) - locpatterns = locs.map(l => new RegExp(makeRegexPatternFrom(l), 'i')) - } - recurselist(data.hitlocations, (e, k, d) => { - if (!locpatterns || locpatterns.find(p => !!e.where && e.where.match(p)) != null) { - let dr = e.dr ?? '' - dr += '' - let m = dr.match(/(\d+) *([/\|]) *(\d+)/) // check for split DR 5|3 or 5/3 - if (!!m) { - dr = parseInt(m[1]) + delta - let dr2 = parseInt(m[3]) + delta - e.dr = dr + m[2] + dr2 - } else if (!isNaN(parseInt(dr))) e.dr = parseInt(dr) + delta - } - }) - } // end DR - } - } - } - } - - /** - * @param {string} key - * @param {any} id - * @returns {string | undefined} - */ - _findEqtkeyForId(key, id) { - var eqtkey - let data = this.system - recurselist(data.equipment.carried, (e, k, d) => { - if (e[key] == id) eqtkey = 'data.equipment.carried.' + k - }) - if (!eqtkey) - recurselist(data.equipment.other, (e, k, d) => { - if (e[key] == id) eqtkey = 'data.equipment.other.' + k - }) - return eqtkey - } - - /** - * @param {{ [key: string]: any }} dict - * @param {string} type - * @returns {number} - */ - _sumeqt(dict, type, checkEquipped = false) { - if (!dict) return 0.0 - let flt = (/** @type {string} */ str) => (!!str ? parseFloat(str) : 0) - let sum = 0 - for (let k in dict) { - let e = dict[k] - let c = flt(e.count) - let t = flt(e[type]) - if (!checkEquipped || !!e.equipped) sum += c * t - sum += this._sumeqt(e.contains, type, checkEquipped) - sum += this._sumeqt(e.collapsed, type, checkEquipped) - } - // @ts-ignore - return parseInt(sum * 100) / 100 - } - - _calculateWeights() { - let data = this.system - let eqt = data.equipment || {} - let eqtsummary = { - eqtcost: this._sumeqt(eqt.carried, 'cost'), - eqtlbs: this._sumeqt( - eqt.carried, - 'weight', - game.settings.get(settings.SYSTEM_NAME, settings.SETTING_CHECK_EQUIPPED) - ), - othercost: this._sumeqt(eqt.other, 'cost'), - } - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ENCUMBRANCE)) - this.checkEncumbance(eqtsummary.eqtlbs) - data.eqtsummary = eqtsummary - } - - _calculateEncumbranceIssues() { - const data = this.system - const encs = data.encumbrance - const isReeling = !!data.conditions.reeling - const isTired = !!data.conditions.exhausted - - // We must assume that the first level of encumbrance has the finally calculated move and dodge settings - if (!!encs) { - const level0 = encs[zeroFill(0)] // if there are encumbrances, there will always be a level0 - let effectiveMove = parseInt(level0.move) - let effectiveDodge = isNaN(parseInt(level0.dodge)) ? '–' : parseInt(level0.dodge) + data.currentdodge - let effectiveSprint = this._getSprintMove() - - if (isReeling) { - effectiveMove = Math.ceil(effectiveMove / 2) - effectiveDodge = isNaN(effectiveDodge) ? '–' : Math.ceil(effectiveDodge / 2) - effectiveSprint = Math.ceil(effectiveSprint / 2) - } - - if (isTired) { - effectiveMove = Math.ceil(effectiveMove / 2) - effectiveDodge = isNaN(effectiveDodge) ? '–' : Math.ceil(effectiveDodge / 2) - effectiveSprint = Math.ceil(effectiveSprint / 2) - } - - for (let enckey in encs) { - let enc = encs[enckey] - let threshold = 1.0 - 0.2 * parseInt(enc.level) // each encumbrance level reduces move by 20% - enc.currentmove = this._getCurrentMove(effectiveMove, threshold) //Math.max(1, Math.floor(m * t)) - enc.currentdodge = isNaN(effectiveDodge) ? '–' : Math.max(1, effectiveDodge - parseInt(enc.level)) - enc.currentsprint = Math.max(1, Math.floor(effectiveSprint * threshold)) - enc.currentmovedisplay = enc.currentmove - // TODO remove additionalresources.showflightmove - // if (!!data.additionalresources?.showflightmove) - enc.currentmovedisplay = this._isEnhancedMove() ? enc.currentmove + '/' + enc.currentsprint : enc.currentmove - if (enc.current) { - // Save the global move/dodge - data.currentmove = enc.currentmove - data.currentdodge = enc.currentdodge - data.currentsprint = enc.currentsprint - } - } - } - - if (!data.equippedparry) data.equippedparry = this.getEquippedParry() - if (!data.equippedblock) data.equippedblock = this.getEquippedBlock() - // catch for older actors that may not have these values set - if (!data.currentmove) data.currentmove = parseInt(data.basicmove.value.toString()) - if (!data.currentdodge && data.dodge.value) data.currentdodge = parseInt(data.dodge.value.toString()) - if (!data.currentflight) data.currentflight = parseFloat(data.basicspeed.value.toString()) * 2 - } - - _isEnhancedMove() { - return !!this._getCurrentMoveMode()?.enhanced - } - - _getSprintMove() { - let current = this._getCurrentMoveMode() - if (!current) return 0 - if (current?.enhanced) return current.enhanced - return Math.floor(current.basic * 1.2) - } - - _getCurrentMoveMode() { - let move = this.system.move - let current = Object.values(move).find(it => it.default) - if (!current && Object.keys(move).length > 0) return move['00000'] - return current - } - - /** - * @param {number} move - * @param {number} threshold - * @returns {number} - */ - _getCurrentMove(move, threshold) { - let inCombat = false - try { - inCombat = !!game.combat?.combatants.filter(c => c.actorId == this.id) - } catch (err) { } // During game startup, an exception is being thrown trying to access 'game.combat' - let updateMove = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_MANEUVER_UPDATES_MOVE) && inCombat - - let maneuver = this._getMoveAdjustedForManeuver(move, threshold) - let posture = this._getMoveAdjustedForPosture(move, threshold) - - if (threshold == 1.0) - this.system.conditions.move = maneuver.move < posture.move ? maneuver.text : posture.text - return updateMove - ? maneuver.move < posture.move - ? maneuver.move - : posture.move - : Math.max(1, Math.floor(move * threshold)) - } - - _getMoveAdjustedForManeuver(move, threshold) { - let adjustment = null - - if (foundry.utils.getProperty(this, PROPERTY_MOVEOVERRIDE_MANEUVER)) { - let value = foundry.utils.getProperty(this, PROPERTY_MOVEOVERRIDE_MANEUVER) - let mv = GURPS.Maneuvers.get(this.system.conditions.maneuver) - let reason = !!mv ? i18n(mv.label) : '' - - adjustment = this._adjustMove(move, threshold, value, reason) - } - return !!adjustment - ? adjustment - : { - move: Math.max(1, Math.floor(move * threshold)), - text: i18n('GURPS.moveFull'), - } - } - - _adjustMove(move, threshold, value, reason) { - switch (value) { - case MOVE_NONE: - return { move: 0, text: i18n_f('GURPS.moveNone', { reason: reason }) } - - case MOVE_ONE: - return { - move: 1, - text: i18n_f('GURPS.moveConstant', { value: 1, unit: 'yard', reason: reason }, '1 {unit}/second'), - } - - case MOVE_STEP: - return { move: this._getStep(), text: i18n_f('GURPS.moveStep', { reason: reason }) } - - case MOVE_ONETHIRD: - return { - move: Math.max(1, Math.ceil((move / 3) * threshold)), - text: i18n_f('GURPS.moveOneThird', { reason: reason }), - } - - case MOVE_HALF: - return { - move: Math.max(1, Math.ceil((move / 2) * threshold)), - text: i18n_f('GURPS.moveHalf', { reason: reason }), - } - - case MOVE_TWOTHIRDS: - return { - move: Math.max(1, Math.ceil(((2 * move) / 3) * threshold)), - text: i18n_f('GURPS.moveTwoThirds', { reason: reason }), - } - } - - return null - } - - _getMoveAdjustedForPosture(move, threshold) { - let adjustment = null - - if (foundry.utils.getProperty(this, PROPERTY_MOVEOVERRIDE_POSTURE)) { - let value = foundry.utils.getProperty(this, PROPERTY_MOVEOVERRIDE_POSTURE) - let reason = i18n(GURPS.StatusEffect.lookup(this.system.conditions.posture).label) - adjustment = this._adjustMove(move, threshold, value, reason) - } - - return !!adjustment - ? adjustment - : { - move: Math.max(1, Math.floor(move * threshold)), - text: i18n('GURPS.moveFull'), - } - } - - _calculateRangedRanges() { - if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_CONVERT_RANGED)) return - let st = +this.system.attributes.ST.value - recurselist(this.system.ranged, r => { - let rng = r.range || '' // Data protection - rng = rng + '' // force to string - let m = rng.match(/^ *[xX]([\d\.]+) *$/) - if (m) { - rng = parseFloat(m[1]) * st - } else { - m = rng.match(/^ *[xX]([\d\.]+) *\/ *[xX]([\d\.]+) *$/) - if (m) { - rng = `${parseFloat(m[1]) * st}/${parseFloat(m[2]) * st}` - } - } - r.range = rng - }) - } - - // Once all of the bonuses are applied, determine the actual level for each feature - _recalcItemFeatures() { - let data = this.system - this._collapseQuantumEq(data.melee, true) - this._collapseQuantumEq(data.ranged) - this._collapseQuantumEq(data.skills) - this._collapseQuantumEq(data.spells) - } - - // convert Item feature OTF formulas into actual skill levels. - /** - * @param {Object} list - */ - _collapseQuantumEq(list, isMelee = false) { - recurselist(list, async e => { - let otf = e.otf - if (!!otf) { - let m = otf.match(/\[(.*)\]/) - if (!!m) otf = m[1] // remove extranious [ ] - if (otf.match(/^ *\d+ *$/)) { - // just a number - e.import = parseInt(otf) - } else { - let action = parselink(otf) - if (!!action.action) { - this.ignoreRender = true - action.action.calcOnly = true - GURPS.performAction(action.action, this).then(ret => { - // @ts-ignore - e.level = ret.target - if (isMelee) { - if (!isNaN(parseInt(e.parry))) { - let p = '' + e.parry - let m = p.match(/([+-]\d+)(.*)/) - // @ts-ignore - if (!m && p.trim() == '0') m = [0, 0] // allow '0' to mean 'no bonus', not skill level = 0 - if (!!m) { - e.parrybonus = parseInt(m[1]) - e.parry = e.parrybonus + 3 + Math.floor(e.level / 2) - } - if (!!m && !!m[2]) e.parry = `${e.parry}${m[2]}` - } - if (!isNaN(parseInt(e.block))) { - let b = '' + e.block - let m = b.match(/([+-]\d+)(.*)/) - // @ts-ignore - if (!m && b.trim() == '0') m = [0, 0] // allow '0' to mean 'no bonus', not skill level = 0 - if (!!m) { - e.blockbonus = parseInt(m[1]) - e.block = e.blockbonus + 3 + Math.floor(e.level / 2) - } - if (!!m && !!m[2]) e.block = `${e.block}${m[2]}` - } - } - }) - } - } - } - }) - } - - _getStep() { - let step = Math.ceil(parseInt(this.system.basicmove.value.toString()) / 10) - return Math.max(1, step) - } - - /** - * For every application associated to this actor, refresh it to reflect any updates. - */ - _renderAllApps() { - Object.values(this.apps).forEach(it => it.render(false)) - } - - /** - * Update this Document using incremental data, saving it to the database. - * @see {@link Document.updateDocuments} - * @param {any} data - Differential update data which modifies the existing values of this document data - * (default: `{}`) - * @param {any} [context] - Additional context which customizes the update workflow (default: `{}`) - * @returns {Promise} The updated Document instance - * @remarks If no document has actually been updated, the returned {@link Promise} resolves to `undefined`. - */ - async update(data, context) { - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ONETHIRD)) { - if (data.hasOwnProperty('data.HP.value')) { - let flag = data['data.HP.value'] < this.system.HP.max / 3 - if (!!this.system.conditions.reeling != flag) { - this.toggleEffectByName('reeling', flag) - - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_CHAT_FOR_REELING_TIRED)) { - // send the chat message - let tag = flag ? 'GURPS.chatTurnOnReeling' : 'GURPS.chatTurnOffReeling' - let msg = i18n_f(tag, { name: this.displayname, pdfref: i18n('GURPS.pdfReeling') }) - this.sendChatMessage(msg) - } - - // update the combat tracker to show/remove condition - ui.combat?.render() - } - } - if (data.hasOwnProperty('data.FP.value')) { - let flag = data['data.FP.value'] < this.system.FP.max / 3 - if (!!this.system.conditions.exhausted != flag) { - this.toggleEffectByName('exhausted', flag) - - // send the chat message - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_CHAT_FOR_REELING_TIRED)) { - let tag = flag ? 'GURPS.chatTurnOnTired' : 'GURPS.chatTurnOffTired' - let msg = i18n_f(tag, { name: this.displayname, pdfref: i18n('GURPS.pdfTired') }) - this.sendChatMessage(msg) - } - - // update the combat tracker to show/remove condition - ui.combat?.render() - } - } - } - - return await super.update(data, context) - } - - sendChatMessage(msg) { - let self = this - - renderTemplate('systems/gurps/templates/chat-processing.html', { lines: [msg] }).then(content => { - let users = self.getOwners() - let ids = /** @type {string[] | undefined} */ (users?.map(it => it.id)) - - let messageData = { - content: content, - whisper: ids || null, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - } - ChatMessage.create(messageData) - }) - } - - async internalUpdate(data, context) { - let ctx = { render: !this.ignoreRender } - if (!!context) ctx = { ...context, ...ctx } - await this.update(data, ctx) - } - - /** - * This method is called when "system.conditions.maneuver" changes on the actor (via the update method) - * @param {string} maneuverText - */ - async replaceManeuver(maneuverText) { - let tokens = this._findTokens() - if (tokens) for (const t of tokens) await t.setManeuver(maneuverText) - } - - async replacePosture(changeData) { - let tokens = this._findTokens() - if (tokens) - for (const t of tokens) { - let id = changeData === 'standing' ? this.system.conditions.posture : changeData - await t.toggleEffect(GURPS.StatusEffect.lookup(id)) - } - } - - /** - * @returns {GurpsToken[]} - */ - _findTokens() { - if (this.isToken && this.token?.layer) { - let token = /** @type {GurpsToken} */ (this.token.object) - return [token] - } - return this.getActiveTokens().map(it => /** @type {GurpsToken} */(it)) - } - - /** - * @param {{ id: unknown; }} effect - */ - isEffectActive(effect) { - for (const it of this.effects) { - let statusId = it.getFlag('core', 'statusId') - if (statusId === effect.id) return true - } - - return false - } - - get _additionalResources() { - return this.system.additionalresources - } - - get displayname() { - let n = this.name - if (!!this.token && this.token.name != n) n = this.token.name + '(' + n + ')' - return n - } - - /** - * - * @param {Object} action - * @param {string} action.orig - the original OTF string - * @param {string} action.costs - "*(per|cost) ${number} ${resource}" -- resource can be '@resource' to prompt user - * @param {string} action.formula - the basic die formula, such as '2d', '1d-2', '3d-1x2', etc. - * @param {string} action.damagetype - one of the recognized damage types (cr, cut, imp, etc) - * @param {string} action.extdamagetype - optional extra damage type, such as 'ex' - * @param {string} action.hitlocation - optional hit location - * @param {boolean} action.accumulate - */ - async accumulateDamageRoll(action) { - // define a new actor property, damageAccumulators, which is an array of object: - // { - // otf: action.orig, - // dieroll: action.formula, - // damagetype: action.damagetype, - // damagemod: action.extdamagetype, - // cost: the value parsed out of action.cost, optional - // resource: the value parsed out of action.cost, optional - // count: -- the accumulator, i.e., how many times this damage is to be invoked. - // } - - // initialize the damageAccumulators property if it doesn't exist: - if (!this.system.conditions.damageAccumulators) - this.system.conditions.damageAccumulators = [] - - let accumulators = this.system.conditions.damageAccumulators - - // first, try to find an existing accumulator, and increment if found - let existing = accumulators.findIndex(it => it.orig === action.orig) - if (existing !== -1) return this.incrementDamageAccumulator(existing) - - // an existing accumulator is not found, create one - action.count = 1 - delete action.accumulate - accumulators.push(action) - await this.update({ 'data.conditions.damageAccumulators': accumulators }) - GURPS.ModifierBucket.render() - //console.log(accumulators) - } - - get damageAccumulators() { - return this.system.conditions.damageAccumulators - } - - async incrementDamageAccumulator(index) { - this.damageAccumulators[index].count++ - await this.update({ 'data.conditions.damageAccumulators': this.damageAccumulators }) - GURPS.ModifierBucket.render() - } - - async decrementDamageAccumulator(index) { - this.damageAccumulators[index].count-- - if (this.damageAccumulators[index].count < 1) this.damageAccumulators.splice(index, 1) - await this.update({ 'data.conditions.damageAccumulators': this.damageAccumulators }) - GURPS.ModifierBucket.render() - } - - async clearDamageAccumulator(index) { - this.damageAccumulators.splice(index, 1) - await this.update({ 'data.conditions.damageAccumulators': this.damageAccumulators }) - GURPS.ModifierBucket.render() - } - - async applyDamageAccumulator(index) { - let accumulator = this.damageAccumulators[index] - let roll = multiplyDice(accumulator.formula, accumulator.count) - if (accumulator.costs) { - let costs = accumulator.costs.match(COSTS_REGEX) - if (!!costs) { - accumulator.costs = `*${costs.groups.verb} ${accumulator.count * costs.groups.cost} ${costs.groups.type}` - } - } - accumulator.formula = roll - this.damageAccumulators.splice(index, 1) - await this.update({ 'data.conditions.damageAccumulators': this.damageAccumulators }) - await GURPS.performAction(accumulator, GURPS.LastActor) - } - - async importCharacter() { - let p = this.system.additionalresources.importpath - if (!!p) { - let m = p.match(/.*[/\\]Data[/\\](.*)/) - if (!!m) { - let f = m[1].replace(/\\/g, '/') - let xhr = new XMLHttpRequest() - xhr.responseType = 'arraybuffer' - xhr.open('GET', f) - - let promise = new Promise(resolve => { - xhr.onload = () => { - if (xhr.status === 200) { - // @ts-ignore - let s = arrayBuffertoBase64(xhr.response) - // @ts-ignore - this.importFromGCSv1(s, m[1], p) - } else this._openImportDialog() - resolve(this) - } - }) - xhr.send(null) - } else this._openImportDialog() - } else this._openImportDialog() - } - - async _openImportDialog() { - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_USE_BROWSER_IMPORTER)) - this._openNonLocallyHostedImportDialog() - else this._openLocallyHostedImportDialog() - } - - async _openNonLocallyHostedImportDialog() { - try { - const file = await SmartImporter.getFileForActor(this) - const res = await this.importFromGCSv1(await file.text(), file.name) - if (res) SmartImporter.setFileForActor(this, file) - } catch (e) { - ui.notifications?.error(e) - throw e - } - } - - async _openLocallyHostedImportDialog() { - setTimeout(async () => { - new Dialog( - { - title: `Import character data for: ${this.name}`, - content: await renderTemplate('systems/gurps/templates/import-gcs-v1-data.html', { - name: '"' + this.name + '"', - }), - buttons: { - import: { - icon: '', - label: 'Import', - callback: html => { - const form = html.find('form')[0] - let files = form.data.files - let file = null - if (!files.length) { - return ui.notifications.error('You did not upload a data file!') - } else { - file = files[0] - GURPS.readTextFromFile(file).then(text => this.importFromGCSv1(text, file.name, file.path)) - } - }, - }, - no: { - icon: '', - label: 'Cancel', - }, - }, - default: 'import', - }, - { - width: 400, - } - ).render(true) - }, 200) - } - - /** - * - * @param {{ [key: string]: any}} json - */ - async importAttributesFromGCSv2(atts, eqp, calc) { - if (!atts) return - let data = this.system - let att = data.attributes - if (!att.QN) { - // upgrade older actors to include Q - att.QN = {} - data.QP = {} - } - - att.ST.import = atts.find(e => e.attr_id === 'st')?.calc?.value || 0 - att.ST.points = atts.find(e => e.attr_id === 'st')?.calc?.points || 0 - att.DX.import = atts.find(e => e.attr_id === 'dx')?.calc?.value || 0 - att.DX.points = atts.find(e => e.attr_id === 'dx')?.calc?.points || 0 - att.IQ.import = atts.find(e => e.attr_id === 'iq')?.calc?.value || 0 - att.IQ.points = atts.find(e => e.attr_id === 'iq')?.calc?.points || 0 - att.HT.import = atts.find(e => e.attr_id === 'ht')?.calc?.value || 0 - att.HT.points = atts.find(e => e.attr_id === 'ht')?.calc?.points || 0 - att.WILL.import = atts.find(e => e.attr_id === 'will')?.calc?.value || 0 - att.WILL.points = atts.find(e => e.attr_id === 'will')?.calc?.points || 0 - att.PER.import = atts.find(e => e.attr_id === 'per')?.calc?.value || 0 - att.PER.points = atts.find(e => e.attr_id === 'per')?.calc?.points || 0 - att.QN.import = atts.find(e => e.attr_id === 'qn')?.calc?.value || 0 - att.QN.points = atts.find(e => e.attr_id === 'qn')?.calc?.points || 0 - - data.HP.max = atts.find(e => e.attr_id === 'hp')?.calc?.value || 0 - data.HP.points = atts.find(e => e.attr_id === 'hp')?.calc?.points || 0 - data.FP.max = atts.find(e => e.attr_id === 'fp')?.calc?.value || 0 - data.FP.points = atts.find(e => e.attr_id === 'fp')?.calc?.points || 0 - data.QP.max = atts.find(e => e.attr_id === 'qp')?.calc?.value || 0 - data.QP.points = atts.find(e => e.attr_id === 'qp')?.calc?.points || 0 - let hp = atts.find(e => e.attr_id === 'hp')?.calc?.current || 0 - let fp = atts.find(e => e.attr_id === 'fp')?.calc?.current || 0 - let qp = atts.find(e => e.attr_id === 'qp')?.calc?.current || 0 - - let saveCurrent = false - - if (!!data.lastImport && (data.HP.value != hp || data.FP.value != fp)) { - let option = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IMPORT_HP_FP) - if (option == 0) { - saveCurrent = true - } - if (option == 2) { - saveCurrent = await new Promise((resolve, reject) => { - let d = new Dialog({ - title: 'Current HP & FP', - content: `Do you want to

    Save the current HP (${data.HP.value}) & FP (${data.FP.value}) values or

    Overwrite it with the import data, HP (${hp}) & FP (${fp})?

     `, - buttons: { - save: { - icon: '', - label: 'Save', - callback: () => resolve(true), - }, - overwrite: { - icon: '', - label: 'Overwrite', - callback: () => resolve(false), - }, - }, - default: 'save', - close: () => resolve(false), // just assume overwrite. Error handling would be too much work right now. - }) - d.render(true) - }) - } - } - if (!saveCurrent) { - data.HP.value = hp - data.FP.value = fp - } - data.QP.value = qp - - let bl_value = parseFloat(calc?.basic_lift.match(/[\d\.]+/g)) - let bl_unit = calc?.basic_lift.replace(bl_value + ' ', '') - - let lm = {} - lm.basiclift = (bl_value * 1).toString() + ' ' + bl_unit - lm.carryonback = (bl_value * 15).toString() + ' ' + bl_unit - lm.onehandedlift = (bl_value * 2).toString() + ' ' + bl_unit - lm.runningshove = (bl_value * 24).toString() + ' ' + bl_unit - lm.shiftslightly = (bl_value * 50).toString() + ' ' + bl_unit - lm.shove = (bl_value * 12).toString() + ' ' + bl_unit - lm.twohandedlift = (bl_value * 8).toString() + ' ' + bl_unit - - let bm = atts.find(e => e.attr_id === 'basic_move')?.calc?.value || 0 - data.basicmove.value = bm.toString() - data.basicmove.points = atts.find(e => e.attr_id === 'basic_move')?.calc?.points || 0 - let bs = atts.find(e => e.attr_id === 'basic_speed')?.calc?.value || 0 - data.basicspeed.value = bs.toString() - data.basicspeed.points = atts.find(e => e.attr_id === 'basic_speed')?.calc?.points || 0 - - data.thrust = calc?.thrust - data.swing = calc?.swing - data.currentmove = data.basicmove.value - data.frightcheck = atts.find(e => e.attr_id === 'fright_check')?.calc?.value || 0 - - data.hearing = atts.find(e => e.attr_id === 'hearing')?.calc?.value || 0 - data.tastesmell = atts.find(e => e.attr_id === 'taste_smell')?.calc?.value || 0 - data.touch = atts.find(e => e.attr_id === 'touch')?.calc?.value || 0 - data.vision = atts.find(e => e.attr_id === 'vision')?.calc?.value || 0 - - let cm = 0 - let cd = 0 - let es = {} - let ew = [1, 2, 3, 6, 10] - let index = 0 - let total_carried = this.calcTotalCarried(eqp) - for (let i = 0; i <= 4; i++) { - let e = new Encumbrance() - e.level = i - e.current = false - e.key = 'enc' + i - let weight_value = bl_value * ew[i] - // e.current = total_carried <= weight_value && (i == 4 || total_carried < bl_value*ew[i+1]); - e.current = - (total_carried < weight_value || i == 4 || bl_value == 0) && (i == 0 || total_carried > bl_value * ew[i - 1]) - e.weight = weight_value.toString() + ' ' + bl_unit - e.move = calc?.move[i].toString() - e.dodge = calc?.dodge[i] - if (e.current) { - cm = e.move - cd = e.dodge - } - GURPS.put(es, e, index++) - } - - return { - 'data.attributes': att, - 'data.HP': data.HP, - 'data.FP': data.FP, - 'data.basiclift': data.basiclift, - 'data.basicmove': data.basicmove, - 'data.basicspeed': data.basicspeed, - 'data.thrust': data.thrust, - 'data.swing': data.swing, - 'data.currentmove': data.currentmove, - 'data.frightcheck': data.frightcheck, - 'data.hearing': data.hearing, - 'data.tastesmell': data.tastesmell, - 'data.touch': data.touch, - 'data.vision': data.vision, - 'data.liftingmoving': lm, - 'data.currentmove': cm, - 'data.currentdodge': cd, - 'data.-=encumbrance': null, - 'data.encumbrance': es, - 'data.QP': data.QP, - } - } - - calcTotalCarried(eqp) { - let t = 0 - if (!eqp) return t - for (let i of eqp) { - let w = 0 - w += parseFloat(i.weight || '0') * (i.type == 'equipment_container' ? 1 : i.quantity || 0) - if (i.children?.length) w += this.calcTotalCarried(i.children) - t += w - } - return t - } - - async importTraitsFromGCSv2(p, cd, md) { - if (!p) return - let ts = {} - ts.race = '' - ts.height = p.height || '' - ts.weight = p.weight || '' - ts.age = p.age || '' - ts.title = p.title || '' - ts.player = p.player_name || '' - ts.createdon = cd || '' - ts.modifiedon = md || '' - ts.religion = p.religion || '' - ts.birthday = p.birthday || '' - ts.hand = p.handedness || '' - ts.techlevel = p.tech_level || '' - ts.gender = p.gender || '' - ts.eyes = p.eyes || '' - ts.hair = p.hair || '' - ts.skin = p.skin || '' - - const r = { - 'data.-=traits': null, - 'data.traits': ts, - } - - if (!!p.portrait && game.settings.get(settings.SYSTEM_NAME, settings.SETTING_OVERWRITE_PORTRAITS)) { - const path = this.getPortraitPath() - let currentDir = '' - for (let i = 0; i < path.split('/').length; i++) { - try { - currentDir += path.split('/')[i] + '/' - await FilePicker.createDirectory('data', currentDir) - } catch (err) { - continue - } - } - const filename = `${this.removeAccents(p.name)}${this.id}_portrait.png`.replaceAll(' ', '_') - const url = `data:image/png;base64,${p.portrait}` - await fetch(url) - .then(res => res.blob()) - .then(blob => { - const file = new File([blob], filename) - FilePicker.upload('data', path, file, {}, { notify: false }) - }) - r.img = (path + '/' + filename).replaceAll(' ', '_').replaceAll('//', '/') - } - return r - } - - getPortraitPath() { - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_PORTRAIT_PATH) == 'global') return 'images/portraits/' - return `worlds/${game.world.id}/images/portraits` - } - - removeAccents(str) { - return str - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') // Remove accents - .replace(/([^\w]+|\s+)/g, '-') // Replace space and other characters by hyphen - .replace(/\-\-+/g, '-') // Replaces multiple hyphens by one hyphen - .replace(/(^-+|-+$)/g, '') - } - - signedNum(x) { - if (x >= 0) return `+${x}` - else return x.toString() - } - - importSizeFromGCSv1(commit, profile, ads, skills, equipment) { - let ts = commit['data.traits'] - let final = profile.SM || 0 - let temp = [].concat(ads, skills, equipment) - let all = [] - for (let i of temp) { - all = all.concat(this.recursiveGet(i)) - } - for (let i of all) { - if (i.features?.length) - for (let f of i.features) { - if (f.type == 'attribute_bonus' && f.attribute == 'sm') - final += f.amount * (!!i.levels ? parseFloat(i.levels) : 1) - } - } - ts.sizemod = this.signedNum(final) - return { - 'data.-=traits': null, - 'data.traits': ts, - } - } - - importAdsFromGCSv3(ads) { - let temp = [] - if (!!ads) - for (let i of ads) { - temp = temp.concat(this.importAd(i, '')) - } - return { - 'data.-=ads': null, - 'data.ads': this.foldList(temp), - } - } - - importAd(i, p) { - let a = new Advantage() - a.name = i.name + (i.levels ? ' ' + i.levels.toString() : '') || 'Advantage' - a.points = i.calc?.points - a.note = i.notes - a.userdesc = i.userdesc - a.notes = '' - - if (i.cr != null) { - a.notes = '[' + game.i18n.localize('GURPS.CR' + i.cr.toString()) + ': ' + a.name + ']' - } - if (i.modifiers?.length) { - for (let j of i.modifiers) - if (!j.disabled) a.notes += `${!!a.notes ? '; ' : ''}${j.name}${!!j.notes ? ' (' + j.notes + ')' : ''}` - } - if (!!a.note) a.notes += (!!a.notes ? '\n' : '') + a.note - if (!!a.userdesc) a.notes += (!!a.notes ? '\n' : '') + a.userdesc - a.pageRef(i.reference) - a.uuid = i.id - a.parentuuid = p - - let old = this._findElementIn('ads', a.uuid) - this._migrateOtfsAndNotes(old, a, i.vtt_notes) - - let ch = [] - if (i.children?.length) { - for (let j of i.children) ch = ch.concat(this.importAd(j, i.id)) - } - return [a].concat(ch) - } - - importSkillsFromGCSv2(sks) { - if (!sks) return - let temp = [] - for (let i of sks) { - temp = temp.concat(this.importSk(i, '')) - } - return { - 'data.-=skills': null, - 'data.skills': this.foldList(temp), - } - } - - importSk(i, p) { - let name = - i.name + (!!i.tech_level ? `/TL${i.tech_level}` : '') + (!!i.specialization ? ` (${i.specialization})` : '') || - 'Skill' - if (i.type == 'technique' && !!i.default) { - let addition = '' - addition = ' (' + i.default.name - if (!!i.default.specialization) { - addition += ' (' + i.default.specialization + ')' - } - name += addition + ')' - } - let s = new Skill(name, '') - s.pageRef(i.reference || '') - s.uuid = i.id - s.parentuuid = p - if (['skill', 'technique'].includes(i.type)) { - s.type = i.type.toUpperCase() - s.import = !!i.calc ? i.calc.level : '' - if (s.level == 0) s.level = '' - s.points = i.points - s.relativelevel = i.calc?.rsl - s.notes = i.notes || '' - } else { - // Usually containers - s.level = '' - } - let old = this._findElementIn('skills', s.uuid) - this._migrateOtfsAndNotes(old, s, i.vtt_notes) - - let ch = [] - if (i.children?.length) { - for (let j of i.children) ch = ch.concat(this.importSk(j, i.id)) - } - return [s].concat(ch) - } - - importSpellsFromGCSv2(sps) { - if (!sps) return - let temp = [] - for (let i of sps) { - temp = temp.concat(this.importSp(i, '')) - } - return { - 'data.-=spells': null, - 'data.spells': this.foldList(temp), - } - } - - importSp(i, p) { - let s = new Spell() - s.name = i.name || 'Spell' - s.uuid = i.id - s.parentuuid = p - s.pageRef(i.reference || '') - if (['spell', 'ritual_magic_spell'].includes(i.type)) { - s.class = i.spell_class || '' - s.college = i.college || '' - s.cost = i.casting_cost || '' - s.maintain = i.maintenance_cost || '' - s.difficulty = i.difficulty.toUpperCase() - s.relativelevel = i.calc?.rsl - s.notes = i.notes || '' - s.duration = i.duration || '' - s.points = i.points || '' - s.casttime = i.casting_time || '' - s.import = i.calc?.level || 0 - } - - let old = this._findElementIn('spells', s.uuid) - this._migrateOtfsAndNotes(old, s, i.vtt_notes) - - let ch = [] - if (i.children?.length) { - for (let j of i.children) ch = ch.concat(this.importSp(j, i.id)) - } - return [s].concat(ch) - } - - importEquipmentFromGCSv2(eq, oeq) { - if (!eq && !oeq) return - let temp = [] - if (!!eq) - for (let i of eq) { - temp = temp.concat(this.importEq(i, '', true)) - } - if (!!oeq) - for (let i of oeq) { - temp = temp.concat(this.importEq(i, '', false)) - } - - recurselist(this.system.equipment?.carried, t => { - t.carried = true - if (!!t.save) temp.push(t) - }) - recurselist(this.system.equipment?.other, t => { - t.carried = false - if (!!t.save) temp.push(t) - }) - - temp.forEach(e => { - e.contains = {} - e.collapsed = {} - }) - - temp.forEach(e => { - if (!!e.parentuuid) { - let parent = null - parent = temp.find(f => f.uuid === e.parentuuid) - if (!!parent) GURPS.put(parent.contains, e) - else e.parentuuid = '' - } - }) - - let equipment = { - carried: {}, - other: {}, - } - let cindex = 0 - let oindex = 0 - - temp.forEach(eqt => { - Equipment.calc(eqt) - if (!eqt.parentuuid) { - if (eqt.carried) GURPS.put(equipment.carried, eqt, cindex++) - else GURPS.put(equipment.other, eqt, oindex++) - } - }) - return { - 'data.-=equipment': null, - 'data.equipment': equipment, - } - } - - importEq(i, p, carried) { - let e = new Equipment() - e.name = i.description || 'Equipment' - e.count = i.type == 'equipment_container' ? '1' : i.quantity || '0' - e.cost = - (parseFloat(i.calc?.extended_value) / (i.type == 'equipment_container' ? 1 : i.quantity || 1)).toString() || '' - e.carried = carried - e.equipped = i.equipped - e.techlevel = i.tech_level || '' - e.legalityclass = i.legality_class || '4' - e.categories = i.categories?.join(', ') || '' - e.uses = i.uses || 0 - e.maxuses = i.max_uses || 0 - e.uuid = i.id - e.parentuuid = p - e.notes = '' - e.note = i.notes || '' - if (i.modifiers?.length) { - for (let j of i.modifiers) - if (!j.disabled) e.notes += `${!!e.notes ? '; ' : ''}${j.name}${!!j.notes ? ' (' + j.notes + ')' : ''}` - } - if (!!e.note) e.notes += (!!e.notes ? '\n' : '') + e.note - e.weight = - (parseFloat(i.calc?.extended_weight) / (i.type == 'equipment_container' ? 1 : i.quantity || 1)).toString() || '0' - e.pageRef(i.reference || '') - let old = this._findElementIn('equipment.carried', e.uuid) - if (!old) old = this._findElementIn('equipment.other', e.uuid) - this._migrateOtfsAndNotes(old, e, i.vtt_notes) - if (!!old) { - e.carried = old.carried - e.equipped = old.equipped - e.parentuuid = old.parentuuid - if (old.ignoreImportQty) { - e.count = old.count - e.uses = old.uses - e.maxuses = old.maxuses - e.ignoreImportQty = true - } - } - let ch = [] - if (i.children?.length) { - for (let j of i.children) ch = ch.concat(this.importEq(j, i.id, carried)) - for (let j of ch) { - e.cost -= j.cost * j.count - e.weight -= j.weight * j.count - } - // let weight_reduction = 0; - // if (!!i.modifiers?.length) for (let m of i.modifiers) if (!m.disabled && !!m.features?.length) for (let mf of m.features) if (mf.type == "contained_weight_reduction") weight_reduction += parseFloat(mf.reduction); - // if (!!i.features?.length) for (let f of i.features) if (f.type == "contained_weight_reduction") weight_reduction += parseFloat(f.reduction); - // for (let j of ch) { - // e.cost -= j.cost*j.count; - // if (weight_reduction == 0) e.weight -= j.weight*j.count; - // else { - // weight_reduction -= j.weight*j.count; - // if (weight_reduction < 0) { - // e.weight += weight_reduction; - // weight_reduction = 0; - // } - // } - // } - } - return [e].concat(ch) - } - - importNotesFromGCSv2(notes) { - if (!notes) return - let temp = [] - for (let i of notes) { - temp = temp.concat(this.importNote(i, '')) - } - recurselist(this.system.notes, t => { - if (!!t.save) temp.push(t) - }) - return { - 'data.-=notes': null, - 'data.notes': this.foldList(temp), - } - } - - importNote(i, p) { - let n = new Note() - n.notes = i.text || '' - n.uuid = i.id - n.parentuuid = p - n.pageRef(i.reference || '') - let old = this._findElementIn('notes', n.uuid) - this._migrateOtfsAndNotes(old, n) - let ch = [] - if (i.children?.length) { - for (let j of i.children) ch = ch.concat(this.importNote(j, i.id)) - } - return [n].concat(ch) - } - - async importProtectionFromGCSv2(hls) { - if (!hls) return - let data = this.system - if (!!data.additionalresources.ignoreinputbodyplan) return - - /** @type {HitLocations.HitLocation[]} */ - let locations = [] - for (let i of hls.locations) { - let l = new HitLocations.HitLocation(i.table_name) - l.import = i.calc?.dr.all?.toString() || '0' - for (let [key, value] of Object.entries(i.calc?.dr)) - if (key != 'all') { - let damtype = GURPS.DamageTables.damageTypeMap[key] - if (!l.split) l.split = {} - l.split[damtype] = +l.import + value - } - l.penalty = i.hit_penalty.toString() - while (locations.filter(it => it.where == l.where).length > 0) { - l.where = l.where + '*' - } - locations.push(l) - } - let vitals = locations.filter(value => value.where === HitLocations.HitLocation.VITALS) - if (vitals.length === 0) { - let hl = new HitLocations.HitLocation(HitLocations.HitLocation.VITALS) - hl.penalty = HitLocations.hitlocationRolls[HitLocations.HitLocation.VITALS].penalty - hl.roll = HitLocations.hitlocationRolls[HitLocations.HitLocation.VITALS].roll - hl.import = '0' - locations.push(hl) - } - // Hit Locations MUST come from an existing bodyplan hit location table, or else ADD (and - // potentially other features) will not work. Sometime in the future, we will look at - // user-entered hit locations. - let bodyplan = hls.id // Was a body plan actually in the import? - if (bodyplan === 'snakemen') bodyplan = 'snakeman' - let table = HitLocations.hitlocationDictionary[bodyplan] // If so, try to use it. - - /** @type {HitLocations.HitLocation[]} */ - let locs = [] - locations.forEach(e => { - if (!!table && !!table[e.where]) { - // if e.where already exists in table, don't map - locs.push(e) - } else { - // map to new name(s) ... sometimes we map 'Legs' to ['Right Leg', 'Left Leg'], for example. - e.locations(false).forEach(l => locs.push(l)) // Map to new names - } - }) - locations = locs - - if (!table) { - locs = [] - locations.forEach(e => { - e.locations(true).forEach(l => locs.push(l)) // Map to new names, but include original to help match against tables - }) - bodyplan = this._getBodyPlan(locs) - table = HitLocations.hitlocationDictionary[bodyplan] - } - // update location's roll and penalty based on the bodyplan - - if (!!table) { - Object.values(locations).forEach(it => { - let [lbl, entry] = HitLocations.HitLocation.findTableEntry(table, it.where) - if (!!entry) { - it.where = lbl // It might be renamed (ex: Skull -> Brain) - if (!it.penalty) it.penalty = entry.penalty - if (!it.roll || it.roll.length === 0 || it.roll === HitLocations.HitLocation.DEFAULT) it.roll = entry.roll - } - }) - } - - // write the hit locations out in bodyplan hit location table order. If there are - // other entries, append them at the end. - /** @type {HitLocations.HitLocation[]} */ - let temp = [] - Object.keys(table).forEach(key => { - let results = Object.values(locations).filter(loc => loc.where === key) - if (results.length > 0) { - if (results.length > 1) { - // If multiple locs have same where, concat the DRs. Leg 7 & Leg 8 both map to "Leg 7-8" - let d = '' - - /** @type {string | null} */ - let last = null - results.forEach(r => { - if (r.import != last) { - d += '|' + r.import - last = r.import - } - }) - - if (!!d) d = d.substr(1) - results[0].import = d - } - temp.push(results[0]) - locations = locations.filter(it => it.where !== key) - } else { - // Didn't find loc that should be in the table. Make a default entry - temp.push(new HitLocations.HitLocation(key, '0', table[key].penalty, table[key].roll)) - } - }) - locations.forEach(it => temp.push(it)) - - let prot = {} - let index = 0 - temp.forEach(it => GURPS.put(prot, it, index++)) - - let saveprot = true - if (!!data.lastImport && !!data.additionalresources.bodyplan && bodyplan != data.additionalresources.bodyplan) { - let option = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IMPORT_BODYPLAN) - if (option == 1) { - saveprot = false - } - if (option == 2) { - saveprot = await new Promise((resolve, reject) => { - let d = new Dialog({ - title: 'Hit Location Body Plan', - content: - `Do you want to

    Save the current Body Plan (${game.i18n.localize( - 'GURPS.BODYPLAN' + data.additionalresources.bodyplan - )}) or ` + - `

    Overwrite it with the Body Plan from the import: (${game.i18n.localize( - 'GURPS.BODYPLAN' + bodyplan - )})?

     `, - buttons: { - save: { - icon: '', - label: 'Save', - callback: () => resolve(false), - }, - overwrite: { - icon: '', - label: 'Overwrite', - callback: () => resolve(true), - }, - }, - default: 'save', - close: () => resolve(false), // just assume overwrite. Error handling would be too much work right now. - }) - d.render(true) - }) - } - } - if (saveprot) { - return { - 'data.-=hitlocations': null, - 'data.hitlocations': prot, - 'data.additionalresources.bodyplan': bodyplan, - } - } else return {} - } - - importPointTotalsFromGCSv2(total, atts, ads, skills, spells) { - if (!ads) ads = [] - if (!skills) skills = [] - if (!spells) spells = [] - let p_atts = 0 - let p_ads = 0 - let p_disads = 0 - let p_quirks = 0 - let p_skills = 0 - let p_spells = 0 - let p_unspent = total - let p_total = total - let p_race = 0 - for (let i of atts) p_atts += i.calc?.points - for (let i of ads) - [p_ads, p_disads, p_quirks, p_race] = this.adPointCount(i, p_ads, p_disads, p_quirks, p_race, true) - for (let i of skills) p_skills = this.skPointCount(i, p_skills) - for (let i of spells) p_spells = this.skPointCount(i, p_spells) - p_unspent -= p_atts + p_ads + p_disads + p_quirks + p_skills + p_spells + p_race - return { - 'data.totalpoints.attributes': p_atts, - 'data.totalpoints.ads': p_ads, - 'data.totalpoints.disads': p_disads, - 'data.totalpoints.quirks': p_quirks, - 'data.totalpoints.skills': p_skills, - 'data.totalpoints.spells': p_spells, - 'data.totalpoints.unspent': p_unspent, - 'data.totalpoints.total': p_total, - 'data.totalpoints.race': p_race, - } - } - - importReactionsFromGCSv3(ads, skills, equipment) { - let rs = {} - let cs = {} - let index_r = 0 - let index_c = 0 - let temp = [].concat(ads, skills, equipment) - let all = [] - for (let i of temp) { - all = all.concat(this.recursiveGet(i)) - } - let temp_r = [] - let temp_c = [] - for (let i of all) { - if (i.features?.length) - for (let f of i.features) { - if (f.type == 'reaction_bonus') { - temp_r.push({ - modifier: f.amount * (f.per_level && !!i.levels ? parseInt(i.levels) : 1), - situation: f.situation, - }) - } else if (f.type == 'conditional_modifier') { - temp_c.push({ - modifier: f.amount * (f.per_level && !!i.levels ? parseInt(i.levels) : 1), - situation: f.situation, - }) - } - } - } - let temp_r2 = [] - let temp_c2 = [] - for (let i of temp_r) { - let existing_condition = temp_r2.find(e => e.situation == i.situation) - if (!!existing_condition) existing_condition.modifier += i.modifier - else temp_r2.push(i) - } - for (let i of temp_c) { - let existing_condition = temp_c2.find(e => e.situation == i.situation) - if (!!existing_condition) existing_condition.modifier += i.modifier - else temp_c2.push(i) - } - for (let i of temp_r2) { - let r = new Reaction() - r.modifier = i.modifier.toString() - r.situation = i.situation - GURPS.put(rs, r, index_r++) - } - for (let i of temp_c2) { - let c = new Modifier() - c.modifier = i.modifier.toString() - c.situation = i.situation - GURPS.put(cs, c, index_c++) - } - return { - 'data.-=reactions': null, - 'data.reactions': rs, - 'data.-=conditionalmods': null, - 'data.conditionalmods': cs, - } - } - - importCombatFromGCSv2(ads, skills, spells, equipment) { - let melee = {} - let ranged = {} - let m_index = 0 - let r_index = 0 - let temp = [].concat(ads, skills, spells, equipment) - let all = [] - for (let i of temp) { - all = all.concat(this.recursiveGet(i)) - } - for (let i of all) { - if (i.weapons?.length) - for (let w of i.weapons) { - if (w.type == 'melee_weapon') { - let m = new Melee() - m.name = i.name || i.description || '' - m.st = w.strength || '' - m.weight = i.weight || '' - m.techlevel = i.tech_level || '' - m.cost = i.value || '' - m.notes = i.notes || '' - if (!!m.notes && w.usage_notes) m.notes += '\n' + w.usage_notes - m.pageRef(i.reference || '') - m.mode = w.usage || '' - m.import = w.calc?.level.toString() || '0' - m.damage = w.calc?.damage || '' - m.reach = w.reach || '' - m.parry = w.calc?.parry || '' - m.block = w.calc?.block || '' - let old = this._findElementIn('melee', false, m.name, m.mode) - this._migrateOtfsAndNotes(old, m, i.vtt_notes) - - GURPS.put(melee, m, m_index++) - } else if (w.type == 'ranged_weapon') { - let r = new Ranged() - r.name = i.name || i.description || '' - r.st = w.strength || '' - r.bulk = w.bulk || '' - r.legalityclass = i.legality_class || '4' - r.ammo = 0 - r.notes = i.notes || '' - if (!!r.notes && w.usage_notes) r.notes += '\n' + w.usage_notes - r.pageRef(i.reference || '') - r.mode = w.usage || '' - r.import = w.calc?.level || '0' - r.damage = w.calc?.damage || '' - r.acc = w.accuracy || '' - r.rof = w.rate_of_fire || '' - r.shots = w.shots || '' - r.rcl = w.recoil || '' - r.range = w.calc?.range || '' - let old = this._findElementIn('ranged', false, r.name, r.mode) - this._migrateOtfsAndNotes(old, r, i.vtt_notes) - - GURPS.put(ranged, r, r_index++) - } - } - } - return { - 'data.-=melee': null, - 'data.melee': melee, - 'data.-=ranged': null, - 'data.ranged': ranged, - } - } - - recursiveGet(i) { - if (!i) return [] - let ch = [] - if (i.children?.length) for (let j of i.children) ch = ch.concat(this.recursiveGet(j)) - if (i.modifiers?.length) for (let j of i.modifiers) ch = ch.concat(this.recursiveGet(j)) - if (!!i.disabled || (i.equipped != null && i.equipped == false)) return [] - return [i].concat(ch) - } - - adPointCount(i, ads, disads, quirks, race, toplevel = false) { - if (i.type == 'advantage_container' && i.container_type == 'race') race += i.calc?.points - else if (i.type == 'advantage_container' && i.container_type == 'alternative_abilities') ads += i.calc?.points - else if (i.type == 'advantage_container' && !!i.children?.length) { - var [a, d] = [0, 0] - for (let j of i.children) [a, d, quirks, race] = this.adPointCount(j, a, d, quirks, race) - if (toplevel) { - if (a > 0) ads += a - else disads += a - } else ads += a + d - } else if (i.calc?.points == -1) quirks += i.calc?.points - else if (i.calc?.points > 0) ads += i.calc?.points - else disads += i.calc?.points - return [ads, disads, quirks, race] - } - - skPointCount(i, skills) { - if (i.type == ('skill_container' || 'spell_container') && i.children?.length) - for (let j of i.children) skills = this.skPointCount(j, skills) - else skills += i.points - return skills - } - - /** - * @param {string} json - * @param {string} importname - * @param {string | undefined} [importpath] - */ - async importFromGCSv2(json, importname, importpath, suppressMessage = false, GCAVersion, GCSVersion) { - let r - let msg = [] - let version = 'Direct GCS Import' - let exit = false - try { - r = JSON.parse(json) - } catch (err) { - msg.push(i18n('GURPS.importNoJSONDetected')) - exit = true - } - if (!!r) { - if (!r.calc) { - msg.push(i18n('GURPS.importOldGCSFile')) - exit = true - } - } - - if (msg.length > 0) { - ui.notifications?.error(msg.join('
    ')) - let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { - lines: msg, - version: version, - GCAVersion: GCAVersion, - GCSVersion: GCSVersion, - url: GURPS.USER_GUIDE_URL, - }) - ChatMessage.create({ - content: content, - user: game.user.id, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - whisper: [game.user.id], - }) - if (exit) return false - } - - let nm = r['profile']['name'] - console.log("Importing '" + nm + "'") - let starttime = performance.now() - let commit = {} - - commit = { ...commit, ...{ 'data.lastImport': new Date().toString().split(' ').splice(1, 4).join(' ') } } - let ar = this.system.additionalresources || {} - ar.importname = importname || ar.importname - ar.importpath = importpath || ar.importpath - try { - commit = { ...commit, ...{ 'data.additionalresources': ar } } - commit = { ...commit, ...(await this.importAttributesFromGCSv2(r.attributes, r.equipment, r.calc)) } - commit = { ...commit, ...(await this.importTraitsFromGCSv2(r.profile, r.created_date, r.modified_date)) } - commit = { ...commit, ...this.importSizeFromGCSv1(commit, r.profile, r.advantages, r.skills, r.equipment) } - commit = { ...commit, ...this.importAdsFromGCSv3(r.advantages) } - commit = { ...commit, ...this.importSkillsFromGCSv2(r.skills) } - commit = { ...commit, ...this.importSpellsFromGCSv2(r.spells) } - commit = { ...commit, ...this.importEquipmentFromGCSv2(r.equipment, r.other_equipment) } - commit = { ...commit, ...this.importNotesFromGCSv2(r.notes) } - - commit = { ...commit, ...(await this.importProtectionFromGCSv2(r.settings.hit_locations)) } - commit = { - ...commit, - ...this.importPointTotalsFromGCSv2(r.total_points, r.attributes, r.advantages, r.skills, r.spells), - } - commit = { ...commit, ...this.importReactionsFromGCSv3(r.advantages, r.skills, r.equipment) } - commit = { ...commit, ...this.importCombatFromGCSv2(r.advantages, r.skills, r.spells, r.equipment) } - } catch (err) { - console.log(err.stack) - msg.push( - i18n_f('GURPS.importGenericError', { - name: nm, - error: err.name, - message: err.message, - }) - ) - let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { - lines: [msg], - version: version, - GCAVersion: GCAVersion, - GCSVersion: GCSVersion, - url: GURPS.USER_GUIDE_URL, - }) - ui.notifications?.warn(msg) - let chatData = { - user: game.user.id, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - content: content, - whisper: [game.user.id], - } - ChatMessage.create(chatData, {}) - // Don't return - } - - console.log('Starting commit') - let deletes = Object.fromEntries(Object.entries(commit).filter(([key, value]) => key.includes('.-='))) - let adds = Object.fromEntries(Object.entries(commit).filter(([key, value]) => !key.includes('.-='))) - - try { - this.ignoreRender = true - await this.internalUpdate(deletes, { diff: false }) - await this.internalUpdate(adds, { diff: false }) - // This has to be done after everything is loaded - await this.postImport() - this._forceRender() - - // Must update name outside of protection so that Actors list (and other external views) update correctly - if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IGNORE_IMPORT_NAME)) { - await this.update({ name: nm, 'token.name': nm }) - } - - if (!suppressMessage) ui.notifications?.info(i18n_f('GURPS.importSuccessful', { name: nm })) - console.log( - 'Done importing (' + - Math.round(performance.now() - starttime) + - 'ms.) You can inspect the character data below:' - ) - console.log(this) - return true - } catch (err) { - console.log(err.stack) - let msg = [i18n_f('GURPS.importGenericError', { name: nm, error: err.name, message: err.message })] - if (err.message == 'Maximum depth exceeded') msg.push(i18n('GURPS.importTooManyContainers')) - ui.notifications?.warn(msg.join('
    ')) - let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { - lines: msg, - version: 'GCS Direct', - GCAVersion: GCAVersion, - GCSVersion: GCSVersion, - url: GURPS.USER_GUIDE_URL, - }) - - let user = game.user - let chatData = { - user: game.user.id, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - content: content, - whisper: [game.user.id], - } - ChatMessage.create(chatData, {}) - return false - } - } - - // First attempt at import GCS FG XML export data. - /** - * @param {string} xml - * @param {string} importname - * @param {string | undefined} [importpath] - */ - async importFromGCSv1(xml, importname, importpath, suppressMessage = false) { - const GCA5Version = 'GCA5-14' - const GCAVersion = 'GCA-11' - const GCSVersion = 'GCS-5' - if (importname.endsWith('.gcs')) - return this.importFromGCSv2(xml, importname, importpath, suppressMessage, GCAVersion, GCSVersion) - var c, ra // The character json, release attributes - let isFoundryGCS = false - let isFoundryGCA = false - let isFoundryGCA5 = false - // need to remove

    and replace

    with newlines from "formatted text" - let origx = GURPS.cleanUpP(xml) - let x = xmlTextToJson(origx) - // @ts-ignore - let r = x.root - let msg = [] - let version = 'unknown' - let vernum = 1 - let exit = false - if (!r) { - if (importname.endsWith('.gca5')) msg.push(i18n('GURPS.importCannotImportGCADirectly')) - if (importname.endsWith('.gca4')) msg.push(i18n('GURPS.importCannotImportGCADirectly')) - else if (!xml.startsWith(' 0) { - ui.notifications?.error(msg.join('
    ')) - let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { - lines: msg, - version: version, - GCAVersion: GCAVersion, - GCSVersion: GCSVersion, - url: GURPS.USER_GUIDE_URL, - }) - - let user = game.user - ChatMessage.create({ - content: content, - user: game.user.id, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - whisper: [game.user.id], - }) - if (exit) return false // Some errors cannot be forgiven ;-) - } - let nm = this.textFrom(c.name) - console.log("Importing '" + nm + "'") - let starttime = performance.now() - let commit = {} - - commit = { ...commit, ...{ 'data.lastImport': new Date().toString().split(' ').splice(1, 4).join(' ') } } - let ar = this.system.additionalresources || {} - ar.importname = importname || ar.importname - ar.importpath = importpath || ar.importpath - ar.importversion = ra.version - commit = { ...commit, ...{ 'data.additionalresources': ar } } - - try { - // This is going to get ugly, so break out various data into different methods - commit = { ...commit, ...(await this.importAttributesFromCGSv1(c.attributes)) } - commit = { ...commit, ...this.importSkillsFromGCSv1(c.abilities?.skilllist, isFoundryGCS) } - commit = { ...commit, ...this.importTraitsfromGCSv1(c.traits) } - commit = { ...commit, ...this.importCombatMeleeFromGCSv1(c.combat?.meleecombatlist, isFoundryGCS) } - commit = { ...commit, ...this.importCombatRangedFromGCSv1(c.combat?.rangedcombatlist, isFoundryGCS) } - commit = { ...commit, ...this.importSpellsFromGCSv1(c.abilities?.spelllist, isFoundryGCS) } - if (isFoundryGCS) { - commit = { ...commit, ...this.importAdsFromGCSv2(c.traits?.adslist) } - commit = { ...commit, ...this.importReactionsFromGCSv2(c.reactions) } - commit = { ...commit, ...this.importConditionalModifiersFromGCSv2(c.conditionalmods) } - } - if (isFoundryGCA) { - commit = { ...commit, ...this.importLangFromGCA(c.traits?.languagelist) } - commit = { ...commit, ...this.importAdsFromGCA(c.traits?.adslist, c.traits?.disadslist) } - commit = { ...commit, ...this.importReactionsFromGCA(c.traits?.reactionmodifiers, vernum) } - } - commit = { ...commit, ...this.importEncumbranceFromGCSv1(c.encumbrance) } - commit = { ...commit, ...this.importPointTotalsFromGCSv1(c.pointtotals) } - commit = { ...commit, ...this.importNotesFromGCSv1(c.description, c.notelist) } - commit = { ...commit, ...this.importEquipmentFromGCSv1(c.inventorylist, isFoundryGCS) } - commit = { ...commit, ...(await this.importProtectionFromGCSv1(c.combat?.protectionlist, isFoundryGCA)) } - } catch (err) { - console.log(err.stack) - let msg = i18n_f('GURPS.importGenericError', { name: nm, error: err.name, message: err.message }) - let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { - lines: [msg], - version: version, - GCAVersion: GCAVersion, - GCSVersion: GCSVersion, - url: GURPS.USER_GUIDE_URL, - }) - - ui.notifications?.warn(msg) - let user = game.user - let chatData = { - user: game.user.id, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - content: content, - whisper: [game.user.id], - } - ChatMessage.create(chatData, {}) - // Don't return, because we want to see how much actually gets committed. - } - console.log('Starting commit') - - let deletes = Object.fromEntries(Object.entries(commit).filter(([key, value]) => key.includes('.-='))) - let adds = Object.fromEntries(Object.entries(commit).filter(([key, value]) => !key.includes('.-='))) - - try { - this.ignoreRender = true - await this.internalUpdate(deletes, { diff: false }) - await this.internalUpdate(adds, { diff: false }) - // This has to be done after everything is loaded - await this.postImport() - this._forceRender() - - // Must update name outside of protection so that Actors list (and other external views) update correctly - if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IGNORE_IMPORT_NAME)) { - await this.update({ name: nm, 'token.name': nm }) - } - - if (!suppressMessage) ui.notifications?.info(i18n_f('GURPS.importSuccessful', { name: nm })) - console.log( - 'Done importing (' + - Math.round(performance.now() - starttime) + - 'ms.) You can inspect the character data below:' - ) - console.log(this) - return true - } catch (err) { - console.log(err.stack) - let msg = [i18n_f('GURPS.importGenericError', { name: nm, error: err.name, message: err.message })] - if (err.message == 'Maximum depth exceeded') msg.push(i18n('GURPS.importTooManyContainers')) - if (!supressMessage) ui.notifications?.warn(msg.join('
    ')) - let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { - lines: msg, - version: version, - GCAVersion: GCAVersion, - GCSVersion: GCSVersion, - url: GURPS.USER_GUIDE_URL, - }) - - let user = game.user - let chatData = { - user: game.user.id, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - content: content, - whisper: [game.user.id], - } - ChatMessage.create(chatData, {}) - return false - } - } - - // hack to get to private text element created by xml->json method. - /** - * @param {{ [key: string]: any }} o - */ - textFrom(o) { - if (!o) return '' - let t = o['#text'] - if (!t) return '' - return t.trim() - } - - // similar hack to get text as integer. - /** - * @param {{ [key: string]: any }} o - */ - intFrom(o) { - if (!o) return 0 - let i = o['#text'] - if (!i) return 0 - return parseInt(i) - } - - /** - * @param {{[key: string] : any}} o - */ - floatFrom(o) { - if (!o) return 0 - let f = o['#text'].trim() - if (!f) return 0 - return f.includes(',') ? parseDecimalNumber(f, { thousands: '.', decimal: ',' }) : parseDecimalNumber(f) - } - - /** - * @param {string} list - * @param {string|boolean} uuid - */ - _findElementIn(list, uuid, name = '', mode = '') { - var foundkey - let l = getProperty(this, list) - recurselist(l, (e, k, d) => { - if ((uuid && e.uuid == uuid) || (!!e.name && e.name.startsWith(name) && e.mode == mode)) foundkey = k - }) - return foundkey == null ? foundkey : getProperty(this, list + '.' + foundkey) - } - - /** - * @param {{ [key: string]: any }} json - */ - importReactionsFromGCSv2(json) { - if (!json) return - let t = this.textFrom - let rs = {} - let index = 0 - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let r = new Reaction() - r.modifier = t(j.modifier) - r.situation = t(j.situation) - GURPS.put(rs, r, index++) - } - } - return { - 'data.-=reactions': null, - 'data.reactions': rs, - } - } - - importConditionalModifiersFromGCSv2(json) { - if (!json) return - let t = this.textFrom - let rs = {} - let index = 0 - for (let key in json) { - if (key.startsWith('id-')) { - let j = json[key] - let r = new Reaction() // ConditionalModifiers are the same format - r.modifier = t(j.modifier) - r.situation = t(j.situation) - GURPS.put(rs, r, index++) - } - } - return { - 'data.-=conditionalmods': null, - 'data.conditionalmods': rs, - } - } - - /** - * @param {{ [key: string]: any }} json - */ - importReactionsFromGCA(json, vernum) { - if (!json) return - let text = this.textFrom(json) - let a = vernum <= 9 ? text.split(',') : text.split('|') - let rs = {} - let index = 0 - a.forEach((/** @type {string} */ m) => { - if (!!m) { - let t = m.trim() - let i = t.indexOf(' ') - let mod = t.substring(0, i) - let sit = t.substr(i + 1) - let r = new Reaction(mod, sit) - GURPS.put(rs, r, index++) - } - }) - return { - 'data.-=reactions': null, - 'data.reactions': rs, - } - } - - importLangFromGCA(json) { - if (!json) return - let langs = {} - let index = 0 - let t = this.textFrom - for (let key in json) { - if (key.startsWith('id-')) { - let j = json[key] - let n = t(j.name) - let s = t(j.spoken) - let w = t(j.written) - let p = t(j.points) - let l = new Language(n, s, w, p) - GURPS.put(langs, l, index++) - } - } - return { - 'data.-=languages': null, - 'data.languages': langs, - } - } - - /** - * @param {{ attributes: Record; ads: Record; disads: Record; quirks: Record; skills: Record; spells: Record; unspentpoints: Record; totalpoints: Record; race: Record; }} json - */ - importPointTotalsFromGCSv1(json) { - if (!json) return - - let i = this.intFrom - return { - 'data.totalpoints.attributes': i(json.attributes), - 'data.totalpoints.ads': i(json.ads), - 'data.totalpoints.disads': i(json.disads), - 'data.totalpoints.quirks': i(json.quirks), - 'data.totalpoints.skills': i(json.skills), - 'data.totalpoints.spells': i(json.spells), - 'data.totalpoints.unspent': i(json.unspentpoints), - 'data.totalpoints.total': i(json.totalpoints), - 'data.totalpoints.race': i(json.race), - } - } - - /** - * @param {{ [key: string]: any }} descjson - * @param {{ [key: string]: any }} json - */ - importNotesFromGCSv1(descjson, json) { - if (!json) return - let t = this.textFrom - let temp = [] - if (!!descjson) { - // support for GCA description - - let n = new Note() - n.notes = t(descjson).replace(/\\r/g, '\n') - n.imported = true - temp.push(n) - } - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let n = /** @type {Note & { imported: boolean, uuid: string, parentuuid: string }} */ (new Note()) - //n.setNotes(t(j.text)); - n.notes = t(j.name) - let txt = t(j.text) - if (!!txt) n.notes = n.notes + '\n' + txt.replace(/\\r/g, '\n') - n.uuid = t(j.uuid) - n.parentuuid = t(j.parentuuid) - n.pageref = t(j.pageref) - let old = this._findElementIn('notes', n.uuid) - this._migrateOtfsAndNotes(old, n) - temp.push(n) - } - } - // Save the old User Entered Notes. - recurselist(this.system.notes, t => { - if (!!t.save) temp.push(t) - }) - return { - 'data.-=notes': null, - 'data.notes': this.foldList(temp), - } - } - - /** - * @param {{ [x: string]: any; bodyplan: Record; }} json - * @param {boolean} isFoundryGCA - */ - async importProtectionFromGCSv1(json, isFoundryGCA) { - if (!json) return - let t = this.textFrom - let data = this.system - if (!!data.additionalresources.ignoreinputbodyplan) return - - /** @type {HitLocations.HitLocation[]} */ - let locations = [] - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let hl = new HitLocations.HitLocation(t(j.location)) - let i = t(j.dr) - if (i.match(/^\d+ *(\+ *\d+ *)?$/)) i = eval(t(j.dr)) // supports "0 + 8" - hl.import = !i ? 0 : i - hl.penalty = t(j.db) - hl.setEquipment(t(j.text)) - - // Some hit location tables have two entries for the same location. The code requires - // each location to be unique. Append an asterisk to the location name in that case. Hexapods and ichthyoid - while (locations.filter(it => it.where == hl.where).length > 0) { - hl.where = hl.where + '*' - } - locations.push(hl) - } - } - - // Do the results contain vitals? If not, add it. - let vitals = locations.filter(value => value.where === HitLocations.HitLocation.VITALS) - if (vitals.length === 0) { - let hl = new HitLocations.HitLocation(HitLocations.HitLocation.VITALS) - hl.penalty = HitLocations.hitlocationRolls[HitLocations.HitLocation.VITALS].penalty - hl.roll = HitLocations.hitlocationRolls[HitLocations.HitLocation.VITALS].roll - hl.import = '0' - locations.push(hl) - } - - // Hit Locations MUST come from an existing bodyplan hit location table, or else ADD (and - // potentially other features) will not work. Sometime in the future, we will look at - // user-entered hit locations. - let bodyplan = t(json.bodyplan)?.toLowerCase() // Was a body plan actually in the import? - if (bodyplan === 'snakemen') bodyplan = 'snakeman' - let table = HitLocations.hitlocationDictionary[bodyplan] // If so, try to use it. - - /** @type {HitLocations.HitLocation[]} */ - let locs = [] - locations.forEach(e => { - if (!!table && !!table[e.where]) { - // if e.where already exists in table, don't map - locs.push(e) - } else { - // map to new name(s) ... sometimes we map 'Legs' to ['Right Leg', 'Left Leg'], for example. - e.locations(false).forEach(l => locs.push(l)) // Map to new names - } - }) - locations = locs - - if (!table) { - locs = [] - locations.forEach(e => { - e.locations(true).forEach(l => locs.push(l)) // Map to new names, but include original to help match against tables - }) - bodyplan = this._getBodyPlan(locs) - table = HitLocations.hitlocationDictionary[bodyplan] - } - // update location's roll and penalty based on the bodyplan - - if (!!table) { - Object.values(locations).forEach(it => { - let [lbl, entry] = HitLocations.HitLocation.findTableEntry(table, it.where) - if (!!entry) { - it.where = lbl // It might be renamed (ex: Skull -> Brain) - if (!it.penalty) it.penalty = entry.penalty - if (!it.roll || it.roll.length === 0 || it.roll === HitLocations.HitLocation.DEFAULT) it.roll = entry.roll - } - }) - } - - // write the hit locations out in bodyplan hit location table order. If there are - // other entries, append them at the end. - /** @type {HitLocations.HitLocation[]} */ - let temp = [] - Object.keys(table).forEach(key => { - let results = Object.values(locations).filter(loc => loc.where === key) - if (results.length > 0) { - if (results.length > 1) { - // If multiple locs have same where, concat the DRs. Leg 7 & Leg 8 both map to "Leg 7-8" - let d = '' - - /** @type {string | null} */ - let last = null - results.forEach(r => { - if (r.import != last) { - d += '|' + r.import - last = r.import - } - }) - - if (!!d) d = d.substr(1) - results[0].import = d - } - temp.push(results[0]) - locations = locations.filter(it => it.where !== key) - } else { - // Didn't find loc that should be in the table. Make a default entry - temp.push(new HitLocations.HitLocation(key, '0', table[key].penalty, table[key].roll)) - } - }) - locations.forEach(it => temp.push(it)) - - let prot = {} - let index = 0 - temp.forEach(it => GURPS.put(prot, it, index++)) - - let saveprot = true - if (!!data.lastImport && !!data.additionalresources.bodyplan && bodyplan != data.additionalresources.bodyplan) { - let option = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IMPORT_BODYPLAN) - if (option == 1) { - saveprot = false - } - if (option == 2) { - saveprot = await new Promise((resolve, reject) => { - let d = new Dialog({ - title: 'Hit Location Body Plan', - content: - `Do you want to

    Save the current Body Plan (${game.i18n.localize( - 'GURPS.BODYPLAN' + data.additionalresources.bodyplan - )}) or ` + - `

    Overwrite it with the Body Plan from the import: (${game.i18n.localize( - 'GURPS.BODYPLAN' + bodyplan - )})?

     `, - buttons: { - save: { - icon: '', - label: 'Save', - callback: () => resolve(false), - }, - overwrite: { - icon: '', - label: 'Overwrite', - callback: () => resolve(true), - }, - }, - default: 'save', - close: () => resolve(false), // just assume overwrite. Error handling would be too much work right now. - }) - d.render(true) - }) - } - } - if (saveprot) - return { - 'data.-=hitlocations': null, - 'data.hitlocations': prot, - 'data.additionalresources.bodyplan': bodyplan, - } - else return {} - } - - /** - * - * @param {Array} locations - */ - _getBodyPlan(locations) { - // each key is a "body plan" name like "humanoid" or "quadruped" - let tableNames = Object.keys(HitLocations.hitlocationDictionary) - - // create a map of tableName:count - /** @type {Record} */ - let tableScores = {} - tableNames.forEach(it => (tableScores[it] = 0)) - - // increment the count for a tableScore if it contains the same hit location as "prot" - locations.forEach(function(hitLocation) { - tableNames.forEach(function(tableName) { - if (HitLocations.hitlocationDictionary[tableName].hasOwnProperty(hitLocation.where)) { - tableScores[tableName] = tableScores[tableName] + 1 - } - }) - }) - - // Select the tableScore with the highest score. - let match = -1 - let name = HitLocations.HitLocation.HUMANOID - Object.keys(tableScores).forEach(function(score) { - if (tableScores[score] > match) { - match = tableScores[score] - name = score - } - }) - - // In the case of a tie, select the one whose score is closest to the number of entries - // in the table. - let results = Object.keys(tableScores).filter(it => tableScores[it] === match) - if (results.length > 1) { - let diff = Number.MAX_SAFE_INTEGER - results.forEach(key => { - // find the smallest difference - let table = HitLocations.hitlocationDictionary[key] - if (Object.keys(table).length - match < diff) { - diff = Object.keys(table).length - match - name = key - } - }) - } - - return name - } - - /** - * @param {{ [key: string]: any }} json - * @param {boolean} isFoundryGCS - */ - importEquipmentFromGCSv1(json, isFoundryGCS) { - if (!json) return - let t = this.textFrom - let i = this.intFrom - let f = this.floatFrom - - /** - * @type {Equipment[]} - */ - let temp = [] - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let eqt = new Equipment() - eqt.name = t(j.name) - eqt.count = t(j.count) - eqt.cost = t(j.cost) - eqt.location = t(j.location) - let cstatus = i(j.carried) - eqt.carried = cstatus >= 1 - eqt.equipped = cstatus == 2 - eqt.techlevel = t(j.tl) - eqt.legalityclass = t(j.lc) - eqt.categories = t(j.type) - eqt.uses = t(j.uses) - eqt.maxuses = t(j.maxuses) - eqt.uuid = t(j.uuid) - eqt.parentuuid = t(j.parentuuid) - if (isFoundryGCS) { - eqt.notes = t(j.notes) - eqt.weight = t(j.weight) - } else { - eqt.setNotes(t(j.notes)) - eqt.weight = t(j.weightsum) // GCA sends calculated weight in 'weightsum' - } - eqt.pageRef(t(j.pageref)) - let old = this._findElementIn('equipment.carried', eqt.uuid) - if (!old) old = this._findElementIn('equipment.other', eqt.uuid) - this._migrateOtfsAndNotes(old, eqt) - if (!!old) { - eqt.carried = old.carried - eqt.equipped = old.equipped - eqt.parentuuid = old.parentuuid - if (old.ignoreImportQty) { - eqt.count = old.count - eqt.uses = old.uses - eqt.maxuses = old.maxuses - eqt.ignoreImportQty = true - } - } - temp.push(eqt) - } - } - - // Save the old User Entered Notes. - recurselist(this.system.equipment?.carried, t => { - t.carried = true - if (!!t.save) temp.push(t) - }) // Ensure carried eqt stays in carried - recurselist(this.system.equipment?.other, t => { - t.carried = false - if (!!t.save) temp.push(t) - }) - - temp.forEach(eqt => { - // Remove all entries from inside items because if they still exist, they will be added back in - eqt.contains = {} - eqt.collapsed = {} - }) - - // Put everything in it container (if found), otherwise at the top level - temp.forEach(eqt => { - if (!!eqt.parentuuid) { - let parent = null - parent = temp.find(e => e.uuid === eqt.parentuuid) - if (!!parent) GURPS.put(parent.contains, eqt) - else eqt.parentuuid = '' // Can't find a parent, so put it in the top list - } - }) - - let equipment = { - carried: {}, - other: {}, - } - let cindex = 0 - let oindex = 0 - - temp.forEach(eqt => { - Equipment.calc(eqt) - if (!eqt.parentuuid) { - if (eqt.carried) GURPS.put(equipment.carried, eqt, cindex++) - else GURPS.put(equipment.other, eqt, oindex++) - } - }) - return { - 'data.-=equipment': null, - 'data.equipment': equipment, - } - } - - // Fold a flat array into a hierarchical target object - /** - * @param {any[]} flat - */ - foldList(flat, target = {}) { - flat.forEach(obj => { - if (!!obj.parentuuid) { - const parent = flat.find(o => o.uuid == obj.parentuuid) - if (!!parent) { - if (!parent.contains) parent.contains = {} // lazy init for older characters - GURPS.put(parent.contains, obj) - } else obj.parentuuid = '' // Can't find a parent, so put it in the top list. should never happen with GCS - } - }) - let index = 0 - flat.forEach(obj => { - if (!obj.parentuuid) GURPS.put(target, obj, index++) - }) - return target - } - - /** - * @param {{ [x: string]: Record; }} json - */ - importEncumbranceFromGCSv1(json) { - if (!json) return - let t = this.textFrom - let es = {} - let index = 0 - let cm = 0 - let cd = 0 - for (let i = 0; i < 5; i++) { - let e = new Encumbrance() - e.level = i - let k = 'enc_' + i - let c = t(json[k]) - e.current = c === '1' - k = 'enc' + i - e.key = k - let k2 = k + '_weight' - e.weight = t(json[k2]) - k2 = k + '_move' - e.move = this.intFrom(json[k2]) - k2 = k + '_dodge' - e.dodge = this.intFrom(json[k2]) - if (e.current) { - cm = e.move - cd = e.dodge - } - GURPS.put(es, e, index++) - } - return { - 'data.currentmove': cm, - 'data.currentdodge': cd, - 'data.-=encumbrance': null, - 'data.encumbrance': es, - } - } - - /** - * Copy old OTFs to the new object, and update the displayable notes - * @param {Skill|Spell|Ranged|Melee} oldobj - * @param {Skill|Spell|Ranged|Melee} newobj - */ - _migrateOtfsAndNotes(oldobj = {}, newobj, importvttnotes = '') { - if (!!importvttnotes) newobj.notes += (!!newobj.notes ? ' ' : '') + importvttnotes - this._updateOtf('check', oldobj, newobj) - this._updateOtf('during', oldobj, newobj) - this._updateOtf('pass', oldobj, newobj) - this._updateOtf('fail', oldobj, newobj) - if (oldobj.notes?.startsWith(newobj.notes)) - // Must be done AFTER OTFs have been stripped out - newobj.notes = oldobj.notes - if (oldobj.name?.startsWith(newobj.name)) newobj.name = oldobj.name - } - - /** - * Search for specific format OTF in the notes (and vttnotes). - * If we find it in the notes, remove it and replace the notes with the shorter version - */ - _updateOtf(otfkey, oldobj, newobj) { - let objkey = otfkey + 'otf' - let oldotf = oldobj[objkey] - newobj[objkey] = oldotf - var notes, newotf - ;[notes, newotf] = this._removeOtf(otfkey, newobj.notes || '') - if (!!newotf) newobj[objkey] = newotf - newobj.notes = notes.trim() - } - - // Looking for OTFs in text. ex: c:[/qty -1] during:[/anim healing c] - _removeOtf(key, text) { - if (!text) return [text, null] - var start - let patstart = text.toLowerCase().indexOf(key[0] + ':[') - if (patstart < 0) { - patstart = text.toLowerCase().indexOf(key + ':[') - if (patstart < 0) return [text, null] - else start = patstart + key.length + 2 - } else start = patstart + 3 - let cnt = 1 - let i = start - if (i >= text.length) return [text, null] - do { - let ch = text[i++] - if (ch == '[') cnt++ - if (ch == ']') cnt-- - } while (i < text.length && cnt > 0) - if (cnt == 0) { - let otf = text.substring(start, i - 1) - let front = text.substring(0, patstart) - let end = text.substr(i) - if ((front == '' || front.endsWith(' ')) && end.startsWith(' ')) end = end.substring(1) - return [front + end, otf] - } else return [text, null] - } - - /** - * @param {{ [key: string]: any }} json - * @param {boolean} isFoundryGCS - */ - importCombatMeleeFromGCSv1(json, isFoundryGCS) { - if (!json) return - let t = this.textFrom - let melee = {} - let index = 0 - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let oldnote = t(j.notes) - for (let k2 in j.meleemodelist) { - if (k2.startsWith('id-')) { - let j2 = j.meleemodelist[k2] - let m = new Melee() - m.name = t(j.name) - m.st = t(j.st) - m.weight = t(j.weight) - m.techlevel = t(j.tl) - m.cost = t(j.cost) - if (isFoundryGCS) { - m.notes = t(j2.notes) || oldnote - m.pageRef(t(j2.pageref)) - } else - try { - m.setNotes(t(j.text)) - } catch { - console.log(m) - console.log(t(j.text)) - } - m.mode = t(j2.name) - m.import = t(j2.level) - m.damage = t(j2.damage) - m.reach = t(j2.reach) - m.parry = t(j2.parry) - m.block = t(j2.block) - let old = this._findElementIn('melee', false, m.name, m.mode) - this._migrateOtfsAndNotes(old, m, t(j2.vtt_notes)) - - GURPS.put(melee, m, index++) - } - } - } - } - return { - 'data.-=melee': null, - 'data.melee': melee, - } - } - - /** - * @param {{ [key: string]: any }} json - * @param {boolean} isFoundryGCS - */ - importCombatRangedFromGCSv1(json, isFoundryGCS) { - if (!json) return - let t = this.textFrom - let ranged = {} - let index = 0 - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let oldnote = t(j.notes) - for (let k2 in j.rangedmodelist) { - if (k2.startsWith('id-')) { - let j2 = j.rangedmodelist[k2] - let r = new Ranged() - r.name = t(j.name) - r.st = t(j.st) - r.bulk = t(j.bulk) - r.legalityclass = t(j.lc) - r.ammo = t(j.ammo) - if (isFoundryGCS) { - r.notes = t(j2.notes) || oldnote - r.pageRef(t(j2.pageref)) - } else - try { - r.setNotes(t(j.text)) - } catch { - console.log(r) - console.log(t(j.text)) - } - r.mode = t(j2.name) - r.import = t(j2.level) - r.damage = t(j2.damage) - r.acc = t(j2.acc) - r.rof = t(j2.rof) - r.shots = t(j2.shots) - r.rcl = t(j2.rcl) - let rng = t(j2.range) - r.range = rng - let old = this._findElementIn('ranged', false, r.name, r.mode) - this._migrateOtfsAndNotes(old, r, t(j2.vtt_notes)) - - GURPS.put(ranged, r, index++) - } - } - } - } - return { - 'data.-=ranged': null, - 'data.ranged': ranged, - } - } - - /** - * @param {{ race: Record; height: Record; weight: Record; age: Record; title: Record; player: Record; createdon: Record; modifiedon: Record; religion: Record; birthday: Record; hand: Record; sizemodifier: Record; tl: Record; appearance: Record; }} json - */ - importTraitsfromGCSv1(json) { - if (!json) return - let t = this.textFrom - let ts = {} - ts.race = t(json.race) - ts.height = t(json.height) - ts.weight = t(json.weight) - ts.age = t(json.age) - ts.title = t(json.title) - ts.player = t(json.player) - ts.createdon = t(json.createdon) - ts.modifiedon = t(json.modifiedon) - ts.religion = t(json.religion) - ts.birthday = t(json.birthday) - ts.hand = t(json.hand) - ts.sizemod = t(json.sizemodifier) - ts.techlevel = t(json.tl) - // @GENDER, Eyes: @EYES, Hair: @HAIR, Skin: @SKIN - let a = t(json.appearance) - ts.appearance = a - try { - let x = a.indexOf(', Eyes: ') - if (x >= 0) { - ts.gender = a.substring(0, x) - let y = a.indexOf(', Hair: ') - ts.eyes = a.substring(x + 8, y) - x = a.indexOf(', Skin: ') - ts.hair = a.substring(y + 8, x) - ts.skin = a.substr(x + 8) - } - } catch { - console.log('Unable to parse appearance traits for ') - console.log(this) - } - return { - 'data.-=traits': null, - 'data.traits': ts, - } - } - - // Import the section of the GCS FG XML file. - /** - * @param {{ [key: string]: any }} json - */ - async importAttributesFromCGSv1(json) { - if (!json) return - let i = this.intFrom // shortcut to make code smaller -- I reject your attempt to make the code smaller. Why does it need to be smaller? - let t = this.textFrom - let data = this.system - let att = data.attributes - - // attribute.values will be calculated in calculateDerivedValues() - att.ST.import = i(json.strength) - att.ST.points = i(json.strength_points) - att.DX.import = i(json.dexterity) - att.DX.points = i(json.dexterity_points) - att.IQ.import = i(json.intelligence) - att.IQ.points = i(json.intelligence_points) - att.HT.import = i(json.health) - att.HT.points = i(json.health_points) - att.WILL.import = i(json.will) - att.WILL.points = i(json.will_points) - att.PER.import = i(json.perception) - att.PER.points = i(json.perception_points) - - data.HP.max = i(json.hitpoints) - data.HP.points = i(json.hitpoints_points) - data.FP.max = i(json.fatiguepoints) - data.FP.points = i(json.fatiguepoints_points) - let hp = i(json.hps) - let fp = i(json.fps) - - let saveCurrent = false - if (!!data.lastImport && (data.HP.value != hp || data.FP.value != fp)) { - let option = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IMPORT_HP_FP) - if (option == 0) { - saveCurrent = true - } - if (option == 2) { - saveCurrent = await new Promise((resolve, reject) => { - let d = new Dialog({ - title: 'Current HP & FP', - content: `Do you want to

    Save the current HP (${data.HP.value}) & FP (${data.FP.value}) values or

    Overwrite it with the import data, HP (${hp}) & FP (${fp})?

     `, - buttons: { - save: { - icon: '', - label: 'Save', - callback: () => resolve(true), - }, - overwrite: { - icon: '', - label: 'Overwrite', - callback: () => resolve(false), - }, - }, - default: 'save', - close: () => resolve(false), // just assume overwrite. Error handling would be too much work right now. - }) - d.render(true) - }) - } - } - if (!saveCurrent) { - data.HP.value = hp - data.FP.value = fp - } - - let lm = {} - lm.basiclift = t(json.basiclift) - lm.carryonback = t(json.carryonback) - lm.onehandedlift = t(json.onehandedlift) - lm.runningshove = t(json.runningshove) - lm.shiftslightly = t(json.shiftslightly) - lm.shove = t(json.shove) - lm.twohandedlift = t(json.twohandedlift) - - data.basicmove.value = t(json.basicmove) - data.basicmove.points = i(json.basicmove_points) - // data.basicspeed.value = t(json.basicspeed) - data.basicspeed.value = this.floatFrom(json.basicspeed) - - data.basicspeed.points = i(json.basicspeed_points) - data.thrust = t(json.thrust) - data.swing = t(json.swing) - data.currentmove = t(json.move) - data.frightcheck = i(json.frightcheck) - - data.hearing = i(json.hearing) - data.tastesmell = i(json.tastesmell) - data.touch = i(json.touch) - data.vision = i(json.vision) - - return { - 'data.attributes': att, - 'data.HP': data.HP, - 'data.FP': data.FP, - 'data.basiclift': data.basiclift, - 'data.basicmove': data.basicmove, - 'data.basicspeed': data.basicspeed, - 'data.thrust': data.thrust, - 'data.swing': data.swing, - 'data.currentmove': data.currentmove, - 'data.frightcheck': data.frightcheck, - 'data.hearing': data.hearing, - 'data.tastesmell': data.tastesmell, - 'data.touch': data.touch, - 'data.vision': data.vision, - 'data.liftingmoving': lm, - } - } - - // create/update the skills. - // NOTE: For the update to work correctly, no two skills can have the same name. - // When reading data, use "this.system.skills", however, when updating, use "system.skills". - /** - * @param {{ [key: string]: any }} json - * @param {boolean} isFoundryGCS - */ - importSkillsFromGCSv1(json, isFoundryGCS) { - if (!json) return - let temp = [] - let t = this.textFrom /// shortcut to make code smaller - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let sk = new Skill() - sk.name = t(j.name) - sk.type = t(j.type) - sk.import = t(j.level) - if (sk.level == 0) sk.level = '' - sk.points = this.intFrom(j.points) - sk.relativelevel = t(j.relativelevel) - if (isFoundryGCS) { - sk.notes = t(j.notes) - sk.pageRef(t(j.pageref)) - } else sk.setNotes(t(j.text)) - if (!!j.pageref) sk.pageRef(t(j.pageref)) - sk.uuid = t(j.uuid) - sk.parentuuid = t(j.parentuuid) - let old = this._findElementIn('skills', sk.uuid) - this._migrateOtfsAndNotes(old, sk, t(j.vtt_notes)) - - temp.push(sk) - } - } - return { - 'data.-=skills': null, - 'data.skills': this.foldList(temp), - } - } - - // create/update the spells. - // NOTE: For the update to work correctly, no two spells can have the same name. - // When reading data, use "this.system.spells", however, when updating, use "system.spells". - /** - * @param {{ [key: string]: any }} json - * @param {boolean} isFoundryGCS - */ - importSpellsFromGCSv1(json, isFoundryGCS) { - if (!json) return - let temp = [] - let t = this.textFrom /// shortcut to make code smaller - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let sp = new Spell() - sp.name = t(j.name) - sp.class = t(j.class) - sp.college = t(j.college) - if (isFoundryGCS) { - sp.cost = t(j.cost) - sp.maintain = t(j.maintain) - sp.difficulty = t(j.difficulty) - sp.relativelevel = t(j.relativelevel) - sp.notes = t(j.notes) - } else { - let cm = t(j.costmaintain) - let i = cm.indexOf('/') - if (i >= 0) { - sp.cost = cm.substring(0, i) - sp.maintain = cm.substr(i + 1) - } else { - sp.cost = cm - } - sp.setNotes(t(j.text)) - } - sp.pageRef(t(j.pageref)) - sp.duration = t(j.duration) - sp.points = t(j.points) - sp.casttime = t(j.time) - sp.import = t(j.level) - sp.uuid = t(j.uuid) - sp.parentuuid = t(j.parentuuid) - let old = this._findElementIn('spells', sp.uuid) - this._migrateOtfsAndNotes(old, sp, t(j.vtt_notes)) - temp.push(sp) - } - } - return { - 'data.-=spells': null, - 'data.spells': this.foldList(temp), - } - } - - /** - * @param {{ [key: string]: any }} adsjson - * @param {{ [key: string]: any }} disadsjson - */ - importAdsFromGCA(adsjson, disadsjson) { - /** @type {Advantage[]} */ - let list = [] - this.importBaseAdvantages(list, adsjson) - this.importBaseAdvantages(list, disadsjson) - return { - 'data.-=ads': null, - 'data.ads': this.foldList(list), - } - } - - /** - * @param {Advantage[]} datalist - * @param {{ [key: string]: any }} json - */ - importBaseAdvantages(datalist, json) { - if (!json) return - let t = this.textFrom /// shortcut to make code smaller - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let a = new Advantage() - a.name = t(j.name) - a.points = this.intFrom(j.points) - a.setNotes(t(j.text)) - a.pageRef(t(j.pageref) || a.pageref) - a.uuid = t(j.uuid) - a.parentuuid = t(j.parentuuid) - let old = this._findElementIn('ads', a.uuid) - this._migrateOtfsAndNotes(old, a, t(j.vtt_notes)) - datalist.push(a) - } - } - } - - // In the new GCS import, all ads/disad/quirks/perks are in one list. - /** - * @param {{ [key: string]: any }} json - */ - importAdsFromGCSv2(json) { - let t = this.textFrom /// shortcut to make code smaller - let temp = [] - for (let key in json) { - if (key.startsWith('id-')) { - // Allows us to skip over junk elements created by xml->json code, and only select the skills. - let j = json[key] - let a = new Advantage() - a.name = t(j.name) - a.points = this.intFrom(j.points) - a.note = t(j.notes) - a.userdesc = t(j.userdesc) - a.notes = '' - if (!!a.note && !!a.userdesc) a.notes = a.note + '\n' + a.userdesc - else if (!!a.note) a.notes = a.note - else if (!!a.userdesc) a.notes = a.userdesc - a.pageRef(t(j.pageref)) - a.uuid = t(j.uuid) - a.parentuuid = t(j.parentuuid) - let old = this._findElementIn('ads', a.uuid) - this._migrateOtfsAndNotes(old, a, t(j.vtt_notes)) - temp.push(a) - } - } - return { - 'data.-=ads': null, - 'data.ads': this.foldList(temp), - } - } - - /** - * Adds any assigned resource trackers to the actor data and sheet. - */ - async setResourceTrackers() { - // find those with non-blank slots - let templates = ResourceTrackerManager.getAllTemplates().filter(it => !!it.slot) - - for (const template of templates) { - // find the matching data on this actor - let index = zeroFill(template.slot, 4) - let path = `additionalresources.tracker.${index}` - let tracker = getProperty(this, path) - - while (!tracker) { - await this.addTracker() - tracker = getProperty(this, path) - } - - // skip if already set - if (!!tracker && tracker.name === template.tracker.name) { - return - } - - // if not blank, don't overwrite - if (!!tracker && !!tracker.name) { - ui.notifications?.warn( - `Will not overwrite Tracker ${template.slot} as its name is set to ${tracker.name}. Create Tracker for ${template.tracker.name} failed.` - ) - return - } - - await this.applyTrackerTemplate(path, template) - } - } - - /** - * Update this tracker slot with the contents of the template. - * @param {String} path JSON data path to the tracker; must start with 'additionalresources.tracker.' - * @param {*} template to apply - */ - async applyTrackerTemplate(path, template) { - // is there an initializer? If so calculate its value - let value = 0 - if (!!template.initialValue) { - value = parseInt(template.initialValue, 10) - if (Number.isNaN(value)) { - // try to use initialValue as a path to another value - value = getProperty(this, template.initialValue) - } - } - template.tracker.max = value - template.tracker.value = template.tracker.isDamageTracker ? template.tracker.min : value - - // remove whatever is there - await this.clearTracker(path) - - // add the new tracker - /** @type {{ [key: string]: any }} */ - let update = {} - update[`data.${path}`] = template.tracker - await this.update(update) - } - - /** - * Overwrites the tracker pointed to by the path with default/blank values. - * @param {String} path JSON data path to the tracker; must start with 'additionalresources.tracker.' - */ - async clearTracker(path) { - // verify that this is a Tracker - const prefix = 'additionalresources.tracker.' - if (!path.startsWith(prefix)) throw `Invalid actor data path, actor=[${this.id}] path=[${path}]` - - /** @type {{[key: string]: string}} */ - let update = {} - update[`data.${path}`] = { - name: '', - alias: '', - pdf: '', - max: 0, - min: 0, - value: 0, - isDamageTracker: false, - isDamageType: false, - initialValue: '', - thresholds: [], - } - await this.update(update) - } - - /** - * Removes the indicated tracker from the object, reindexing the keys. - * @param {String} path JSON data path to the tracker; must start with 'additionalresources.tracker.' - */ - async removeTracker(path) { - this.ignoreRender = true - const prefix = 'additionalresources.tracker.' - - // verify that this is a Tracker - if (!path.startsWith(prefix)) throw `Invalid actor data path, actor=[${this.id}] path=[${path}]` - - let key = path.replace(prefix, '') - let trackerData = this.system.additionalresources.tracker - delete trackerData[key] - let trackers = objectToArray(trackerData) - let data = arrayToObject(trackers) - - // remove all trackers - await this.update({ 'data.additionalresources.-=tracker': null }) - // add the new "array" of trackers - if (data) this.update({ 'data.additionalresources.tracker': data }) - else this.update('data.additionalresources.tracker', {}) - - this._forceRender() - } - - async addTracker() { - this.ignoreRender = true - - let trackerData = { name: '', value: 0, min: 0, max: 0, points: 0 } - let data = GurpsActor.addTrackerToDataObject(this.system, trackerData) - - await this.update({ 'data.additionalresources.-=tracker': null }) - await this.update({ 'data.additionalresources.tracker': data }) - - this._forceRender() - } - - static addTrackerToDataObject(data, trackerData) { - let trackers = GurpsActor.getTrackersAsArray(data) - trackers.push(trackerData) - return arrayToObject(trackers) - } - - static getTrackersAsArray(data) { - let trackerArray = data.additionalresources.tracker - if (!trackerArray) trackerArray = {} - return objectToArray(trackerArray) - } - - async setMoveDefault(value) { - this.ignoreRender = true - let move = this.system.move - for (const key in move) { - move[key].default = value === key - } - await this.update({ 'data.-=move': null }) - await this.update({ 'data.move': move }) - this._forceRender() - } - - // --- Functions to handle events on actor --- - - /** - * @param {any[]} damageData - */ - handleDamageDrop(damageData) { - if (game.user.isGM || !game.settings.get(settings.SYSTEM_NAME, settings.SETTING_ONLY_GMS_OPEN_ADD)) - new ApplyDamageDialog(this, damageData).render(true) - else ui.notifications?.warn('Only GMs are allowed to Apply Damage.') - } - - // Drag and drop from Item colletion - /** - * @param {{ type: any; x?: number; y?: number; payload?: any; pack?: any; id?: any; data?: any; }} dragData - */ - async handleItemDrop(dragData) { - if (!this.isOwner) { - ui.notifications?.warn(i18n('GURPS.youDoNotHavePermssion')) - return - } - const uuid = - typeof dragData.pack === 'string' - ? `Compendium.${dragData.pack}.${dragData.id}` - : `${dragData.type}.${dragData.id}` - let global = await fromUuid(uuid) - let data = !!global ? global.data : dragData.data - if (!data) { - ui.notifications?.warn('NO ITEM DATA!') - return - } - ui.notifications?.info(data.name + ' => ' + this.name) - if (!data.data.globalid) await data.update({ _id: data._id, 'data.globalid': uuid }) - this.ignoreRender = true - await this.addNewItemData(data) - this._forceRender() - } - - _forceRender() { - this.ignoreRender = false - //console.log("Force Render") - this.render() - } - - // Drag and drop from an equipment list - /** - * @param {{ type?: string; x?: number; y?: number; payload?: any; actorid?: any; itemid?: any; isLinked?: any; key?: any; itemData?: any; }} dragData - */ - async handleEquipmentDrop(dragData) { - if (dragData.actorid == this.id) return false // same sheet drag and drop handled elsewhere - if (!dragData.itemid) { - ui.notifications?.warn(i18n('GURPS.cannotDragNonFoundryEqt')) - return - } - if (!dragData.isLinked) { - ui.notifications?.warn("You cannot drag from an un-linked token. The source must have 'Linked Actor Data'") - return - } - let srcActor = game.actors.get(dragData.actorid) - let eqt = getProperty(srcActor, dragData.key) - if ( - (!!eqt.contains && Object.keys(eqt.contains).length > 0) || - (!!eqt.collapsed && Object.keys(eqt.collapsed).length > 0) - ) { - ui.notifications?.warn('You cannot transfer an Item that contains other equipment.') - return - } - - if (!!this.isOwner && !!srcActor.isOwner) { - // same owner - if (eqt.count < 2) { - let destKey = this._findEqtkeyForId('globalid', eqt.globalid) - if (!!destKey) { - // already have some - let destEqt = getProperty(this, destKey) - await this.updateEqtCount(destKey, destEqt.count + eqt.count) - await srcActor.deleteEquipment(dragData.key) - } else { - let item = await srcActor.deleteEquipment(dragData.key) - await this.addNewItemData(item.data) - } - } else { - let content = await renderTemplate('systems/gurps/templates/transfer-equipment.html', { eqt: eqt }) - let callback = async (/** @type {JQuery | HTMLElement} */ html) => { - // @ts-ignore - let qty = parseInt(html.find('#qty').val()) - let destKey = this._findEqtkeyForId('globalid', eqt.globalid) - if (!!destKey) { - // already have some - let destEqt = getProperty(this, destKey) - await this.updateEqtCount(destKey, destEqt.count + qty) - } else { - let item = /** @type {GurpsItem} */ (srcActor.items.get(eqt.itemid)) - item.system.eqt.count = qty - await this.addNewItemData(item.data) - } - if (qty >= eqt.count) await srcActor.deleteEquipment(dragData.key) - else await srcActor.updateEqtCount(dragData.key, eqt.count - qty) - } - - Dialog.prompt({ - title: i18n('GURPS.TransferTo') + ' ' + this.name, - label: i18n('GURPS.ok'), - content: content, - callback: callback, - rejectClose: false, // Do not "reject" if the user presses the "close" gadget - }) - } - } else { - // different owners - let count = eqt.count - if (eqt.count > 1) { - let content = await renderTemplate('systems/gurps/templates/transfer-equipment.html', { eqt: eqt }) - let callback = async (/** @type {HTMLElement | JQuery} */ html) => - // @ts-ignore - (count = parseInt(html.find('#qty').val())) - await Dialog.prompt({ - title: i18n('GURPS.TransferTo') + ' ' + this.name, - label: i18n('GURPS.ok'), - content: content, - callback: callback, - }) - } - if (count > eqt.count) count = eqt.count - let destowner = game.users?.players.find(p => this.testUserPermission(p, 'OWNER')) - if (!!destowner) { - ui.notifications?.info(`Asking ${this.name} if they want ${eqt.name}`) - dragData.itemData.data.eqt.count = count // They may not have given all of them - game.socket.emit('system.gurps', { - type: 'dragEquipment1', - srckey: dragData.key, - srcuserid: game.user.id, - srcactorid: dragData.actorid, - destuserid: destowner.id, - destactorid: this.id, - itemData: dragData.itemData, - count: count, - }) - } else ui.notifications?.warn(i18n('GURPS.youDoNotHavePermssion')) - } - } - - // Called from the ItemEditor to let us know our personal Item has been modified - /** - * @param {Item} item - */ - async updateItem(item) { - // @ts-ignore - delete item.editingActor - this.ignoreRender = true - if (item.id) await this._removeItemAdditions(item.id) - let _data = GurpsItem.asGurpsItem(item).system - let oldkey = this._findEqtkeyForId('globalid', _data.globalid) - var oldeqt - if (!!oldkey) oldeqt = getProperty(this, oldkey) - let other = item.id ? await this._removeItemElement(item.id, 'equipment.other') : null // try to remove from other - if (!other) { - // if not in other, remove from carried, and then re-add everything - if (item.id) await this._removeItemElement(item.id, 'equipment.carried') - await this.addItemData(item.data) - } else { - // If was in other... just add back to other (and forget addons) - await this._addNewItemEquipment(item.data, 'data.equipment.other.' + zeroFill(0)) - } - let newkey = this._findEqtkeyForId('globalid', _data.globalid) - if (!!oldeqt && (!!oldeqt.contains || !!oldeqt.collapsed)) { - this.update({ - [newkey + '.contains']: oldeqt.contains, - [newkey + '.collapsed']: oldeqt.collapsed, - }) - } - this._forceRender() - } - - // create a new embedded item based on this item data and place in the carried list - // This is how all Items are added originally. - /** - * @param {ItemData} itemData - * @param {string | null} [targetkey] - */ - async addNewItemData(itemData, targetkey = null) { - let d = itemData - // @ts-ignore - if (typeof itemData.toObject === 'function') d = itemData.toObject() - // @ts-ignore - let localItems = await this.createEmbeddedDocuments('Item', [d]) // add a local Foundry Item based on some Item data - let localItem = localItems[0] - await this.updateEmbeddedDocuments('Item', [{ _id: localItem.id, 'system.eqt.uuid': generateUniqueId() }]) - await this.addItemData(localItem.data, targetkey) // only created 1 item - } - - // Once the Items has been added to our items list, add the equipment and any features - /** - * @param {ItemData} itemData - * @param {string | null} [targetkey] - */ - async addItemData(itemData, targetkey) { - let [eqtkey, addFeatures] = await this._addNewItemEquipment(itemData, targetkey) - if (addFeatures) { - await this._addItemAdditions(itemData, eqtkey) - } - } - - // Make the initial equipment object (unless it already exists, saved in a user equipment) - /** - * @param {ItemData} itemData - * @param {string | null} targetkey - */ - async _addNewItemEquipment(itemData, targetkey) { - let existing = this._findEqtkeyForId('itemid', itemData._id) - if (!!existing) { - // it may already exist (due to qty updates), so don't add it again - let eqt = getProperty(this, existing) - return [existing, eqt.carried && eqt.equipped] - } - let _data = /** @type {GurpsItemData} */ (itemData.data) - if (!!_data.eqt.parentuuid) { - var found - recurselist(this.system.equipment.carried, (e, k, d) => { - if (e.uuid == _data.eqt.parentuuid) found = 'data.equipment.carried.' + k - }) - if (!found) - recurselist(this.system.equipment.other, (e, k, d) => { - if (e.uuid == _data.eqt.parentuuid) found = 'data.equipment.other.' + k - }) - if (!!found) { - targetkey = found + '.contains.' + zeroFill(0) - } - } - if (targetkey == null) - if (_data.carried) { - // new carried items go at the end - targetkey = 'data.equipment.carried' - let index = 0 - let list = getProperty(this, targetkey) - while (list.hasOwnProperty(zeroFill(index))) index++ - targetkey += '.' + zeroFill(index) - } else targetkey = 'data.equipment.other' - if (targetkey.match(/^data\.equipment\.\w+$/)) targetkey += '.' + zeroFill(0) //if just 'carried' or 'other' - let eqt = _data.eqt - if (!eqt) { - ui.notifications?.warn('Item: ' + itemData._id + ' (Global:' + _data.globalid + ') missing equipment') - return ['', false] - } else { - eqt.itemid = itemData._id - eqt.globalid = _data.globalid - //eqt.uuid = 'item-' + eqt.itemid - eqt.equipped = !!itemData.data.equipped ?? true - eqt.img = itemData.img - eqt.carried = !!itemData.data.carried ?? true - await GURPS.insertBeforeKey(this, targetkey, eqt) - await this.updateParentOf(targetkey, true) - return [targetkey, eqt.carried && eqt.equipped] - } - } - - /** - * @param {GurpsItemData} itemData - * @param {string} eqtkey - */ - async _addItemAdditions(itemData, eqtkey) { - let commit = {} - commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'melee')) } - commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'ranged')) } - commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'ads')) } - commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'skills')) } - commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'spells')) } - await this.internalUpdate(commit, { diff: false }) - this.calculateDerivedValues() // new skills and bonuses may affect other items... force a recalc - } - - // called when equipment is being moved - /** - * @param {Equipment} eqt - * @param {string} targetPath - */ - async updateItemAdditionsBasedOn(eqt, targetPath) { - await this._updateEqtStatus(eqt, targetPath, targetPath.includes('.carried')) - } - - // Equipment may carry other eqt, so we must adjust the carried status all the way down. - /** - * @param {Equipment} eqt - * @param {string} eqtkey - * @param {boolean} carried - */ - async _updateEqtStatus(eqt, eqtkey, carried) { - eqt.carried = carried - if (!!eqt.itemid) { - let item = /** @type {Item} */ (await this.items.get(eqt.itemid)) - await this.updateEmbeddedDocuments('Item', [ - { _id: item.id, 'data.equipped': eqt.equipped, 'data.carried': carried }, - ]) - if (!carried || !eqt.equipped) await this._removeItemAdditions(eqt.itemid) - if (carried && eqt.equipped) await this._addItemAdditions(item.data, eqtkey) - } - for (const k in eqt.contains) await this._updateEqtStatus(eqt.contains[k], eqtkey + '.contains.' + k, carried) - for (const k in eqt.collapsed) await this._updateEqtStatus(eqt.collapsed[k], eqtkey + '.collapsed.' + k, carried) - } - - /** - * @param {ItemData} itemData - * @param {string} eqtkey - * @param {string} key - */ - async _addItemElement(itemData, eqtkey, key) { - let found = false - // @ts-ignore - recurselist(this.system[key], (e, k, d) => { - if (e.itemid == itemData._id) found = true - }) - if (found) return - // @ts-ignore - let list = { ...this.system[key] } // shallow copy - let i = 0 - // @ts-ignore - for (const k in itemData.data[key]) { - // @ts-ignore - let e = duplicate(itemData.data[key][k]) - e.itemid = itemData._id - e.uuid = key + '-' + i++ + '-' + e.itemid - e.eqtkey = eqtkey - e.img = itemData.img - GURPS.put(list, e) - } - return i == 0 ? {} : { ['data.' + key]: list } - } - - // return the item data that was deleted (since it might be transferred) - /** - * @param {string} path - */ - async deleteEquipment(path, depth = 0) { - let eqt = getProperty(this, path) - if (!eqt) return eqt - if (depth == 0) this.ignoreRender = true - - // Delete in reverse order so the keys don't get messed up - if (!!eqt.contains) - for (const k of Object.keys(eqt.contains).sort().reverse()) - await this.deleteEquipment(path + '.contains.' + k, depth + 1) - if (!!eqt.collpased) - for (const k of Object.keys(eqt.collapsed).sort().reverse()) - await this.deleteEquipment(path + '.collapsed.' + k, depth + 1) - - var item - if (!!eqt.itemid) { - item = await this.items.get(eqt.itemid) - if (!!item) await item.delete() // data protect for messed up mooks - await this._removeItemAdditions(eqt.itemid) - } - await GURPS.removeKey(this, path) - if (depth == 0) this._forceRender() - return item - } - - /** - * @param {string} itemid - */ - async _removeItemAdditions(itemid) { - let saved = this.ignoreRender - this.ignoreRender = true - await this._removeItemElement(itemid, 'melee') - await this._removeItemElement(itemid, 'ranged') - await this._removeItemElement(itemid, 'ads') - await this._removeItemElement(itemid, 'skills') - await this._removeItemElement(itemid, 'spells') - this.ignoreRender = saved - } - - // We have to remove matching items after we searched through the list - // because we cannot safely modify the list why iterating over it - // and as such, we can only remove 1 key at a time and must use thw while loop to check again - /** - * @param {string} itemid - * @param {string} key - */ - async _removeItemElement(itemid, key) { - let found = true - let any = false - while (!!found) { - found = false - let list = getProperty(this, key) - recurselist(list, (e, k, d) => { - if (e.itemid == itemid) found = k - }) - if (!!found) { - any = true - await GURPS.removeKey(this, 'data.' + key + '.' + found) - } - } - return any - } - - /** - * @param {string} srckey - * @param {string} targetkey - * @param {boolean} shiftkey - */ - async moveEquipment(srckey, targetkey, shiftkey) { - if (srckey == targetkey) return - if (shiftkey && (await this._splitEquipment(srckey, targetkey))) return - // Because we may be modifing the same list, we have to check the order of the keys and - // apply the operation that occurs later in the list, first (to keep the indexes the same) - let srca = srckey.split('.') - srca.splice(0, 3) - let tara = targetkey.split('.') - tara.splice(0, 3) - let max = Math.min(srca.length, tara.length) - let isSrcFirst = max == 0 ? srca.length > tara.length : false - for (let i = 0; i < max; i++) { - if (parseInt(srca[i]) < parseInt(tara[i])) isSrcFirst = true - } - let object = getProperty(this, srckey) - if (targetkey.match(/^data\.equipment\.\w+$/)) { - this.ignoreRender = true - object.parentuuid = '' - if (!!object.itemid) { - let item = /** @type {Item} */ (this.items.get(object.itemid)) - await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.eqt.parentuuid': '' }]) - } - let target = { ...GURPS.decode(this.data, targetkey) } // shallow copy the list - if (!isSrcFirst) await GURPS.removeKey(this, srckey) - let eqtkey = GURPS.put(target, object) - await this.updateItemAdditionsBasedOn(object, targetkey + '.' + eqtkey) - await this.update({ [targetkey]: target }) - if (isSrcFirst) await GURPS.removeKey(this, srckey) - return this._forceRender() - } - if (await this._checkForMerging(srckey, targetkey)) return - if (srckey.includes(targetkey) || targetkey.includes(srckey)) { - ui.notifications.error('Unable to drag and drop withing the same hierarchy. Try moving it elsewhere first.') - return - } - this.toggleExpand(targetkey, true) - let d = new Dialog({ - title: object.name, - content: '

    Where do you want to place this?

    ', - buttons: { - one: { - icon: '', - label: 'Before', - callback: async () => { - this.ignoreRender = true - if (!isSrcFirst) { - await GURPS.removeKey(this, srckey) - await this.updateParentOf(srckey, false) - } - await this.updateItemAdditionsBasedOn(object, targetkey) - await GURPS.insertBeforeKey(this, targetkey, object) - await this.updateParentOf(targetkey, true) - if (isSrcFirst) { - await GURPS.removeKey(this, srckey) - await this.updateParentOf(srckey, false) - } - this._forceRender() - }, - }, - two: { - icon: '', - label: 'In', - callback: async () => { - this.ignoreRender = true - if (!isSrcFirst) { - await GURPS.removeKey(this, srckey) - await this.updateParentOf(srckey, false) - } - let k = targetkey + '.contains.' + zeroFill(0) - let targ = getProperty(this, targetkey) - - await this.updateItemAdditionsBasedOn(object, targetkey) - await GURPS.insertBeforeKey(this, k, object) - await this.updateParentOf(k, true) - if (isSrcFirst) { - await GURPS.removeKey(this, srckey) - await this.updateParentOf(srckey, false) - } - this._forceRender() - }, - }, - }, - default: 'one', - }) - d.render(true) - } - - /** - * @param {string} path - */ - async toggleExpand(path, expandOnly = false) { - let obj = getProperty(this, path) - if (!!obj.collapsed && Object.keys(obj.collapsed).length > 0) { - let temp = { ...obj.contains, ...obj.collapsed } - let update = { - [path + '.-=collapsed']: null, - [path + '.collapsed']: {}, - [path + '.contains']: temp, - } - await this.update(update) - } else if (!expandOnly && !!obj.contains && Object.keys(obj.contains).length > 0) { - let temp = { ...obj.contains, ...obj.collapsed } - let update = { - [path + '.-=contains']: null, - [path + '.contains']: {}, - [path + '.collapsed']: temp, - } - await this.update(update) - } - } - - /** - * @param {string} srckey - * @param {string} targetkey - */ - async _splitEquipment(srckey, targetkey) { - let srceqt = getProperty(this, srckey) - if (srceqt.count <= 1) return false // nothing to split - let content = await renderTemplate('systems/gurps/templates/transfer-equipment.html', { eqt: srceqt }) - let count = 0 - let callback = async (/** @type {JQuery} */ html) => - (count = parseInt(html.find('#qty').val()?.toString() || '0')) - await Dialog.prompt({ - title: 'Split stack', - label: i18n('GURPS.ok'), - content: content, - callback: callback, - }) - if (count <= 0) return true // didn't want to split - if (count >= srceqt.count) return false // not a split, but a move - if (targetkey.match(/^data\.equipment\.\w+$/)) targetkey += '.' + zeroFill(0) - if (!!srceqt.globalid) { - this.ignoreRender = true - await this.updateEqtCount(srckey, srceqt.count - count) - let rawItem = this.items.get(srceqt.itemid) - if (rawItem) { - let item = GurpsItem.asGurpsItem(rawItem) - item.system.eqt.count = count - await this.addNewItemData(item.data, targetkey) - await this.updateParentOf(targetkey, true) - } - this._forceRender() - return true - } else { - // simple eqt - let neqt = duplicate(srceqt) - neqt.count = count - this.ignoreRender = true - await this.updateEqtCount(srckey, srceqt.count - count) - await GURPS.insertBeforeKey(this, targetkey, neqt) - await this.updateParentOf(targetkey, true) - this._forceRender() - return true - } - return false - } - - /** - * @param {string} srckey - * @param {string} targetkey - */ - async _checkForMerging(srckey, targetkey) { - let srceqt = getProperty(this, srckey) - let desteqt = getProperty(this, targetkey) - if ( - (!!srceqt.globalid && srceqt.globalid == desteqt.globalid) || - (!srceqt.globalid && srceqt.name == desteqt.name) - ) { - this.ignoreRender = true - await this.updateEqtCount(targetkey, parseInt(srceqt.count) + parseInt(desteqt.count)) - //if (srckey.includes('.carried') && targetkey.includes('.other')) await this._removeItemAdditionsBasedOn(desteqt) - await this.deleteEquipment(srckey) - this._forceRender() - return true - } - return false - } - - // This function merges the 'where' and 'dr' properties of this actor's hitlocations - // with the roll value from the HitLocations.hitlocationRolls, converting the - // roll from a string to an array of numbers (see the _convertRollStringToArrayOfInt - // function). - // - // return value is an object with the following structure: - // { - // where: string value from the hitlocations table, - // dr: int DR value from the hitlocations table, - // rollText: string value of the roll from the hitlocations table (examples: '5', '6-9', '-') - // roll: array of int of the values that match rollText (examples: [5], [6,7,8,9], []) - // } - get hitLocationsWithDR() { - let myhitlocations = [] - let table = this._hitLocationRolls - for (const [key, value] of Object.entries(this.system.hitlocations)) { - let rollText = value.roll - if (!value.roll && !!table[value.where]) - // Can happen if manually edited - rollText = table[value.where].roll - if (!rollText) rollText = HitLocations.HitLocation.DEFAULT - let dr = parseInt(value.dr) - if (isNaN(dr)) dr = 0 - let entry = new HitLocationEntry(value.where, dr, rollText, value?.split) - myhitlocations.push(entry) - } - return myhitlocations - } - - /** - * @returns the appropriate hitlocation table based on the actor's bodyplan - */ - get _hitLocationRolls() { - return HitLocations.HitLocation.getHitLocationRolls(this.system.additionalresources?.bodyplan) - } - - // Return the 'where' value of the default hit location, or 'Random' - // NOTE: could also return 'Large-Area'? - get defaultHitLocation() { - // TODO implement a system setting but (potentially) allow it to be overridden - return game.settings.get('gurps', 'default-hitlocation') - } - - getCurrentDodge() { - return this.system.currentdodge - } - - getCurrentMove() { - return this.system.currentmove - } - - getTorsoDr() { - if (!this.system.hitlocations) return 0 - let hl = Object.values(this.system.hitlocations).find(h => h.penalty == 0) - return !!hl ? hl : { dr: 0 } - } - - /** - * @param {string} key - */ - getEquipped(key) { - let val = 0 - let txt = '' - if (!!this.system.melee && !!this.system.equipment?.carried) - Object.values(this.system.melee).forEach(melee => { - recurselist(this.system.equipment.carried, (e, k, d) => { - if (!!e && !val && e.equipped && !!melee.name.match(makeRegexPatternFrom(e.name, false))) { - let t = parseInt(melee[key]) - if (!isNaN(t)) { - val = t - txt = '' + melee[key] - } - } - }) - }) - // @ts-ignore - if (!val && !!this.system[key]) { - txt = '' + this.system[key] - val = parseInt(txt) - } - return [txt, val] - } - - getEquippedParry() { - let [txt, val] = this.getEquipped('parry') - this.system.equippedparryisfencing = !!txt && txt.match(/f$/i) - return val - } - - getEquippedBlock() { - return this.getEquipped('block')[1] - } - - /** - * - * @param {string} name of the status effect - * @param {boolean} active (desired) state - true or false - */ - toggleEffectByName(name, active) { - let tokens = this.getActiveTokens(true) - for (const token of tokens) { - token.setEffectActive(name, active) - } - } - - /** - * @param {string} pattern - */ - findEquipmentByName(pattern, otherFirst = false) { - while (pattern[0] == '/') pattern = pattern.substr(1) - pattern = makeRegexPatternFrom(pattern, false) - let pats = pattern - .substr(1) // remove the ^ from the beginning of the string - .split('/') - .map(e => new RegExp('^' + e, 'i')) // and apply it to each pattern - /** - * @type {any} - */ - var eqt, key - let list1 = otherFirst ? this.system.equipment.other : this.system.equipment.carried - let list2 = otherFirst ? this.system.equipment.carried : this.system.equipment.other - let pkey1 = otherFirst ? 'data.equipment.other.' : 'data.equipment.carried.' - let pkey2 = otherFirst ? 'data.equipment.carried.' : 'data.equipment.other.' - recurselist( - list1, - (e, k, d) => { - let l = pats.length - 1 - let p = pats[Math.min(d, l)] - if (e.name.match(p)) { - if (!eqt && (d == l || pats.length == 1)) { - eqt = e - key = k - } - } else return pats.length == 1 - }, - pkey1 - ) - recurselist( - list2, - (e, k, d) => { - let l = pats.length - 1 - let p = pats[Math.min(d, l)] - if (e.name.match(p)) { - if (!eqt && (d == l || pats.length == 1)) { - eqt = e - key = k - } - } else return pats.length == 1 - }, - pkey2 - ) - return [eqt, key] - } - - /** - * @param {number} currentWeight - */ - checkEncumbance(currentWeight) { - /** @type {{ [key: string]: any }} */ - let encs = this.system.encumbrance - let last = zeroFill(0) // if there are encumbrances, there will always be a level0 - var best, prev - for (let key in encs) { - let enc = encs[key] - if (enc.current) prev = key - let w = parseFloat(enc.weight) - if (w > 0) { - last = key - if (currentWeight <= w) { - best = key - break - } - } - } - if (!best) best = last // that has a weight - if (best != prev) { - for (let key in encs) { - let enc = encs[key] - let t = 'data.encumbrance.' + key + '.current' - if (key === best) { - enc.current = true - this.system.currentmove = parseInt(enc.currentmove) - this.system.currentdodge = parseInt(enc.currentdodge) - } else if (enc.current) { - enc.current = false - } - } - } - } - - // Set the equipment count to 'count' and then recalc sums - /** - * @param {string} eqtkey - * @param {number} count - */ - async updateEqtCount(eqtkey, count) { - /** @type {{ [key: string]: any }} */ - let update = { [eqtkey + '.count']: count } - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATICALLY_SET_IGNOREQTY)) - update[eqtkey + '.ignoreImportQty'] = true - await this.update(update) - let eqt = getProperty(this, eqtkey) - await this.updateParentOf(eqtkey, false) - if (!!eqt.itemid) { - let item = this.items.get(eqt.itemid) - if (!!item) await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.eqt.count': count }]) - else { - ui.notifications?.warn('Invalid Item in Actor... removing all features') - this._removeItemAdditions(eqt.itemid) - } - } - } - - // Used to recalculate weight and cost sums for a whole tree. - /** - * @param {string} srckey - */ - async updateParentOf(srckey, updatePuuid = true) { - // pindex = 4 for equipment - let pindex = 4 - let paths = srckey.split('.') - let sp = paths.slice(0, pindex).join('.') // find the top level key in this list - // But count may have changed... if (srckey == sp) return // no parent for this eqt - let parent = getProperty(this, sp) - if (!!parent) { - // data protection - await Equipment.calcUpdate(this, parent, sp) // and re-calc cost and weight sums from the top down - if (updatePuuid) { - let puuid = '' - if (paths.length >= 6) { - sp = paths.slice(0, -2).join('.') - puuid = getProperty(this, sp).uuid - } - await this.internalUpdate({ [srckey + '.parentuuid']: puuid }) - let eqt = getProperty(this, srckey) - if (!!eqt.itemid) { - let item = this.items.get(eqt.itemid) - if (item) await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.eqt.parentuuid': puuid }]) - } - } - } - } - - isEmptyActor() { - let d = this.system - let chkAttr = (/** @type {string} */ attr) => { - return d.attributes[attr].import != 10 - } - - if (d.HP.max != 0) return false - if (d.HP.value != 0) return false - if (d.FP.max != 0) return false - if (d.FP.value != 0) return false - if (chkAttr('ST')) return false - if (chkAttr('DX')) return false - if (chkAttr('IQ')) return false - if (chkAttr('HT')) return false - if (chkAttr('WILL')) return false - if (chkAttr('PER')) return false - - return true - } + /** @override */ + getRollData() { + const data = super.getRollData() + return data + } + + /** + * @returns {GurpsActor} + */ + asGurpsActor() { + // @ts-ignore + return /** @type {GurpsActor} */ (this) + } + + // Return collection os Users that have ownership on this actor + getOwners() { + return game.users?.contents.filter(u => this.getUserLevel(u) >= CONST.DOCUMENT_PERMISSION_LEVELS.OWNER) + } + + // 0.8.x added steps necessary to switch sheets + /** + * @param {Application} newSheet + */ + async openSheet(newSheet) { + const sheet = this.sheet + if (!!sheet) { + await sheet.close() + this._sheet = null + delete this.apps[sheet.appId] + await this.setFlag('core', 'sheetClass', newSheet) + this.ignoreRender = false + this.sheet.render(true) + } + } + + prepareData() { + super.prepareData() + // By default, it does this: + // this.data.reset() + // this.prepareBaseData() + // this.prepareEmbeddedEntities() + // this.prepareDerivedData() + } + + prepareBaseData() { + super.prepareBaseData() + + this.system.conditions.posture = 'standing' + this.system.conditions.self = { modifiers: [] } + this.system.conditions.target = { modifiers: [] } + this.system.conditions.exhausted = false + this.system.conditions.reeling = false + + { + // Oh how I wish we had a typesafe model! + // I hate treating everything as "maybe its a number, maybe its a string...?!" + + let sizemod = this.system.traits?.sizemod.toString() || '+0' + if (sizemod.match(/^\d/g)) sizemod = `+${sizemod}` + if (sizemod !== '0' && sizemod !== '+0') { + this.system.conditions.target.modifiers.push( + i18n_f('GURPS.modifiersSize', { sm: sizemod }, '{sm} for Size Modifier') + ) + } + } + + let attributes = this.system.attributes + if (foundry.utils.getType(attributes.ST.import) === 'string') + this.system.attributes.ST.import = parseInt(attributes.ST.import) + } + + prepareEmbeddedEntities() { + // Calls this.applyActiveEffects() + super.prepareEmbeddedEntities() + } + + prepareDerivedData() { + super.prepareDerivedData() + + // Handle new move data -- if data.move exists, use the default value in that object to set the move + // value in the first entry of the encumbrance object. + if (this.system.encumbrance) { + let move = this.system.move + if (!move) { + let currentMove = this.system.encumbrance['00000'].move ?? this.system.basicmove.value + let value = { mode: MoveModes.Ground, basic: currentMove, default: true } + setProperty(this.system, 'move.00000', value) + move = this.system.move + } + + let current = Object.values(move).find(it => it.default) + if (current) { + // This is nonpersistent, derived values only. + this.system.encumbrance['00000'].move = current.basic + } + } + + this.calculateDerivedValues() + } + + // execute after every import. + async postImport() { + this.calculateDerivedValues() + + // Convoluted code to add Items (and features) into the equipment list + // @ts-ignore + let orig = /** @type {GurpsItem[]} */ (this.items.contents.slice().sort((a, b) => b.name.localeCompare(a.name))) // in case items are in the same list... add them alphabetically + /** + * @type {any[]} + */ + let good = [] + while (orig.length > 0) { + // We are trying to place 'parent' items before we place 'children' items + let left = [] + let atLeastOne = false + for (const i of orig) { + // @ts-ignore + if (!i.system.eqt.parentuuid || good.find(e => e.system.eqt.uuid == i.system.eqt.parentuuid)) { + atLeastOne = true + good.push(i) // Add items in 'parent' order... parents before children (so children can find parent when inserted into list) + } else left.push(i) + } + if (atLeastOne) orig = left + else { + // if unable to move at least one, just copy the rest and hope for the best + good = [...good, ...left] + orig = [] + } + } + for (const item of good) await this.addItemData(item.data) // re-add the item equipment and features + + await this.update({ '_stats.systemVersion': game.system.version }, { diff: false, render: false }) + // Set custom trackers based on templates. should be last because it may need other data to initialize... + await this.setResourceTrackers() + await this.syncLanguages() + } + + // Ensure Language Advantages conform to a standard (for Polygot module) + async syncLanguages() { + if (this.system.languages) { + let updated = false + let newads = { ...this.system.ads } + let langn = new RegExp('Language:?', 'i') + let langt = new RegExp(i18n('GURPS.language') + ':?', 'i') + recurselist(this.system.languages, (e, k, d) => { + let a = GURPS.findAdDisad(this, '*' + e.name) // is there an Adv including the same name + if (a) { + if (!a.name.match(langn) && !a.name.match(langt)) { + // GCA4/GCS style + a.name = i18n('GURPS.language') + ': ' + a.name + updated = true + } + } else { + // GCA5 style (Language without Adv) + let n = i18n('GURPS.language') + ': ' + e.name + if (e.spoken == e.written) + // If equal, then just report single level + n += ' (' + e.spoken + ')' + else if (!!e.spoken) + // Otherwise, report type and level (like GCA4) + n += ' (' + i18n('GURPS.spoken') + ') (' + e.spoken + ')' + else n += ' (' + i18n('GURPS.written') + ') (' + e.written + ')' + let a = new Advantage() + a.name = n + a.points = e.points + GURPS.put(newads, a) + updated = true + } + }) + if (updated) { + await this.update({ 'data.ads': newads }) + } + } + } + + // This will ensure that every characater at least starts with these new data values. actor-sheet.js may change them. + calculateDerivedValues() { + let saved = !!this.ignoreRender + this.ignoreRender = true + this._initializeStartingValues() + this._applyItemBonuses() + + // Must be done after bonuses, but before weights + this._calculateEncumbranceIssues() + + // Must be after bonuses and encumbrance effects on ST + this._recalcItemFeatures() + this._calculateRangedRanges() + + // Must be done at end + this._calculateWeights() + + let maneuver = this.effects.contents.find(it => it.data.flags?.core?.statusId === 'maneuver') + this.system.conditions.maneuver = !!maneuver ? maneuver.data.flags.gurps.name : 'undefined' + this.ignoreRender = saved + if (!saved) setTimeout(() => this._forceRender(), 500) + } + + // Initialize the attribute starting values/levels. The code is expecting 'value' or 'level' for many things, and instead of changing all of the GUIs and OTF logic + // we are just going to switch the rug out from underneath. "Import" data will be in the 'import' key and then we will calculate value/level when the actor is loaded. + _initializeStartingValues() { + const data = this.system + data.currentdodge = 0 // start at zero, and bonuses will add, and then they will be finalized later + if (!!data.equipment && !data.equipment.carried) data.equipment.carried = {} // data protection + if (!!data.equipment && !data.equipment.other) data.equipment.other = {} + + if (!data.migrationversion) return // Prior to v0.9.6, this did not exist + let v = /** @type {SemanticVersion} */ (SemanticVersion.fromString(data.migrationversion)) + + // Attributes need to have 'value' set because Foundry expects objs with value and max to be attributes (so we can't use currentvalue) + // Need to protect against data errors + for (const attr in data.attributes) { + if (typeof data.attributes[attr] === 'object' && data.attributes[attr] !== null) + if (isNaN(data.attributes[attr].import)) data.attributes[attr].value = 0 + else data.attributes[attr].value = parseInt(data.attributes[attr].import) + } + // After all of the attributes are copied over, apply tired to ST + // if (!!data.conditions.exhausted) + // data.attributes.ST.value = Math.ceil(parseInt(data.attributes.ST.value.toString()) / 2) + recurselist(data.skills, (e, k, d) => { + // @ts-ignore + if (!!e.import) e.level = parseInt(+e.import) + }) + recurselist(data.spells, (e, k, d) => { + // @ts-ignore + if (!!e.import) e.level = parseInt(+e.import) + }) + + // we don't really need to use recurselist for melee/ranged... but who knows, they may become hierarchical in the future + recurselist(data.melee, (e, k, d) => { + if (!!e.import) { + e.level = parseInt(e.import) + if (!isNaN(parseInt(e.parry))) { + // allows for '14f' and 'no' + let base = 3 + Math.floor(e.level / 2) + let bonus = parseInt(e.parry) - base + if (bonus != 0) { + e.parrybonus = (bonus > 0 ? '+' : '') + bonus + } + } + if (!isNaN(parseInt(e.block))) { + let base = 3 + Math.floor(e.level / 2) + let bonus = parseInt(e.block) - base + if (bonus != 0) { + e.blockbonus = (bonus > 0 ? '+' : '') + bonus + } + } + } else { + e.parrybonus = e.parry + e.blockbonus = e.block + } + }) + + recurselist(data.ranged, (e, k, d) => { + e.level = parseInt(e.import) + }) + + // Only prep hitlocation DRs from v0.9.7 or higher (we don't really need to use recurselist... but who knows, hitlocations may become hierarchical in the future) + if (!v.isLowerThan(settings.VERSION_097)) + recurselist(data.hitlocations, (e, k, d) => { + e.dr = e.import + }) + } + + _applyItemBonuses() { + let pi = (/** @type {string | undefined} */ n) => (!!n ? parseInt(n) : 0) + /** @type {string[]} */ + let gids = [] //only allow each global bonus to add once + const data = this.system + for (const item of this.items.contents) { + let itemData = GurpsItem.asGurpsItem(item).system + if (itemData.equipped && itemData.carried && !!itemData.bonuses && !gids.includes(itemData.globalid)) { + gids.push(itemData.globalid) + let bonuses = itemData.bonuses.split('\n') + for (let bonus of bonuses) { + let m = bonus.match(/\[(.*)\]/) + if (!!m) bonus = m[1] // remove extranious [ ] + let link = parselink(bonus) // ATM, we only support attribute and skill + if (!!link.action) { + // start OTF + recurselist(data.melee, (e, k, d) => { + e.level = pi(e.level) + if (link.action.type == 'attribute' && link.action.attrkey == 'DX') { + // All melee attack skills affected by DX + e.level += pi(link.action.mod) + if (!isNaN(parseInt(e.parry))) { + // handles '11f' + let m = (e.parry + '').match(/(\d+)(.*)/) + e.parry = 3 + Math.floor(e.level / 2) + if (!!e.parrybonus) e.parry += pi(e.parrybonus) + if (!!m) e.parry += m[2] + } + if (!isNaN(parseInt(e.block))) { + // handles 'no' + e.block = 3 + Math.floor(e.level / 2) + if (!!e.blockbonus) e.block += pi(e.blockbonus) + } + } + if (link.action.type == 'attack' && !!link.action.isMelee) { + if (e.name.match(makeRegexPatternFrom(link.action.name, false))) { + e.level += pi(link.action.mod) + if (!isNaN(parseInt(e.parry))) { + // handles '11f' + let m = (e.parry + '').match(/(\d+)(.*)/) + e.parry = 3 + Math.floor(e.level / 2) + if (!!e.parrybonus) e.parry += pi(e.parrybonus) + if (!!m) e.parry += m[2] + } + if (!isNaN(parseInt(e.block))) { + // handles 'no' + e.block = 3 + Math.floor(e.level / 2) + if (!!e.blockbonus) e.block += pi(e.blockbonus) + } + } + } + }) // end melee + recurselist(data.ranged, (e, k, d) => { + e.level = pi(e.level) + if (link.action.type == 'attribute' && link.action.attrkey == 'DX') e.level += pi(link.action.mod) + if (link.action.type == 'attack' && !!link.action.isRanged) { + if (e.name.match(makeRegexPatternFrom(link.action.name, false))) e.level += pi(link.action.mod) + } + }) // end ranged + recurselist(data.skills, (e, k, d) => { + e.level = pi(e.level) + if (link.action.type == 'attribute') { + // skills affected by attribute changes + if (e.relativelevel?.toUpperCase().startsWith(link.action.attrkey)) e.level += pi(link.action.mod) + } + if (link.action.type == 'skill-spell' && !link.action.isSpellOnly) { + if (e.name.match(makeRegexPatternFrom(link.action.name, false))) e.level += pi(link.action.mod) + } + }) // end skills + recurselist(data.spells, (e, k, d) => { + e.level = pi(e.level) + if (link.action.type == 'attribute') { + // spells affected by attribute changes + if (e.relativelevel?.toUpperCase().startsWith(link.action.attrkey)) e.level += pi(link.action.mod) + } + if (link.action.type == 'skill-spell' && !link.action.isSkillOnly) { + if (e.name.match(makeRegexPatternFrom(link.action.name, false))) e.level += pi(link.action.mod) + } + }) // end spells + if (link.action.type == 'attribute') { + let paths = link.action.path.split('.') + let last = paths.pop() + let data = this.system + if (paths.length > 0) data = getProperty(data, paths.join('.')) + // regular attributes have a path + else { + // only accept DODGE + if (link.action.attrkey != 'DODGE') break + } + data[last] = pi(data[last]) + pi(link.action.mod) // enforce that attribute is int + } // end attributes & Dodge + } // end OTF + + // parse bonus for other forms, DR+x? + m = bonus.match(/DR *([+-]\d+) *(.*)/) // DR+1 *Arms "Left Leg" ... + if (!!m) { + let delta = parseInt(m[1]) + let locpatterns = null + if (!!m[2]) { + let locs = splitArgs(m[2]) + locpatterns = locs.map(l => new RegExp(makeRegexPatternFrom(l), 'i')) + } + recurselist(data.hitlocations, (e, k, d) => { + if (!locpatterns || locpatterns.find(p => !!e.where && e.where.match(p)) != null) { + let dr = e.dr ?? '' + dr += '' + let m = dr.match(/(\d+) *([/\|]) *(\d+)/) // check for split DR 5|3 or 5/3 + if (!!m) { + dr = parseInt(m[1]) + delta + let dr2 = parseInt(m[3]) + delta + e.dr = dr + m[2] + dr2 + } else if (!isNaN(parseInt(dr))) e.dr = parseInt(dr) + delta + } + }) + } // end DR + } + } + } + } + + /** + * @param {string} key + * @param {any} id + * @returns {string | undefined} + */ + _findEqtkeyForId(key, id) { + var eqtkey + let data = this.system + recurselist(data.equipment.carried, (e, k, d) => { + if (e[key] == id) eqtkey = 'data.equipment.carried.' + k + }) + if (!eqtkey) + recurselist(data.equipment.other, (e, k, d) => { + if (e[key] == id) eqtkey = 'data.equipment.other.' + k + }) + return eqtkey + } + + /** + * @param {{ [key: string]: any }} dict + * @param {string} type + * @returns {number} + */ + _sumeqt(dict, type, checkEquipped = false) { + if (!dict) return 0.0 + let flt = (/** @type {string} */ str) => (!!str ? parseFloat(str) : 0) + let sum = 0 + for (let k in dict) { + let e = dict[k] + let c = flt(e.count) + let t = flt(e[type]) + if (!checkEquipped || !!e.equipped) sum += c * t + sum += this._sumeqt(e.contains, type, checkEquipped) + sum += this._sumeqt(e.collapsed, type, checkEquipped) + } + // @ts-ignore + return parseInt(sum * 100) / 100 + } + + _calculateWeights() { + let data = this.system + let eqt = data.equipment || {} + let eqtsummary = { + eqtcost: this._sumeqt(eqt.carried, 'cost'), + eqtlbs: this._sumeqt( + eqt.carried, + 'weight', + game.settings.get(settings.SYSTEM_NAME, settings.SETTING_CHECK_EQUIPPED) + ), + othercost: this._sumeqt(eqt.other, 'cost'), + } + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ENCUMBRANCE)) + this.checkEncumbance(eqtsummary.eqtlbs) + data.eqtsummary = eqtsummary + } + + _calculateEncumbranceIssues() { + const data = this.system + const encs = data.encumbrance + const isReeling = !!data.conditions.reeling + const isTired = !!data.conditions.exhausted + + // We must assume that the first level of encumbrance has the finally calculated move and dodge settings + if (!!encs) { + const level0 = encs[zeroFill(0)] // if there are encumbrances, there will always be a level0 + let effectiveMove = parseInt(level0.move) + let effectiveDodge = isNaN(parseInt(level0.dodge)) ? '–' : parseInt(level0.dodge) + data.currentdodge + let effectiveSprint = this._getSprintMove() + + if (isReeling) { + effectiveMove = Math.ceil(effectiveMove / 2) + effectiveDodge = isNaN(effectiveDodge) ? '–' : Math.ceil(effectiveDodge / 2) + effectiveSprint = Math.ceil(effectiveSprint / 2) + } + + if (isTired) { + effectiveMove = Math.ceil(effectiveMove / 2) + effectiveDodge = isNaN(effectiveDodge) ? '–' : Math.ceil(effectiveDodge / 2) + effectiveSprint = Math.ceil(effectiveSprint / 2) + } + + for (let enckey in encs) { + let enc = encs[enckey] + let threshold = 1.0 - 0.2 * parseInt(enc.level) // each encumbrance level reduces move by 20% + enc.currentmove = this._getCurrentMove(effectiveMove, threshold) //Math.max(1, Math.floor(m * t)) + enc.currentdodge = isNaN(effectiveDodge) ? '–' : Math.max(1, effectiveDodge - parseInt(enc.level)) + enc.currentsprint = Math.max(1, Math.floor(effectiveSprint * threshold)) + enc.currentmovedisplay = enc.currentmove + // TODO remove additionalresources.showflightmove + // if (!!data.additionalresources?.showflightmove) + enc.currentmovedisplay = this._isEnhancedMove() ? enc.currentmove + '/' + enc.currentsprint : enc.currentmove + if (enc.current) { + // Save the global move/dodge + data.currentmove = enc.currentmove + data.currentdodge = enc.currentdodge + data.currentsprint = enc.currentsprint + } + } + } + + if (!data.equippedparry) data.equippedparry = this.getEquippedParry() + if (!data.equippedblock) data.equippedblock = this.getEquippedBlock() + // catch for older actors that may not have these values set + if (!data.currentmove) data.currentmove = parseInt(data.basicmove.value.toString()) + if (!data.currentdodge && data.dodge.value) data.currentdodge = parseInt(data.dodge.value.toString()) + if (!data.currentflight) data.currentflight = parseFloat(data.basicspeed.value.toString()) * 2 + } + + _isEnhancedMove() { + return !!this._getCurrentMoveMode()?.enhanced + } + + _getSprintMove() { + let current = this._getCurrentMoveMode() + if (!current) return 0 + if (current?.enhanced) return current.enhanced + return Math.floor(current.basic * 1.2) + } + + _getCurrentMoveMode() { + let move = this.system.move + let current = Object.values(move).find(it => it.default) + if (!current && Object.keys(move).length > 0) return move['00000'] + return current + } + + /** + * @param {number} move + * @param {number} threshold + * @returns {number} + */ + _getCurrentMove(move, threshold) { + let inCombat = false + try { + inCombat = !!game.combat?.combatants.filter(c => c.actorId == this.id) + } catch (err) {} // During game startup, an exception is being thrown trying to access 'game.combat' + let updateMove = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_MANEUVER_UPDATES_MOVE) && inCombat + + let maneuver = this._getMoveAdjustedForManeuver(move, threshold) + let posture = this._getMoveAdjustedForPosture(move, threshold) + + if (threshold == 1.0) this.system.conditions.move = maneuver.move < posture.move ? maneuver.text : posture.text + return updateMove + ? maneuver.move < posture.move + ? maneuver.move + : posture.move + : Math.max(1, Math.floor(move * threshold)) + } + + _getMoveAdjustedForManeuver(move, threshold) { + let adjustment = null + + if (foundry.utils.getProperty(this, PROPERTY_MOVEOVERRIDE_MANEUVER)) { + let value = foundry.utils.getProperty(this, PROPERTY_MOVEOVERRIDE_MANEUVER) + let mv = GURPS.Maneuvers.get(this.system.conditions.maneuver) + let reason = !!mv ? i18n(mv.label) : '' + + adjustment = this._adjustMove(move, threshold, value, reason) + } + return !!adjustment + ? adjustment + : { + move: Math.max(1, Math.floor(move * threshold)), + text: i18n('GURPS.moveFull'), + } + } + + _adjustMove(move, threshold, value, reason) { + switch (value) { + case MOVE_NONE: + return { move: 0, text: i18n_f('GURPS.moveNone', { reason: reason }) } + + case MOVE_ONE: + return { + move: 1, + text: i18n_f('GURPS.moveConstant', { value: 1, unit: 'yard', reason: reason }, '1 {unit}/second'), + } + + case MOVE_STEP: + return { move: this._getStep(), text: i18n_f('GURPS.moveStep', { reason: reason }) } + + case MOVE_ONETHIRD: + return { + move: Math.max(1, Math.ceil((move / 3) * threshold)), + text: i18n_f('GURPS.moveOneThird', { reason: reason }), + } + + case MOVE_HALF: + return { + move: Math.max(1, Math.ceil((move / 2) * threshold)), + text: i18n_f('GURPS.moveHalf', { reason: reason }), + } + + case MOVE_TWOTHIRDS: + return { + move: Math.max(1, Math.ceil(((2 * move) / 3) * threshold)), + text: i18n_f('GURPS.moveTwoThirds', { reason: reason }), + } + } + + return null + } + + _getMoveAdjustedForPosture(move, threshold) { + let adjustment = null + + if (foundry.utils.getProperty(this, PROPERTY_MOVEOVERRIDE_POSTURE)) { + let value = foundry.utils.getProperty(this, PROPERTY_MOVEOVERRIDE_POSTURE) + let reason = i18n(GURPS.StatusEffect.lookup(this.system.conditions.posture).label) + adjustment = this._adjustMove(move, threshold, value, reason) + } + + return !!adjustment + ? adjustment + : { + move: Math.max(1, Math.floor(move * threshold)), + text: i18n('GURPS.moveFull'), + } + } + + _calculateRangedRanges() { + if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_CONVERT_RANGED)) return + let st = +this.system.attributes.ST.value + recurselist(this.system.ranged, r => { + let rng = r.range || '' // Data protection + rng = rng + '' // force to string + let m = rng.match(/^ *[xX]([\d\.]+) *$/) + if (m) { + rng = parseFloat(m[1]) * st + } else { + m = rng.match(/^ *[xX]([\d\.]+) *\/ *[xX]([\d\.]+) *$/) + if (m) { + rng = `${parseFloat(m[1]) * st}/${parseFloat(m[2]) * st}` + } + } + r.range = rng + }) + } + + // Once all of the bonuses are applied, determine the actual level for each feature + _recalcItemFeatures() { + let data = this.system + this._collapseQuantumEq(data.melee, true) + this._collapseQuantumEq(data.ranged) + this._collapseQuantumEq(data.skills) + this._collapseQuantumEq(data.spells) + } + + // convert Item feature OTF formulas into actual skill levels. + /** + * @param {Object} list + */ + _collapseQuantumEq(list, isMelee = false) { + recurselist(list, async e => { + let otf = e.otf + if (!!otf) { + let m = otf.match(/\[(.*)\]/) + if (!!m) otf = m[1] // remove extranious [ ] + if (otf.match(/^ *\d+ *$/)) { + // just a number + e.import = parseInt(otf) + } else { + let action = parselink(otf) + if (!!action.action) { + this.ignoreRender = true + action.action.calcOnly = true + GURPS.performAction(action.action, this).then(ret => { + // @ts-ignore + e.level = ret.target + if (isMelee) { + if (!isNaN(parseInt(e.parry))) { + let p = '' + e.parry + let m = p.match(/([+-]\d+)(.*)/) + // @ts-ignore + if (!m && p.trim() == '0') m = [0, 0] // allow '0' to mean 'no bonus', not skill level = 0 + if (!!m) { + e.parrybonus = parseInt(m[1]) + e.parry = e.parrybonus + 3 + Math.floor(e.level / 2) + } + if (!!m && !!m[2]) e.parry = `${e.parry}${m[2]}` + } + if (!isNaN(parseInt(e.block))) { + let b = '' + e.block + let m = b.match(/([+-]\d+)(.*)/) + // @ts-ignore + if (!m && b.trim() == '0') m = [0, 0] // allow '0' to mean 'no bonus', not skill level = 0 + if (!!m) { + e.blockbonus = parseInt(m[1]) + e.block = e.blockbonus + 3 + Math.floor(e.level / 2) + } + if (!!m && !!m[2]) e.block = `${e.block}${m[2]}` + } + } + }) + } + } + } + }) + } + + _getStep() { + let step = Math.ceil(parseInt(this.system.basicmove.value.toString()) / 10) + return Math.max(1, step) + } + + /** + * For every application associated to this actor, refresh it to reflect any updates. + */ + _renderAllApps() { + Object.values(this.apps).forEach(it => it.render(false)) + } + + /** + * Update this Document using incremental data, saving it to the database. + * @see {@link Document.updateDocuments} + * @param {any} data - Differential update data which modifies the existing values of this document data + * (default: `{}`) + * @param {any} [context] - Additional context which customizes the update workflow (default: `{}`) + * @returns {Promise} The updated Document instance + * @remarks If no document has actually been updated, the returned {@link Promise} resolves to `undefined`. + */ + async update(data, context) { + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATIC_ONETHIRD)) { + if (data.hasOwnProperty('data.HP.value')) { + let flag = data['data.HP.value'] < this.system.HP.max / 3 + if (!!this.system.conditions.reeling != flag) { + this.toggleEffectByName('reeling', flag) + + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_CHAT_FOR_REELING_TIRED)) { + // send the chat message + let tag = flag ? 'GURPS.chatTurnOnReeling' : 'GURPS.chatTurnOffReeling' + let msg = i18n_f(tag, { name: this.displayname, pdfref: i18n('GURPS.pdfReeling') }) + this.sendChatMessage(msg) + } + + // update the combat tracker to show/remove condition + ui.combat?.render() + } + } + if (data.hasOwnProperty('data.FP.value')) { + let flag = data['data.FP.value'] < this.system.FP.max / 3 + if (!!this.system.conditions.exhausted != flag) { + this.toggleEffectByName('exhausted', flag) + + // send the chat message + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SHOW_CHAT_FOR_REELING_TIRED)) { + let tag = flag ? 'GURPS.chatTurnOnTired' : 'GURPS.chatTurnOffTired' + let msg = i18n_f(tag, { name: this.displayname, pdfref: i18n('GURPS.pdfTired') }) + this.sendChatMessage(msg) + } + + // update the combat tracker to show/remove condition + ui.combat?.render() + } + } + } + + return await super.update(data, context) + } + + sendChatMessage(msg) { + let self = this + + renderTemplate('systems/gurps/templates/chat-processing.html', { lines: [msg] }).then(content => { + let users = self.getOwners() + let ids = /** @type {string[] | undefined} */ (users?.map(it => it.id)) + + let messageData = { + content: content, + whisper: ids || null, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + } + ChatMessage.create(messageData) + }) + } + + async internalUpdate(data, context) { + let ctx = { render: !this.ignoreRender } + if (!!context) ctx = { ...context, ...ctx } + await this.update(data, ctx) + } + + /** + * This method is called when "system.conditions.maneuver" changes on the actor (via the update method) + * @param {string} maneuverText + */ + async replaceManeuver(maneuverText) { + let tokens = this._findTokens() + if (tokens) for (const t of tokens) await t.setManeuver(maneuverText) + } + + async replacePosture(changeData) { + let tokens = this._findTokens() + if (tokens) + for (const t of tokens) { + let id = changeData === 'standing' ? this.system.conditions.posture : changeData + await t.toggleEffect(GURPS.StatusEffect.lookup(id)) + } + } + + /** + * @returns {GurpsToken[]} + */ + _findTokens() { + if (this.isToken && this.token?.layer) { + let token = /** @type {GurpsToken} */ (this.token.object) + return [token] + } + return this.getActiveTokens().map(it => /** @type {GurpsToken} */ (it)) + } + + /** + * @param {{ id: unknown; }} effect + */ + isEffectActive(effect) { + for (const it of this.effects) { + let statusId = it.getFlag('core', 'statusId') + if (statusId === effect.id) return true + } + + return false + } + + get _additionalResources() { + return this.system.additionalresources + } + + get displayname() { + let n = this.name + if (!!this.token && this.token.name != n) n = this.token.name + '(' + n + ')' + return n + } + + /** + * + * @param {Object} action + * @param {string} action.orig - the original OTF string + * @param {string} action.costs - "*(per|cost) ${number} ${resource}" -- resource can be '@resource' to prompt user + * @param {string} action.formula - the basic die formula, such as '2d', '1d-2', '3d-1x2', etc. + * @param {string} action.damagetype - one of the recognized damage types (cr, cut, imp, etc) + * @param {string} action.extdamagetype - optional extra damage type, such as 'ex' + * @param {string} action.hitlocation - optional hit location + * @param {boolean} action.accumulate + */ + async accumulateDamageRoll(action) { + // define a new actor property, damageAccumulators, which is an array of object: + // { + // otf: action.orig, + // dieroll: action.formula, + // damagetype: action.damagetype, + // damagemod: action.extdamagetype, + // cost: the value parsed out of action.cost, optional + // resource: the value parsed out of action.cost, optional + // count: -- the accumulator, i.e., how many times this damage is to be invoked. + // } + + // initialize the damageAccumulators property if it doesn't exist: + if (!this.system.conditions.damageAccumulators) this.system.conditions.damageAccumulators = [] + + let accumulators = this.system.conditions.damageAccumulators + + // first, try to find an existing accumulator, and increment if found + let existing = accumulators.findIndex(it => it.orig === action.orig) + if (existing !== -1) return this.incrementDamageAccumulator(existing) + + // an existing accumulator is not found, create one + action.count = 1 + delete action.accumulate + accumulators.push(action) + await this.update({ 'data.conditions.damageAccumulators': accumulators }) + GURPS.ModifierBucket.render() + //console.log(accumulators) + } + + get damageAccumulators() { + return this.system.conditions.damageAccumulators + } + + async incrementDamageAccumulator(index) { + this.damageAccumulators[index].count++ + await this.update({ 'data.conditions.damageAccumulators': this.damageAccumulators }) + GURPS.ModifierBucket.render() + } + + async decrementDamageAccumulator(index) { + this.damageAccumulators[index].count-- + if (this.damageAccumulators[index].count < 1) this.damageAccumulators.splice(index, 1) + await this.update({ 'data.conditions.damageAccumulators': this.damageAccumulators }) + GURPS.ModifierBucket.render() + } + + async clearDamageAccumulator(index) { + this.damageAccumulators.splice(index, 1) + await this.update({ 'data.conditions.damageAccumulators': this.damageAccumulators }) + GURPS.ModifierBucket.render() + } + + async applyDamageAccumulator(index) { + let accumulator = this.damageAccumulators[index] + let roll = multiplyDice(accumulator.formula, accumulator.count) + if (accumulator.costs) { + let costs = accumulator.costs.match(COSTS_REGEX) + if (!!costs) { + accumulator.costs = `*${costs.groups.verb} ${accumulator.count * costs.groups.cost} ${costs.groups.type}` + } + } + accumulator.formula = roll + this.damageAccumulators.splice(index, 1) + await this.update({ 'data.conditions.damageAccumulators': this.damageAccumulators }) + await GURPS.performAction(accumulator, GURPS.LastActor) + } + + async importCharacter() { + let p = this.system.additionalresources.importpath + if (!!p) { + let m = p.match(/.*[/\\]Data[/\\](.*)/) + if (!!m) { + let f = m[1].replace(/\\/g, '/') + let xhr = new XMLHttpRequest() + xhr.responseType = 'arraybuffer' + xhr.open('GET', f) + + let promise = new Promise(resolve => { + xhr.onload = () => { + if (xhr.status === 200) { + // @ts-ignore + let s = arrayBuffertoBase64(xhr.response) + // @ts-ignore + this.importFromGCSv1(s, m[1], p) + } else this._openImportDialog() + resolve(this) + } + }) + xhr.send(null) + } else this._openImportDialog() + } else this._openImportDialog() + } + + async _openImportDialog() { + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_USE_BROWSER_IMPORTER)) + this._openNonLocallyHostedImportDialog() + else this._openLocallyHostedImportDialog() + } + + async _openNonLocallyHostedImportDialog() { + try { + const file = await SmartImporter.getFileForActor(this) + const res = await this.importFromGCSv1(await file.text(), file.name) + if (res) SmartImporter.setFileForActor(this, file) + } catch (e) { + ui.notifications?.error(e) + throw e + } + } + + async _openLocallyHostedImportDialog() { + setTimeout(async () => { + new Dialog( + { + title: `Import character data for: ${this.name}`, + content: await renderTemplate('systems/gurps/templates/import-gcs-v1-data.html', { + name: '"' + this.name + '"', + }), + buttons: { + import: { + icon: '', + label: 'Import', + callback: html => { + const form = html.find('form')[0] + let files = form.data.files + let file = null + if (!files.length) { + return ui.notifications.error('You did not upload a data file!') + } else { + file = files[0] + GURPS.readTextFromFile(file).then(text => this.importFromGCSv1(text, file.name, file.path)) + } + }, + }, + no: { + icon: '', + label: 'Cancel', + }, + }, + default: 'import', + }, + { + width: 400, + } + ).render(true) + }, 200) + } + + /** + * + * @param {{ [key: string]: any}} json + */ + async importAttributesFromGCSv2(atts, eqp, calc) { + if (!atts) return + let data = this.system + let att = data.attributes + if (!att.QN) { + // upgrade older actors to include Q + att.QN = {} + data.QP = {} + } + + att.ST.import = atts.find(e => e.attr_id === 'st')?.calc?.value || 0 + att.ST.points = atts.find(e => e.attr_id === 'st')?.calc?.points || 0 + att.DX.import = atts.find(e => e.attr_id === 'dx')?.calc?.value || 0 + att.DX.points = atts.find(e => e.attr_id === 'dx')?.calc?.points || 0 + att.IQ.import = atts.find(e => e.attr_id === 'iq')?.calc?.value || 0 + att.IQ.points = atts.find(e => e.attr_id === 'iq')?.calc?.points || 0 + att.HT.import = atts.find(e => e.attr_id === 'ht')?.calc?.value || 0 + att.HT.points = atts.find(e => e.attr_id === 'ht')?.calc?.points || 0 + att.WILL.import = atts.find(e => e.attr_id === 'will')?.calc?.value || 0 + att.WILL.points = atts.find(e => e.attr_id === 'will')?.calc?.points || 0 + att.PER.import = atts.find(e => e.attr_id === 'per')?.calc?.value || 0 + att.PER.points = atts.find(e => e.attr_id === 'per')?.calc?.points || 0 + att.QN.import = atts.find(e => e.attr_id === 'qn')?.calc?.value || 0 + att.QN.points = atts.find(e => e.attr_id === 'qn')?.calc?.points || 0 + + data.HP.max = atts.find(e => e.attr_id === 'hp')?.calc?.value || 0 + data.HP.points = atts.find(e => e.attr_id === 'hp')?.calc?.points || 0 + data.FP.max = atts.find(e => e.attr_id === 'fp')?.calc?.value || 0 + data.FP.points = atts.find(e => e.attr_id === 'fp')?.calc?.points || 0 + data.QP.max = atts.find(e => e.attr_id === 'qp')?.calc?.value || 0 + data.QP.points = atts.find(e => e.attr_id === 'qp')?.calc?.points || 0 + let hp = atts.find(e => e.attr_id === 'hp')?.calc?.current || 0 + let fp = atts.find(e => e.attr_id === 'fp')?.calc?.current || 0 + let qp = atts.find(e => e.attr_id === 'qp')?.calc?.current || 0 + + let saveCurrent = false + + if (!!data.lastImport && (data.HP.value != hp || data.FP.value != fp)) { + let option = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IMPORT_HP_FP) + if (option == 0) { + saveCurrent = true + } + if (option == 2) { + saveCurrent = await new Promise((resolve, reject) => { + let d = new Dialog({ + title: 'Current HP & FP', + content: `Do you want to

    Save the current HP (${data.HP.value}) & FP (${data.FP.value}) values or

    Overwrite it with the import data, HP (${hp}) & FP (${fp})?

     `, + buttons: { + save: { + icon: '', + label: 'Save', + callback: () => resolve(true), + }, + overwrite: { + icon: '', + label: 'Overwrite', + callback: () => resolve(false), + }, + }, + default: 'save', + close: () => resolve(false), // just assume overwrite. Error handling would be too much work right now. + }) + d.render(true) + }) + } + } + if (!saveCurrent) { + data.HP.value = hp + data.FP.value = fp + } + data.QP.value = qp + + let bl_value = parseFloat(calc?.basic_lift.match(/[\d\.]+/g)) + let bl_unit = calc?.basic_lift.replace(bl_value + ' ', '') + + let lm = {} + lm.basiclift = (bl_value * 1).toString() + ' ' + bl_unit + lm.carryonback = (bl_value * 15).toString() + ' ' + bl_unit + lm.onehandedlift = (bl_value * 2).toString() + ' ' + bl_unit + lm.runningshove = (bl_value * 24).toString() + ' ' + bl_unit + lm.shiftslightly = (bl_value * 50).toString() + ' ' + bl_unit + lm.shove = (bl_value * 12).toString() + ' ' + bl_unit + lm.twohandedlift = (bl_value * 8).toString() + ' ' + bl_unit + + let bm = atts.find(e => e.attr_id === 'basic_move')?.calc?.value || 0 + data.basicmove.value = bm.toString() + data.basicmove.points = atts.find(e => e.attr_id === 'basic_move')?.calc?.points || 0 + let bs = atts.find(e => e.attr_id === 'basic_speed')?.calc?.value || 0 + data.basicspeed.value = bs.toString() + data.basicspeed.points = atts.find(e => e.attr_id === 'basic_speed')?.calc?.points || 0 + + data.thrust = calc?.thrust + data.swing = calc?.swing + data.currentmove = data.basicmove.value + data.frightcheck = atts.find(e => e.attr_id === 'fright_check')?.calc?.value || 0 + + data.hearing = atts.find(e => e.attr_id === 'hearing')?.calc?.value || 0 + data.tastesmell = atts.find(e => e.attr_id === 'taste_smell')?.calc?.value || 0 + data.touch = atts.find(e => e.attr_id === 'touch')?.calc?.value || 0 + data.vision = atts.find(e => e.attr_id === 'vision')?.calc?.value || 0 + + let cm = 0 + let cd = 0 + let es = {} + let ew = [1, 2, 3, 6, 10] + let index = 0 + let total_carried = this.calcTotalCarried(eqp) + for (let i = 0; i <= 4; i++) { + let e = new Encumbrance() + e.level = i + e.current = false + e.key = 'enc' + i + let weight_value = bl_value * ew[i] + // e.current = total_carried <= weight_value && (i == 4 || total_carried < bl_value*ew[i+1]); + e.current = + (total_carried < weight_value || i == 4 || bl_value == 0) && (i == 0 || total_carried > bl_value * ew[i - 1]) + e.weight = weight_value.toString() + ' ' + bl_unit + e.move = calc?.move[i].toString() + e.dodge = calc?.dodge[i] + if (e.current) { + cm = e.move + cd = e.dodge + } + GURPS.put(es, e, index++) + } + + return { + 'data.attributes': att, + 'data.HP': data.HP, + 'data.FP': data.FP, + 'data.basiclift': data.basiclift, + 'data.basicmove': data.basicmove, + 'data.basicspeed': data.basicspeed, + 'data.thrust': data.thrust, + 'data.swing': data.swing, + 'data.currentmove': data.currentmove, + 'data.frightcheck': data.frightcheck, + 'data.hearing': data.hearing, + 'data.tastesmell': data.tastesmell, + 'data.touch': data.touch, + 'data.vision': data.vision, + 'data.liftingmoving': lm, + 'data.currentmove': cm, + 'data.currentdodge': cd, + 'data.-=encumbrance': null, + 'data.encumbrance': es, + 'data.QP': data.QP, + } + } + + calcTotalCarried(eqp) { + let t = 0 + if (!eqp) return t + for (let i of eqp) { + let w = 0 + w += parseFloat(i.weight || '0') * (i.type == 'equipment_container' ? 1 : i.quantity || 0) + if (i.children?.length) w += this.calcTotalCarried(i.children) + t += w + } + return t + } + + async importTraitsFromGCSv2(p, cd, md) { + if (!p) return + let ts = {} + ts.race = '' + ts.height = p.height || '' + ts.weight = p.weight || '' + ts.age = p.age || '' + ts.title = p.title || '' + ts.player = p.player_name || '' + ts.createdon = cd || '' + ts.modifiedon = md || '' + ts.religion = p.religion || '' + ts.birthday = p.birthday || '' + ts.hand = p.handedness || '' + ts.techlevel = p.tech_level || '' + ts.gender = p.gender || '' + ts.eyes = p.eyes || '' + ts.hair = p.hair || '' + ts.skin = p.skin || '' + + const r = { + 'data.-=traits': null, + 'data.traits': ts, + } + + if (!!p.portrait && game.settings.get(settings.SYSTEM_NAME, settings.SETTING_OVERWRITE_PORTRAITS)) { + const path = this.getPortraitPath() + let currentDir = '' + for (let i = 0; i < path.split('/').length; i++) { + try { + currentDir += path.split('/')[i] + '/' + await FilePicker.createDirectory('data', currentDir) + } catch (err) { + continue + } + } + const filename = `${this.removeAccents(p.name)}${this.id}_portrait.png`.replaceAll(' ', '_') + const url = `data:image/png;base64,${p.portrait}` + await fetch(url) + .then(res => res.blob()) + .then(blob => { + const file = new File([blob], filename) + FilePicker.upload('data', path, file, {}, { notify: false }) + }) + r.img = (path + '/' + filename).replaceAll(' ', '_').replaceAll('//', '/') + } + return r + } + + getPortraitPath() { + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_PORTRAIT_PATH) == 'global') return 'images/portraits/' + return `worlds/${game.world.id}/images/portraits` + } + + removeAccents(str) { + return str + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // Remove accents + .replace(/([^\w]+|\s+)/g, '-') // Replace space and other characters by hyphen + .replace(/\-\-+/g, '-') // Replaces multiple hyphens by one hyphen + .replace(/(^-+|-+$)/g, '') + } + + signedNum(x) { + if (x >= 0) return `+${x}` + else return x.toString() + } + + importSizeFromGCSv1(commit, profile, ads, skills, equipment) { + let ts = commit['data.traits'] + let final = profile.SM || 0 + let temp = [].concat(ads, skills, equipment) + let all = [] + for (let i of temp) { + all = all.concat(this.recursiveGet(i)) + } + for (let i of all) { + if (i.features?.length) + for (let f of i.features) { + if (f.type == 'attribute_bonus' && f.attribute == 'sm') + final += f.amount * (!!i.levels ? parseFloat(i.levels) : 1) + } + } + ts.sizemod = this.signedNum(final) + return { + 'data.-=traits': null, + 'data.traits': ts, + } + } + + importAdsFromGCSv3(ads) { + let temp = [] + if (!!ads) + for (let i of ads) { + temp = temp.concat(this.importAd(i, '')) + } + return { + 'data.-=ads': null, + 'data.ads': this.foldList(temp), + } + } + + importAd(i, p) { + let a = new Advantage() + a.name = i.name + (i.levels ? ' ' + i.levels.toString() : '') || 'Advantage' + a.points = i.calc?.points + a.note = i.notes + a.userdesc = i.userdesc + a.notes = '' + + if (i.cr != null) { + a.notes = '[' + game.i18n.localize('GURPS.CR' + i.cr.toString()) + ': ' + a.name + ']' + } + if (i.modifiers?.length) { + for (let j of i.modifiers) + if (!j.disabled) a.notes += `${!!a.notes ? '; ' : ''}${j.name}${!!j.notes ? ' (' + j.notes + ')' : ''}` + } + if (!!a.note) a.notes += (!!a.notes ? '\n' : '') + a.note + if (!!a.userdesc) a.notes += (!!a.notes ? '\n' : '') + a.userdesc + a.pageRef(i.reference) + a.uuid = i.id + a.parentuuid = p + + let old = this._findElementIn('ads', a.uuid) + this._migrateOtfsAndNotes(old, a, i.vtt_notes) + + let ch = [] + if (i.children?.length) { + for (let j of i.children) ch = ch.concat(this.importAd(j, i.id)) + } + return [a].concat(ch) + } + + importSkillsFromGCSv2(sks) { + if (!sks) return + let temp = [] + for (let i of sks) { + temp = temp.concat(this.importSk(i, '')) + } + return { + 'data.-=skills': null, + 'data.skills': this.foldList(temp), + } + } + + importSk(i, p) { + let name = + i.name + (!!i.tech_level ? `/TL${i.tech_level}` : '') + (!!i.specialization ? ` (${i.specialization})` : '') || + 'Skill' + if (i.type == 'technique' && !!i.default) { + let addition = '' + addition = ' (' + i.default.name + if (!!i.default.specialization) { + addition += ' (' + i.default.specialization + ')' + } + name += addition + ')' + } + let s = new Skill(name, '') + s.pageRef(i.reference || '') + s.uuid = i.id + s.parentuuid = p + if (['skill', 'technique'].includes(i.type)) { + s.type = i.type.toUpperCase() + s.import = !!i.calc ? i.calc.level : '' + if (s.level == 0) s.level = '' + s.points = i.points + s.relativelevel = i.calc?.rsl + s.notes = i.notes || '' + } else { + // Usually containers + s.level = '' + } + let old = this._findElementIn('skills', s.uuid) + this._migrateOtfsAndNotes(old, s, i.vtt_notes) + + let ch = [] + if (i.children?.length) { + for (let j of i.children) ch = ch.concat(this.importSk(j, i.id)) + } + return [s].concat(ch) + } + + importSpellsFromGCSv2(sps) { + if (!sps) return + let temp = [] + for (let i of sps) { + temp = temp.concat(this.importSp(i, '')) + } + return { + 'data.-=spells': null, + 'data.spells': this.foldList(temp), + } + } + + importSp(i, p) { + let s = new Spell() + s.name = i.name || 'Spell' + s.uuid = i.id + s.parentuuid = p + s.pageRef(i.reference || '') + if (['spell', 'ritual_magic_spell'].includes(i.type)) { + s.class = i.spell_class || '' + s.college = i.college || '' + s.cost = i.casting_cost || '' + s.maintain = i.maintenance_cost || '' + s.difficulty = i.difficulty.toUpperCase() + s.relativelevel = i.calc?.rsl + s.notes = i.notes || '' + s.duration = i.duration || '' + s.points = i.points || '' + s.casttime = i.casting_time || '' + s.import = i.calc?.level || 0 + } + + let old = this._findElementIn('spells', s.uuid) + this._migrateOtfsAndNotes(old, s, i.vtt_notes) + + let ch = [] + if (i.children?.length) { + for (let j of i.children) ch = ch.concat(this.importSp(j, i.id)) + } + return [s].concat(ch) + } + + importEquipmentFromGCSv2(eq, oeq) { + if (!eq && !oeq) return + let temp = [] + if (!!eq) + for (let i of eq) { + temp = temp.concat(this.importEq(i, '', true)) + } + if (!!oeq) + for (let i of oeq) { + temp = temp.concat(this.importEq(i, '', false)) + } + + recurselist(this.system.equipment?.carried, t => { + t.carried = true + if (!!t.save) temp.push(t) + }) + recurselist(this.system.equipment?.other, t => { + t.carried = false + if (!!t.save) temp.push(t) + }) + + temp.forEach(e => { + e.contains = {} + e.collapsed = {} + }) + + temp.forEach(e => { + if (!!e.parentuuid) { + let parent = null + parent = temp.find(f => f.uuid === e.parentuuid) + if (!!parent) GURPS.put(parent.contains, e) + else e.parentuuid = '' + } + }) + + let equipment = { + carried: {}, + other: {}, + } + let cindex = 0 + let oindex = 0 + + temp.forEach(eqt => { + Equipment.calc(eqt) + if (!eqt.parentuuid) { + if (eqt.carried) GURPS.put(equipment.carried, eqt, cindex++) + else GURPS.put(equipment.other, eqt, oindex++) + } + }) + return { + 'data.-=equipment': null, + 'data.equipment': equipment, + } + } + + importEq(i, p, carried) { + let e = new Equipment() + e.name = i.description || 'Equipment' + e.count = i.type == 'equipment_container' ? '1' : i.quantity || '0' + e.cost = + (parseFloat(i.calc?.extended_value) / (i.type == 'equipment_container' ? 1 : i.quantity || 1)).toString() || '' + e.carried = carried + e.equipped = i.equipped + e.techlevel = i.tech_level || '' + e.legalityclass = i.legality_class || '4' + e.categories = i.categories?.join(', ') || '' + e.uses = i.uses || 0 + e.maxuses = i.max_uses || 0 + e.uuid = i.id + e.parentuuid = p + e.notes = '' + e.note = i.notes || '' + if (i.modifiers?.length) { + for (let j of i.modifiers) + if (!j.disabled) e.notes += `${!!e.notes ? '; ' : ''}${j.name}${!!j.notes ? ' (' + j.notes + ')' : ''}` + } + if (!!e.note) e.notes += (!!e.notes ? '\n' : '') + e.note + e.weight = + (parseFloat(i.calc?.extended_weight) / (i.type == 'equipment_container' ? 1 : i.quantity || 1)).toString() || '0' + e.pageRef(i.reference || '') + let old = this._findElementIn('equipment.carried', e.uuid) + if (!old) old = this._findElementIn('equipment.other', e.uuid) + this._migrateOtfsAndNotes(old, e, i.vtt_notes) + if (!!old) { + e.carried = old.carried + e.equipped = old.equipped + e.parentuuid = old.parentuuid + if (old.ignoreImportQty) { + e.count = old.count + e.uses = old.uses + e.maxuses = old.maxuses + e.ignoreImportQty = true + } + } + let ch = [] + if (i.children?.length) { + for (let j of i.children) ch = ch.concat(this.importEq(j, i.id, carried)) + for (let j of ch) { + e.cost -= j.cost * j.count + e.weight -= j.weight * j.count + } + // let weight_reduction = 0; + // if (!!i.modifiers?.length) for (let m of i.modifiers) if (!m.disabled && !!m.features?.length) for (let mf of m.features) if (mf.type == "contained_weight_reduction") weight_reduction += parseFloat(mf.reduction); + // if (!!i.features?.length) for (let f of i.features) if (f.type == "contained_weight_reduction") weight_reduction += parseFloat(f.reduction); + // for (let j of ch) { + // e.cost -= j.cost*j.count; + // if (weight_reduction == 0) e.weight -= j.weight*j.count; + // else { + // weight_reduction -= j.weight*j.count; + // if (weight_reduction < 0) { + // e.weight += weight_reduction; + // weight_reduction = 0; + // } + // } + // } + } + return [e].concat(ch) + } + + importNotesFromGCSv2(notes) { + if (!notes) return + let temp = [] + for (let i of notes) { + temp = temp.concat(this.importNote(i, '')) + } + recurselist(this.system.notes, t => { + if (!!t.save) temp.push(t) + }) + return { + 'data.-=notes': null, + 'data.notes': this.foldList(temp), + } + } + + importNote(i, p) { + let n = new Note() + n.notes = i.text || '' + n.uuid = i.id + n.parentuuid = p + n.pageRef(i.reference || '') + let old = this._findElementIn('notes', n.uuid) + this._migrateOtfsAndNotes(old, n) + let ch = [] + if (i.children?.length) { + for (let j of i.children) ch = ch.concat(this.importNote(j, i.id)) + } + return [n].concat(ch) + } + + async importProtectionFromGCSv2(hls) { + if (!hls) return + let data = this.system + if (!!data.additionalresources.ignoreinputbodyplan) return + + /** @type {HitLocations.HitLocation[]} */ + let locations = [] + for (let i of hls.locations) { + let l = new HitLocations.HitLocation(i.table_name) + l.import = i.calc?.dr.all?.toString() || '0' + for (let [key, value] of Object.entries(i.calc?.dr)) + if (key != 'all') { + let damtype = GURPS.DamageTables.damageTypeMap[key] + if (!l.split) l.split = {} + l.split[damtype] = +l.import + value + } + l.penalty = i.hit_penalty.toString() + while (locations.filter(it => it.where == l.where).length > 0) { + l.where = l.where + '*' + } + locations.push(l) + } + let vitals = locations.filter(value => value.where === HitLocations.HitLocation.VITALS) + if (vitals.length === 0) { + let hl = new HitLocations.HitLocation(HitLocations.HitLocation.VITALS) + hl.penalty = HitLocations.hitlocationRolls[HitLocations.HitLocation.VITALS].penalty + hl.roll = HitLocations.hitlocationRolls[HitLocations.HitLocation.VITALS].roll + hl.import = '0' + locations.push(hl) + } + // Hit Locations MUST come from an existing bodyplan hit location table, or else ADD (and + // potentially other features) will not work. Sometime in the future, we will look at + // user-entered hit locations. + let bodyplan = hls.id // Was a body plan actually in the import? + if (bodyplan === 'snakemen') bodyplan = 'snakeman' + let table = HitLocations.hitlocationDictionary[bodyplan] // If so, try to use it. + + /** @type {HitLocations.HitLocation[]} */ + let locs = [] + locations.forEach(e => { + if (!!table && !!table[e.where]) { + // if e.where already exists in table, don't map + locs.push(e) + } else { + // map to new name(s) ... sometimes we map 'Legs' to ['Right Leg', 'Left Leg'], for example. + e.locations(false).forEach(l => locs.push(l)) // Map to new names + } + }) + locations = locs + + if (!table) { + locs = [] + locations.forEach(e => { + e.locations(true).forEach(l => locs.push(l)) // Map to new names, but include original to help match against tables + }) + bodyplan = this._getBodyPlan(locs) + table = HitLocations.hitlocationDictionary[bodyplan] + } + // update location's roll and penalty based on the bodyplan + + if (!!table) { + Object.values(locations).forEach(it => { + let [lbl, entry] = HitLocations.HitLocation.findTableEntry(table, it.where) + if (!!entry) { + it.where = lbl // It might be renamed (ex: Skull -> Brain) + if (!it.penalty) it.penalty = entry.penalty + if (!it.roll || it.roll.length === 0 || it.roll === HitLocations.HitLocation.DEFAULT) it.roll = entry.roll + } + }) + } + + // write the hit locations out in bodyplan hit location table order. If there are + // other entries, append them at the end. + /** @type {HitLocations.HitLocation[]} */ + let temp = [] + Object.keys(table).forEach(key => { + let results = Object.values(locations).filter(loc => loc.where === key) + if (results.length > 0) { + if (results.length > 1) { + // If multiple locs have same where, concat the DRs. Leg 7 & Leg 8 both map to "Leg 7-8" + let d = '' + + /** @type {string | null} */ + let last = null + results.forEach(r => { + if (r.import != last) { + d += '|' + r.import + last = r.import + } + }) + + if (!!d) d = d.substr(1) + results[0].import = d + } + temp.push(results[0]) + locations = locations.filter(it => it.where !== key) + } else { + // Didn't find loc that should be in the table. Make a default entry + temp.push(new HitLocations.HitLocation(key, '0', table[key].penalty, table[key].roll)) + } + }) + locations.forEach(it => temp.push(it)) + + let prot = {} + let index = 0 + temp.forEach(it => GURPS.put(prot, it, index++)) + + let saveprot = true + if (!!data.lastImport && !!data.additionalresources.bodyplan && bodyplan != data.additionalresources.bodyplan) { + let option = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IMPORT_BODYPLAN) + if (option == 1) { + saveprot = false + } + if (option == 2) { + saveprot = await new Promise((resolve, reject) => { + let d = new Dialog({ + title: 'Hit Location Body Plan', + content: + `Do you want to

    Save the current Body Plan (${game.i18n.localize( + 'GURPS.BODYPLAN' + data.additionalresources.bodyplan + )}) or ` + + `

    Overwrite it with the Body Plan from the import: (${game.i18n.localize( + 'GURPS.BODYPLAN' + bodyplan + )})?

     `, + buttons: { + save: { + icon: '', + label: 'Save', + callback: () => resolve(false), + }, + overwrite: { + icon: '', + label: 'Overwrite', + callback: () => resolve(true), + }, + }, + default: 'save', + close: () => resolve(false), // just assume overwrite. Error handling would be too much work right now. + }) + d.render(true) + }) + } + } + if (saveprot) { + return { + 'data.-=hitlocations': null, + 'data.hitlocations': prot, + 'data.additionalresources.bodyplan': bodyplan, + } + } else return {} + } + + importPointTotalsFromGCSv2(total, atts, ads, skills, spells) { + if (!ads) ads = [] + if (!skills) skills = [] + if (!spells) spells = [] + let p_atts = 0 + let p_ads = 0 + let p_disads = 0 + let p_quirks = 0 + let p_skills = 0 + let p_spells = 0 + let p_unspent = total + let p_total = total + let p_race = 0 + for (let i of atts) p_atts += i.calc?.points + for (let i of ads) + [p_ads, p_disads, p_quirks, p_race] = this.adPointCount(i, p_ads, p_disads, p_quirks, p_race, true) + for (let i of skills) p_skills = this.skPointCount(i, p_skills) + for (let i of spells) p_spells = this.skPointCount(i, p_spells) + p_unspent -= p_atts + p_ads + p_disads + p_quirks + p_skills + p_spells + p_race + return { + 'data.totalpoints.attributes': p_atts, + 'data.totalpoints.ads': p_ads, + 'data.totalpoints.disads': p_disads, + 'data.totalpoints.quirks': p_quirks, + 'data.totalpoints.skills': p_skills, + 'data.totalpoints.spells': p_spells, + 'data.totalpoints.unspent': p_unspent, + 'data.totalpoints.total': p_total, + 'data.totalpoints.race': p_race, + } + } + + importReactionsFromGCSv3(ads, skills, equipment) { + let rs = {} + let cs = {} + let index_r = 0 + let index_c = 0 + let temp = [].concat(ads, skills, equipment) + let all = [] + for (let i of temp) { + all = all.concat(this.recursiveGet(i)) + } + let temp_r = [] + let temp_c = [] + for (let i of all) { + if (i.features?.length) + for (let f of i.features) { + if (f.type == 'reaction_bonus') { + temp_r.push({ + modifier: f.amount * (f.per_level && !!i.levels ? parseInt(i.levels) : 1), + situation: f.situation, + }) + } else if (f.type == 'conditional_modifier') { + temp_c.push({ + modifier: f.amount * (f.per_level && !!i.levels ? parseInt(i.levels) : 1), + situation: f.situation, + }) + } + } + } + let temp_r2 = [] + let temp_c2 = [] + for (let i of temp_r) { + let existing_condition = temp_r2.find(e => e.situation == i.situation) + if (!!existing_condition) existing_condition.modifier += i.modifier + else temp_r2.push(i) + } + for (let i of temp_c) { + let existing_condition = temp_c2.find(e => e.situation == i.situation) + if (!!existing_condition) existing_condition.modifier += i.modifier + else temp_c2.push(i) + } + for (let i of temp_r2) { + let r = new Reaction() + r.modifier = i.modifier.toString() + r.situation = i.situation + GURPS.put(rs, r, index_r++) + } + for (let i of temp_c2) { + let c = new Modifier() + c.modifier = i.modifier.toString() + c.situation = i.situation + GURPS.put(cs, c, index_c++) + } + return { + 'data.-=reactions': null, + 'data.reactions': rs, + 'data.-=conditionalmods': null, + 'data.conditionalmods': cs, + } + } + + importCombatFromGCSv2(ads, skills, spells, equipment) { + let melee = {} + let ranged = {} + let m_index = 0 + let r_index = 0 + let temp = [].concat(ads, skills, spells, equipment) + let all = [] + for (let i of temp) { + all = all.concat(this.recursiveGet(i)) + } + for (let i of all) { + if (i.weapons?.length) + for (let w of i.weapons) { + if (w.type == 'melee_weapon') { + let m = new Melee() + m.name = i.name || i.description || '' + m.st = w.strength || '' + m.weight = i.weight || '' + m.techlevel = i.tech_level || '' + m.cost = i.value || '' + m.notes = i.notes || '' + if (!!m.notes && w.usage_notes) m.notes += '\n' + w.usage_notes + m.pageRef(i.reference || '') + m.mode = w.usage || '' + m.import = w.calc?.level.toString() || '0' + m.damage = w.calc?.damage || '' + m.reach = w.reach || '' + m.parry = w.calc?.parry || '' + m.block = w.calc?.block || '' + let old = this._findElementIn('melee', false, m.name, m.mode) + this._migrateOtfsAndNotes(old, m, i.vtt_notes) + + GURPS.put(melee, m, m_index++) + } else if (w.type == 'ranged_weapon') { + let r = new Ranged() + r.name = i.name || i.description || '' + r.st = w.strength || '' + r.bulk = w.bulk || '' + r.legalityclass = i.legality_class || '4' + r.ammo = 0 + r.notes = i.notes || '' + if (!!r.notes && w.usage_notes) r.notes += '\n' + w.usage_notes + r.pageRef(i.reference || '') + r.mode = w.usage || '' + r.import = w.calc?.level || '0' + r.damage = w.calc?.damage || '' + r.acc = w.accuracy || '' + r.rof = w.rate_of_fire || '' + r.shots = w.shots || '' + r.rcl = w.recoil || '' + r.range = w.calc?.range || '' + let old = this._findElementIn('ranged', false, r.name, r.mode) + this._migrateOtfsAndNotes(old, r, i.vtt_notes) + + GURPS.put(ranged, r, r_index++) + } + } + } + return { + 'data.-=melee': null, + 'data.melee': melee, + 'data.-=ranged': null, + 'data.ranged': ranged, + } + } + + recursiveGet(i) { + if (!i) return [] + let ch = [] + if (i.children?.length) for (let j of i.children) ch = ch.concat(this.recursiveGet(j)) + if (i.modifiers?.length) for (let j of i.modifiers) ch = ch.concat(this.recursiveGet(j)) + if (!!i.disabled || (i.equipped != null && i.equipped == false)) return [] + return [i].concat(ch) + } + + adPointCount(i, ads, disads, quirks, race, toplevel = false) { + if (i.type == 'advantage_container' && i.container_type == 'race') race += i.calc?.points + else if (i.type == 'advantage_container' && i.container_type == 'alternative_abilities') ads += i.calc?.points + else if (i.type == 'advantage_container' && !!i.children?.length) { + var [a, d] = [0, 0] + for (let j of i.children) [a, d, quirks, race] = this.adPointCount(j, a, d, quirks, race) + if (toplevel) { + if (a > 0) ads += a + else disads += a + } else ads += a + d + } else if (i.calc?.points == -1) quirks += i.calc?.points + else if (i.calc?.points > 0) ads += i.calc?.points + else disads += i.calc?.points + return [ads, disads, quirks, race] + } + + skPointCount(i, skills) { + if (i.type == ('skill_container' || 'spell_container') && i.children?.length) + for (let j of i.children) skills = this.skPointCount(j, skills) + else skills += i.points + return skills + } + + /** + * @param {string} json + * @param {string} importname + * @param {string | undefined} [importpath] + */ + async importFromGCSv2(json, importname, importpath, suppressMessage = false, GCAVersion, GCSVersion) { + let r + let msg = [] + let version = 'Direct GCS Import' + let exit = false + try { + r = JSON.parse(json) + } catch (err) { + msg.push(i18n('GURPS.importNoJSONDetected')) + exit = true + } + if (!!r) { + if (!r.calc) { + msg.push(i18n('GURPS.importOldGCSFile')) + exit = true + } + } + + if (msg.length > 0) { + ui.notifications?.error(msg.join('
    ')) + let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { + lines: msg, + version: version, + GCAVersion: GCAVersion, + GCSVersion: GCSVersion, + url: GURPS.USER_GUIDE_URL, + }) + ChatMessage.create({ + content: content, + user: game.user.id, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + whisper: [game.user.id], + }) + if (exit) return false + } + + let nm = r['profile']['name'] + console.log("Importing '" + nm + "'") + let starttime = performance.now() + let commit = {} + + commit = { ...commit, ...{ 'data.lastImport': new Date().toString().split(' ').splice(1, 4).join(' ') } } + let ar = this.system.additionalresources || {} + ar.importname = importname || ar.importname + ar.importpath = importpath || ar.importpath + try { + commit = { ...commit, ...{ 'data.additionalresources': ar } } + commit = { ...commit, ...(await this.importAttributesFromGCSv2(r.attributes, r.equipment, r.calc)) } + commit = { ...commit, ...(await this.importTraitsFromGCSv2(r.profile, r.created_date, r.modified_date)) } + commit = { ...commit, ...this.importSizeFromGCSv1(commit, r.profile, r.advantages, r.skills, r.equipment) } + commit = { ...commit, ...this.importAdsFromGCSv3(r.advantages) } + commit = { ...commit, ...this.importSkillsFromGCSv2(r.skills) } + commit = { ...commit, ...this.importSpellsFromGCSv2(r.spells) } + commit = { ...commit, ...this.importEquipmentFromGCSv2(r.equipment, r.other_equipment) } + commit = { ...commit, ...this.importNotesFromGCSv2(r.notes) } + + commit = { ...commit, ...(await this.importProtectionFromGCSv2(r.settings.hit_locations)) } + commit = { + ...commit, + ...this.importPointTotalsFromGCSv2(r.total_points, r.attributes, r.advantages, r.skills, r.spells), + } + commit = { ...commit, ...this.importReactionsFromGCSv3(r.advantages, r.skills, r.equipment) } + commit = { ...commit, ...this.importCombatFromGCSv2(r.advantages, r.skills, r.spells, r.equipment) } + } catch (err) { + console.log(err.stack) + msg.push( + i18n_f('GURPS.importGenericError', { + name: nm, + error: err.name, + message: err.message, + }) + ) + let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { + lines: [msg], + version: version, + GCAVersion: GCAVersion, + GCSVersion: GCSVersion, + url: GURPS.USER_GUIDE_URL, + }) + ui.notifications?.warn(msg) + let chatData = { + user: game.user.id, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + content: content, + whisper: [game.user.id], + } + ChatMessage.create(chatData, {}) + // Don't return + } + + console.log('Starting commit') + let deletes = Object.fromEntries(Object.entries(commit).filter(([key, value]) => key.includes('.-='))) + let adds = Object.fromEntries(Object.entries(commit).filter(([key, value]) => !key.includes('.-='))) + + try { + this.ignoreRender = true + await this.internalUpdate(deletes, { diff: false }) + await this.internalUpdate(adds, { diff: false }) + // This has to be done after everything is loaded + await this.postImport() + this._forceRender() + + // Must update name outside of protection so that Actors list (and other external views) update correctly + if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IGNORE_IMPORT_NAME)) { + await this.update({ name: nm, 'token.name': nm }) + } + + if (!suppressMessage) ui.notifications?.info(i18n_f('GURPS.importSuccessful', { name: nm })) + console.log( + 'Done importing (' + + Math.round(performance.now() - starttime) + + 'ms.) You can inspect the character data below:' + ) + console.log(this) + return true + } catch (err) { + console.log(err.stack) + let msg = [i18n_f('GURPS.importGenericError', { name: nm, error: err.name, message: err.message })] + if (err.message == 'Maximum depth exceeded') msg.push(i18n('GURPS.importTooManyContainers')) + ui.notifications?.warn(msg.join('
    ')) + let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { + lines: msg, + version: 'GCS Direct', + GCAVersion: GCAVersion, + GCSVersion: GCSVersion, + url: GURPS.USER_GUIDE_URL, + }) + + let user = game.user + let chatData = { + user: game.user.id, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + content: content, + whisper: [game.user.id], + } + ChatMessage.create(chatData, {}) + return false + } + } + + // First attempt at import GCS FG XML export data. + /** + * @param {string} xml + * @param {string} importname + * @param {string | undefined} [importpath] + */ + async importFromGCSv1(xml, importname, importpath, suppressMessage = false) { + const GCA5Version = 'GCA5-14' + const GCAVersion = 'GCA-11' + const GCSVersion = 'GCS-5' + if (importname.endsWith('.gcs')) + return this.importFromGCSv2(xml, importname, importpath, suppressMessage, GCAVersion, GCSVersion) + var c, ra // The character json, release attributes + let isFoundryGCS = false + let isFoundryGCA = false + let isFoundryGCA5 = false + // need to remove

    and replace

    with newlines from "formatted text" + let origx = GURPS.cleanUpP(xml) + let x = xmlTextToJson(origx) + // @ts-ignore + let r = x.root + let msg = [] + let version = 'unknown' + let vernum = 1 + let exit = false + if (!r) { + if (importname.endsWith('.gca5')) msg.push(i18n('GURPS.importCannotImportGCADirectly')) + if (importname.endsWith('.gca4')) msg.push(i18n('GURPS.importCannotImportGCADirectly')) + else if (!xml.startsWith(' 0) { + ui.notifications?.error(msg.join('
    ')) + let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { + lines: msg, + version: version, + GCAVersion: GCAVersion, + GCSVersion: GCSVersion, + url: GURPS.USER_GUIDE_URL, + }) + + let user = game.user + ChatMessage.create({ + content: content, + user: game.user.id, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + whisper: [game.user.id], + }) + if (exit) return false // Some errors cannot be forgiven ;-) + } + let nm = this.textFrom(c.name) + console.log("Importing '" + nm + "'") + let starttime = performance.now() + let commit = {} + + commit = { ...commit, ...{ 'data.lastImport': new Date().toString().split(' ').splice(1, 4).join(' ') } } + let ar = this.system.additionalresources || {} + ar.importname = importname || ar.importname + ar.importpath = importpath || ar.importpath + ar.importversion = ra.version + commit = { ...commit, ...{ 'data.additionalresources': ar } } + + try { + // This is going to get ugly, so break out various data into different methods + commit = { ...commit, ...(await this.importAttributesFromCGSv1(c.attributes)) } + commit = { ...commit, ...this.importSkillsFromGCSv1(c.abilities?.skilllist, isFoundryGCS) } + commit = { ...commit, ...this.importTraitsfromGCSv1(c.traits) } + commit = { ...commit, ...this.importCombatMeleeFromGCSv1(c.combat?.meleecombatlist, isFoundryGCS) } + commit = { ...commit, ...this.importCombatRangedFromGCSv1(c.combat?.rangedcombatlist, isFoundryGCS) } + commit = { ...commit, ...this.importSpellsFromGCSv1(c.abilities?.spelllist, isFoundryGCS) } + if (isFoundryGCS) { + commit = { ...commit, ...this.importAdsFromGCSv2(c.traits?.adslist) } + commit = { ...commit, ...this.importReactionsFromGCSv2(c.reactions) } + commit = { ...commit, ...this.importConditionalModifiersFromGCSv2(c.conditionalmods) } + } + if (isFoundryGCA) { + commit = { ...commit, ...this.importLangFromGCA(c.traits?.languagelist) } + commit = { ...commit, ...this.importAdsFromGCA(c.traits?.adslist, c.traits?.disadslist) } + commit = { ...commit, ...this.importReactionsFromGCA(c.traits?.reactionmodifiers, vernum) } + } + commit = { ...commit, ...this.importEncumbranceFromGCSv1(c.encumbrance) } + commit = { ...commit, ...this.importPointTotalsFromGCSv1(c.pointtotals) } + commit = { ...commit, ...this.importNotesFromGCSv1(c.description, c.notelist) } + commit = { ...commit, ...this.importEquipmentFromGCSv1(c.inventorylist, isFoundryGCS) } + commit = { ...commit, ...(await this.importProtectionFromGCSv1(c.combat?.protectionlist, isFoundryGCA)) } + } catch (err) { + console.log(err.stack) + let msg = i18n_f('GURPS.importGenericError', { name: nm, error: err.name, message: err.message }) + let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { + lines: [msg], + version: version, + GCAVersion: GCAVersion, + GCSVersion: GCSVersion, + url: GURPS.USER_GUIDE_URL, + }) + + ui.notifications?.warn(msg) + let user = game.user + let chatData = { + user: game.user.id, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + content: content, + whisper: [game.user.id], + } + ChatMessage.create(chatData, {}) + // Don't return, because we want to see how much actually gets committed. + } + console.log('Starting commit') + + let deletes = Object.fromEntries(Object.entries(commit).filter(([key, value]) => key.includes('.-='))) + let adds = Object.fromEntries(Object.entries(commit).filter(([key, value]) => !key.includes('.-='))) + + try { + this.ignoreRender = true + await this.internalUpdate(deletes, { diff: false }) + await this.internalUpdate(adds, { diff: false }) + // This has to be done after everything is loaded + await this.postImport() + this._forceRender() + + // Must update name outside of protection so that Actors list (and other external views) update correctly + if (!game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IGNORE_IMPORT_NAME)) { + await this.update({ name: nm, 'token.name': nm }) + } + + if (!suppressMessage) ui.notifications?.info(i18n_f('GURPS.importSuccessful', { name: nm })) + console.log( + 'Done importing (' + + Math.round(performance.now() - starttime) + + 'ms.) You can inspect the character data below:' + ) + console.log(this) + return true + } catch (err) { + console.log(err.stack) + let msg = [i18n_f('GURPS.importGenericError', { name: nm, error: err.name, message: err.message })] + if (err.message == 'Maximum depth exceeded') msg.push(i18n('GURPS.importTooManyContainers')) + if (!supressMessage) ui.notifications?.warn(msg.join('
    ')) + let content = await renderTemplate('systems/gurps/templates/chat-import-actor-errors.html', { + lines: msg, + version: version, + GCAVersion: GCAVersion, + GCSVersion: GCSVersion, + url: GURPS.USER_GUIDE_URL, + }) + + let user = game.user + let chatData = { + user: game.user.id, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + content: content, + whisper: [game.user.id], + } + ChatMessage.create(chatData, {}) + return false + } + } + + // hack to get to private text element created by xml->json method. + /** + * @param {{ [key: string]: any }} o + */ + textFrom(o) { + if (!o) return '' + let t = o['#text'] + if (!t) return '' + return t.trim() + } + + // similar hack to get text as integer. + /** + * @param {{ [key: string]: any }} o + */ + intFrom(o) { + if (!o) return 0 + let i = o['#text'] + if (!i) return 0 + return parseInt(i) + } + + /** + * @param {{[key: string] : any}} o + */ + floatFrom(o) { + if (!o) return 0 + let f = o['#text'].trim() + if (!f) return 0 + return f.includes(',') ? parseDecimalNumber(f, { thousands: '.', decimal: ',' }) : parseDecimalNumber(f) + } + + /** + * @param {string} list + * @param {string|boolean} uuid + */ + _findElementIn(list, uuid, name = '', mode = '') { + var foundkey + let l = getProperty(this, list) + recurselist(l, (e, k, d) => { + if ((uuid && e.uuid == uuid) || (!!e.name && e.name.startsWith(name) && e.mode == mode)) foundkey = k + }) + return foundkey == null ? foundkey : getProperty(this, list + '.' + foundkey) + } + + /** + * @param {{ [key: string]: any }} json + */ + importReactionsFromGCSv2(json) { + if (!json) return + let t = this.textFrom + let rs = {} + let index = 0 + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let r = new Reaction() + r.modifier = t(j.modifier) + r.situation = t(j.situation) + GURPS.put(rs, r, index++) + } + } + return { + 'data.-=reactions': null, + 'data.reactions': rs, + } + } + + importConditionalModifiersFromGCSv2(json) { + if (!json) return + let t = this.textFrom + let rs = {} + let index = 0 + for (let key in json) { + if (key.startsWith('id-')) { + let j = json[key] + let r = new Reaction() // ConditionalModifiers are the same format + r.modifier = t(j.modifier) + r.situation = t(j.situation) + GURPS.put(rs, r, index++) + } + } + return { + 'data.-=conditionalmods': null, + 'data.conditionalmods': rs, + } + } + + /** + * @param {{ [key: string]: any }} json + */ + importReactionsFromGCA(json, vernum) { + if (!json) return + let text = this.textFrom(json) + let a = vernum <= 9 ? text.split(',') : text.split('|') + let rs = {} + let index = 0 + a.forEach((/** @type {string} */ m) => { + if (!!m) { + let t = m.trim() + let i = t.indexOf(' ') + let mod = t.substring(0, i) + let sit = t.substr(i + 1) + let r = new Reaction(mod, sit) + GURPS.put(rs, r, index++) + } + }) + return { + 'data.-=reactions': null, + 'data.reactions': rs, + } + } + + importLangFromGCA(json) { + if (!json) return + let langs = {} + let index = 0 + let t = this.textFrom + for (let key in json) { + if (key.startsWith('id-')) { + let j = json[key] + let n = t(j.name) + let s = t(j.spoken) + let w = t(j.written) + let p = t(j.points) + let l = new Language(n, s, w, p) + GURPS.put(langs, l, index++) + } + } + return { + 'data.-=languages': null, + 'data.languages': langs, + } + } + + /** + * @param {{ attributes: Record; ads: Record; disads: Record; quirks: Record; skills: Record; spells: Record; unspentpoints: Record; totalpoints: Record; race: Record; }} json + */ + importPointTotalsFromGCSv1(json) { + if (!json) return + + let i = this.intFrom + return { + 'data.totalpoints.attributes': i(json.attributes), + 'data.totalpoints.ads': i(json.ads), + 'data.totalpoints.disads': i(json.disads), + 'data.totalpoints.quirks': i(json.quirks), + 'data.totalpoints.skills': i(json.skills), + 'data.totalpoints.spells': i(json.spells), + 'data.totalpoints.unspent': i(json.unspentpoints), + 'data.totalpoints.total': i(json.totalpoints), + 'data.totalpoints.race': i(json.race), + } + } + + /** + * @param {{ [key: string]: any }} descjson + * @param {{ [key: string]: any }} json + */ + importNotesFromGCSv1(descjson, json) { + if (!json) return + let t = this.textFrom + let temp = [] + if (!!descjson) { + // support for GCA description + + let n = new Note() + n.notes = t(descjson).replace(/\\r/g, '\n') + n.imported = true + temp.push(n) + } + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let n = /** @type {Note & { imported: boolean, uuid: string, parentuuid: string }} */ (new Note()) + //n.setNotes(t(j.text)); + n.notes = t(j.name) + let txt = t(j.text) + if (!!txt) n.notes = n.notes + '\n' + txt.replace(/\\r/g, '\n') + n.uuid = t(j.uuid) + n.parentuuid = t(j.parentuuid) + n.pageref = t(j.pageref) + let old = this._findElementIn('notes', n.uuid) + this._migrateOtfsAndNotes(old, n) + temp.push(n) + } + } + // Save the old User Entered Notes. + recurselist(this.system.notes, t => { + if (!!t.save) temp.push(t) + }) + return { + 'data.-=notes': null, + 'data.notes': this.foldList(temp), + } + } + + /** + * @param {{ [x: string]: any; bodyplan: Record; }} json + * @param {boolean} isFoundryGCA + */ + async importProtectionFromGCSv1(json, isFoundryGCA) { + if (!json) return + let t = this.textFrom + let data = this.system + if (!!data.additionalresources.ignoreinputbodyplan) return + + /** @type {HitLocations.HitLocation[]} */ + let locations = [] + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let hl = new HitLocations.HitLocation(t(j.location)) + let i = t(j.dr) + if (i.match(/^\d+ *(\+ *\d+ *)?$/)) i = eval(t(j.dr)) // supports "0 + 8" + hl.import = !i ? 0 : i + hl.penalty = t(j.db) + hl.setEquipment(t(j.text)) + + // Some hit location tables have two entries for the same location. The code requires + // each location to be unique. Append an asterisk to the location name in that case. Hexapods and ichthyoid + while (locations.filter(it => it.where == hl.where).length > 0) { + hl.where = hl.where + '*' + } + locations.push(hl) + } + } + + // Do the results contain vitals? If not, add it. + let vitals = locations.filter(value => value.where === HitLocations.HitLocation.VITALS) + if (vitals.length === 0) { + let hl = new HitLocations.HitLocation(HitLocations.HitLocation.VITALS) + hl.penalty = HitLocations.hitlocationRolls[HitLocations.HitLocation.VITALS].penalty + hl.roll = HitLocations.hitlocationRolls[HitLocations.HitLocation.VITALS].roll + hl.import = '0' + locations.push(hl) + } + + // Hit Locations MUST come from an existing bodyplan hit location table, or else ADD (and + // potentially other features) will not work. Sometime in the future, we will look at + // user-entered hit locations. + let bodyplan = t(json.bodyplan)?.toLowerCase() // Was a body plan actually in the import? + if (bodyplan === 'snakemen') bodyplan = 'snakeman' + let table = HitLocations.hitlocationDictionary[bodyplan] // If so, try to use it. + + /** @type {HitLocations.HitLocation[]} */ + let locs = [] + locations.forEach(e => { + if (!!table && !!table[e.where]) { + // if e.where already exists in table, don't map + locs.push(e) + } else { + // map to new name(s) ... sometimes we map 'Legs' to ['Right Leg', 'Left Leg'], for example. + e.locations(false).forEach(l => locs.push(l)) // Map to new names + } + }) + locations = locs + + if (!table) { + locs = [] + locations.forEach(e => { + e.locations(true).forEach(l => locs.push(l)) // Map to new names, but include original to help match against tables + }) + bodyplan = this._getBodyPlan(locs) + table = HitLocations.hitlocationDictionary[bodyplan] + } + // update location's roll and penalty based on the bodyplan + + if (!!table) { + Object.values(locations).forEach(it => { + let [lbl, entry] = HitLocations.HitLocation.findTableEntry(table, it.where) + if (!!entry) { + it.where = lbl // It might be renamed (ex: Skull -> Brain) + if (!it.penalty) it.penalty = entry.penalty + if (!it.roll || it.roll.length === 0 || it.roll === HitLocations.HitLocation.DEFAULT) it.roll = entry.roll + } + }) + } + + // write the hit locations out in bodyplan hit location table order. If there are + // other entries, append them at the end. + /** @type {HitLocations.HitLocation[]} */ + let temp = [] + Object.keys(table).forEach(key => { + let results = Object.values(locations).filter(loc => loc.where === key) + if (results.length > 0) { + if (results.length > 1) { + // If multiple locs have same where, concat the DRs. Leg 7 & Leg 8 both map to "Leg 7-8" + let d = '' + + /** @type {string | null} */ + let last = null + results.forEach(r => { + if (r.import != last) { + d += '|' + r.import + last = r.import + } + }) + + if (!!d) d = d.substr(1) + results[0].import = d + } + temp.push(results[0]) + locations = locations.filter(it => it.where !== key) + } else { + // Didn't find loc that should be in the table. Make a default entry + temp.push(new HitLocations.HitLocation(key, '0', table[key].penalty, table[key].roll)) + } + }) + locations.forEach(it => temp.push(it)) + + let prot = {} + let index = 0 + temp.forEach(it => GURPS.put(prot, it, index++)) + + let saveprot = true + if (!!data.lastImport && !!data.additionalresources.bodyplan && bodyplan != data.additionalresources.bodyplan) { + let option = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IMPORT_BODYPLAN) + if (option == 1) { + saveprot = false + } + if (option == 2) { + saveprot = await new Promise((resolve, reject) => { + let d = new Dialog({ + title: 'Hit Location Body Plan', + content: + `Do you want to

    Save the current Body Plan (${game.i18n.localize( + 'GURPS.BODYPLAN' + data.additionalresources.bodyplan + )}) or ` + + `

    Overwrite it with the Body Plan from the import: (${game.i18n.localize( + 'GURPS.BODYPLAN' + bodyplan + )})?

     `, + buttons: { + save: { + icon: '', + label: 'Save', + callback: () => resolve(false), + }, + overwrite: { + icon: '', + label: 'Overwrite', + callback: () => resolve(true), + }, + }, + default: 'save', + close: () => resolve(false), // just assume overwrite. Error handling would be too much work right now. + }) + d.render(true) + }) + } + } + if (saveprot) + return { + 'data.-=hitlocations': null, + 'data.hitlocations': prot, + 'data.additionalresources.bodyplan': bodyplan, + } + else return {} + } + + /** + * + * @param {Array} locations + */ + _getBodyPlan(locations) { + // each key is a "body plan" name like "humanoid" or "quadruped" + let tableNames = Object.keys(HitLocations.hitlocationDictionary) + + // create a map of tableName:count + /** @type {Record} */ + let tableScores = {} + tableNames.forEach(it => (tableScores[it] = 0)) + + // increment the count for a tableScore if it contains the same hit location as "prot" + locations.forEach(function (hitLocation) { + tableNames.forEach(function (tableName) { + if (HitLocations.hitlocationDictionary[tableName].hasOwnProperty(hitLocation.where)) { + tableScores[tableName] = tableScores[tableName] + 1 + } + }) + }) + + // Select the tableScore with the highest score. + let match = -1 + let name = HitLocations.HitLocation.HUMANOID + Object.keys(tableScores).forEach(function (score) { + if (tableScores[score] > match) { + match = tableScores[score] + name = score + } + }) + + // In the case of a tie, select the one whose score is closest to the number of entries + // in the table. + let results = Object.keys(tableScores).filter(it => tableScores[it] === match) + if (results.length > 1) { + let diff = Number.MAX_SAFE_INTEGER + results.forEach(key => { + // find the smallest difference + let table = HitLocations.hitlocationDictionary[key] + if (Object.keys(table).length - match < diff) { + diff = Object.keys(table).length - match + name = key + } + }) + } + + return name + } + + /** + * @param {{ [key: string]: any }} json + * @param {boolean} isFoundryGCS + */ + importEquipmentFromGCSv1(json, isFoundryGCS) { + if (!json) return + let t = this.textFrom + let i = this.intFrom + let f = this.floatFrom + + /** + * @type {Equipment[]} + */ + let temp = [] + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let eqt = new Equipment() + eqt.name = t(j.name) + eqt.count = t(j.count) + eqt.cost = t(j.cost) + eqt.location = t(j.location) + let cstatus = i(j.carried) + eqt.carried = cstatus >= 1 + eqt.equipped = cstatus == 2 + eqt.techlevel = t(j.tl) + eqt.legalityclass = t(j.lc) + eqt.categories = t(j.type) + eqt.uses = t(j.uses) + eqt.maxuses = t(j.maxuses) + eqt.uuid = t(j.uuid) + eqt.parentuuid = t(j.parentuuid) + if (isFoundryGCS) { + eqt.notes = t(j.notes) + eqt.weight = t(j.weight) + } else { + eqt.setNotes(t(j.notes)) + eqt.weight = t(j.weightsum) // GCA sends calculated weight in 'weightsum' + } + eqt.pageRef(t(j.pageref)) + let old = this._findElementIn('equipment.carried', eqt.uuid) + if (!old) old = this._findElementIn('equipment.other', eqt.uuid) + this._migrateOtfsAndNotes(old, eqt) + if (!!old) { + eqt.carried = old.carried + eqt.equipped = old.equipped + eqt.parentuuid = old.parentuuid + if (old.ignoreImportQty) { + eqt.count = old.count + eqt.uses = old.uses + eqt.maxuses = old.maxuses + eqt.ignoreImportQty = true + } + } + temp.push(eqt) + } + } + + // Save the old User Entered Notes. + recurselist(this.system.equipment?.carried, t => { + t.carried = true + if (!!t.save) temp.push(t) + }) // Ensure carried eqt stays in carried + recurselist(this.system.equipment?.other, t => { + t.carried = false + if (!!t.save) temp.push(t) + }) + + temp.forEach(eqt => { + // Remove all entries from inside items because if they still exist, they will be added back in + eqt.contains = {} + eqt.collapsed = {} + }) + + // Put everything in it container (if found), otherwise at the top level + temp.forEach(eqt => { + if (!!eqt.parentuuid) { + let parent = null + parent = temp.find(e => e.uuid === eqt.parentuuid) + if (!!parent) GURPS.put(parent.contains, eqt) + else eqt.parentuuid = '' // Can't find a parent, so put it in the top list + } + }) + + let equipment = { + carried: {}, + other: {}, + } + let cindex = 0 + let oindex = 0 + + temp.forEach(eqt => { + Equipment.calc(eqt) + if (!eqt.parentuuid) { + if (eqt.carried) GURPS.put(equipment.carried, eqt, cindex++) + else GURPS.put(equipment.other, eqt, oindex++) + } + }) + return { + 'data.-=equipment': null, + 'data.equipment': equipment, + } + } + + // Fold a flat array into a hierarchical target object + /** + * @param {any[]} flat + */ + foldList(flat, target = {}) { + flat.forEach(obj => { + if (!!obj.parentuuid) { + const parent = flat.find(o => o.uuid == obj.parentuuid) + if (!!parent) { + if (!parent.contains) parent.contains = {} // lazy init for older characters + GURPS.put(parent.contains, obj) + } else obj.parentuuid = '' // Can't find a parent, so put it in the top list. should never happen with GCS + } + }) + let index = 0 + flat.forEach(obj => { + if (!obj.parentuuid) GURPS.put(target, obj, index++) + }) + return target + } + + /** + * @param {{ [x: string]: Record; }} json + */ + importEncumbranceFromGCSv1(json) { + if (!json) return + let t = this.textFrom + let es = {} + let index = 0 + let cm = 0 + let cd = 0 + for (let i = 0; i < 5; i++) { + let e = new Encumbrance() + e.level = i + let k = 'enc_' + i + let c = t(json[k]) + e.current = c === '1' + k = 'enc' + i + e.key = k + let k2 = k + '_weight' + e.weight = t(json[k2]) + k2 = k + '_move' + e.move = this.intFrom(json[k2]) + k2 = k + '_dodge' + e.dodge = this.intFrom(json[k2]) + if (e.current) { + cm = e.move + cd = e.dodge + } + GURPS.put(es, e, index++) + } + return { + 'data.currentmove': cm, + 'data.currentdodge': cd, + 'data.-=encumbrance': null, + 'data.encumbrance': es, + } + } + + /** + * Copy old OTFs to the new object, and update the displayable notes + * @param {Skill|Spell|Ranged|Melee} oldobj + * @param {Skill|Spell|Ranged|Melee} newobj + */ + _migrateOtfsAndNotes(oldobj = {}, newobj, importvttnotes = '') { + if (!!importvttnotes) newobj.notes += (!!newobj.notes ? ' ' : '') + importvttnotes + this._updateOtf('check', oldobj, newobj) + this._updateOtf('during', oldobj, newobj) + this._updateOtf('pass', oldobj, newobj) + this._updateOtf('fail', oldobj, newobj) + if (oldobj.notes?.startsWith(newobj.notes)) + // Must be done AFTER OTFs have been stripped out + newobj.notes = oldobj.notes + if (oldobj.name?.startsWith(newobj.name)) newobj.name = oldobj.name + } + + /** + * Search for specific format OTF in the notes (and vttnotes). + * If we find it in the notes, remove it and replace the notes with the shorter version + */ + _updateOtf(otfkey, oldobj, newobj) { + let objkey = otfkey + 'otf' + let oldotf = oldobj[objkey] + newobj[objkey] = oldotf + var notes, newotf + ;[notes, newotf] = this._removeOtf(otfkey, newobj.notes || '') + if (!!newotf) newobj[objkey] = newotf + newobj.notes = notes.trim() + } + + // Looking for OTFs in text. ex: c:[/qty -1] during:[/anim healing c] + _removeOtf(key, text) { + if (!text) return [text, null] + var start + let patstart = text.toLowerCase().indexOf(key[0] + ':[') + if (patstart < 0) { + patstart = text.toLowerCase().indexOf(key + ':[') + if (patstart < 0) return [text, null] + else start = patstart + key.length + 2 + } else start = patstart + 3 + let cnt = 1 + let i = start + if (i >= text.length) return [text, null] + do { + let ch = text[i++] + if (ch == '[') cnt++ + if (ch == ']') cnt-- + } while (i < text.length && cnt > 0) + if (cnt == 0) { + let otf = text.substring(start, i - 1) + let front = text.substring(0, patstart) + let end = text.substr(i) + if ((front == '' || front.endsWith(' ')) && end.startsWith(' ')) end = end.substring(1) + return [front + end, otf] + } else return [text, null] + } + + /** + * @param {{ [key: string]: any }} json + * @param {boolean} isFoundryGCS + */ + importCombatMeleeFromGCSv1(json, isFoundryGCS) { + if (!json) return + let t = this.textFrom + let melee = {} + let index = 0 + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let oldnote = t(j.notes) + for (let k2 in j.meleemodelist) { + if (k2.startsWith('id-')) { + let j2 = j.meleemodelist[k2] + let m = new Melee() + m.name = t(j.name) + m.st = t(j.st) + m.weight = t(j.weight) + m.techlevel = t(j.tl) + m.cost = t(j.cost) + if (isFoundryGCS) { + m.notes = t(j2.notes) || oldnote + m.pageRef(t(j2.pageref)) + } else + try { + m.setNotes(t(j.text)) + } catch { + console.log(m) + console.log(t(j.text)) + } + m.mode = t(j2.name) + m.import = t(j2.level) + m.damage = t(j2.damage) + m.reach = t(j2.reach) + m.parry = t(j2.parry) + m.block = t(j2.block) + let old = this._findElementIn('melee', false, m.name, m.mode) + this._migrateOtfsAndNotes(old, m, t(j2.vtt_notes)) + + GURPS.put(melee, m, index++) + } + } + } + } + return { + 'data.-=melee': null, + 'data.melee': melee, + } + } + + /** + * @param {{ [key: string]: any }} json + * @param {boolean} isFoundryGCS + */ + importCombatRangedFromGCSv1(json, isFoundryGCS) { + if (!json) return + let t = this.textFrom + let ranged = {} + let index = 0 + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let oldnote = t(j.notes) + for (let k2 in j.rangedmodelist) { + if (k2.startsWith('id-')) { + let j2 = j.rangedmodelist[k2] + let r = new Ranged() + r.name = t(j.name) + r.st = t(j.st) + r.bulk = t(j.bulk) + r.legalityclass = t(j.lc) + r.ammo = t(j.ammo) + if (isFoundryGCS) { + r.notes = t(j2.notes) || oldnote + r.pageRef(t(j2.pageref)) + } else + try { + r.setNotes(t(j.text)) + } catch { + console.log(r) + console.log(t(j.text)) + } + r.mode = t(j2.name) + r.import = t(j2.level) + r.damage = t(j2.damage) + r.acc = t(j2.acc) + r.rof = t(j2.rof) + r.shots = t(j2.shots) + r.rcl = t(j2.rcl) + let rng = t(j2.range) + r.range = rng + let old = this._findElementIn('ranged', false, r.name, r.mode) + this._migrateOtfsAndNotes(old, r, t(j2.vtt_notes)) + + GURPS.put(ranged, r, index++) + } + } + } + } + return { + 'data.-=ranged': null, + 'data.ranged': ranged, + } + } + + /** + * @param {{ race: Record; height: Record; weight: Record; age: Record; title: Record; player: Record; createdon: Record; modifiedon: Record; religion: Record; birthday: Record; hand: Record; sizemodifier: Record; tl: Record; appearance: Record; }} json + */ + importTraitsfromGCSv1(json) { + if (!json) return + let t = this.textFrom + let ts = {} + ts.race = t(json.race) + ts.height = t(json.height) + ts.weight = t(json.weight) + ts.age = t(json.age) + ts.title = t(json.title) + ts.player = t(json.player) + ts.createdon = t(json.createdon) + ts.modifiedon = t(json.modifiedon) + ts.religion = t(json.religion) + ts.birthday = t(json.birthday) + ts.hand = t(json.hand) + ts.sizemod = t(json.sizemodifier) + ts.techlevel = t(json.tl) + // @GENDER, Eyes: @EYES, Hair: @HAIR, Skin: @SKIN + let a = t(json.appearance) + ts.appearance = a + try { + let x = a.indexOf(', Eyes: ') + if (x >= 0) { + ts.gender = a.substring(0, x) + let y = a.indexOf(', Hair: ') + ts.eyes = a.substring(x + 8, y) + x = a.indexOf(', Skin: ') + ts.hair = a.substring(y + 8, x) + ts.skin = a.substr(x + 8) + } + } catch { + console.log('Unable to parse appearance traits for ') + console.log(this) + } + return { + 'data.-=traits': null, + 'data.traits': ts, + } + } + + // Import the section of the GCS FG XML file. + /** + * @param {{ [key: string]: any }} json + */ + async importAttributesFromCGSv1(json) { + if (!json) return + let i = this.intFrom // shortcut to make code smaller -- I reject your attempt to make the code smaller. Why does it need to be smaller? + let t = this.textFrom + let data = this.system + let att = data.attributes + + // attribute.values will be calculated in calculateDerivedValues() + att.ST.import = i(json.strength) + att.ST.points = i(json.strength_points) + att.DX.import = i(json.dexterity) + att.DX.points = i(json.dexterity_points) + att.IQ.import = i(json.intelligence) + att.IQ.points = i(json.intelligence_points) + att.HT.import = i(json.health) + att.HT.points = i(json.health_points) + att.WILL.import = i(json.will) + att.WILL.points = i(json.will_points) + att.PER.import = i(json.perception) + att.PER.points = i(json.perception_points) + + data.HP.max = i(json.hitpoints) + data.HP.points = i(json.hitpoints_points) + data.FP.max = i(json.fatiguepoints) + data.FP.points = i(json.fatiguepoints_points) + let hp = i(json.hps) + let fp = i(json.fps) + + let saveCurrent = false + if (!!data.lastImport && (data.HP.value != hp || data.FP.value != fp)) { + let option = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_IMPORT_HP_FP) + if (option == 0) { + saveCurrent = true + } + if (option == 2) { + saveCurrent = await new Promise((resolve, reject) => { + let d = new Dialog({ + title: 'Current HP & FP', + content: `Do you want to

    Save the current HP (${data.HP.value}) & FP (${data.FP.value}) values or

    Overwrite it with the import data, HP (${hp}) & FP (${fp})?

     `, + buttons: { + save: { + icon: '', + label: 'Save', + callback: () => resolve(true), + }, + overwrite: { + icon: '', + label: 'Overwrite', + callback: () => resolve(false), + }, + }, + default: 'save', + close: () => resolve(false), // just assume overwrite. Error handling would be too much work right now. + }) + d.render(true) + }) + } + } + if (!saveCurrent) { + data.HP.value = hp + data.FP.value = fp + } + + let lm = {} + lm.basiclift = t(json.basiclift) + lm.carryonback = t(json.carryonback) + lm.onehandedlift = t(json.onehandedlift) + lm.runningshove = t(json.runningshove) + lm.shiftslightly = t(json.shiftslightly) + lm.shove = t(json.shove) + lm.twohandedlift = t(json.twohandedlift) + + data.basicmove.value = t(json.basicmove) + data.basicmove.points = i(json.basicmove_points) + // data.basicspeed.value = t(json.basicspeed) + data.basicspeed.value = this.floatFrom(json.basicspeed) + + data.basicspeed.points = i(json.basicspeed_points) + data.thrust = t(json.thrust) + data.swing = t(json.swing) + data.currentmove = t(json.move) + data.frightcheck = i(json.frightcheck) + + data.hearing = i(json.hearing) + data.tastesmell = i(json.tastesmell) + data.touch = i(json.touch) + data.vision = i(json.vision) + + return { + 'data.attributes': att, + 'data.HP': data.HP, + 'data.FP': data.FP, + 'data.basiclift': data.basiclift, + 'data.basicmove': data.basicmove, + 'data.basicspeed': data.basicspeed, + 'data.thrust': data.thrust, + 'data.swing': data.swing, + 'data.currentmove': data.currentmove, + 'data.frightcheck': data.frightcheck, + 'data.hearing': data.hearing, + 'data.tastesmell': data.tastesmell, + 'data.touch': data.touch, + 'data.vision': data.vision, + 'data.liftingmoving': lm, + } + } + + // create/update the skills. + // NOTE: For the update to work correctly, no two skills can have the same name. + // When reading data, use "this.system.skills", however, when updating, use "system.skills". + /** + * @param {{ [key: string]: any }} json + * @param {boolean} isFoundryGCS + */ + importSkillsFromGCSv1(json, isFoundryGCS) { + if (!json) return + let temp = [] + let t = this.textFrom /// shortcut to make code smaller + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let sk = new Skill() + sk.name = t(j.name) + sk.type = t(j.type) + sk.import = t(j.level) + if (sk.level == 0) sk.level = '' + sk.points = this.intFrom(j.points) + sk.relativelevel = t(j.relativelevel) + if (isFoundryGCS) { + sk.notes = t(j.notes) + sk.pageRef(t(j.pageref)) + } else sk.setNotes(t(j.text)) + if (!!j.pageref) sk.pageRef(t(j.pageref)) + sk.uuid = t(j.uuid) + sk.parentuuid = t(j.parentuuid) + let old = this._findElementIn('skills', sk.uuid) + this._migrateOtfsAndNotes(old, sk, t(j.vtt_notes)) + + temp.push(sk) + } + } + return { + 'data.-=skills': null, + 'data.skills': this.foldList(temp), + } + } + + // create/update the spells. + // NOTE: For the update to work correctly, no two spells can have the same name. + // When reading data, use "this.system.spells", however, when updating, use "system.spells". + /** + * @param {{ [key: string]: any }} json + * @param {boolean} isFoundryGCS + */ + importSpellsFromGCSv1(json, isFoundryGCS) { + if (!json) return + let temp = [] + let t = this.textFrom /// shortcut to make code smaller + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let sp = new Spell() + sp.name = t(j.name) + sp.class = t(j.class) + sp.college = t(j.college) + if (isFoundryGCS) { + sp.cost = t(j.cost) + sp.maintain = t(j.maintain) + sp.difficulty = t(j.difficulty) + sp.relativelevel = t(j.relativelevel) + sp.notes = t(j.notes) + } else { + let cm = t(j.costmaintain) + let i = cm.indexOf('/') + if (i >= 0) { + sp.cost = cm.substring(0, i) + sp.maintain = cm.substr(i + 1) + } else { + sp.cost = cm + } + sp.setNotes(t(j.text)) + } + sp.pageRef(t(j.pageref)) + sp.duration = t(j.duration) + sp.points = t(j.points) + sp.casttime = t(j.time) + sp.import = t(j.level) + sp.uuid = t(j.uuid) + sp.parentuuid = t(j.parentuuid) + let old = this._findElementIn('spells', sp.uuid) + this._migrateOtfsAndNotes(old, sp, t(j.vtt_notes)) + temp.push(sp) + } + } + return { + 'data.-=spells': null, + 'data.spells': this.foldList(temp), + } + } + + /** + * @param {{ [key: string]: any }} adsjson + * @param {{ [key: string]: any }} disadsjson + */ + importAdsFromGCA(adsjson, disadsjson) { + /** @type {Advantage[]} */ + let list = [] + this.importBaseAdvantages(list, adsjson) + this.importBaseAdvantages(list, disadsjson) + return { + 'data.-=ads': null, + 'data.ads': this.foldList(list), + } + } + + /** + * @param {Advantage[]} datalist + * @param {{ [key: string]: any }} json + */ + importBaseAdvantages(datalist, json) { + if (!json) return + let t = this.textFrom /// shortcut to make code smaller + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let a = new Advantage() + a.name = t(j.name) + a.points = this.intFrom(j.points) + a.setNotes(t(j.text)) + a.pageRef(t(j.pageref) || a.pageref) + a.uuid = t(j.uuid) + a.parentuuid = t(j.parentuuid) + let old = this._findElementIn('ads', a.uuid) + this._migrateOtfsAndNotes(old, a, t(j.vtt_notes)) + datalist.push(a) + } + } + } + + // In the new GCS import, all ads/disad/quirks/perks are in one list. + /** + * @param {{ [key: string]: any }} json + */ + importAdsFromGCSv2(json) { + let t = this.textFrom /// shortcut to make code smaller + let temp = [] + for (let key in json) { + if (key.startsWith('id-')) { + // Allows us to skip over junk elements created by xml->json code, and only select the skills. + let j = json[key] + let a = new Advantage() + a.name = t(j.name) + a.points = this.intFrom(j.points) + a.note = t(j.notes) + a.userdesc = t(j.userdesc) + a.notes = '' + if (!!a.note && !!a.userdesc) a.notes = a.note + '\n' + a.userdesc + else if (!!a.note) a.notes = a.note + else if (!!a.userdesc) a.notes = a.userdesc + a.pageRef(t(j.pageref)) + a.uuid = t(j.uuid) + a.parentuuid = t(j.parentuuid) + let old = this._findElementIn('ads', a.uuid) + this._migrateOtfsAndNotes(old, a, t(j.vtt_notes)) + temp.push(a) + } + } + return { + 'data.-=ads': null, + 'data.ads': this.foldList(temp), + } + } + + /** + * Adds any assigned resource trackers to the actor data and sheet. + */ + async setResourceTrackers() { + // find those with non-blank slots + let templates = ResourceTrackerManager.getAllTemplates().filter(it => !!it.slot) + + for (const template of templates) { + // find the matching data on this actor + let index = zeroFill(template.slot, 4) + let path = `additionalresources.tracker.${index}` + let tracker = getProperty(this, path) + + while (!tracker) { + await this.addTracker() + tracker = getProperty(this, path) + } + + // skip if already set + if (!!tracker && tracker.name === template.tracker.name) { + return + } + + // if not blank, don't overwrite + if (!!tracker && !!tracker.name) { + ui.notifications?.warn( + `Will not overwrite Tracker ${template.slot} as its name is set to ${tracker.name}. Create Tracker for ${template.tracker.name} failed.` + ) + return + } + + await this.applyTrackerTemplate(path, template) + } + } + + /** + * Update this tracker slot with the contents of the template. + * @param {String} path JSON data path to the tracker; must start with 'additionalresources.tracker.' + * @param {*} template to apply + */ + async applyTrackerTemplate(path, template) { + // is there an initializer? If so calculate its value + let value = 0 + if (!!template.initialValue) { + value = parseInt(template.initialValue, 10) + if (Number.isNaN(value)) { + // try to use initialValue as a path to another value + value = getProperty(this, template.initialValue) + } + } + template.tracker.max = value + template.tracker.value = template.tracker.isDamageTracker ? template.tracker.min : value + + // remove whatever is there + await this.clearTracker(path) + + // add the new tracker + /** @type {{ [key: string]: any }} */ + let update = {} + update[`data.${path}`] = template.tracker + await this.update(update) + } + + /** + * Overwrites the tracker pointed to by the path with default/blank values. + * @param {String} path JSON data path to the tracker; must start with 'additionalresources.tracker.' + */ + async clearTracker(path) { + // verify that this is a Tracker + const prefix = 'additionalresources.tracker.' + if (!path.startsWith(prefix)) throw `Invalid actor data path, actor=[${this.id}] path=[${path}]` + + /** @type {{[key: string]: string}} */ + let update = {} + update[`data.${path}`] = { + name: '', + alias: '', + pdf: '', + max: 0, + min: 0, + value: 0, + isDamageTracker: false, + isDamageType: false, + initialValue: '', + thresholds: [], + } + await this.update(update) + } + + /** + * Removes the indicated tracker from the object, reindexing the keys. + * @param {String} path JSON data path to the tracker; must start with 'additionalresources.tracker.' + */ + async removeTracker(path) { + this.ignoreRender = true + const prefix = 'additionalresources.tracker.' + + // verify that this is a Tracker + if (!path.startsWith(prefix)) throw `Invalid actor data path, actor=[${this.id}] path=[${path}]` + + let key = path.replace(prefix, '') + let trackerData = this.system.additionalresources.tracker + delete trackerData[key] + let trackers = objectToArray(trackerData) + let data = arrayToObject(trackers) + + // remove all trackers + await this.update({ 'data.additionalresources.-=tracker': null }) + // add the new "array" of trackers + if (data) this.update({ 'data.additionalresources.tracker': data }) + else this.update('data.additionalresources.tracker', {}) + + this._forceRender() + } + + async addTracker() { + this.ignoreRender = true + + let trackerData = { name: '', value: 0, min: 0, max: 0, points: 0 } + let data = GurpsActor.addTrackerToDataObject(this.system, trackerData) + + await this.update({ 'data.additionalresources.-=tracker': null }) + await this.update({ 'data.additionalresources.tracker': data }) + + this._forceRender() + } + + static addTrackerToDataObject(data, trackerData) { + let trackers = GurpsActor.getTrackersAsArray(data) + trackers.push(trackerData) + return arrayToObject(trackers) + } + + static getTrackersAsArray(data) { + let trackerArray = data.additionalresources.tracker + if (!trackerArray) trackerArray = {} + return objectToArray(trackerArray) + } + + async setMoveDefault(value) { + this.ignoreRender = true + let move = this.system.move + for (const key in move) { + move[key].default = value === key + } + await this.update({ 'data.-=move': null }) + await this.update({ 'data.move': move }) + this._forceRender() + } + + // --- Functions to handle events on actor --- + + /** + * @param {any[]} damageData + */ + handleDamageDrop(damageData) { + if (game.user.isGM || !game.settings.get(settings.SYSTEM_NAME, settings.SETTING_ONLY_GMS_OPEN_ADD)) + new ApplyDamageDialog(this, damageData).render(true) + else ui.notifications?.warn('Only GMs are allowed to Apply Damage.') + } + + // Drag and drop from Item colletion + /** + * @param {{ type: any; x?: number; y?: number; payload?: any; pack?: any; id?: any; data?: any; }} dragData + */ + async handleItemDrop(dragData) { + if (!this.isOwner) { + ui.notifications?.warn(i18n('GURPS.youDoNotHavePermssion')) + return + } + const uuid = + typeof dragData.pack === 'string' + ? `Compendium.${dragData.pack}.${dragData.id}` + : `${dragData.type}.${dragData.id}` + let global = await fromUuid(uuid) + let data = !!global ? global.data : dragData.data + if (!data) { + ui.notifications?.warn('NO ITEM DATA!') + return + } + ui.notifications?.info(data.name + ' => ' + this.name) + if (!data.data.globalid) await data.update({ _id: data._id, 'data.globalid': uuid }) + this.ignoreRender = true + await this.addNewItemData(data) + this._forceRender() + } + + _forceRender() { + this.ignoreRender = false + //console.log("Force Render") + this.render() + } + + // Drag and drop from an equipment list + /** + * @param {{ type?: string; x?: number; y?: number; payload?: any; actorid?: any; itemid?: any; isLinked?: any; key?: any; itemData?: any; }} dragData + */ + async handleEquipmentDrop(dragData) { + if (dragData.actorid == this.id) return false // same sheet drag and drop handled elsewhere + if (!dragData.itemid) { + ui.notifications?.warn(i18n('GURPS.cannotDragNonFoundryEqt')) + return + } + if (!dragData.isLinked) { + ui.notifications?.warn("You cannot drag from an un-linked token. The source must have 'Linked Actor Data'") + return + } + let srcActor = game.actors.get(dragData.actorid) + let eqt = getProperty(srcActor, dragData.key) + if ( + (!!eqt.contains && Object.keys(eqt.contains).length > 0) || + (!!eqt.collapsed && Object.keys(eqt.collapsed).length > 0) + ) { + ui.notifications?.warn('You cannot transfer an Item that contains other equipment.') + return + } + + if (!!this.isOwner && !!srcActor.isOwner) { + // same owner + if (eqt.count < 2) { + let destKey = this._findEqtkeyForId('globalid', eqt.globalid) + if (!!destKey) { + // already have some + let destEqt = getProperty(this, destKey) + await this.updateEqtCount(destKey, destEqt.count + eqt.count) + await srcActor.deleteEquipment(dragData.key) + } else { + let item = await srcActor.deleteEquipment(dragData.key) + await this.addNewItemData(item.data) + } + } else { + let content = await renderTemplate('systems/gurps/templates/transfer-equipment.html', { eqt: eqt }) + let callback = async (/** @type {JQuery | HTMLElement} */ html) => { + // @ts-ignore + let qty = parseInt(html.find('#qty').val()) + let destKey = this._findEqtkeyForId('globalid', eqt.globalid) + if (!!destKey) { + // already have some + let destEqt = getProperty(this, destKey) + await this.updateEqtCount(destKey, destEqt.count + qty) + } else { + let item = /** @type {GurpsItem} */ (srcActor.items.get(eqt.itemid)) + item.system.eqt.count = qty + await this.addNewItemData(item.data) + } + if (qty >= eqt.count) await srcActor.deleteEquipment(dragData.key) + else await srcActor.updateEqtCount(dragData.key, eqt.count - qty) + } + + Dialog.prompt({ + title: i18n('GURPS.TransferTo') + ' ' + this.name, + label: i18n('GURPS.ok'), + content: content, + callback: callback, + rejectClose: false, // Do not "reject" if the user presses the "close" gadget + }) + } + } else { + // different owners + let count = eqt.count + if (eqt.count > 1) { + let content = await renderTemplate('systems/gurps/templates/transfer-equipment.html', { eqt: eqt }) + let callback = async (/** @type {HTMLElement | JQuery} */ html) => + // @ts-ignore + (count = parseInt(html.find('#qty').val())) + await Dialog.prompt({ + title: i18n('GURPS.TransferTo') + ' ' + this.name, + label: i18n('GURPS.ok'), + content: content, + callback: callback, + }) + } + if (count > eqt.count) count = eqt.count + let destowner = game.users?.players.find(p => this.testUserPermission(p, 'OWNER')) + if (!!destowner) { + ui.notifications?.info(`Asking ${this.name} if they want ${eqt.name}`) + dragData.itemData.data.eqt.count = count // They may not have given all of them + game.socket.emit('system.gurps', { + type: 'dragEquipment1', + srckey: dragData.key, + srcuserid: game.user.id, + srcactorid: dragData.actorid, + destuserid: destowner.id, + destactorid: this.id, + itemData: dragData.itemData, + count: count, + }) + } else ui.notifications?.warn(i18n('GURPS.youDoNotHavePermssion')) + } + } + + // Called from the ItemEditor to let us know our personal Item has been modified + /** + * @param {Item} item + */ + async updateItem(item) { + // @ts-ignore + delete item.editingActor + this.ignoreRender = true + if (item.id) await this._removeItemAdditions(item.id) + let _data = GurpsItem.asGurpsItem(item).system + let oldkey = this._findEqtkeyForId('globalid', _data.globalid) + var oldeqt + if (!!oldkey) oldeqt = getProperty(this, oldkey) + let other = item.id ? await this._removeItemElement(item.id, 'equipment.other') : null // try to remove from other + if (!other) { + // if not in other, remove from carried, and then re-add everything + if (item.id) await this._removeItemElement(item.id, 'equipment.carried') + await this.addItemData(item.data) + } else { + // If was in other... just add back to other (and forget addons) + await this._addNewItemEquipment(item.data, 'data.equipment.other.' + zeroFill(0)) + } + let newkey = this._findEqtkeyForId('globalid', _data.globalid) + if (!!oldeqt && (!!oldeqt.contains || !!oldeqt.collapsed)) { + this.update({ + [newkey + '.contains']: oldeqt.contains, + [newkey + '.collapsed']: oldeqt.collapsed, + }) + } + this._forceRender() + } + + // create a new embedded item based on this item data and place in the carried list + // This is how all Items are added originally. + /** + * @param {ItemData} itemData + * @param {string | null} [targetkey] + */ + async addNewItemData(itemData, targetkey = null) { + let d = itemData + // @ts-ignore + if (typeof itemData.toObject === 'function') d = itemData.toObject() + // @ts-ignore + let localItems = await this.createEmbeddedDocuments('Item', [d]) // add a local Foundry Item based on some Item data + let localItem = localItems[0] + await this.updateEmbeddedDocuments('Item', [{ _id: localItem.id, 'system.eqt.uuid': generateUniqueId() }]) + await this.addItemData(localItem.data, targetkey) // only created 1 item + } + + // Once the Items has been added to our items list, add the equipment and any features + /** + * @param {ItemData} itemData + * @param {string | null} [targetkey] + */ + async addItemData(itemData, targetkey) { + let [eqtkey, addFeatures] = await this._addNewItemEquipment(itemData, targetkey) + if (addFeatures) { + await this._addItemAdditions(itemData, eqtkey) + } + } + + // Make the initial equipment object (unless it already exists, saved in a user equipment) + /** + * @param {ItemData} itemData + * @param {string | null} targetkey + */ + async _addNewItemEquipment(itemData, targetkey) { + let existing = this._findEqtkeyForId('itemid', itemData._id) + if (!!existing) { + // it may already exist (due to qty updates), so don't add it again + let eqt = getProperty(this, existing) + return [existing, eqt.carried && eqt.equipped] + } + let _data = /** @type {GurpsItemData} */ (itemData.data) + if (!!_data.eqt.parentuuid) { + var found + recurselist(this.system.equipment.carried, (e, k, d) => { + if (e.uuid == _data.eqt.parentuuid) found = 'data.equipment.carried.' + k + }) + if (!found) + recurselist(this.system.equipment.other, (e, k, d) => { + if (e.uuid == _data.eqt.parentuuid) found = 'data.equipment.other.' + k + }) + if (!!found) { + targetkey = found + '.contains.' + zeroFill(0) + } + } + if (targetkey == null) + if (_data.carried) { + // new carried items go at the end + targetkey = 'data.equipment.carried' + let index = 0 + let list = getProperty(this, targetkey) + while (list.hasOwnProperty(zeroFill(index))) index++ + targetkey += '.' + zeroFill(index) + } else targetkey = 'data.equipment.other' + if (targetkey.match(/^data\.equipment\.\w+$/)) targetkey += '.' + zeroFill(0) //if just 'carried' or 'other' + let eqt = _data.eqt + if (!eqt) { + ui.notifications?.warn('Item: ' + itemData._id + ' (Global:' + _data.globalid + ') missing equipment') + return ['', false] + } else { + eqt.itemid = itemData._id + eqt.globalid = _data.globalid + //eqt.uuid = 'item-' + eqt.itemid + eqt.equipped = !!itemData.data.equipped ?? true + eqt.img = itemData.img + eqt.carried = !!itemData.data.carried ?? true + await GURPS.insertBeforeKey(this, targetkey, eqt) + await this.updateParentOf(targetkey, true) + return [targetkey, eqt.carried && eqt.equipped] + } + } + + /** + * @param {GurpsItemData} itemData + * @param {string} eqtkey + */ + async _addItemAdditions(itemData, eqtkey) { + let commit = {} + commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'melee')) } + commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'ranged')) } + commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'ads')) } + commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'skills')) } + commit = { ...commit, ...(await this._addItemElement(itemData, eqtkey, 'spells')) } + await this.internalUpdate(commit, { diff: false }) + this.calculateDerivedValues() // new skills and bonuses may affect other items... force a recalc + } + + // called when equipment is being moved + /** + * @param {Equipment} eqt + * @param {string} targetPath + */ + async updateItemAdditionsBasedOn(eqt, targetPath) { + await this._updateEqtStatus(eqt, targetPath, targetPath.includes('.carried')) + } + + // Equipment may carry other eqt, so we must adjust the carried status all the way down. + /** + * @param {Equipment} eqt + * @param {string} eqtkey + * @param {boolean} carried + */ + async _updateEqtStatus(eqt, eqtkey, carried) { + eqt.carried = carried + if (!!eqt.itemid) { + let item = /** @type {Item} */ (await this.items.get(eqt.itemid)) + await this.updateEmbeddedDocuments('Item', [ + { _id: item.id, 'data.equipped': eqt.equipped, 'data.carried': carried }, + ]) + if (!carried || !eqt.equipped) await this._removeItemAdditions(eqt.itemid) + if (carried && eqt.equipped) await this._addItemAdditions(item.data, eqtkey) + } + for (const k in eqt.contains) await this._updateEqtStatus(eqt.contains[k], eqtkey + '.contains.' + k, carried) + for (const k in eqt.collapsed) await this._updateEqtStatus(eqt.collapsed[k], eqtkey + '.collapsed.' + k, carried) + } + + /** + * @param {ItemData} itemData + * @param {string} eqtkey + * @param {string} key + */ + async _addItemElement(itemData, eqtkey, key) { + let found = false + // @ts-ignore + recurselist(this.system[key], (e, k, d) => { + if (e.itemid == itemData._id) found = true + }) + if (found) return + // @ts-ignore + let list = { ...this.system[key] } // shallow copy + let i = 0 + // @ts-ignore + for (const k in itemData.data[key]) { + // @ts-ignore + let e = duplicate(itemData.data[key][k]) + e.itemid = itemData._id + e.uuid = key + '-' + i++ + '-' + e.itemid + e.eqtkey = eqtkey + e.img = itemData.img + GURPS.put(list, e) + } + return i == 0 ? {} : { ['data.' + key]: list } + } + + // return the item data that was deleted (since it might be transferred) + /** + * @param {string} path + */ + async deleteEquipment(path, depth = 0) { + let eqt = getProperty(this, path) + if (!eqt) return eqt + if (depth == 0) this.ignoreRender = true + + // Delete in reverse order so the keys don't get messed up + if (!!eqt.contains) + for (const k of Object.keys(eqt.contains).sort().reverse()) + await this.deleteEquipment(path + '.contains.' + k, depth + 1) + if (!!eqt.collpased) + for (const k of Object.keys(eqt.collapsed).sort().reverse()) + await this.deleteEquipment(path + '.collapsed.' + k, depth + 1) + + var item + if (!!eqt.itemid) { + item = await this.items.get(eqt.itemid) + if (!!item) await item.delete() // data protect for messed up mooks + await this._removeItemAdditions(eqt.itemid) + } + await GURPS.removeKey(this, path) + if (depth == 0) this._forceRender() + return item + } + + /** + * @param {string} itemid + */ + async _removeItemAdditions(itemid) { + let saved = this.ignoreRender + this.ignoreRender = true + await this._removeItemElement(itemid, 'melee') + await this._removeItemElement(itemid, 'ranged') + await this._removeItemElement(itemid, 'ads') + await this._removeItemElement(itemid, 'skills') + await this._removeItemElement(itemid, 'spells') + this.ignoreRender = saved + } + + // We have to remove matching items after we searched through the list + // because we cannot safely modify the list why iterating over it + // and as such, we can only remove 1 key at a time and must use thw while loop to check again + /** + * @param {string} itemid + * @param {string} key + */ + async _removeItemElement(itemid, key) { + let found = true + let any = false + while (!!found) { + found = false + let list = getProperty(this, key) + recurselist(list, (e, k, d) => { + if (e.itemid == itemid) found = k + }) + if (!!found) { + any = true + await GURPS.removeKey(this, 'data.' + key + '.' + found) + } + } + return any + } + + /** + * @param {string} srckey + * @param {string} targetkey + * @param {boolean} shiftkey + */ + async moveEquipment(srckey, targetkey, shiftkey) { + if (srckey == targetkey) return + if (shiftkey && (await this._splitEquipment(srckey, targetkey))) return + // Because we may be modifing the same list, we have to check the order of the keys and + // apply the operation that occurs later in the list, first (to keep the indexes the same) + let srca = srckey.split('.') + srca.splice(0, 3) + let tara = targetkey.split('.') + tara.splice(0, 3) + let max = Math.min(srca.length, tara.length) + let isSrcFirst = max == 0 ? srca.length > tara.length : false + for (let i = 0; i < max; i++) { + if (parseInt(srca[i]) < parseInt(tara[i])) isSrcFirst = true + } + let object = getProperty(this, srckey) + if (targetkey.match(/^data\.equipment\.\w+$/)) { + this.ignoreRender = true + object.parentuuid = '' + if (!!object.itemid) { + let item = /** @type {Item} */ (this.items.get(object.itemid)) + await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.eqt.parentuuid': '' }]) + } + let target = { ...GURPS.decode(this.data, targetkey) } // shallow copy the list + if (!isSrcFirst) await GURPS.removeKey(this, srckey) + let eqtkey = GURPS.put(target, object) + await this.updateItemAdditionsBasedOn(object, targetkey + '.' + eqtkey) + await this.update({ [targetkey]: target }) + if (isSrcFirst) await GURPS.removeKey(this, srckey) + return this._forceRender() + } + if (await this._checkForMerging(srckey, targetkey)) return + if (srckey.includes(targetkey) || targetkey.includes(srckey)) { + ui.notifications.error('Unable to drag and drop withing the same hierarchy. Try moving it elsewhere first.') + return + } + this.toggleExpand(targetkey, true) + let d = new Dialog({ + title: object.name, + content: '

    Where do you want to place this?

    ', + buttons: { + one: { + icon: '', + label: 'Before', + callback: async () => { + this.ignoreRender = true + if (!isSrcFirst) { + await GURPS.removeKey(this, srckey) + await this.updateParentOf(srckey, false) + } + await this.updateItemAdditionsBasedOn(object, targetkey) + await GURPS.insertBeforeKey(this, targetkey, object) + await this.updateParentOf(targetkey, true) + if (isSrcFirst) { + await GURPS.removeKey(this, srckey) + await this.updateParentOf(srckey, false) + } + this._forceRender() + }, + }, + two: { + icon: '', + label: 'In', + callback: async () => { + this.ignoreRender = true + if (!isSrcFirst) { + await GURPS.removeKey(this, srckey) + await this.updateParentOf(srckey, false) + } + let k = targetkey + '.contains.' + zeroFill(0) + let targ = getProperty(this, targetkey) + + await this.updateItemAdditionsBasedOn(object, targetkey) + await GURPS.insertBeforeKey(this, k, object) + await this.updateParentOf(k, true) + if (isSrcFirst) { + await GURPS.removeKey(this, srckey) + await this.updateParentOf(srckey, false) + } + this._forceRender() + }, + }, + }, + default: 'one', + }) + d.render(true) + } + + /** + * @param {string} path + */ + async toggleExpand(path, expandOnly = false) { + let obj = getProperty(this, path) + if (!!obj.collapsed && Object.keys(obj.collapsed).length > 0) { + let temp = { ...obj.contains, ...obj.collapsed } + let update = { + [path + '.-=collapsed']: null, + [path + '.collapsed']: {}, + [path + '.contains']: temp, + } + await this.update(update) + } else if (!expandOnly && !!obj.contains && Object.keys(obj.contains).length > 0) { + let temp = { ...obj.contains, ...obj.collapsed } + let update = { + [path + '.-=contains']: null, + [path + '.contains']: {}, + [path + '.collapsed']: temp, + } + await this.update(update) + } + } + + /** + * @param {string} srckey + * @param {string} targetkey + */ + async _splitEquipment(srckey, targetkey) { + let srceqt = getProperty(this, srckey) + if (srceqt.count <= 1) return false // nothing to split + let content = await renderTemplate('systems/gurps/templates/transfer-equipment.html', { eqt: srceqt }) + let count = 0 + let callback = async (/** @type {JQuery} */ html) => + (count = parseInt(html.find('#qty').val()?.toString() || '0')) + await Dialog.prompt({ + title: 'Split stack', + label: i18n('GURPS.ok'), + content: content, + callback: callback, + }) + if (count <= 0) return true // didn't want to split + if (count >= srceqt.count) return false // not a split, but a move + if (targetkey.match(/^data\.equipment\.\w+$/)) targetkey += '.' + zeroFill(0) + if (!!srceqt.globalid) { + this.ignoreRender = true + await this.updateEqtCount(srckey, srceqt.count - count) + let rawItem = this.items.get(srceqt.itemid) + if (rawItem) { + let item = GurpsItem.asGurpsItem(rawItem) + item.system.eqt.count = count + await this.addNewItemData(item.data, targetkey) + await this.updateParentOf(targetkey, true) + } + this._forceRender() + return true + } else { + // simple eqt + let neqt = duplicate(srceqt) + neqt.count = count + this.ignoreRender = true + await this.updateEqtCount(srckey, srceqt.count - count) + await GURPS.insertBeforeKey(this, targetkey, neqt) + await this.updateParentOf(targetkey, true) + this._forceRender() + return true + } + return false + } + + /** + * @param {string} srckey + * @param {string} targetkey + */ + async _checkForMerging(srckey, targetkey) { + let srceqt = getProperty(this, srckey) + let desteqt = getProperty(this, targetkey) + if ( + (!!srceqt.globalid && srceqt.globalid == desteqt.globalid) || + (!srceqt.globalid && srceqt.name == desteqt.name) + ) { + this.ignoreRender = true + await this.updateEqtCount(targetkey, parseInt(srceqt.count) + parseInt(desteqt.count)) + //if (srckey.includes('.carried') && targetkey.includes('.other')) await this._removeItemAdditionsBasedOn(desteqt) + await this.deleteEquipment(srckey) + this._forceRender() + return true + } + return false + } + + // This function merges the 'where' and 'dr' properties of this actor's hitlocations + // with the roll value from the HitLocations.hitlocationRolls, converting the + // roll from a string to an array of numbers (see the _convertRollStringToArrayOfInt + // function). + // + // return value is an object with the following structure: + // { + // where: string value from the hitlocations table, + // dr: int DR value from the hitlocations table, + // rollText: string value of the roll from the hitlocations table (examples: '5', '6-9', '-') + // roll: array of int of the values that match rollText (examples: [5], [6,7,8,9], []) + // } + get hitLocationsWithDR() { + let myhitlocations = [] + let table = this._hitLocationRolls + for (const [key, value] of Object.entries(this.system.hitlocations)) { + let rollText = value.roll + if (!value.roll && !!table[value.where]) + // Can happen if manually edited + rollText = table[value.where].roll + if (!rollText) rollText = HitLocations.HitLocation.DEFAULT + let dr = parseInt(value.dr) + if (isNaN(dr)) dr = 0 + let entry = new HitLocationEntry(value.where, dr, rollText, value?.split) + myhitlocations.push(entry) + } + return myhitlocations + } + + /** + * @returns the appropriate hitlocation table based on the actor's bodyplan + */ + get _hitLocationRolls() { + return HitLocations.HitLocation.getHitLocationRolls(this.system.additionalresources?.bodyplan) + } + + // Return the 'where' value of the default hit location, or 'Random' + // NOTE: could also return 'Large-Area'? + get defaultHitLocation() { + // TODO implement a system setting but (potentially) allow it to be overridden + return game.settings.get('gurps', 'default-hitlocation') + } + + getCurrentDodge() { + return this.system.currentdodge + } + + getCurrentMove() { + return this.system.currentmove + } + + getTorsoDr() { + if (!this.system.hitlocations) return 0 + let hl = Object.values(this.system.hitlocations).find(h => h.penalty == 0) + return !!hl ? hl : { dr: 0 } + } + + /** + * @param {string} key + */ + getEquipped(key) { + let val = 0 + let txt = '' + if (!!this.system.melee && !!this.system.equipment?.carried) + Object.values(this.system.melee).forEach(melee => { + recurselist(this.system.equipment.carried, (e, k, d) => { + if (!!e && !val && e.equipped && !!melee.name.match(makeRegexPatternFrom(e.name, false))) { + let t = parseInt(melee[key]) + if (!isNaN(t)) { + val = t + txt = '' + melee[key] + } + } + }) + }) + // @ts-ignore + if (!val && !!this.system[key]) { + txt = '' + this.system[key] + val = parseInt(txt) + } + return [txt, val] + } + + getEquippedParry() { + let [txt, val] = this.getEquipped('parry') + this.system.equippedparryisfencing = !!txt && txt.match(/f$/i) + return val + } + + getEquippedBlock() { + return this.getEquipped('block')[1] + } + + /** + * + * @param {string} name of the status effect + * @param {boolean} active (desired) state - true or false + */ + toggleEffectByName(name, active) { + let tokens = this.getActiveTokens(true) + for (const token of tokens) { + token.setEffectActive(name, active) + } + } + + /** + * @param {string} pattern + */ + findEquipmentByName(pattern, otherFirst = false) { + while (pattern[0] == '/') pattern = pattern.substr(1) + pattern = makeRegexPatternFrom(pattern, false) + let pats = pattern + .substr(1) // remove the ^ from the beginning of the string + .split('/') + .map(e => new RegExp('^' + e, 'i')) // and apply it to each pattern + /** + * @type {any} + */ + var eqt, key + let list1 = otherFirst ? this.system.equipment.other : this.system.equipment.carried + let list2 = otherFirst ? this.system.equipment.carried : this.system.equipment.other + let pkey1 = otherFirst ? 'data.equipment.other.' : 'data.equipment.carried.' + let pkey2 = otherFirst ? 'data.equipment.carried.' : 'data.equipment.other.' + recurselist( + list1, + (e, k, d) => { + let l = pats.length - 1 + let p = pats[Math.min(d, l)] + if (e.name.match(p)) { + if (!eqt && (d == l || pats.length == 1)) { + eqt = e + key = k + } + } else return pats.length == 1 + }, + pkey1 + ) + recurselist( + list2, + (e, k, d) => { + let l = pats.length - 1 + let p = pats[Math.min(d, l)] + if (e.name.match(p)) { + if (!eqt && (d == l || pats.length == 1)) { + eqt = e + key = k + } + } else return pats.length == 1 + }, + pkey2 + ) + return [eqt, key] + } + + /** + * @param {number} currentWeight + */ + checkEncumbance(currentWeight) { + /** @type {{ [key: string]: any }} */ + let encs = this.system.encumbrance + let last = zeroFill(0) // if there are encumbrances, there will always be a level0 + var best, prev + for (let key in encs) { + let enc = encs[key] + if (enc.current) prev = key + let w = parseFloat(enc.weight) + if (w > 0) { + last = key + if (currentWeight <= w) { + best = key + break + } + } + } + if (!best) best = last // that has a weight + if (best != prev) { + for (let key in encs) { + let enc = encs[key] + let t = 'data.encumbrance.' + key + '.current' + if (key === best) { + enc.current = true + this.system.currentmove = parseInt(enc.currentmove) + this.system.currentdodge = parseInt(enc.currentdodge) + } else if (enc.current) { + enc.current = false + } + } + } + } + + // Set the equipment count to 'count' and then recalc sums + /** + * @param {string} eqtkey + * @param {number} count + */ + async updateEqtCount(eqtkey, count) { + /** @type {{ [key: string]: any }} */ + let update = { [eqtkey + '.count']: count } + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_AUTOMATICALLY_SET_IGNOREQTY)) + update[eqtkey + '.ignoreImportQty'] = true + await this.update(update) + let eqt = getProperty(this, eqtkey) + await this.updateParentOf(eqtkey, false) + if (!!eqt.itemid) { + let item = this.items.get(eqt.itemid) + if (!!item) await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.eqt.count': count }]) + else { + ui.notifications?.warn('Invalid Item in Actor... removing all features') + this._removeItemAdditions(eqt.itemid) + } + } + } + + // Used to recalculate weight and cost sums for a whole tree. + /** + * @param {string} srckey + */ + async updateParentOf(srckey, updatePuuid = true) { + // pindex = 4 for equipment + let pindex = 4 + let paths = srckey.split('.') + let sp = paths.slice(0, pindex).join('.') // find the top level key in this list + // But count may have changed... if (srckey == sp) return // no parent for this eqt + let parent = getProperty(this, sp) + if (!!parent) { + // data protection + await Equipment.calcUpdate(this, parent, sp) // and re-calc cost and weight sums from the top down + if (updatePuuid) { + let puuid = '' + if (paths.length >= 6) { + sp = paths.slice(0, -2).join('.') + puuid = getProperty(this, sp).uuid + } + await this.internalUpdate({ [srckey + '.parentuuid']: puuid }) + let eqt = getProperty(this, srckey) + if (!!eqt.itemid) { + let item = this.items.get(eqt.itemid) + if (item) await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.eqt.parentuuid': puuid }]) + } + } + } + } + + isEmptyActor() { + let d = this.system + let chkAttr = (/** @type {string} */ attr) => { + return d.attributes[attr].import != 10 + } + + if (d.HP.max != 0) return false + if (d.HP.value != 0) return false + if (d.FP.max != 0) return false + if (d.FP.value != 0) return false + if (chkAttr('ST')) return false + if (chkAttr('DX')) return false + if (chkAttr('IQ')) return false + if (chkAttr('HT')) return false + if (chkAttr('WILL')) return false + if (chkAttr('PER')) return false + + return true + } } diff --git a/module/actor/effect-modifier-popout.js b/module/actor/effect-modifier-popout.js index 5aa72033c..ac3a27241 100644 --- a/module/actor/effect-modifier-popout.js +++ b/module/actor/effect-modifier-popout.js @@ -3,87 +3,85 @@ import { i18n, i18n_f } from '../../lib/utilities.js' import { gurpslink } from '../../module/utilities/gurpslink.js' export class EffectModifierPopout extends Application { - constructor(token, callback, options = {}) { - super(options) - this._token = token - this._callback = callback - } + constructor(token, callback, options = {}) { + super(options) + this._token = token + this._callback = callback + } - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - template: 'systems/gurps/templates/actor/effect-modifier-popout.hbs', - classes: ['sidebar-popout effect-modifiers-app'], - popOut: true, - top: 0, - width: 400, - height: 'auto', - minimizable: true, - jQuery: true, - resizable: true, - title: i18n('GURPS.effectModifierPopout', 'Effect Modifiers'), - }) - } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + template: 'systems/gurps/templates/actor/effect-modifier-popout.hbs', + classes: ['sidebar-popout effect-modifiers-app'], + popOut: true, + top: 0, + width: 400, + height: 'auto', + minimizable: true, + jQuery: true, + resizable: true, + title: i18n('GURPS.effectModifierPopout', 'Effect Modifiers'), + }) + } - /** @override */ - getData(options) { - return mergeObject(super.getData(options), { - selected: this.selectedToken, - selfmodifiers: this._token ? this.convertModifiers(this._token.actor.system.conditions.self.modifiers) : [], - targetmodifiers: this._token - ? this.convertModifiers(this._token.actor.system.conditions.target.modifiers) - : [], - targets: this.targets, - }) - } + /** @override */ + getData(options) { + return mergeObject(super.getData(options), { + selected: this.selectedToken, + selfmodifiers: this._token ? this.convertModifiers(this._token.actor.system.conditions.self.modifiers) : [], + targetmodifiers: this._token ? this.convertModifiers(this._token.actor.system.conditions.target.modifiers) : [], + targets: this.targets, + }) + } - get targets() { - let results = [] - for (const target of Array.from(game.user.targets)) { - let result = {} - result.name = target.data.name - result.targetmodifiers = target.actor - ? this.convertModifiers(target.actor.system.conditions.target.modifiers) - : [] - results.push(result) - } - return results - } + get targets() { + let results = [] + for (const target of Array.from(game.user.targets)) { + let result = {} + result.name = target.data.name + result.targetmodifiers = target.actor + ? this.convertModifiers(target.actor.system.conditions.target.modifiers) + : [] + results.push(result) + } + return results + } - convertModifiers(list) { - return list.map(it => `[${i18n(it)}]`).map(it => gurpslink(it)) - } + convertModifiers(list) { + return list.map(it => `[${i18n(it)}]`).map(it => gurpslink(it)) + } - get selectedToken() { - return this._token?.name ?? i18n('GURPS.effectModNoTokenSelected') - } + get selectedToken() { + return this._token?.name ?? i18n('GURPS.effectModNoTokenSelected') + } - getToken() { - return this._token - } + getToken() { + return this._token + } - async setToken(value) { - this._token = value - await this.render(false) - } + async setToken(value) { + this._token = value + await this.render(false) + } - /** @override */ - activateListeners(html) { - GurpsWiring.hookupAllEvents(html) - GurpsWiring.hookupGurpsRightClick(html) + /** @override */ + activateListeners(html) { + GurpsWiring.hookupAllEvents(html) + GurpsWiring.hookupGurpsRightClick(html) - html - .closest('div.effect-modifiers-app') - .find('.window-title') - .text(i18n_f('GURPS.effectModifierPopout', { name: this.selectedToken }, 'Effect Modifiers: {name}')) - } + html + .closest('div.effect-modifiers-app') + .find('.window-title') + .text(i18n_f('GURPS.effectModifierPopout', { name: this.selectedToken }, 'Effect Modifiers: {name}')) + } - /** @override */ - async close(options) { - this._callback.close(options) - } + /** @override */ + async close(options) { + this._callback.close(options) + } - async closeApp(options) { - super.close(options) - } + async closeApp(options) { + super.close(options) + } } diff --git a/module/actor/maneuver.js b/module/actor/maneuver.js index 89d482b57..db27826a8 100644 --- a/module/actor/maneuver.js +++ b/module/actor/maneuver.js @@ -21,17 +21,17 @@ const oldTemporaryEffects = Object.getOwnPropertyDescriptor(Actor.prototype, 'te // Override Actor.temporaryEffects getter to sort maneuvers to the front of the array Object.defineProperty(Actor.prototype, 'temporaryEffects', { - get: function() { - let results = oldTemporaryEffects?.get?.call(this) - - if (!!results && results.length > 1) { - // get the active temporary effects that are also maneuvers - const maneuvers = results.filter((/** @type {ActiveEffect} */ e) => e.getFlag('core', 'statusId') === MANEUVER) - const notManeuvers = results.filter((/** @type {ActiveEffect} */ e) => e.getFlag('core', 'statusId') !== MANEUVER) - results = [...maneuvers, ...notManeuvers] - } - return results - }, + get: function () { + let results = oldTemporaryEffects?.get?.call(this) + + if (!!results && results.length > 1) { + // get the active temporary effects that are also maneuvers + const maneuvers = results.filter((/** @type {ActiveEffect} */ e) => e.getFlag('core', 'statusId') === MANEUVER) + const notManeuvers = results.filter((/** @type {ActiveEffect} */ e) => e.getFlag('core', 'statusId') !== MANEUVER) + results = [...maneuvers, ...notManeuvers] + } + return results + }, }) /** @@ -45,331 +45,331 @@ Object.defineProperty(Actor.prototype, 'temporaryEffects', { * The purpose of this class is to help generate data that can be used in an ActiveEffect. */ class Maneuver { - static filepath = 'systems/gurps/icons/maneuvers/' - /** - * @param {_data} data - */ - constructor(data) { - data.move = data.move || MOVE_STEP - data.defense = data.defense || DEFENSE_ANY - data.fullturn = !!data.fullturn - data.icon = Maneuver.filepath + data.icon - data.alt = !!data.alt ? Maneuver.filepath + data.alt : null - this._data = data - } - - get icon() { - return this._data.icon - } - - get move() { - return this._data.move - } - - /** @returns {ManeuverData} */ - get data() { - return { - id: MANEUVER, - label: this._data.label, - icon: this._data.icon, - flags: { - gurps: { - name: this._data.name, - move: this._data.move, - defense: this._data.defense, - fullturn: this._data.fullturn, - icon: this._data.icon, - alt: this._data.alt, - }, - }, - changes: this.changes, - } - } - - /** @returns {import('@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/effectChangeData').EffectChangeDataConstructorData[]} */ - get changes() { - let changes = [] - - changes.push({ - key: 'data.conditions.maneuver', - value: this._data.name, - mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE, - }) - - changes.push({ key: PROPERTY_MOVEOVERRIDE_MANEUVER, value: this.move, mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE }) - - return changes - } + static filepath = 'systems/gurps/icons/maneuvers/' + /** + * @param {_data} data + */ + constructor(data) { + data.move = data.move || MOVE_STEP + data.defense = data.defense || DEFENSE_ANY + data.fullturn = !!data.fullturn + data.icon = Maneuver.filepath + data.icon + data.alt = !!data.alt ? Maneuver.filepath + data.alt : null + this._data = data + } + + get icon() { + return this._data.icon + } + + get move() { + return this._data.move + } + + /** @returns {ManeuverData} */ + get data() { + return { + id: MANEUVER, + label: this._data.label, + icon: this._data.icon, + flags: { + gurps: { + name: this._data.name, + move: this._data.move, + defense: this._data.defense, + fullturn: this._data.fullturn, + icon: this._data.icon, + alt: this._data.alt, + }, + }, + changes: this.changes, + } + } + + /** @returns {import('@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/effectChangeData').EffectChangeDataConstructorData[]} */ + get changes() { + let changes = [] + + changes.push({ + key: 'data.conditions.maneuver', + value: this._data.name, + mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE, + }) + + changes.push({ key: PROPERTY_MOVEOVERRIDE_MANEUVER, value: this.move, mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE }) + + return changes + } } const maneuvers = { - do_nothing: new Maneuver({ - name: 'do_nothing', - label: 'GURPS.maneuverDoNothing', - icon: 'man-nothing.png', - move: MOVE_NONE, - }), - move: new Maneuver({ - name: 'move', - label: 'GURPS.maneuverMove', - icon: 'man-move.png', - move: MOVE_FULL, - }), - aim: new Maneuver({ - name: 'aim', - fullturn: true, - icon: 'man-aim.png', - label: 'GURPS.maneuverAim', - }), - change_posture: new Maneuver({ - name: 'change_posture', - move: MOVE_NONE, - icon: 'man-change-posture.png', - label: 'GURPS.maneuverChangePosture', - }), - evaluate: new Maneuver({ - name: 'evaluate', - icon: 'man-evaluate.png', - label: 'GURPS.maneuverEvaluate', - }), - attack: new Maneuver({ - name: 'attack', - icon: 'man-attack.png', - label: 'GURPS.maneuverAttack', - }), - feint: new Maneuver({ - name: 'feint', - icon: 'man-feint.png', - label: 'GURPS.maneuverFeint', - alt: 'man-attack.png', - }), - allout_attack: new Maneuver({ - name: 'allout_attack', - move: MOVE_HALF, - defense: DEFENSE_NONE, - icon: 'man-allout-attack.png', - label: 'GURPS.maneuverAllOutAttack', - }), - aoa_determined: new Maneuver({ - name: 'aoa_determined', - move: MOVE_HALF, - defense: DEFENSE_NONE, - icon: 'man-aoa-determined.png', - label: 'GURPS.maneuverAllOutAttackDetermined', - alt: 'man-allout-attack.png', - }), - aoa_double: new Maneuver({ - name: 'aoa_double', - move: MOVE_HALF, - defense: DEFENSE_NONE, - icon: 'man-aoa-double.png', - label: 'GURPS.maneuverAllOutAttackDouble', - alt: 'man-allout-attack.png', - }), - aoa_feint: new Maneuver({ - name: 'aoa_feint', - move: MOVE_HALF, - defense: DEFENSE_NONE, - icon: 'man-aoa-feint.png', - label: 'GURPS.maneuverAllOutAttackFeint', - alt: 'man-allout-attack.png', - }), - aoa_strong: new Maneuver({ - name: 'aoa_strong', - move: MOVE_HALF, - defense: DEFENSE_NONE, - alt: 'man-allout-attack.png', - icon: 'man-aoa-strong.png', - label: 'GURPS.maneuverAllOutAttackStrong', - }), - aoa_suppress: new Maneuver({ - name: 'aoa_suppress', - move: MOVE_HALF, - defense: DEFENSE_NONE, - alt: 'man-allout-attack.png', - icon: 'man-aoa-suppress.png', - label: 'GURPS.maneuverAllOutAttackSuppressFire', - }), - move_and_attack: new Maneuver({ - name: 'move_and_attack', - move: MOVE_FULL, - defense: DEFENSE_DODGEBLOCK, - icon: 'man-move-attack.png', - label: 'GURPS.maneuverMoveAttack', - }), - allout_defense: new Maneuver({ - name: 'allout_defense', - move: MOVE_HALF, - icon: 'man-defense.png', - label: 'GURPS.maneuverAllOutDefense', - }), - aod_dodge: new Maneuver({ - name: 'aod_dodge', - move: MOVE_HALF, - alt: 'man-defense.png', - icon: 'man-def-dodge.png', - label: 'GURPS.maneuverAllOutDefenseDodge', - }), - aod_parry: new Maneuver({ - name: 'aod_parry', - alt: 'man-defense.png', - icon: 'man-def-parry.png', - label: 'GURPS.maneuverAllOutDefenseParry', - }), - aod_block: new Maneuver({ - name: 'aod_block', - alt: 'man-defense.png', - icon: 'man-def-block.png', - label: 'GURPS.maneuverAllOutDefenseBlock', - }), - aod_double: new Maneuver({ - name: 'aod_double', - alt: 'man-defense.png', - icon: 'man-def-double.png', - label: 'GURPS.maneuverAllOutDefenseDouble', - }), - ready: new Maneuver({ - name: 'ready', - icon: 'man-ready.png', - label: 'GURPS.maneuverReady', - }), - concentrate: new Maneuver({ - name: 'concentrate', - fullturn: true, - icon: 'man-concentrate.png', - label: 'GURPS.maneuverConcentrate', - }), - wait: new Maneuver({ - name: 'wait', - move: MOVE_NONE, - icon: 'man-wait.png', - label: 'GURPS.maneuverWait', - }), + do_nothing: new Maneuver({ + name: 'do_nothing', + label: 'GURPS.maneuverDoNothing', + icon: 'man-nothing.png', + move: MOVE_NONE, + }), + move: new Maneuver({ + name: 'move', + label: 'GURPS.maneuverMove', + icon: 'man-move.png', + move: MOVE_FULL, + }), + aim: new Maneuver({ + name: 'aim', + fullturn: true, + icon: 'man-aim.png', + label: 'GURPS.maneuverAim', + }), + change_posture: new Maneuver({ + name: 'change_posture', + move: MOVE_NONE, + icon: 'man-change-posture.png', + label: 'GURPS.maneuverChangePosture', + }), + evaluate: new Maneuver({ + name: 'evaluate', + icon: 'man-evaluate.png', + label: 'GURPS.maneuverEvaluate', + }), + attack: new Maneuver({ + name: 'attack', + icon: 'man-attack.png', + label: 'GURPS.maneuverAttack', + }), + feint: new Maneuver({ + name: 'feint', + icon: 'man-feint.png', + label: 'GURPS.maneuverFeint', + alt: 'man-attack.png', + }), + allout_attack: new Maneuver({ + name: 'allout_attack', + move: MOVE_HALF, + defense: DEFENSE_NONE, + icon: 'man-allout-attack.png', + label: 'GURPS.maneuverAllOutAttack', + }), + aoa_determined: new Maneuver({ + name: 'aoa_determined', + move: MOVE_HALF, + defense: DEFENSE_NONE, + icon: 'man-aoa-determined.png', + label: 'GURPS.maneuverAllOutAttackDetermined', + alt: 'man-allout-attack.png', + }), + aoa_double: new Maneuver({ + name: 'aoa_double', + move: MOVE_HALF, + defense: DEFENSE_NONE, + icon: 'man-aoa-double.png', + label: 'GURPS.maneuverAllOutAttackDouble', + alt: 'man-allout-attack.png', + }), + aoa_feint: new Maneuver({ + name: 'aoa_feint', + move: MOVE_HALF, + defense: DEFENSE_NONE, + icon: 'man-aoa-feint.png', + label: 'GURPS.maneuverAllOutAttackFeint', + alt: 'man-allout-attack.png', + }), + aoa_strong: new Maneuver({ + name: 'aoa_strong', + move: MOVE_HALF, + defense: DEFENSE_NONE, + alt: 'man-allout-attack.png', + icon: 'man-aoa-strong.png', + label: 'GURPS.maneuverAllOutAttackStrong', + }), + aoa_suppress: new Maneuver({ + name: 'aoa_suppress', + move: MOVE_HALF, + defense: DEFENSE_NONE, + alt: 'man-allout-attack.png', + icon: 'man-aoa-suppress.png', + label: 'GURPS.maneuverAllOutAttackSuppressFire', + }), + move_and_attack: new Maneuver({ + name: 'move_and_attack', + move: MOVE_FULL, + defense: DEFENSE_DODGEBLOCK, + icon: 'man-move-attack.png', + label: 'GURPS.maneuverMoveAttack', + }), + allout_defense: new Maneuver({ + name: 'allout_defense', + move: MOVE_HALF, + icon: 'man-defense.png', + label: 'GURPS.maneuverAllOutDefense', + }), + aod_dodge: new Maneuver({ + name: 'aod_dodge', + move: MOVE_HALF, + alt: 'man-defense.png', + icon: 'man-def-dodge.png', + label: 'GURPS.maneuverAllOutDefenseDodge', + }), + aod_parry: new Maneuver({ + name: 'aod_parry', + alt: 'man-defense.png', + icon: 'man-def-parry.png', + label: 'GURPS.maneuverAllOutDefenseParry', + }), + aod_block: new Maneuver({ + name: 'aod_block', + alt: 'man-defense.png', + icon: 'man-def-block.png', + label: 'GURPS.maneuverAllOutDefenseBlock', + }), + aod_double: new Maneuver({ + name: 'aod_double', + alt: 'man-defense.png', + icon: 'man-def-double.png', + label: 'GURPS.maneuverAllOutDefenseDouble', + }), + ready: new Maneuver({ + name: 'ready', + icon: 'man-ready.png', + label: 'GURPS.maneuverReady', + }), + concentrate: new Maneuver({ + name: 'concentrate', + fullturn: true, + icon: 'man-concentrate.png', + label: 'GURPS.maneuverConcentrate', + }), + wait: new Maneuver({ + name: 'wait', + move: MOVE_NONE, + icon: 'man-wait.png', + label: 'GURPS.maneuverWait', + }), } export default class Maneuvers { - /** - * @param {string} id - * @returns {ManeuverData} - */ - static get(id) { - // @ts-ignore - return maneuvers[id]?.data - } - - /** - * @param {string} text - * @returns {boolean} true if the text represents a maneuver icon path. - * @memberof Maneuvers - */ - static isManeuverIcon(text) { - return Object.values(maneuvers) - .map(m => m.icon) - .includes(text) - } - - /** - * Return the sublist that are Maneuver icon paths. - * @param {string[]} list of icon pathnames - * @returns {string[]} the pathnames that represent Maneuvers - * @memberof Maneuvers - */ - static getManeuverIcons(list) { - return list.filter(it => Maneuvers.isManeuverIcon(it)) - } - - /** - * @param {string} maneuverText - * @returns {ManeuverData} - */ - static getManeuver(maneuverText) { - // @ts-ignore - return maneuvers[maneuverText].data - } - - /** - * @param {string} maneuverText - * @returns {string|null} - */ - static getIcon(maneuverText) { - return Maneuvers.getManeuver(maneuverText).icon ?? null - } - - static getAll() { - return maneuvers - } - - static getAllData() { - let data = {} - for (const key in maneuvers) { - // @ts-ignore - data[key] = maneuvers[key].data - } - - return data - } - - /** - * @param {string} icon - * @returns {ManeuverData[]|undefined} - */ - static getByIcon(icon) { - return Object.values(maneuvers) - .filter(it => it.icon === icon) - .map(it => it.data) - } - - /** - * The ActiveEffect is a Maneuver if its statusId is 'maneuver'. - * @param {ActiveEffect} activeEffect - * @returns {boolean} - */ - static isActiveEffectManeuver(activeEffect) { - return activeEffect.getFlag ? activeEffect.getFlag('core', 'statusId') === MANEUVER : false - } - - /** - * @param {ActiveEffect[]|undefined} effects - * @return {ActiveEffect[]} just the ActiveEffects that are also Maneuvers - */ - static getActiveEffectManeuvers(effects) { - return effects ? effects.filter(it => Maneuvers.isActiveEffectManeuver(it)) : [] - } + /** + * @param {string} id + * @returns {ManeuverData} + */ + static get(id) { + // @ts-ignore + return maneuvers[id]?.data + } + + /** + * @param {string} text + * @returns {boolean} true if the text represents a maneuver icon path. + * @memberof Maneuvers + */ + static isManeuverIcon(text) { + return Object.values(maneuvers) + .map(m => m.icon) + .includes(text) + } + + /** + * Return the sublist that are Maneuver icon paths. + * @param {string[]} list of icon pathnames + * @returns {string[]} the pathnames that represent Maneuvers + * @memberof Maneuvers + */ + static getManeuverIcons(list) { + return list.filter(it => Maneuvers.isManeuverIcon(it)) + } + + /** + * @param {string} maneuverText + * @returns {ManeuverData} + */ + static getManeuver(maneuverText) { + // @ts-ignore + return maneuvers[maneuverText].data + } + + /** + * @param {string} maneuverText + * @returns {string|null} + */ + static getIcon(maneuverText) { + return Maneuvers.getManeuver(maneuverText).icon ?? null + } + + static getAll() { + return maneuvers + } + + static getAllData() { + let data = {} + for (const key in maneuvers) { + // @ts-ignore + data[key] = maneuvers[key].data + } + + return data + } + + /** + * @param {string} icon + * @returns {ManeuverData[]|undefined} + */ + static getByIcon(icon) { + return Object.values(maneuvers) + .filter(it => it.icon === icon) + .map(it => it.data) + } + + /** + * The ActiveEffect is a Maneuver if its statusId is 'maneuver'. + * @param {ActiveEffect} activeEffect + * @returns {boolean} + */ + static isActiveEffectManeuver(activeEffect) { + return activeEffect.getFlag ? activeEffect.getFlag('core', 'statusId') === MANEUVER : false + } + + /** + * @param {ActiveEffect[]|undefined} effects + * @return {ActiveEffect[]} just the ActiveEffects that are also Maneuvers + */ + static getActiveEffectManeuvers(effects) { + return effects ? effects.filter(it => Maneuvers.isActiveEffectManeuver(it)) : [] + } } // on create combatant, set the maneuver Hooks.on('createCombatant', (/** @type {Combatant} */ combat, /** @type {any} */ _options, /** @type {any} */ id) => { - if (game.user?.isGM) { - console.log(id) - let token = /** @type {GurpsToken} */ (combat.token?.object) - if (!!token && token.id) token.setManeuver('do_nothing') - } + if (game.user?.isGM) { + console.log(id) + let token = /** @type {GurpsToken} */ (combat.token?.object) + if (!!token && token.id) token.setManeuver('do_nothing') + } }) // on delete combatant, remove the maneuver Hooks.on('deleteCombatant', (/** @type {Combatant} */ combat, /** @type {any} */ _options, /** @type {any} */ id) => { - if (game.user?.isGM) { - console.log(id) - let token = /** @type {GurpsToken} */ (combat.token?.object) - if (!!token && token.id) { - console.log(`Delete Combatant: remove maneuver token[${token.id}]`) - token.removeManeuver() - } - } + if (game.user?.isGM) { + console.log(id) + let token = /** @type {GurpsToken} */ (combat.token?.object) + if (!!token && token.id) { + console.log(`Delete Combatant: remove maneuver token[${token.id}]`) + token.removeManeuver() + } + } }) // On delete combat, remove the maneuver from every combatant Hooks.on('deleteCombat', (/** @type {Combat} */ combat, /** @type {any} */ _options, /** @type {any} */ _id) => { - if (game.user?.isGM) { - let combatants = combat.data.combatants.contents - for (const combatant of combatants) { - if (combatant?.token) { - let token = /** @type {GurpsToken} */ (combatant.token.object) - console.log(`Delete Combat: remove maneuver token[${token.id}]`) - token.removeManeuver() - } - } - } + if (game.user?.isGM) { + let combatants = combat.data.combatants.contents + for (const combatant of combatants) { + if (combatant?.token) { + let token = /** @type {GurpsToken} */ (combatant.token.object) + console.log(`Delete Combat: remove maneuver token[${token.id}]`) + token.removeManeuver() + } + } + } }) // TODO consider subtracting 1 FP from every combatant that leaves combat diff --git a/module/actor/move-mode-editor.js b/module/actor/move-mode-editor.js index 6b63fe5d3..d4795d7bf 100644 --- a/module/actor/move-mode-editor.js +++ b/module/actor/move-mode-editor.js @@ -1,180 +1,180 @@ import { i18n_f } from '../../lib/utilities.js' export default class MoveModeEditor extends Application { - constructor(actor, options) { - super(options) - - this.actor = actor - } - - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['sheet'], - width: 400, - height: 'auto', - resizable: false, - }) - } - - get template() { - return 'systems/gurps/templates/actor/move-mode-editor.hbs' - } - - getData() { - const sheetData = super.getData() - sheetData.modes = this.actor.system.move - return sheetData - } - - get title() { - let name = this.actor?.name || 'UNKNOWN' - return i18n_f('GURPS.moveModeEditor.Title', { name: name }, 'Movement Modes for {name}') - } - - activateListeners(html) { - super.activateListeners(html) - - html.find('.move-mode-control').on('change click keyup', this._onEffectControl.bind(this, html)) - } - - async _onEffectControl(html, event) { - event.preventDefault() - const a = event.currentTarget - const key = a.dataset.key ?? null - const value = a.value ?? null - const action = a.dataset.action ?? null - - if (event.type === 'change') this._change(action, key, value, html) - if (event.type === 'click') this._click(action, key, value, html) - if (event.type === 'keyup') this._keyup(action, key, event.key, html) - } - - async _keyup(action, key, value, html) { - if (action === 'other' && value === 'Escape') { - html.find(`#expand-contract-${key}`).removeClass('contracted') - this.render(true) - } - } - - /** Since the default move object may not actually be part of the DB, + constructor(actor, options) { + super(options) + + this.actor = actor + } + + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['sheet'], + width: 400, + height: 'auto', + resizable: false, + }) + } + + get template() { + return 'systems/gurps/templates/actor/move-mode-editor.hbs' + } + + getData() { + const sheetData = super.getData() + sheetData.modes = this.actor.system.move + return sheetData + } + + get title() { + let name = this.actor?.name || 'UNKNOWN' + return i18n_f('GURPS.moveModeEditor.Title', { name: name }, 'Movement Modes for {name}') + } + + activateListeners(html) { + super.activateListeners(html) + + html.find('.move-mode-control').on('change click keyup', this._onEffectControl.bind(this, html)) + } + + async _onEffectControl(html, event) { + event.preventDefault() + const a = event.currentTarget + const key = a.dataset.key ?? null + const value = a.value ?? null + const action = a.dataset.action ?? null + + if (event.type === 'change') this._change(action, key, value, html) + if (event.type === 'click') this._click(action, key, value, html) + if (event.type === 'keyup') this._keyup(action, key, event.key, html) + } + + async _keyup(action, key, value, html) { + if (action === 'other' && value === 'Escape') { + html.find(`#expand-contract-${key}`).removeClass('contracted') + this.render(true) + } + } + + /** Since the default move object may not actually be part of the DB, (laissez-faire init), we need to update the full move object. */ - async _updateMoveData(moveId, attrib, value) { - let old = this.moveData[moveId] - old[attrib] = value - await this.actor.update({ [`data.move.${moveId}`]: old }) - } - - async _change(action, key, value, html) { - switch (action) { - // change: action [mode] key [00000] value [Ground] - case 'mode': - { - // if 'other', don't trigger an update ... just display the hidden field - if (value === 'other') { - html.find(`#expand-contract-${key}`).removeClass('contracted') - return - } - - // hide the field and update the actor - html.find(`#expand-contract-${key}`).addClass('contracted') - await this._updateMoveData(key, action, value) - } - break - - // change: action [value] key [00000] value [6] - case 'basic': - await this._updateMoveData(key, action, value) - break - - // change: action [value] key [00000] value [6] - case 'enhanced': - await this._updateMoveData(key, action, value) - break - - case 'other': - html.find(`#expand-contract-${key}`).removeClass('contracted') - await this._updateMoveData(key, 'mode', value) - break - - default: - return - } - this.render(true) - } - - get moveData() { - return this.actor.system.move - } - - async _click(action, key, value) { - switch (action) { - // click: action [create] key [null] value [null] - case 'create': - { - // copy existing entries - let move = {} - for (const k in this.moveData) - setProperty(move, k, { - mode: this.moveData[k].mode, - basic: this.moveData[k].basic, - enhanced: this.moveData[k].enhanced, - default: this.moveData[k].default, - }) - - // add a new entry at the end. - let empty = Object.values(this.moveData).length === 0 - GURPS.put(move, { mode: 'other', basic: 0, default: empty ? true : false }) - - // remove existing entries - await this.actor.update({ 'data.-=move': null }) - - // add the new entries - await this.actor.update({ 'data.move': move }) - } - break - - // click: action [default] key [00000] value [on|off] - case 'default': - { - let state = getProperty(this.moveData, `${key}.default`) - if (getType(state) === 'string') state = state === 'true' - - // only handle changing from false to true - if (!state) { - let json = [] - // turn off everything whose key isn't 'k' - for (const k in this.moveData) json.push(`"data.move.${k}.default": ${key === k}`) - let text = '{ ' + json.join(',') + ' }' - await this.actor.update(JSON.parse(text)) - } - } - break - - // click: action [delete] key [00000] value [null] - case 'delete': - { - let move = {} - // Copy every entry except the one to delete. - for (const k in this.moveData) { - if (k !== key) - GURPS.put(move, { - mode: this.moveData[k].mode, - basic: this.moveData[k].basic, - enhanced: this.moveData[k].enhanced, - default: this.moveData[k].default, - }) - } - // remove existing entries - await this.actor.update({ 'data.-=move': null }) - // add the new entries - await this.actor.update({ 'data.move': move }) - } - break - - default: - return - } - this.render(true) - } + async _updateMoveData(moveId, attrib, value) { + let old = this.moveData[moveId] + old[attrib] = value + await this.actor.update({ [`data.move.${moveId}`]: old }) + } + + async _change(action, key, value, html) { + switch (action) { + // change: action [mode] key [00000] value [Ground] + case 'mode': + { + // if 'other', don't trigger an update ... just display the hidden field + if (value === 'other') { + html.find(`#expand-contract-${key}`).removeClass('contracted') + return + } + + // hide the field and update the actor + html.find(`#expand-contract-${key}`).addClass('contracted') + await this._updateMoveData(key, action, value) + } + break + + // change: action [value] key [00000] value [6] + case 'basic': + await this._updateMoveData(key, action, value) + break + + // change: action [value] key [00000] value [6] + case 'enhanced': + await this._updateMoveData(key, action, value) + break + + case 'other': + html.find(`#expand-contract-${key}`).removeClass('contracted') + await this._updateMoveData(key, 'mode', value) + break + + default: + return + } + this.render(true) + } + + get moveData() { + return this.actor.system.move + } + + async _click(action, key, value) { + switch (action) { + // click: action [create] key [null] value [null] + case 'create': + { + // copy existing entries + let move = {} + for (const k in this.moveData) + setProperty(move, k, { + mode: this.moveData[k].mode, + basic: this.moveData[k].basic, + enhanced: this.moveData[k].enhanced, + default: this.moveData[k].default, + }) + + // add a new entry at the end. + let empty = Object.values(this.moveData).length === 0 + GURPS.put(move, { mode: 'other', basic: 0, default: empty ? true : false }) + + // remove existing entries + await this.actor.update({ 'data.-=move': null }) + + // add the new entries + await this.actor.update({ 'data.move': move }) + } + break + + // click: action [default] key [00000] value [on|off] + case 'default': + { + let state = getProperty(this.moveData, `${key}.default`) + if (getType(state) === 'string') state = state === 'true' + + // only handle changing from false to true + if (!state) { + let json = [] + // turn off everything whose key isn't 'k' + for (const k in this.moveData) json.push(`"data.move.${k}.default": ${key === k}`) + let text = '{ ' + json.join(',') + ' }' + await this.actor.update(JSON.parse(text)) + } + } + break + + // click: action [delete] key [00000] value [null] + case 'delete': + { + let move = {} + // Copy every entry except the one to delete. + for (const k in this.moveData) { + if (k !== key) + GURPS.put(move, { + mode: this.moveData[k].mode, + basic: this.moveData[k].basic, + enhanced: this.moveData[k].enhanced, + default: this.moveData[k].default, + }) + } + // remove existing entries + await this.actor.update({ 'data.-=move': null }) + // add the new entries + await this.actor.update({ 'data.move': move }) + } + break + + default: + return + } + this.render(true) + } } diff --git a/module/actor/resource-tracker-editor.js b/module/actor/resource-tracker-editor.js index 4303fb598..c1c57dd4b 100644 --- a/module/actor/resource-tracker-editor.js +++ b/module/actor/resource-tracker-editor.js @@ -1,184 +1,184 @@ import { i18n_f } from '../../lib/utilities.js' export class ResourceTrackerEditor extends Application { - /** - * Create a new Resource Tracker Editor - * @param {Tracker} tracker data to update - * @param {*} options - */ - constructor(tracker, isActor, options = {}) { - super(options) - - this._tracker = tracker - this._isActor = isActor - } - - /** - * - * @param {*} actor - * @param {*} path - * @param {*} options - */ - static editForActor(actor, path, options) { - let tracker = getProperty(actor.system, path) - let temp = JSON.stringify(tracker) - let dialog = new ResourceTrackerEditor(JSON.parse(temp), true, options) - dialog._updateTracker = async () => { - let update = {} - update[`data.${path}`] = dialog._tracker - actor.update(update) - dialog.close() - } - dialog.render(true) - } - - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - template: 'systems/gurps/templates/resource-editor-popup.hbs', - width: 360, - height: 472, - popOut: true, - minimizable: false, - jQuery: true, - resizable: false, - title: game.i18n.localize('GURPS.resourceTrackerEditor'), - }) - } - - /** @override */ - getData(options) { - const data = super.getData(options) - data.tracker = this._tracker - data.isActor = this._isActor - return data - } - - /** - * By default, do nothing. Each specific use will need its own update method. - * @param {*} html - */ - async _updateTracker(html) { } - - /** @override */ - activateListeners(html) { - super.activateListeners(html) - - html.find('.name input').change(ev => { - this._tracker.name = ev.currentTarget.value - }) - - html.find('.inputs .alias').change(ev => { - // change the regex from /(w+)(.*)/ to /([A-Za-z0-9_+-]+)(.*)/ to make sure we recognize pi-, pi+ and pi++ - let alias = ev.currentTarget.value - if (/^[A-Za-z0-9_+-]+$/.test(alias)) this._tracker.alias = alias - else { - ui.notifications.warn(i18n_f('GURPS.resourceInvalidAlias', { alias: alias })) - ev.currentTarget.value = this._tracker.alias - } - }) - - html.find('[name="damage-type"]').click(ev => { - let element = $(ev.currentTarget) - this._tracker.isDamageType = element.is(':checked') - this.render(false) - }) - - html.find('[name="enforce-minimum"]').click(ev => { - let element = $(ev.currentTarget) - this._tracker.isMinimumEnforced = element.is(':checked') - this.render(false) - }) - - html.find('[name="enforce-maximum"]').click(ev => { - let element = $(ev.currentTarget) - this._tracker.isMaximumEnforced = element.is(':checked') - this.render(false) - }) - - html.find('[name="damage-tracker"]').click(ev => { - let element = $(ev.currentTarget) - this._tracker.isDamageTracker = element.is(':checked') - this.render(false) - }) - - html.find('.inputs .current').change(ev => { - this._tracker.value = parseInt(ev.currentTarget.value) - }) - - html.find('.inputs .minimum').change(ev => { - this._tracker.min = parseInt(ev.currentTarget.value) - }) - - html.find('.inputs .maximum').change(ev => { - this._tracker.max = parseInt(ev.currentTarget.value) - }) - - html.find('.inputs .pdf-ref').change(ev => { - this._tracker.pdf = ev.currentTarget.value - }) - - html.find('#threshold-add').click(() => { - if (!this._tracker.thresholds) { - this._tracker.thresholds = [] - } - - this._tracker.thresholds.push({ - comparison: '>', - operator: '×', - value: 1, - condition: game.i18n.localize('GURPS.normal'), - color: '#FFFFFF', - }) - this.render(false) - }) - - html.find('[name="delete-threshold"]').click(ev => { - let index = $(ev.currentTarget).attr('data') - this._tracker.thresholds.splice(index, 1) - this.render(false) - }) - - html.find('[name="comparison"]').change(ev => { - let index = $(ev.currentTarget).attr('data') - this._tracker.thresholds[index].comparison = ev.currentTarget.value - }) - - html.find('[name="operator"]').change(ev => { - let index = $(ev.currentTarget).attr('data') - this._tracker.thresholds[index].operator = ev.currentTarget.value - }) - - html.find('[name="value"]').change(ev => { - let index = $(ev.currentTarget).attr('data') - this._tracker.thresholds[index].value = parseFloat(ev.currentTarget.value) - }) - - html.find('[name="condition"]').change(ev => { - let index = $(ev.currentTarget).attr('data') - this._tracker.thresholds[index].condition = ev.currentTarget.value - }) - - html.find('[name="color"]').change(ev => { - let index = $(ev.currentTarget).attr('data') - this._tracker.thresholds[index].color = ev.currentTarget.value - }) - - html.find('#update').click(() => this._updateTracker()) - - html.find('#reset').on('click', ev => { - this._tracker = { - name: '', - alias: '', - pdf: '', - max: 0, - min: 0, - value: 0, - isDamageTracker: false, - isDamageType: false, - initialValue: '', - thresholds: [], - } - this.render(false) - }) - } + /** + * Create a new Resource Tracker Editor + * @param {Tracker} tracker data to update + * @param {*} options + */ + constructor(tracker, isActor, options = {}) { + super(options) + + this._tracker = tracker + this._isActor = isActor + } + + /** + * + * @param {*} actor + * @param {*} path + * @param {*} options + */ + static editForActor(actor, path, options) { + let tracker = getProperty(actor.system, path) + let temp = JSON.stringify(tracker) + let dialog = new ResourceTrackerEditor(JSON.parse(temp), true, options) + dialog._updateTracker = async () => { + let update = {} + update[`data.${path}`] = dialog._tracker + actor.update(update) + dialog.close() + } + dialog.render(true) + } + + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + template: 'systems/gurps/templates/resource-editor-popup.hbs', + width: 360, + height: 472, + popOut: true, + minimizable: false, + jQuery: true, + resizable: false, + title: game.i18n.localize('GURPS.resourceTrackerEditor'), + }) + } + + /** @override */ + getData(options) { + const data = super.getData(options) + data.tracker = this._tracker + data.isActor = this._isActor + return data + } + + /** + * By default, do nothing. Each specific use will need its own update method. + * @param {*} html + */ + async _updateTracker(html) {} + + /** @override */ + activateListeners(html) { + super.activateListeners(html) + + html.find('.name input').change(ev => { + this._tracker.name = ev.currentTarget.value + }) + + html.find('.inputs .alias').change(ev => { + // change the regex from /(w+)(.*)/ to /([A-Za-z0-9_+-]+)(.*)/ to make sure we recognize pi-, pi+ and pi++ + let alias = ev.currentTarget.value + if (/^[A-Za-z0-9_+-]+$/.test(alias)) this._tracker.alias = alias + else { + ui.notifications.warn(i18n_f('GURPS.resourceInvalidAlias', { alias: alias })) + ev.currentTarget.value = this._tracker.alias + } + }) + + html.find('[name="damage-type"]').click(ev => { + let element = $(ev.currentTarget) + this._tracker.isDamageType = element.is(':checked') + this.render(false) + }) + + html.find('[name="enforce-minimum"]').click(ev => { + let element = $(ev.currentTarget) + this._tracker.isMinimumEnforced = element.is(':checked') + this.render(false) + }) + + html.find('[name="enforce-maximum"]').click(ev => { + let element = $(ev.currentTarget) + this._tracker.isMaximumEnforced = element.is(':checked') + this.render(false) + }) + + html.find('[name="damage-tracker"]').click(ev => { + let element = $(ev.currentTarget) + this._tracker.isDamageTracker = element.is(':checked') + this.render(false) + }) + + html.find('.inputs .current').change(ev => { + this._tracker.value = parseInt(ev.currentTarget.value) + }) + + html.find('.inputs .minimum').change(ev => { + this._tracker.min = parseInt(ev.currentTarget.value) + }) + + html.find('.inputs .maximum').change(ev => { + this._tracker.max = parseInt(ev.currentTarget.value) + }) + + html.find('.inputs .pdf-ref').change(ev => { + this._tracker.pdf = ev.currentTarget.value + }) + + html.find('#threshold-add').click(() => { + if (!this._tracker.thresholds) { + this._tracker.thresholds = [] + } + + this._tracker.thresholds.push({ + comparison: '>', + operator: '×', + value: 1, + condition: game.i18n.localize('GURPS.normal'), + color: '#FFFFFF', + }) + this.render(false) + }) + + html.find('[name="delete-threshold"]').click(ev => { + let index = $(ev.currentTarget).attr('data') + this._tracker.thresholds.splice(index, 1) + this.render(false) + }) + + html.find('[name="comparison"]').change(ev => { + let index = $(ev.currentTarget).attr('data') + this._tracker.thresholds[index].comparison = ev.currentTarget.value + }) + + html.find('[name="operator"]').change(ev => { + let index = $(ev.currentTarget).attr('data') + this._tracker.thresholds[index].operator = ev.currentTarget.value + }) + + html.find('[name="value"]').change(ev => { + let index = $(ev.currentTarget).attr('data') + this._tracker.thresholds[index].value = parseFloat(ev.currentTarget.value) + }) + + html.find('[name="condition"]').change(ev => { + let index = $(ev.currentTarget).attr('data') + this._tracker.thresholds[index].condition = ev.currentTarget.value + }) + + html.find('[name="color"]').change(ev => { + let index = $(ev.currentTarget).attr('data') + this._tracker.thresholds[index].color = ev.currentTarget.value + }) + + html.find('#update').click(() => this._updateTracker()) + + html.find('#reset').on('click', ev => { + this._tracker = { + name: '', + alias: '', + pdf: '', + max: 0, + min: 0, + value: 0, + isDamageTracker: false, + isDamageType: false, + initialValue: '', + thresholds: [], + } + this.render(false) + }) + } } diff --git a/module/actor/splitdr-editor.js b/module/actor/splitdr-editor.js index a311b7db9..8dc916703 100644 --- a/module/actor/splitdr-editor.js +++ b/module/actor/splitdr-editor.js @@ -1,143 +1,143 @@ import { i18n_f } from '../../lib/utilities.js' export default class SplitDREditor extends Application { - constructor(actor, key, options) { - super(options) - - this.actor = actor - this.key = key - } - - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['sheet'], - width: 300, - height: 'auto', - resizeable: false, - }) - } - - get template() { - return 'systems/gurps/templates/actor/splitdr-editor.hbs' - } - - getData() { - const sheetData = super.getData() - sheetData.location = this.location - return sheetData - } - - get title() { - return i18n_f('GURPS.drSplitEditorTitle', { where: this.location.where }, 'Split DR ({where})') - } - - get location() { - return getProperty(this.actor, this.key) - } - - activateListeners(html) { - super.activateListeners(html) - - html.find('.splitdr-control').on('change click keyup', this._onSplitDRControl.bind(this, html)) - } - - async _onSplitDRControl(html, event) { - event.preventDefault() - const a = event.currentTarget - const value = a.value ?? null - const action = a.dataset.action ?? null - - if (event.type === 'change') this._change(action, value, html) - if (event.type === 'keyup') this._keyup(action, event.key, html) - if (event.type === 'click') this._click(action, value, html) - } - - async _click(action, value) { - switch (action) { - // click: action [create] key [null] value [null] - case 'create': - { - // add a new entry - let entry = {} - entry[`${this.key}.split`] = { none: 0 } - await this.actor.update(entry) - } - break - - // click: action [delete] key [00000] value [null] - case 'delete': - { - // remove existing entries - let entry = {} - entry[`${this.key}.-=split`] = null - await this.actor.update(entry) - } - break - - default: - return - } - this.render(true) - } - - async _keyup(action, value, html) { - // Escape key while in the "other" textfield closes it. - if (action === 'other' && value === 'Escape') { - html.find(`#expand-contract-splitdr`).removeClass('contracted') - this.render(true) - } - } - - async _change(action, value, html) { - let [existingKey, existingValue] = Object.entries(this.location.split)[0] - - switch (action) { - // change: action [mode] key [00000] value [Ground] - case 'type': - { - // if 'other', don't trigger an update ... just display the hidden field - if (value === 'other') { - html.find(`#expand-contract-splitdr`).removeClass('contracted') - return - } - - // hide the field and update the actor - html.find(`#expand-contract-splitdr`).addClass('contracted') - await this._updateSplitDRKey(existingValue, value) - } - break - - // change: action [value] key [00000] value [6] - case 'value': - await this._updateSplitDRValue(existingKey, value) - break - - case 'other': - html.find(`#expand-contract-splitdr`).removeClass('contracted') - await this._updateSplitDRKey(existingValue, value) - break - - default: - return - } - this.render(true) - } - - async _updateSplitDRValue(existingKey, newValue) { - let updated = {} - updated[`${this.key}.split.${existingKey}`] = +newValue - await this.actor.update(updated) - } - - async _updateSplitDRKey(existingValue, newKey) { - let old = {} - old[`${this.key}.-=split`] = null - await this.actor.update(old) - - let split = {} - split[newKey] = existingValue - let updated = {} - updated[`${this.key}.split`] = split - await this.actor.update(updated) - } + constructor(actor, key, options) { + super(options) + + this.actor = actor + this.key = key + } + + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['sheet'], + width: 300, + height: 'auto', + resizeable: false, + }) + } + + get template() { + return 'systems/gurps/templates/actor/splitdr-editor.hbs' + } + + getData() { + const sheetData = super.getData() + sheetData.location = this.location + return sheetData + } + + get title() { + return i18n_f('GURPS.drSplitEditorTitle', { where: this.location.where }, 'Split DR ({where})') + } + + get location() { + return getProperty(this.actor, this.key) + } + + activateListeners(html) { + super.activateListeners(html) + + html.find('.splitdr-control').on('change click keyup', this._onSplitDRControl.bind(this, html)) + } + + async _onSplitDRControl(html, event) { + event.preventDefault() + const a = event.currentTarget + const value = a.value ?? null + const action = a.dataset.action ?? null + + if (event.type === 'change') this._change(action, value, html) + if (event.type === 'keyup') this._keyup(action, event.key, html) + if (event.type === 'click') this._click(action, value, html) + } + + async _click(action, value) { + switch (action) { + // click: action [create] key [null] value [null] + case 'create': + { + // add a new entry + let entry = {} + entry[`${this.key}.split`] = { none: 0 } + await this.actor.update(entry) + } + break + + // click: action [delete] key [00000] value [null] + case 'delete': + { + // remove existing entries + let entry = {} + entry[`${this.key}.-=split`] = null + await this.actor.update(entry) + } + break + + default: + return + } + this.render(true) + } + + async _keyup(action, value, html) { + // Escape key while in the "other" textfield closes it. + if (action === 'other' && value === 'Escape') { + html.find(`#expand-contract-splitdr`).removeClass('contracted') + this.render(true) + } + } + + async _change(action, value, html) { + let [existingKey, existingValue] = Object.entries(this.location.split)[0] + + switch (action) { + // change: action [mode] key [00000] value [Ground] + case 'type': + { + // if 'other', don't trigger an update ... just display the hidden field + if (value === 'other') { + html.find(`#expand-contract-splitdr`).removeClass('contracted') + return + } + + // hide the field and update the actor + html.find(`#expand-contract-splitdr`).addClass('contracted') + await this._updateSplitDRKey(existingValue, value) + } + break + + // change: action [value] key [00000] value [6] + case 'value': + await this._updateSplitDRValue(existingKey, value) + break + + case 'other': + html.find(`#expand-contract-splitdr`).removeClass('contracted') + await this._updateSplitDRKey(existingValue, value) + break + + default: + return + } + this.render(true) + } + + async _updateSplitDRValue(existingKey, newValue) { + let updated = {} + updated[`${this.key}.split.${existingKey}`] = +newValue + await this.actor.update(updated) + } + + async _updateSplitDRKey(existingValue, newKey) { + let old = {} + old[`${this.key}.-=split`] = null + await this.actor.update(old) + + let split = {} + split[newKey] = existingValue + let updated = {} + updated[`${this.key}.split`] = split + await this.actor.update(updated) + } } diff --git a/module/chat.js b/module/chat.js index 46c318d44..25e3dc2f8 100755 --- a/module/chat.js +++ b/module/chat.js @@ -11,329 +11,329 @@ import { gurpslink } from '../module/utilities/gurpslink.js' * */ class HelpChatProcessor extends ChatProcessor { - help() { - return null - } - - /** @param {string} line */ - matches(line) { - return line.match(/[!\/\?]help/i) - } - - /** - * Override to process a chat command - * @param {string} _line - * @param {any} _msgs - */ - async process(_line, _msgs) { - let l = _line.split(' ') - if (l.length > 1) return this.registry.handle('?' + l[1].trim()) - - let t = `
    ${i18n('GURPS.gameAidUsersGuide')}
    ` - let all = ChatProcessors.processorsForAll() - .filter(it => !!it.help()) - .map(it => it.help()) - let gmonly = ChatProcessors.processorsForGMOnly() - .filter(it => !!it.help()) - .map(it => it.help()) - all.sort() - t += all.join('
    ') - if (gmonly.length > 0) { - gmonly.sort() - t += '
    --- GM only ---
    ' - t += gmonly.join('
    ') - } - t += '

    ' + i18n('GURPS.chatHelpHelp') - this.priv(t) - } + help() { + return null + } + + /** @param {string} line */ + matches(line) { + return line.match(/[!\/\?]help/i) + } + + /** + * Override to process a chat command + * @param {string} _line + * @param {any} _msgs + */ + async process(_line, _msgs) { + let l = _line.split(' ') + if (l.length > 1) return this.registry.handle('?' + l[1].trim()) + + let t = `${i18n('GURPS.gameAidUsersGuide')}
    ` + let all = ChatProcessors.processorsForAll() + .filter(it => !!it.help()) + .map(it => it.help()) + let gmonly = ChatProcessors.processorsForGMOnly() + .filter(it => !!it.help()) + .map(it => it.help()) + all.sort() + t += all.join('
    ') + if (gmonly.length > 0) { + gmonly.sort() + t += '
    --- GM only ---
    ' + t += gmonly.join('
    ') + } + t += '

    ' + i18n('GURPS.chatHelpHelp') + this.priv(t) + } } class ChatProcessorRegistry { - constructor() { - /** - * @type {ChatProcessor[]} - */ - this._processors = [] - - this.registerProcessor(new HelpChatProcessor()) - - /** @type {{pub: string[], priv: string[], data: any, quiet?: boolean, oldQuiet?: boolean, event?: any}} */ - this.msgs = { pub: [], priv: [], data: null } - } - - processorsForAll() { - return this._processors.filter(it => !it.isGMOnly()) - } - processorsForGMOnly() { - return this._processors.filter(it => it.isGMOnly()) - } - - /** - * Make a pre-emptive decision if we are going to handle any of the lines in this message - * @param {string} message - */ - willTryToHandle(message) { - let lines = message.split('\n') // Just need a simple split by newline... more advanced splitting will occur later - for (const line of lines) - for (const p of this._processors) { - if (p.usagematches(line) || (line[0] == '!' ? p.matches(line.substr(1)) : p.matches(line))) return true - } - return false - } - - /** - * At this point, we just have to assume that we are going to handle some (if not all) of the messages in lines. - * From this point on, we want to be in a single thread... so we await any async methods to ensure that - * we get a response. - * @param {string} message - * @param {{shiftKey: boolean;ctrlKey: boolean;data: {};} | undefined} [event] - * @param {import('@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/chatMessageData').ChatMessageDataConstructorData | { speaker: any}} chatmsgData - * @returns {Promise} - */ - async startProcessingLines(message, chatmsgData, event) { - if (!chatmsgData) - chatmsgData = { - user: game.user?.id || null, - // @ts-ignore - speaker: { - actor: !!GURPS.LastActor ? GURPS.LastActor.id : undefined, - }, - } - - this.msgs.quiet = false - this.msgs.oldQuiet = false - this.msgs.data = chatmsgData - this.msgs.event = event || { shiftKey: false, ctrlKey: false, data: {} } - - let answer = await this.processLines(message) - this.send() - return answer - } - - /** - * @param {string} message - */ - async processLines(message) { - // Look for non-escaped { } double backslash "\\" and convert to newlines. I just couldn't figure out a good regex pattern... so I just did it manually. - let lines = [] - let start = 0 - let escaped = 0 - let backslash = false - for (let i = 0; i < message.length; i++) { - const c = message[i] - if (c == '\\') { - if (escaped == 0) { - if (backslash) { - lines.push(message.substring(start, i - 1)) - start = i + 1 - backslash = false - } else backslash = true - } - } else backslash = false - if (c == '{') escaped++ - if (c == '}') escaped-- - if (c == '\n') { - lines.push(message.substring(start, i)) - start = i + 1 - } - } - if (start < message.length) lines.push(message.substr(start)) - let answer = false - - // TODO consider Promise.then(Promise)... - for (const line of lines) { - // use for loop to ensure single thread - answer = await this.processLine(line) - } - return answer - } - - /** - * @param {string} line - */ - async processLine(line) { - line = line.trim() - this.msgs.oldQuiet = this.msgs.quiet - if (line[0] == '!') { - this.msgs.quiet = true - line = line.substr(1) - } - let [handled, answer] = await this.handle(line) - if (!handled) { - if (line.trim().startsWith('/')) { - // immediately flush our stored msgs, and execute the slash command using the default parser - this.send() - GURPS.ChatCommandsInProcess.push(line) // Remember which chat message we are running, so we don't run it again! - ui.chat // @ts-ignore - ?.processMessage(line) - .catch(err => { - ui.notifications?.error(err) - console.error(err) - }) - } else this.pub(line) // If not handled, must just be public text - } - this.msgs.quiet = this.msgs.oldQuiet - return answer - } - - /** - * Handle the chat message - * @param {String} line - chat input - * @returns true, if handled - */ - async handle(line) { - let answer = false - let processor = this._processors.find(it => it.matches(line)) - if (!!processor) { - if (processor.isGMOnly() && !game.user?.isGM) ui.notifications?.warn(i18n('GURPS.chatYouMustBeGM')) - else { - try { - answer = await processor.process(line) - } catch (err) { - ui.notifications?.error(err) - console.error(err) - } - return [true, answer != false] - } - } - // if nothing matchs, check for chat command without options... and return a help output - processor = this._processors.find(it => it.usagematches(line)) - if (!!processor) { - if (processor.isGMOnly() && !game.user?.isGM) ui.notifications?.warn(i18n('GURPS.chatYouMustBeGM')) - else this.priv(line) - this.priv('
    ') - this.priv(processor.usage().replaceAll('\n', '
    ')) - return [true, true] - } - return [false, false] - } - - /** - * Register a chat processor - * @param {ChatProcessor} processor - */ - registerProcessor(processor) { - processor.registry = this // Provide a back pointer so that processors can get access to the message structure - this._processors.push(processor) - } - - /** - * @param {string[]} priv - */ - _sendPriv(priv) { - if (priv.length == 0) return - let lines = priv.slice() - renderTemplate('systems/gurps/templates/chat-processing.html', { - lines: lines, - }).then(content => { - ChatMessage.create({ - alreadyProcessed: true, - content: content, - user: game.user?.id, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - whisper: [game.user?.id || ''], - }) - }) - priv.length = 0 - } - - /** - * @param {string[]} pub - * @param {any} chatData - */ - _sendPub(pub, chatData) { - if (pub.length == 0) return - - let d = duplicate(chatData) // duplicate the original chat data (to maintain speaker, etc.) - d.alreadyProcessed = true - let lines = pub.slice() - renderTemplate('systems/gurps/templates/chat-processing.html', { - lines: lines, - }).then(content => { - d.content = content - ChatMessage.create(d) - }) - pub.length = 0 - } - - // Dump everything we have saved in messages - send() { - this._sendPriv(this.msgs.priv) - this._sendPub(this.msgs.pub, this.msgs.data) - } - - // Stack up as many public messages as we can, until we need to print a private one (to reduce the number of chat messages) - /** - * @param {string} txt - */ - pub(txt) { - if (this.msgs.priv.length > 0) this.send() - this.msgs.pub.push(txt) - } - - // Stack up as many private messages as we can, until we need to print a public one (to reduce the number of chat messages) - /** - * @param {string} txt - */ - priv(txt, force = false) { - if (this.msgs.quiet && !force) return - if (this.msgs.pub.length > 0) this.send() - this.msgs.priv.push(txt) - } - - // Uncertain if these should be priv or pub - /** - * @param {string} txt - */ - prnt(txt) { - let p_setting = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_PLAYER_CHAT_PRIVATE) - if (game.user?.isGM || p_setting) this.priv(txt) - else this.pub(txt) - } - - // Attempt to convert original chat data into a whisper (for use when the player presses SHIFT key to make roll private) - /** - * @param {boolean} quiet - * @param {boolean} shift - * @param {boolean} ctrl - */ - setEventFlags(quiet, shift, ctrl) { - this.msgs.quiet = quiet - this.msgs.oldQuiet = quiet - this.msgs.data.type = CONST.CHAT_MESSAGE_TYPES.WHISPER - this.msgs.data.whisper = [game.user?.id] - mergeObject(this.msgs.event, { shiftKey: shift, ctrlKey: ctrl }) - } + constructor() { + /** + * @type {ChatProcessor[]} + */ + this._processors = [] + + this.registerProcessor(new HelpChatProcessor()) + + /** @type {{pub: string[], priv: string[], data: any, quiet?: boolean, oldQuiet?: boolean, event?: any}} */ + this.msgs = { pub: [], priv: [], data: null } + } + + processorsForAll() { + return this._processors.filter(it => !it.isGMOnly()) + } + processorsForGMOnly() { + return this._processors.filter(it => it.isGMOnly()) + } + + /** + * Make a pre-emptive decision if we are going to handle any of the lines in this message + * @param {string} message + */ + willTryToHandle(message) { + let lines = message.split('\n') // Just need a simple split by newline... more advanced splitting will occur later + for (const line of lines) + for (const p of this._processors) { + if (p.usagematches(line) || (line[0] == '!' ? p.matches(line.substr(1)) : p.matches(line))) return true + } + return false + } + + /** + * At this point, we just have to assume that we are going to handle some (if not all) of the messages in lines. + * From this point on, we want to be in a single thread... so we await any async methods to ensure that + * we get a response. + * @param {string} message + * @param {{shiftKey: boolean;ctrlKey: boolean;data: {};} | undefined} [event] + * @param {import('@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/chatMessageData').ChatMessageDataConstructorData | { speaker: any}} chatmsgData + * @returns {Promise} + */ + async startProcessingLines(message, chatmsgData, event) { + if (!chatmsgData) + chatmsgData = { + user: game.user?.id || null, + // @ts-ignore + speaker: { + actor: !!GURPS.LastActor ? GURPS.LastActor.id : undefined, + }, + } + + this.msgs.quiet = false + this.msgs.oldQuiet = false + this.msgs.data = chatmsgData + this.msgs.event = event || { shiftKey: false, ctrlKey: false, data: {} } + + let answer = await this.processLines(message) + this.send() + return answer + } + + /** + * @param {string} message + */ + async processLines(message) { + // Look for non-escaped { } double backslash "\\" and convert to newlines. I just couldn't figure out a good regex pattern... so I just did it manually. + let lines = [] + let start = 0 + let escaped = 0 + let backslash = false + for (let i = 0; i < message.length; i++) { + const c = message[i] + if (c == '\\') { + if (escaped == 0) { + if (backslash) { + lines.push(message.substring(start, i - 1)) + start = i + 1 + backslash = false + } else backslash = true + } + } else backslash = false + if (c == '{') escaped++ + if (c == '}') escaped-- + if (c == '\n') { + lines.push(message.substring(start, i)) + start = i + 1 + } + } + if (start < message.length) lines.push(message.substr(start)) + let answer = false + + // TODO consider Promise.then(Promise)... + for (const line of lines) { + // use for loop to ensure single thread + answer = await this.processLine(line) + } + return answer + } + + /** + * @param {string} line + */ + async processLine(line) { + line = line.trim() + this.msgs.oldQuiet = this.msgs.quiet + if (line[0] == '!') { + this.msgs.quiet = true + line = line.substr(1) + } + let [handled, answer] = await this.handle(line) + if (!handled) { + if (line.trim().startsWith('/')) { + // immediately flush our stored msgs, and execute the slash command using the default parser + this.send() + GURPS.ChatCommandsInProcess.push(line) // Remember which chat message we are running, so we don't run it again! + ui.chat // @ts-ignore + ?.processMessage(line) + .catch(err => { + ui.notifications?.error(err) + console.error(err) + }) + } else this.pub(line) // If not handled, must just be public text + } + this.msgs.quiet = this.msgs.oldQuiet + return answer + } + + /** + * Handle the chat message + * @param {String} line - chat input + * @returns true, if handled + */ + async handle(line) { + let answer = false + let processor = this._processors.find(it => it.matches(line)) + if (!!processor) { + if (processor.isGMOnly() && !game.user?.isGM) ui.notifications?.warn(i18n('GURPS.chatYouMustBeGM')) + else { + try { + answer = await processor.process(line) + } catch (err) { + ui.notifications?.error(err) + console.error(err) + } + return [true, answer != false] + } + } + // if nothing matchs, check for chat command without options... and return a help output + processor = this._processors.find(it => it.usagematches(line)) + if (!!processor) { + if (processor.isGMOnly() && !game.user?.isGM) ui.notifications?.warn(i18n('GURPS.chatYouMustBeGM')) + else this.priv(line) + this.priv('
    ') + this.priv(processor.usage().replaceAll('\n', '
    ')) + return [true, true] + } + return [false, false] + } + + /** + * Register a chat processor + * @param {ChatProcessor} processor + */ + registerProcessor(processor) { + processor.registry = this // Provide a back pointer so that processors can get access to the message structure + this._processors.push(processor) + } + + /** + * @param {string[]} priv + */ + _sendPriv(priv) { + if (priv.length == 0) return + let lines = priv.slice() + renderTemplate('systems/gurps/templates/chat-processing.html', { + lines: lines, + }).then(content => { + ChatMessage.create({ + alreadyProcessed: true, + content: content, + user: game.user?.id, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + whisper: [game.user?.id || ''], + }) + }) + priv.length = 0 + } + + /** + * @param {string[]} pub + * @param {any} chatData + */ + _sendPub(pub, chatData) { + if (pub.length == 0) return + + let d = duplicate(chatData) // duplicate the original chat data (to maintain speaker, etc.) + d.alreadyProcessed = true + let lines = pub.slice() + renderTemplate('systems/gurps/templates/chat-processing.html', { + lines: lines, + }).then(content => { + d.content = content + ChatMessage.create(d) + }) + pub.length = 0 + } + + // Dump everything we have saved in messages + send() { + this._sendPriv(this.msgs.priv) + this._sendPub(this.msgs.pub, this.msgs.data) + } + + // Stack up as many public messages as we can, until we need to print a private one (to reduce the number of chat messages) + /** + * @param {string} txt + */ + pub(txt) { + if (this.msgs.priv.length > 0) this.send() + this.msgs.pub.push(txt) + } + + // Stack up as many private messages as we can, until we need to print a public one (to reduce the number of chat messages) + /** + * @param {string} txt + */ + priv(txt, force = false) { + if (this.msgs.quiet && !force) return + if (this.msgs.pub.length > 0) this.send() + this.msgs.priv.push(txt) + } + + // Uncertain if these should be priv or pub + /** + * @param {string} txt + */ + prnt(txt) { + let p_setting = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_PLAYER_CHAT_PRIVATE) + if (game.user?.isGM || p_setting) this.priv(txt) + else this.pub(txt) + } + + // Attempt to convert original chat data into a whisper (for use when the player presses SHIFT key to make roll private) + /** + * @param {boolean} quiet + * @param {boolean} shift + * @param {boolean} ctrl + */ + setEventFlags(quiet, shift, ctrl) { + this.msgs.quiet = quiet + this.msgs.oldQuiet = quiet + this.msgs.data.type = CONST.CHAT_MESSAGE_TYPES.WHISPER + this.msgs.data.whisper = [game.user?.id] + mergeObject(this.msgs.event, { shiftKey: shift, ctrlKey: ctrl }) + } } export let ChatProcessors = new ChatProcessorRegistry() export default function addChatHooks() { - Hooks.once('init', async function() { - GURPS.ChatProcessors = ChatProcessors - Hooks.on('chatMessage', (_log, message, chatmsgData) => { - // @ts-ignore - if (!!chatmsgData.alreadyProcessed) return true // The chat message has already been parsed for GURPS commands show it should just be displayed - - if (GURPS.ChatCommandsInProcess.includes(message)) { - GURPS.ChatCommandsInProcess = GURPS.ChatCommandsInProcess.filter( - (/** @type {string} */ item) => item !== message - ) - return true // Ok. this is a big hack, and only used for singe line chat commands... but since arrays are synchronous and I don't expect chat floods, this is safe - } - - // Due to Foundry's non-async way of handling the 'chatMessage' response, we have to decide beforehand - // if we are going to process this message, and if so, return false so Foundry doesn't - if (ChatProcessors.willTryToHandle(message)) { - // Now we can handle the processing of each line in an async method, so we can ensure a single thread - // @ts-ignore - ChatProcessors.startProcessingLines(message, chatmsgData) - return false - } else return true - }) - - // Look for blind messages with .message-results and remove them - /* Hooks.on("renderChatMessage", (log, content, data) => { + Hooks.once('init', async function () { + GURPS.ChatProcessors = ChatProcessors + Hooks.on('chatMessage', (_log, message, chatmsgData) => { + // @ts-ignore + if (!!chatmsgData.alreadyProcessed) return true // The chat message has already been parsed for GURPS commands show it should just be displayed + + if (GURPS.ChatCommandsInProcess.includes(message)) { + GURPS.ChatCommandsInProcess = GURPS.ChatCommandsInProcess.filter( + (/** @type {string} */ item) => item !== message + ) + return true // Ok. this is a big hack, and only used for singe line chat commands... but since arrays are synchronous and I don't expect chat floods, this is safe + } + + // Due to Foundry's non-async way of handling the 'chatMessage' response, we have to decide beforehand + // if we are going to process this message, and if so, return false so Foundry doesn't + if (ChatProcessors.willTryToHandle(message)) { + // Now we can handle the processing of each line in an async method, so we can ensure a single thread + // @ts-ignore + ChatProcessors.startProcessingLines(message, chatmsgData) + return false + } else return true + }) + + // Look for blind messages with .message-results and remove them + /* Hooks.on("renderChatMessage", (log, content, data) => { if (!!data.message.blind) { if (data.author?.isSelf && !data.author.isGm) { // We are rendering the chat message for the sender (and they are not the GM) $(content).find(".gurps-results").html("..."); // Replace gurps-results with "...". Does nothing if not there. @@ -341,82 +341,82 @@ export default function addChatHooks() { } }); */ - // Add the "for" attribute to a collapsible panel label. This is needed - // because the server in 0.7.8 strips the "for" attribute in an attempt - // to guard against weird security hacks. When "for" is whitelisted as - // a valid attribute (future) we can remove this. - Hooks.on('renderChatMessage', (_app, html, _msg) => { - // this is a fucking hack - let wrapper = html.find('.collapsible-wrapper') - if (wrapper.length > 0) { - //console.log($(wrapper).html()) - let input = $(wrapper).find('input.toggle')[0] - let label = $(input).siblings('label.label-toggle')[0] - let id = input.id - let labelFor = $(label).attr('for') - if (labelFor !== id) { - $(label).attr('for', id) - console.log(`add the 'for' attribute if needed: ${$(wrapper).html()}`) - } - } - }) - - // Look for RESULTS from a RollTable. RollTables do not generate regular chat messages - Hooks.on( - 'preCreateChatMessage', - (/** @type {ChatMessage} */ chatMessage, /** @type {any} */ _options, /** @type {any} */ _userId) => { - let c = chatMessage.content - try { - let html = $(c) - let rt = html.find('.result-text') // Ugly hack to find results of a roll table to see if an OtF should be "rolled" /r /roll - let re = /^(\/r|\/roll|\/pr|\/private) \[([^\]]+)\]/ - let t = rt[0]?.innerText - if (!!t) { - t.split('\n').forEach(e => { - let m = e.match(re) - if (!!m && !!m[2]) { - let action = parselink(m[2]) - if (!!action.action) { - GURPS.performAction(action.action, GURPS.LastActor, { - shiftKey: e.startsWith('/pr'), - }) - // return false; // Return false if we don't want the rolltable chat message displayed. But I think we want to display the rolltable result. - } - } - }) - } - } catch (e) { } // a dangerous game... but limited to GURPs /roll OtF - let newContent = gurpslink(c) - setProperty(chatMessage, "_source.content", newContent); - return true - } - ) - - Hooks.on('renderChatMessage', (_app, html, _msg) => { - GurpsWiring.hookupAllEvents(html) - // html.find('[data-otf]').each((_, li) => { - // li.setAttribute('draggable', 'true') - // li.addEventListener('dragstart', ev => { - // return ev.dataTransfer?.setData( - // 'text/plain', - // JSON.stringify({ - // otf: li.getAttribute('data-otf'), - // }) - // ) - // }) - // }) - }) - - Hooks.on( - 'diceSoNiceRollComplete', - async (/** @type {any} */ _app, /** @type {any} */ _html, /** @type {any} */ _msg) => { - let otf = GURPS.PendingOTFs.pop() - while (otf) { - let action = parselink(otf) - if (!!action.action) await GURPS.performAction(action.action, GURPS.LastActor || game.user) - otf = GURPS.PendingOTFs.pop() - } - } - ) - }) // End of "init" + // Add the "for" attribute to a collapsible panel label. This is needed + // because the server in 0.7.8 strips the "for" attribute in an attempt + // to guard against weird security hacks. When "for" is whitelisted as + // a valid attribute (future) we can remove this. + Hooks.on('renderChatMessage', (_app, html, _msg) => { + // this is a fucking hack + let wrapper = html.find('.collapsible-wrapper') + if (wrapper.length > 0) { + //console.log($(wrapper).html()) + let input = $(wrapper).find('input.toggle')[0] + let label = $(input).siblings('label.label-toggle')[0] + let id = input.id + let labelFor = $(label).attr('for') + if (labelFor !== id) { + $(label).attr('for', id) + console.log(`add the 'for' attribute if needed: ${$(wrapper).html()}`) + } + } + }) + + // Look for RESULTS from a RollTable. RollTables do not generate regular chat messages + Hooks.on( + 'preCreateChatMessage', + (/** @type {ChatMessage} */ chatMessage, /** @type {any} */ _options, /** @type {any} */ _userId) => { + let c = chatMessage.content + try { + let html = $(c) + let rt = html.find('.result-text') // Ugly hack to find results of a roll table to see if an OtF should be "rolled" /r /roll + let re = /^(\/r|\/roll|\/pr|\/private) \[([^\]]+)\]/ + let t = rt[0]?.innerText + if (!!t) { + t.split('\n').forEach(e => { + let m = e.match(re) + if (!!m && !!m[2]) { + let action = parselink(m[2]) + if (!!action.action) { + GURPS.performAction(action.action, GURPS.LastActor, { + shiftKey: e.startsWith('/pr'), + }) + // return false; // Return false if we don't want the rolltable chat message displayed. But I think we want to display the rolltable result. + } + } + }) + } + } catch (e) {} // a dangerous game... but limited to GURPs /roll OtF + let newContent = gurpslink(c) + setProperty(chatMessage, '_source.content', newContent) + return true + } + ) + + Hooks.on('renderChatMessage', (_app, html, _msg) => { + GurpsWiring.hookupAllEvents(html) + // html.find('[data-otf]').each((_, li) => { + // li.setAttribute('draggable', 'true') + // li.addEventListener('dragstart', ev => { + // return ev.dataTransfer?.setData( + // 'text/plain', + // JSON.stringify({ + // otf: li.getAttribute('data-otf'), + // }) + // ) + // }) + // }) + }) + + Hooks.on( + 'diceSoNiceRollComplete', + async (/** @type {any} */ _app, /** @type {any} */ _html, /** @type {any} */ _msg) => { + let otf = GURPS.PendingOTFs.pop() + while (otf) { + let action = parselink(otf) + if (!!action.action) await GURPS.performAction(action.action, GURPS.LastActor || game.user) + otf = GURPS.PendingOTFs.pop() + } + } + ) + }) // End of "init" } diff --git a/module/chat/anim.js b/module/chat/anim.js index 2dbc4c542..ec583e4b4 100644 --- a/module/chat/anim.js +++ b/module/chat/anim.js @@ -2,7 +2,7 @@ import ChatProcessor from './chat-processor.js' import { parselink } from '../../lib/parselink.js' -import { i18n, makeRegexPatternFrom, splitArgs, wait} from '../../lib/utilities.js' +import { i18n, makeRegexPatternFrom, splitArgs, wait } from '../../lib/utilities.js' const JB2A_PATREON = 'jb2a_patreon' const JB2A_FREE = 'JB2A_DnD5e' @@ -13,8 +13,7 @@ const JAAMOD = 'jaamod' let ANIM_LIBRARY = [] Hooks.on('ready', async () => { - if (!addToLibrary(JB2A_PATREON)) - addToLibrary(JB2A_FREE) + if (!addToLibrary(JB2A_PATREON)) addToLibrary(JB2A_FREE) addToLibrary(JK_ANIM_SPELL) addToLibrary(JK_ANIM_SPELL_CARTOON) addToLibrary(JAAMOD) @@ -22,14 +21,14 @@ Hooks.on('ready', async () => { function addToLibrary(module) { if (game.modules.get(module)) { - let xhr = new XMLHttpRequest(); - xhr.open('GET', 'systems/gurps/utils/' + module + '.txt', true); - xhr.responseType = 'text'; + let xhr = new XMLHttpRequest() + xhr.open('GET', 'systems/gurps/utils/' + module + '.txt', true) + xhr.responseType = 'text' xhr.onload = function () { if (xhr.readyState === xhr.DONE) { if (xhr.status === 200) { - let list = xhr.responseText.split('\n').map(s => s.replace(/^\.\//, "")) - list = list.map(s => s.replace(/,width=/, ",W:")) + let list = xhr.responseText.split('\n').map(s => s.replace(/^\.\//, '')) + list = list.map(s => s.replace(/,width=/, ',W:')) list = list.map(s => `modules/${module}/${s}`) list = list.filter(f => fileWidth(f).width > 0) console.log(`Loaded ${list.length} ${module} records`) @@ -45,24 +44,24 @@ function addToLibrary(module) { function fileWidth(entry) { let m = entry.match(/\.webm,W:(\d+)/) - if (!!m) return { file: entry.split(",")[0], width: +m[1] } -// console.log("Unknown format: " + entry) + if (!!m) return { file: entry.split(',')[0], width: +m[1] } + // console.log("Unknown format: " + entry) return { file: entry, width: 1 } } // stolen from the Ping module function getMousePos(foundryCanvas) { - const mouse = foundryCanvas.app.renderer.plugins.interaction.mouse.global; - const t = foundryCanvas.stage.worldTransform; + const mouse = foundryCanvas.app.renderer.plugins.interaction.mouse.global + const t = foundryCanvas.stage.worldTransform function calcCoord(axis) { - return (mouse[axis] - t['t' + axis]) / foundryCanvas.stage.scale[axis]; + return (mouse[axis] - t['t' + axis]) / foundryCanvas.stage.scale[axis] } return { x: calcCoord('x'), - y: calcCoord('y') - }; + y: calcCoord('y'), + } } export class AnimChatProcessor extends ChatProcessor { @@ -73,34 +72,31 @@ export class AnimChatProcessor extends ChatProcessor { async drawEffect(effect, fromToken, toTokensArray) { let used = [] for (const to of toTokensArray) - if (effect.centered) - used = [...used, ...await this.drawSpecialToward(effect, to, fromToken)] - else - used = [...used, ...await this.drawSpecialToward(effect, fromToken, to)] + if (effect.centered) used = [...used, ...(await this.drawSpecialToward(effect, to, fromToken))] + else used = [...used, ...(await this.drawSpecialToward(effect, fromToken, to))] return used } - + randFile(list) { let i = Math.floor(Math.random() * list.length) - return list.splice(i,1) + return list.splice(i, 1) } - - + async drawSpecialToward(effect, tok1, tok2) { const origin = { x: tok1.position.x + tok1.w / 2, - y: tok1.position.y + tok1.h / 2 + y: tok1.position.y + tok1.h / 2, } const target = { x: tok2.position.x + tok2.w / 2, - y: tok2.position.y + tok2.h / 2 + y: tok2.position.y + tok2.h / 2, } // Compute angle const deltaX = target.x - origin.x const deltaY = target.y - origin.y - let rotation = (effect.noRotate || (effect.centered && !effect.move)) ? 0 : Math.atan2(deltaY, deltaX) - let distance = (effect.centered && !effect.move) ? 0 : Math.sqrt(deltaX * deltaX + deltaY * deltaY) - + let rotation = effect.noRotate || (effect.centered && !effect.move) ? 0 : Math.atan2(deltaY, deltaX) + let distance = effect.centered && !effect.move ? 0 : Math.sqrt(deltaX * deltaX + deltaY * deltaY) + let files = [] if (effect.centered) { files = effect.files.map(f => fileWidth(f)) @@ -139,11 +135,11 @@ export class AnimChatProcessor extends ChatProcessor { let effects = [] let stretch = 0 for (let i = 0; i < effect.count; i++) { - const effectData = {...effect} + const effectData = { ...effect } effectData.position = { - x: origin.x, - y: origin.y - } + x: origin.x, + y: origin.y, + } effectData.rotation = rotation stretch = distance * (1 + effectData.stretch) let file = this.randFile(possible)[0] @@ -153,8 +149,8 @@ export class AnimChatProcessor extends ChatProcessor { if (effectData.flip) effectData.scale.x *= -1 if (effectData.move) effectData.distance = stretch } else { - let s = stretch / file.width - effectData.scale = {x: s, y: (Math.random() < 0.5 ? -effectData.scale.y : effectData.scale.y)} // randomly flip vert orientation + let s = stretch / file.width + effectData.scale = { x: s, y: Math.random() < 0.5 ? -effectData.scale.y : effectData.scale.y } // randomly flip vert orientation } effects.push(effectData) used.push(effectData.file.split('/').pop()) @@ -162,54 +158,59 @@ export class AnimChatProcessor extends ChatProcessor { this.executeEffects(effect.count, effects) // do NOT await this. return [used, stretch] } - + async executeEffects(count, effects) { for (let effectData of effects) { count-- - game.socket.emit('module.fxmaster', effectData); + game.socket.emit('module.fxmaster', effectData) // Throw effect locally try { canvas.specials.playVideo(effectData) - } catch (e) { //in case people have older versions of fxmaster + } catch (e) { + //in case people have older versions of fxmaster canvas.fxmaster.playVideo(effectData) } //console.log(GURPS.objToString(effectData)) if (count > 0) await wait(effectData.delay) } } - + errorExit(str) { - ui.notifications.error(str); + ui.notifications.error(str) return false } - + async awaitClick(line) { GURPS.IgnoreTokenSelect = true - return new Promise( (resolve, reject) => { - window.addEventListener('mousedown', (e) => { - let pt = getMousePos(game.canvas) - e.preventDefault() - e.stopPropagation() - let grid_size = canvas.scene.data.grid - canvas.tokens.targetObjects({ - x: pt.x - grid_size / 2, - y: pt.y - grid_size / 2, - height: grid_size, - width: grid_size, - releaseOthers: true, - }) - GURPS.IgnoreTokenSelect = false - line = line + " @" + parseInt(pt.x) + "," + parseInt(pt.y) - this.registry.processLine(line) - resolve() - }, {once: true}) + return new Promise((resolve, reject) => { + window.addEventListener( + 'mousedown', + e => { + let pt = getMousePos(game.canvas) + e.preventDefault() + e.stopPropagation() + let grid_size = canvas.scene.data.grid + canvas.tokens.targetObjects({ + x: pt.x - grid_size / 2, + y: pt.y - grid_size / 2, + height: grid_size, + width: grid_size, + releaseOthers: true, + }) + GURPS.IgnoreTokenSelect = false + line = line + ' @' + parseInt(pt.x) + ',' + parseInt(pt.y) + this.registry.processLine(line) + resolve() + }, + { once: true } + ) }) } - + async displaylist() { - let list = ANIM_LIBRARY.map(s => s.replace(/modules\//,"")) - list = [ "Total: " + ANIM_LIBRARY.length, ...list] - + let list = ANIM_LIBRARY.map(s => s.replace(/modules\//, '')) + list = ['Total: ' + ANIM_LIBRARY.length, ...list] + let t = await renderTemplate('systems/gurps/templates/import-stat-block.html', { block: list.join('\n') }) //let t = await renderTemplate('systems/gurps/templates/chat-processing.html', { lines: list }) let d = new Dialog( @@ -217,7 +218,7 @@ export class AnimChatProcessor extends ChatProcessor { title: `Anim library`, content: t, buttons: { - no: { + no: { icon: '', label: 'OK', }, @@ -232,42 +233,47 @@ export class AnimChatProcessor extends ChatProcessor { d.render(true) } - matches(line) { -// this.match = line.match(/^\/anim *(?list)? *(?[\S]+)? *(?
    cf?\d*)? *(?\*[\d\.]+)? *(?[-+][\d\.]+)? *(?[-+][\d\.]+)? *(?[\d\.]+[xX])?(?:[\d\.]+)? *(?@\d+,\d+)? *(?@(s|self|src)?)?/) - this.match = line.match(/^\/anim +(?list)? *(?w[\d\.]+)? *(?[\S]+)? *(?
    cf?m?n?\d*(:[\d\.]+,[\d\.]+)?)? *(?\*[\d\.]+)? *(?-[\d\.]+)? *(?[\+>][\d\.]+)? *(?[\d\.]+[xX])?(?:[\d\.]+)? *(?@\d+,\d+)? *(?@(s|self|src)?)?/) + matches(line) { + // this.match = line.match(/^\/anim *(?list)? *(?[\S]+)? *(?
    cf?\d*)? *(?\*[\d\.]+)? *(?[-+][\d\.]+)? *(?[-+][\d\.]+)? *(?[\d\.]+[xX])?(?:[\d\.]+)? *(?@\d+,\d+)? *(?@(s|self|src)?)?/) + this.match = line.match( + /^\/anim +(?list)? *(?w[\d\.]+)? *(?[\S]+)? *(?
    cf?m?n?\d*(:[\d\.]+,[\d\.]+)?)? *(?\*[\d\.]+)? *(?-[\d\.]+)? *(?[\+>][\d\.]+)? *(?[\d\.]+[xX])?(?:[\d\.]+)? *(?@\d+,\d+)? *(?@(s|self|src)?)?/ + ) return !!this.match } - + usagematches(line) { return line.match(/^[\/\?]anim$/i) } usage() { - return i18n("GURPS.chatHelpAnim"); + return i18n('GURPS.chatHelpAnim') } async process(line) { - if (!canvas.fxmaster) return this.errorExit("This macro depends on the FXMaster module. Make sure it is installed and enabled") + if (!canvas.fxmaster) + return this.errorExit('This macro depends on the FXMaster module. Make sure it is installed and enabled') let files = [] let m = this.match.groups - if (ANIM_LIBRARY.length == 0) return this.errorExit("This macro depends on one of the JB2A modules (free or patreon) or Animated Spell Effects. Make sure at least one is installed.") + if (ANIM_LIBRARY.length == 0) + return this.errorExit( + 'This macro depends on one of the JB2A modules (free or patreon) or Animated Spell Effects. Make sure at least one is installed.' + ) if (!!m.list) { this.displaylist() - return true; + return true } - if (!m.file) return this.errorExit("Must provide animation name") - if (m.file[0] == '/') - files = [m.file.substr(1)] + if (!m.file) return this.errorExit('Must provide animation name') + if (m.file[0] == '/') files = [m.file.substr(1)] else { let pat = new RegExp(m.file.split('*').join('.*?').replace(/\//g, '\\/'), 'i') files = ANIM_LIBRARY.filter(e => e.match(pat)) if (files.length == 0) return this.errorExit(`Unable to find animation for '${pat}'`) } - + if (!!m.wait) { let s = parseFloat(m.wait.substr(1)) await wait(s * 1000) } - + let opts = [] let scale = 1.0 let flip = false @@ -294,62 +300,65 @@ export class AnimChatProcessor extends ChatProcessor { x = parseFloat(offset[0]) y = parseFloat(offset[1]) } - opts.push("Centered " + (flip ? " flip":"") + (move ? " move":"") + ":" + angle) - opts.push("Offset: " + x + "," + y) - } else opts.push("Targeted") - if (!!m.scale) { - scale = parseFloat(m.scale.substr(1)) - opts.push("Scale:" + scale) - if (!centered) this.priv("Scale option only valid on Centered animation") + opts.push('Centered ' + (flip ? ' flip' : '') + (move ? ' move' : '') + ':' + angle) + opts.push('Offset: ' + x + ',' + y) + } else opts.push('Targeted') + if (!!m.scale) { + scale = parseFloat(m.scale.substr(1)) + opts.push('Scale:' + scale) + if (!centered) this.priv('Scale option only valid on Centered animation') } if (!!m.count) { count = parseInt(m.count.slice(0, -1)) - opts.push("Count:" + count) + opts.push('Count:' + count) } if (!!m.delay) { delay = 1000 * parseFloat(m.delay.substr(1)) - opts.push("Delay:" + m.delay.substr(1)) + opts.push('Delay:' + m.delay.substr(1)) } if (!!m.x) { x = parseFloat(m.x.substr(1)) - opts.push("Anchor:-" + x) - if (centered) this.priv("Anchor option only valid on Targeted animation") + opts.push('Anchor:-' + x) + if (centered) this.priv('Anchor option only valid on Targeted animation') } if (!!m.stretch) { - stretch = parseFloat(m.stretch.substr(1)) - opts.push("Stretch:+" + stretch) - if (centered && !move) this.priv("Stretch option only valid on moving animations") + stretch = parseFloat(m.stretch.substr(1)) + opts.push('Stretch:+' + stretch) + if (centered && !move) this.priv('Stretch option only valid on moving animations') } - + let srcToken = canvas.tokens.placeables.find(e => e.actor == GURPS.LastActor) if (!srcToken) srcToken = canvas.tokens.controlled[0] let destTokens = Array.from(game.user.targets) - if (destTokens.length == 0 && game.user.isGM) destTokens = canvas.tokens.controlled - if (m.self && srcToken) destTokens = [ srcToken ] + if (destTokens.length == 0 && game.user.isGM) destTokens = canvas.tokens.controlled + if (m.self && srcToken) destTokens = [srcToken] if (m.dest) { let d = m.dest.substr(1).split(',') - destTokens = [ { - name: "User click", - w: 0, - h: 0, - position: { - x: +d[0], - y: +d[1] - } - }] + destTokens = [ + { + name: 'User click', + w: 0, + h: 0, + position: { + x: +d[0], + y: +d[1], + }, + }, + ] } if (destTokens.length == 0) { - ui.notifications.info("Please click the target location") + ui.notifications.info('Please click the target location') this.send() - await this.awaitClick((this.msgs().quiet ? '!' : '') + line.replace(/@ *$/,'')) - return true; + await this.awaitClick((this.msgs().quiet ? '!' : '') + line.replace(/@ *$/, '')) + return true } - if (!srcToken) srcToken = destTokens[0] // centered anims should show on target (or selection) - if ((!centered || move) && destTokens.length == 1 && destTokens[0] == srcToken) return this.errorExit("Source and Destination cannot be the same token with using a moving animation") + if (!srcToken) srcToken = destTokens[0] // centered anims should show on target (or selection) + if ((!centered || move) && destTokens.length == 1 && destTokens[0] == srcToken) + return this.errorExit('Source and Destination cannot be the same token with using a moving animation') if (move) { let temp = srcToken - srcToken = destTokens[0] - destTokens = [ temp ] + srcToken = destTokens[0] + destTokens = [temp] } let effect = { files: files, @@ -368,16 +377,16 @@ export class AnimChatProcessor extends ChatProcessor { noRotate: noRotate, flip: flip, count: count, - delay: delay - } - this.priv("Src:" + srcToken?.name) - this.priv("Dest:" + destTokens.map(e => e?.name)) - this.priv("Opts: " + opts.join(', ')) - this.priv("Possible:") + delay: delay, + } + this.priv('Src:' + srcToken?.name) + this.priv('Dest:' + destTokens.map(e => e?.name)) + this.priv('Opts: ' + opts.join(', ')) + this.priv('Possible:') this.priv(files.map(e => e.split('/').pop()).join('
    ')) let [used, dist] = await this.drawEffect(effect, srcToken, destTokens) - this.priv("Used:
    " + used.join('
    ')) - this.priv("Dist: " + dist) + this.priv('Used:
    ' + used.join('
    ')) + this.priv('Dist: ' + dist) this.send() } -} \ No newline at end of file +} diff --git a/module/chat/chat-processors.js b/module/chat/chat-processors.js index 738173062..84954067c 100644 --- a/module/chat/chat-processors.js +++ b/module/chat/chat-processors.js @@ -6,13 +6,23 @@ import { parselink, parseForRollOrDamage } from '../../lib/parselink.js' import { NpcInput } from '../../lib/npc-input.js' import { FrightCheckChatProcessor } from './frightcheck.js' import { - EveryoneAChatProcessor, - EveryoneBChatProcessor, - EveryoneCChatProcessor, - RemoteChatProcessor, + EveryoneAChatProcessor, + EveryoneBChatProcessor, + EveryoneCChatProcessor, + RemoteChatProcessor, } from './everything.js' import { IfChatProcessor } from './if.js' -import { isNiceDiceEnabled, i18n, i18n_f, splitArgs, makeRegexPatternFrom, wait, zeroFill, locateToken, requestFpHp } from '../../lib/utilities.js' +import { + isNiceDiceEnabled, + i18n, + i18n_f, + splitArgs, + makeRegexPatternFrom, + wait, + zeroFill, + locateToken, + requestFpHp, +} from '../../lib/utilities.js' import StatusChatProcessor from '../chat/status.js' import SlamChatProcessor from '../chat/slam.js' import TrackerChatProcessor from '../chat/tracker.js' @@ -21,1048 +31,1074 @@ import { AnimChatProcessor } from '../chat/anim.js' import Maneuvers from '../actor/maneuver.js' export default function RegisterChatProcessors() { - ChatProcessors.registerProcessor(new RollAgainstChatProcessor()) - ChatProcessors.registerProcessor(new MookChatProcessor()) - ChatProcessors.registerProcessor(new SelectChatProcessor()) - ChatProcessors.registerProcessor(new EveryoneAChatProcessor()) - ChatProcessors.registerProcessor(new EveryoneBChatProcessor()) - ChatProcessors.registerProcessor(new EveryoneCChatProcessor()) - ChatProcessors.registerProcessor(new RollChatProcessor()) - ChatProcessors.registerProcessor(new ShowMBsChatProcessor()) - ChatProcessors.registerProcessor(new ClearMBsChatProcessor()) - ChatProcessors.registerProcessor(new StatusChatProcessor()) - ChatProcessors.registerProcessor(new FrightCheckChatProcessor()) - ChatProcessors.registerProcessor(new UsesChatProcessor()) - ChatProcessors.registerProcessor(new QtyChatProcessor()) - ChatProcessors.registerProcessor(new FpHpChatProcessor()) - ChatProcessors.registerProcessor(new SendMBChatProcessor()) - ChatProcessors.registerProcessor(new ChatExecuteChatProcessor()) - ChatProcessors.registerProcessor(new TrackerChatProcessor()) - ChatProcessors.registerProcessor(new IfChatProcessor()) - ChatProcessors.registerProcessor(new SetEventFlagsChatProcessor()) - ChatProcessors.registerProcessor(new RemoteChatProcessor()) - ChatProcessors.registerProcessor(new SlamChatProcessor()) - ChatProcessors.registerProcessor(new LightChatProcessor()) - ChatProcessors.registerProcessor(new ForceMigrateChatProcessor()) - ChatProcessors.registerProcessor(new ReimportChatProcessor()) - ChatProcessors.registerProcessor(new ShowChatProcessor()) - ChatProcessors.registerProcessor(new AnimChatProcessor()) - ChatProcessors.registerProcessor(new WaitChatProcessor()) - ChatProcessors.registerProcessor(new WhisperChatProcessor()) - ChatProcessors.registerProcessor(new RolltableChatProcessor()) - ChatProcessors.registerProcessor(new RefreshItemsChatProcessor()) - ChatProcessors.registerProcessor(new QuickDamageChatProcessor()) - ChatProcessors.registerProcessor(new SoundChatProcessor()) - ChatProcessors.registerProcessor(new DevChatProcessor()) - ChatProcessors.registerProcessor(new ManeuverChatProcessor()) - ChatProcessors.registerProcessor(new RepeatChatProcessor()) - ChatProcessors.registerProcessor(new StopChatProcessor()) + ChatProcessors.registerProcessor(new RollAgainstChatProcessor()) + ChatProcessors.registerProcessor(new MookChatProcessor()) + ChatProcessors.registerProcessor(new SelectChatProcessor()) + ChatProcessors.registerProcessor(new EveryoneAChatProcessor()) + ChatProcessors.registerProcessor(new EveryoneBChatProcessor()) + ChatProcessors.registerProcessor(new EveryoneCChatProcessor()) + ChatProcessors.registerProcessor(new RollChatProcessor()) + ChatProcessors.registerProcessor(new ShowMBsChatProcessor()) + ChatProcessors.registerProcessor(new ClearMBsChatProcessor()) + ChatProcessors.registerProcessor(new StatusChatProcessor()) + ChatProcessors.registerProcessor(new FrightCheckChatProcessor()) + ChatProcessors.registerProcessor(new UsesChatProcessor()) + ChatProcessors.registerProcessor(new QtyChatProcessor()) + ChatProcessors.registerProcessor(new FpHpChatProcessor()) + ChatProcessors.registerProcessor(new SendMBChatProcessor()) + ChatProcessors.registerProcessor(new ChatExecuteChatProcessor()) + ChatProcessors.registerProcessor(new TrackerChatProcessor()) + ChatProcessors.registerProcessor(new IfChatProcessor()) + ChatProcessors.registerProcessor(new SetEventFlagsChatProcessor()) + ChatProcessors.registerProcessor(new RemoteChatProcessor()) + ChatProcessors.registerProcessor(new SlamChatProcessor()) + ChatProcessors.registerProcessor(new LightChatProcessor()) + ChatProcessors.registerProcessor(new ForceMigrateChatProcessor()) + ChatProcessors.registerProcessor(new ReimportChatProcessor()) + ChatProcessors.registerProcessor(new ShowChatProcessor()) + ChatProcessors.registerProcessor(new AnimChatProcessor()) + ChatProcessors.registerProcessor(new WaitChatProcessor()) + ChatProcessors.registerProcessor(new WhisperChatProcessor()) + ChatProcessors.registerProcessor(new RolltableChatProcessor()) + ChatProcessors.registerProcessor(new RefreshItemsChatProcessor()) + ChatProcessors.registerProcessor(new QuickDamageChatProcessor()) + ChatProcessors.registerProcessor(new SoundChatProcessor()) + ChatProcessors.registerProcessor(new DevChatProcessor()) + ChatProcessors.registerProcessor(new ManeuverChatProcessor()) + ChatProcessors.registerProcessor(new RepeatChatProcessor()) + ChatProcessors.registerProcessor(new StopChatProcessor()) } class SoundChatProcessor extends ChatProcessor { - help() { - return '/sound <path-to-sound>' - } + help() { + return '/sound <path-to-sound>' + } - matches(line) { - this.match = line.match(/^\/sound +(?w[\d\.]+)? *(?v[\d\.]+)? *(?.*)/i) - return !!this.match - } - usagematches(line) { - return line.match(/^[\/\?]sound$/i) - } - usage() { return i18n("GURPS.chatHelpSound") } + matches(line) { + this.match = line.match(/^\/sound +(?w[\d\.]+)? *(?v[\d\.]+)? *(?.*)/i) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?]sound$/i) + } + usage() { + return i18n('GURPS.chatHelpSound') + } - async process(line) { - let v = 0.8 - if (this.match.groups.vol) v = parseFloat(this.match.groups.vol.substr(1)) - if (this.match.groups.wait) await wait(parseFloat(this.match.groups.wait.substr(1)) * 1000) - let data = { - src: this.match.groups.file.trim(), - volume: v, - loop: false, - } - AudioHelper.play(data, true).then(sound => { - if (sound.failed) ui.notifications.warn("Unable to play: " + data.src) - }) - } + async process(line) { + let v = 0.8 + if (this.match.groups.vol) v = parseFloat(this.match.groups.vol.substr(1)) + if (this.match.groups.wait) await wait(parseFloat(this.match.groups.wait.substr(1)) * 1000) + let data = { + src: this.match.groups.file.trim(), + volume: v, + loop: false, + } + AudioHelper.play(data, true).then(sound => { + if (sound.failed) ui.notifications.warn('Unable to play: ' + data.src) + }) + } } class QuickDamageChatProcessor extends ChatProcessor { - help() { - return '/<damage formula>' - } + help() { + return '/<damage formula>' + } - matches(line) { - this.match = line.match(/^[\.\/](.*?)( +[xX\*]?(?\d+))?$/) - if (!!this.match) { - this.action = parselink(this.match[1]) - return this.action?.action?.type == 'damage' || this.action?.action?.type == 'roll' - } - return false - } - async process(line) { - let event = { - shiftKey: this.action.action.blindroll, - data: { - repeat: this.match.groups.num - } - } - await GURPS.performAction(this.action.action, GURPS.LastActor, event) - } + matches(line) { + this.match = line.match(/^[\.\/](.*?)( +[xX\*]?(?\d+))?$/) + if (!!this.match) { + this.action = parselink(this.match[1]) + return this.action?.action?.type == 'damage' || this.action?.action?.type == 'roll' + } + return false + } + async process(line) { + let event = { + shiftKey: this.action.action.blindroll, + data: { + repeat: this.match.groups.num, + }, + } + await GURPS.performAction(this.action.action, GURPS.LastActor, event) + } } class RefreshItemsChatProcessor extends ChatProcessor { - help() { - return null - } + help() { + return null + } - matches(line) { - this.match = line.match(/^\/refreshitems/i) - return !!this.match - } - async process(line) { - ui.notifications.info('Starting Item refresh...') - for (const a of game.actors.contents) { - console.log('Executeing postImport() on ' + a.name) - await a.postImport() - } - ui.notifications.info('Item refresh done.') - } + matches(line) { + this.match = line.match(/^\/refreshitems/i) + return !!this.match + } + async process(line) { + ui.notifications.info('Starting Item refresh...') + for (const a of game.actors.contents) { + console.log('Executeing postImport() on ' + a.name) + await a.postImport() + } + ui.notifications.info('Item refresh done.') + } } class ForceMigrateChatProcessor extends ChatProcessor { - help() { - return null - } + help() { + return null + } - matches(line) { - this.match = line.match(/^\/forcemigrate/i) - return !!this.match - } - async process(line) { - await Migration.migrateTo096() - await Migration.migrateTo097() - await Migration.migrateTo0104() - await Migration.fixDataModelProblems() - } + matches(line) { + this.match = line.match(/^\/forcemigrate/i) + return !!this.match + } + async process(line) { + await Migration.migrateTo096() + await Migration.migrateTo097() + await Migration.migrateTo0104() + await Migration.fixDataModelProblems() + } } class RolltableChatProcessor extends ChatProcessor { - help() { - return '/rolltable <tablename>' - } - matches(line) { - this.match = line.match(/^\/rolltable(.*)/) - return !!this.match - } + help() { + return '/rolltable <tablename>' + } + matches(line) { + this.match = line.match(/^\/rolltable(.*)/) + return !!this.match + } - async process(line) { - let tblname = this.match[1].trim() - let pat = new RegExp(makeRegexPatternFrom(tblname, false), 'i') - let tables = game.tables.contents.filter(t => t.name.match(pat)) - if (tables.length == 0) { - ui.notifications.error("No table found for '" + tblname + "'") - return false - } - if (tables.length > 1) { - ui.notifications.error("More than one table matched '" + tblname + "'") - return false - } - let table = tables[0] - let r = await table.roll() - table.draw(r) - return true - } + async process(line) { + let tblname = this.match[1].trim() + let pat = new RegExp(makeRegexPatternFrom(tblname, false), 'i') + let tables = game.tables.contents.filter(t => t.name.match(pat)) + if (tables.length == 0) { + ui.notifications.error("No table found for '" + tblname + "'") + return false + } + if (tables.length > 1) { + ui.notifications.error("More than one table matched '" + tblname + "'") + return false + } + let table = tables[0] + let r = await table.roll() + table.draw(r) + return true + } } class WhisperChatProcessor extends ChatProcessor { - help() { - return '/w [players] or @' - } - matches(line) { - this.match = line.match(/^\/w +@ +(.+)$/) - return !!this.match - } - usagematches(line) { - return line.match(/^[\/\?]w$/i) - } - usage() { - return i18n("GURPS.chatHelpW") - } - process(line) { - let destTokens = Array.from(game.user.targets) - if (destTokens.length == 0) destTokens = canvas.tokens.controlled - if (destTokens.length == 0) return false - let users = [] - for (const token of destTokens) { - let owners = game.users.contents.filter(u => token.actor.getUserLevel(u) >= CONST.DOCUMENT_PERMISSION_LEVELS.OWNER) - for (const user of owners) if (!user.isGM) users.push(user) - } - if (users.length == 0) return false - this.registry.processLine('/w [' + users.map(u => u.name).join(',') + '] ' + this.match[1]) - } + help() { + return '/w [players] or @' + } + matches(line) { + this.match = line.match(/^\/w +@ +(.+)$/) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?]w$/i) + } + usage() { + return i18n('GURPS.chatHelpW') + } + process(line) { + let destTokens = Array.from(game.user.targets) + if (destTokens.length == 0) destTokens = canvas.tokens.controlled + if (destTokens.length == 0) return false + let users = [] + for (const token of destTokens) { + let owners = game.users.contents.filter( + u => token.actor.getUserLevel(u) >= CONST.DOCUMENT_PERMISSION_LEVELS.OWNER + ) + for (const user of owners) if (!user.isGM) users.push(user) + } + if (users.length == 0) return false + this.registry.processLine('/w [' + users.map(u => u.name).join(',') + '] ' + this.match[1]) + } } class ReimportChatProcessor extends ChatProcessor { - isGMOnly() { - return true - } - help() { - return '/reimport <optional character names>' - } - matches(line) { - return line.startsWith('/reimport') - } - process(line) { - this.priv(line) - let actornames = line.replace(/^\/reimport/, '').trim() - actornames = splitArgs(actornames) - let allPlayerActors = game.actors.entities.filter(a => a.hasPlayerOwner) - let actors = [] - for (const name of actornames) { - let actor = allPlayerActors.find(a => a.name.match(makeRegexPattern(name, false))) - if (!!actor) actors.push(actor) - } - if (actornames.length == 0) actors = allPlayerActors - actors.forEach(e => e.importCharacter()) - } + isGMOnly() { + return true + } + help() { + return '/reimport <optional character names>' + } + matches(line) { + return line.startsWith('/reimport') + } + process(line) { + this.priv(line) + let actornames = line.replace(/^\/reimport/, '').trim() + actornames = splitArgs(actornames) + let allPlayerActors = game.actors.entities.filter(a => a.hasPlayerOwner) + let actors = [] + for (const name of actornames) { + let actor = allPlayerActors.find(a => a.name.match(makeRegexPattern(name, false))) + if (!!actor) actors.push(actor) + } + if (actornames.length == 0) actors = allPlayerActors + actors.forEach(e => e.importCharacter()) + } } class WaitChatProcessor extends ChatProcessor { - help() { - return '/wait <milliseconds>' - } - matches(line) { - this.match = line.match(/^\/wait +(\d+)/) - return this.match - } - usagematches(line) { - return line.match(/^[\/\?]wait$/i) - } - usage() { - return i18n("GURPS.chatHelpWait") - } - async process(line) { - this.priv(line) - await wait(+this.match[1]) - } + help() { + return '/wait <milliseconds>' + } + matches(line) { + this.match = line.match(/^\/wait +(\d+)/) + return this.match + } + usagematches(line) { + return line.match(/^[\/\?]wait$/i) + } + usage() { + return i18n('GURPS.chatHelpWait') + } + async process(line) { + this.priv(line) + await wait(+this.match[1]) + } } class SetEventFlagsChatProcessor extends ChatProcessor { - help() { - return null - } - matches(line) { - return line.startsWith('/setEventFlags') - } - process(line) { - let m = line.match(/\/setEventFlags (\w+) (\w+) (\w+)/) - this.registry.setEventFlags(m[1] == 'true', m[2] == 'true', m[3] == 'true') - } + help() { + return null + } + matches(line) { + return line.startsWith('/setEventFlags') + } + process(line) { + let m = line.match(/\/setEventFlags (\w+) (\w+) (\w+)/) + this.registry.setEventFlags(m[1] == 'true', m[2] == 'true', m[3] == 'true') + } } class ClearMBsChatProcessor extends ChatProcessor { - help() { - return '/clearmb' - } - matches(line) { - return line.startsWith('/clearmb') - } - process(line) { - this.priv(line) - GURPS.ModifierBucket.clear(!line.endsWith('no-update')) - } + help() { + return '/clearmb' + } + matches(line) { + return line.startsWith('/clearmb') + } + process(line) { + this.priv(line) + GURPS.ModifierBucket.clear(!line.endsWith('no-update')) + } } class ShowMBsChatProcessor extends ChatProcessor { - help() { - return '/showmbs' - } - matches(line) { - return line === '/showmbs' - } - process(line) { - this.priv(line) - // TODO Go to ModifierBucket and use chat-processor.html there too - setTimeout(() => GURPS.ModifierBucket.showOthers(), 1000) // Need time for clients to update...and - } + help() { + return '/showmbs' + } + matches(line) { + return line === '/showmbs' + } + process(line) { + this.priv(line) + // TODO Go to ModifierBucket and use chat-processor.html there too + setTimeout(() => GURPS.ModifierBucket.showOthers(), 1000) // Need time for clients to update...and + } } class RollAgainstChatProcessor extends ChatProcessor { - help() { - return '/ra N | Skillname-N' - } - matches(line) { - this.match = line.match(/^([\.\/]p?ra) +([\w-'" ]+-)?(\d+)/i) - return !!this.match - } - usagematches(line) { - return line.match(/^[\/\?\.]p?ra$/i) - } - usage() { - return i18n("GURPS.chatHelpRa") - } - async process(line) { - let m = this.match - let skill = m[2] || 'Default=' - skill = skill.replace('-', '=') + m[3] - if (skill.includes(' ')) skill = '"' + skill + '"' - let action = parselink('S:' + skill) - this.send() // send what we have - await GURPS.performAction(action.action, GURPS.LastActor, { - shiftKey: line.substr(1).startsWith('pra') || this.msgs().event?.shiftKey, - }) - } + help() { + return '/ra N | Skillname-N' + } + matches(line) { + this.match = line.match(/^([\.\/]p?ra) +([\w-'" ]+-)?(\d+)/i) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?\.]p?ra$/i) + } + usage() { + return i18n('GURPS.chatHelpRa') + } + async process(line) { + let m = this.match + let skill = m[2] || 'Default=' + skill = skill.replace('-', '=') + m[3] + if (skill.includes(' ')) skill = '"' + skill + '"' + let action = parselink('S:' + skill) + this.send() // send what we have + await GURPS.performAction(action.action, GURPS.LastActor, { + shiftKey: line.substr(1).startsWith('pra') || this.msgs().event?.shiftKey, + }) + } } class MookChatProcessor extends ChatProcessor { - isGMOnly() { - return true - } - help() { - return '/mook' - } - matches(line) { - this.match = line.match(/^\/mook/i) - return !!this.match - } - process(line) { - new NpcInput().render(true) - this.priv('Opening Mook Generator') - } + isGMOnly() { + return true + } + help() { + return '/mook' + } + matches(line) { + this.match = line.match(/^\/mook/i) + return !!this.match + } + process(line) { + new NpcInput().render(true) + this.priv('Opening Mook Generator') + } } class ChatExecuteChatProcessor extends ChatProcessor { - help() { - return '/:<macro name> args (arguments require "Advanced Macros" module)' - } - matches(line) { - return line.startsWith('/:') - } - process(line) { - GURPS.chatreturn = false - let args = splitArgs(line.substr(2)) - GURPS.chatargs = args - let m = Object.values(game.macros.contents).filter(m => m.name.startsWith(args[0])) - if (m.length > 0) { - this.send() - GURPS.chatreturn = m[0].execute(args) ?? GURPS.chatreturn // if Advanced macros is loaded, take advantage of the return value - } else this.priv(`${i18n('GURPS.chatUnableToFindMacro')} '${line.substr(2)}'`) - return GURPS.chatreturn - } + help() { + return '/:<macro name> args (arguments require "Advanced Macros" module)' + } + matches(line) { + return line.startsWith('/:') + } + process(line) { + GURPS.chatreturn = false + let args = splitArgs(line.substr(2)) + GURPS.chatargs = args + let m = Object.values(game.macros.contents).filter(m => m.name.startsWith(args[0])) + if (m.length > 0) { + this.send() + GURPS.chatreturn = m[0].execute(args) ?? GURPS.chatreturn // if Advanced macros is loaded, take advantage of the return value + } else this.priv(`${i18n('GURPS.chatUnableToFindMacro')} '${line.substr(2)}'`) + return GURPS.chatreturn + } } class SendMBChatProcessor extends ChatProcessor { - isGMOnly() { - return true - } - help() { - return '/sendmb <OtF> <playername(s)>' - } - matches(line) { - return line.startsWith('/sendmb') - } - process(line) { - this.priv(line) - let users = line.replace(/^\/sendmb/, '').trim() - let m = users.match(/\[(.*)\](.*)/) - if (!!m) { - let otf = m[1] - let t = parselink(otf) - if (!!t.action && t.action.type == 'modifier') { - GURPS.ModifierBucket.sendToPlayers(t.action, splitArgs(m[2])) - return - } else ui.notifications.warn(i18n('GURPS.chatYouMayOnlySendMod')) - } else GURPS.ModifierBucket.sendToPlayers(null, splitArgs(users)) - } + isGMOnly() { + return true + } + help() { + return '/sendmb <OtF> <playername(s)>' + } + matches(line) { + return line.startsWith('/sendmb') + } + process(line) { + this.priv(line) + let users = line.replace(/^\/sendmb/, '').trim() + let m = users.match(/\[(.*)\](.*)/) + if (!!m) { + let otf = m[1] + let t = parselink(otf) + if (!!t.action && t.action.type == 'modifier') { + GURPS.ModifierBucket.sendToPlayers(t.action, splitArgs(m[2])) + return + } else ui.notifications.warn(i18n('GURPS.chatYouMayOnlySendMod')) + } else GURPS.ModifierBucket.sendToPlayers(null, splitArgs(users)) + } } class FpHpChatProcessor extends ChatProcessor { - help() { - return '/fp (or /hp) <formula>' - } - matches(line) { - this.match = line.match(/^\/([fh]p) +([+-]\d+d\d*)?([+-=]\d+)?(!)?(reset)?(.*)/i) - return !!this.match - } - usagematches(line) { - return line.match(/^[\/\?][fh]p$/i) - } - usage() { - return i18n("GURPS.chatHelpFpHp"); - } + help() { + return '/fp (or /hp) <formula>' + } + matches(line) { + this.match = line.match(/^\/([fh]p) +([+-]\d+d\d*)?([+-=]\d+)?(!)?(reset)?(.*)/i) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?][fh]p$/i) + } + usage() { + return i18n('GURPS.chatHelpFpHp') + } - async process(line) { - let m = this.match - let actor = GURPS.LastActor - if (!actor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - if (m[6]?.trim().toLowerCase() == '@target') { - let targets = Array.from(game.user.targets).map(t => t.id) - if (targets.length == 0) { - ui.notifications.warn(i18n('GURPS.noTargetSelected')) - return false - } - line = line.replace(/@target/gi, '') - let remotes = [] - let locals = [] - targets.map(tid => { - let ta = game.canvas.tokens.get(tid).actor - let remote = false - var any - ta.getOwners().forEach(o => { - if (!o.isGM && o.active && !any) { - remote = true - any = o - remotes.push([o.id, tid]) - } - }) - if (!remote) locals.push(['', tid]) - this.priv(`${i18n_f('GURPS.chatSentTo', { cmd: line, name: ta.name })}`) - }) + async process(line) { + let m = this.match + let actor = GURPS.LastActor + if (!actor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + if (m[6]?.trim().toLowerCase() == '@target') { + let targets = Array.from(game.user.targets).map(t => t.id) + if (targets.length == 0) { + ui.notifications.warn(i18n('GURPS.noTargetSelected')) + return false + } + line = line.replace(/@target/gi, '') + let remotes = [] + let locals = [] + targets.map(tid => { + let ta = game.canvas.tokens.get(tid).actor + let remote = false + var any + ta.getOwners().forEach(o => { + if (!o.isGM && o.active && !any) { + remote = true + any = o + remotes.push([o.id, tid]) + } + }) + if (!remote) locals.push(['', tid]) + this.priv(`${i18n_f('GURPS.chatSentTo', { cmd: line, name: ta.name })}`) + }) - game.socket?.emit('system.gurps', { - type: 'playerFpHp', - actorname: actor.name, - targets: remotes, - command: line - }) + game.socket?.emit('system.gurps', { + type: 'playerFpHp', + actorname: actor.name, + targets: remotes, + command: line, + }) - requestFpHp({ - actorname: actor.name, - targets: locals, - command: line - }) - return true - } + requestFpHp({ + actorname: actor.name, + targets: locals, + command: line, + }) + return true + } - let attr = m[1].toUpperCase() - let delta = parseInt(m[3]) - const max = actor.system[attr].max - let reset = '' - if (!!m[5]) { - await actor.update({ ['data.' + attr + '.value']: max }) - this.prnt(`${actor.displayname} reset to ${max} ${attr}`) - } else if (isNaN(delta) && !!m[3]) { - // only happens with '=' - delta = parseInt(m[3].substr(1)) - if (isNaN(delta)) ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${line}'`) - else { - let mtxt = '' - if (delta > max) { - delta = max - mtxt = ` (max: ${max})` - } - await actor.update({ ['data.' + attr + '.value']: delta }) - this.prnt(`${actor.displayname} ${i18n('GURPS.chatSetTo')} ${delta} ${attr}${mtxt}`) - } - } else if (!!m[2] || !!m[3]) { - let mtxt = '' - let mod = m[3] || '' - delta = parseInt(mod) - let dice = m[2] || '' - let txt = '' - if (!!dice) { - let sign = dice[0] == '-' ? -1 : 1 - let d = dice.match(/[+-](\d+)d(\d*)/) - let r = d[1] + 'd' + (!!d[2] ? d[2] : '6') + `[/${attr}]` - let roll = Roll.create(r) - await roll.evaluate({ async: true }) - if (isNiceDiceEnabled()) { - let throws = [] - let dc = [] - roll.dice.forEach(die => { - let type = 'd' + die.faces - die.results.forEach(s => - dc.push({ - result: s.result, - resultLabel: s.result, - type: type, - vectors: [], - options: {}, - }) - ) - }) - throws.push({ dice: dc }) - if (dc.length > 0) { - // The user made a "multi-damage" roll... let them see the dice! - // @ts-ignore - game.dice3d.show({ throws: throws }) - } - } - delta = roll.total - if (!!mod) - if (isNaN(mod)) { - ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${line}'`) - return - } else delta += parseInt(mod) - delta = Math.max(delta, !!m[4] ? 1 : 0) - if (!!m[4]) mod += '!' - delta *= sign - txt = `(${delta}) ` - } - delta += actor.system[attr].value - if (delta > max) { - delta = max - mtxt = ` (max: ${max})` - } - await actor.update({ ['data.' + attr + '.value']: delta }) - this.prnt(`${actor.displayname} ${attr} ${dice}${mod} ${txt}${mtxt}`) - } else ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${line}'`) - } + let attr = m[1].toUpperCase() + let delta = parseInt(m[3]) + const max = actor.system[attr].max + let reset = '' + if (!!m[5]) { + await actor.update({ ['data.' + attr + '.value']: max }) + this.prnt(`${actor.displayname} reset to ${max} ${attr}`) + } else if (isNaN(delta) && !!m[3]) { + // only happens with '=' + delta = parseInt(m[3].substr(1)) + if (isNaN(delta)) ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${line}'`) + else { + let mtxt = '' + if (delta > max) { + delta = max + mtxt = ` (max: ${max})` + } + await actor.update({ ['data.' + attr + '.value']: delta }) + this.prnt(`${actor.displayname} ${i18n('GURPS.chatSetTo')} ${delta} ${attr}${mtxt}`) + } + } else if (!!m[2] || !!m[3]) { + let mtxt = '' + let mod = m[3] || '' + delta = parseInt(mod) + let dice = m[2] || '' + let txt = '' + if (!!dice) { + let sign = dice[0] == '-' ? -1 : 1 + let d = dice.match(/[+-](\d+)d(\d*)/) + let r = d[1] + 'd' + (!!d[2] ? d[2] : '6') + `[/${attr}]` + let roll = Roll.create(r) + await roll.evaluate({ async: true }) + if (isNiceDiceEnabled()) { + let throws = [] + let dc = [] + roll.dice.forEach(die => { + let type = 'd' + die.faces + die.results.forEach(s => + dc.push({ + result: s.result, + resultLabel: s.result, + type: type, + vectors: [], + options: {}, + }) + ) + }) + throws.push({ dice: dc }) + if (dc.length > 0) { + // The user made a "multi-damage" roll... let them see the dice! + // @ts-ignore + game.dice3d.show({ throws: throws }) + } + } + delta = roll.total + if (!!mod) + if (isNaN(mod)) { + ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${line}'`) + return + } else delta += parseInt(mod) + delta = Math.max(delta, !!m[4] ? 1 : 0) + if (!!m[4]) mod += '!' + delta *= sign + txt = `(${delta}) ` + } + delta += actor.system[attr].value + if (delta > max) { + delta = max + mtxt = ` (max: ${max})` + } + await actor.update({ ['data.' + attr + '.value']: delta }) + this.prnt(`${actor.displayname} ${attr} ${dice}${mod} ${txt}${mtxt}`) + } else ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${line}'`) + } } class SelectChatProcessor extends ChatProcessor { - help() { - return '/select <Actor name>' - } - matches(line) { - this.match = line.match(/^\/(select|sel) ?(\@self)?([^!]*)(!)?/) - return !!this.match - } - usagematches(line) { - return line.match(/^\?(select|sel)$/i) // can't match on /select since that is a valid command - } - usage() { - return i18n("GURPS.chatHelpSelect") - } - process(line) { - let m = this.match - if (!!m[2]) { - // @self - for (const a of game.actors.entities) { - let users = a - .getOwners() - .filter(u => !u.isGM) - .map(u => u.id) - if (users.includes(game.user.id)) { - let tokens = canvas.tokens.placeables.filter(t => t.actor == a) - if (tokens.length == 1) { - tokens[0].control({ releaseOthers: true }) // Foundry 'select' - GURPS.SetLastActor(a, tokens[0].document) - } else - GURPS.SetLastActor(a) - this.priv('Selecting ' + a.displayname) - return - } - } - } else if (!m[3]) { - if (!!GURPS.LastActor) { - let tokens = canvas.tokens.placeables.filter(t => t.actor == GURPS.LastActor) - if (tokens.length == 1) tokens[0].release() - } - GURPS.ClearLastActor(GURPS.LastActor) - this.priv(i18n('GURPS.chatClearingLastActor')) - } else { - let pat = makeRegexPatternFrom(m[3]) + help() { + return '/select <Actor name>' + } + matches(line) { + this.match = line.match(/^\/(select|sel) ?(\@self)?([^!]*)(!)?/) + return !!this.match + } + usagematches(line) { + return line.match(/^\?(select|sel)$/i) // can't match on /select since that is a valid command + } + usage() { + return i18n('GURPS.chatHelpSelect') + } + process(line) { + let m = this.match + if (!!m[2]) { + // @self + for (const a of game.actors.entities) { + let users = a + .getOwners() + .filter(u => !u.isGM) + .map(u => u.id) + if (users.includes(game.user.id)) { + let tokens = canvas.tokens.placeables.filter(t => t.actor == a) + if (tokens.length == 1) { + tokens[0].control({ releaseOthers: true }) // Foundry 'select' + GURPS.SetLastActor(a, tokens[0].document) + } else GURPS.SetLastActor(a) + this.priv('Selecting ' + a.displayname) + return + } + } + } else if (!m[3]) { + if (!!GURPS.LastActor) { + let tokens = canvas.tokens.placeables.filter(t => t.actor == GURPS.LastActor) + if (tokens.length == 1) tokens[0].release() + } + GURPS.ClearLastActor(GURPS.LastActor) + this.priv(i18n('GURPS.chatClearingLastActor')) + } else { + let pat = makeRegexPatternFrom(m[3]) - let list = - game.scenes.viewed?.data.tokens.map(t => { - // get the token's actor which might be a synthetic actor - if (t.actor) return t.actor - // otherwise, get the 'canonical' actor (non-synthetic) - return game.actors.get(t.actorId) - }) || [] + let list = + game.scenes.viewed?.data.tokens.map(t => { + // get the token's actor which might be a synthetic actor + if (t.actor) return t.actor + // otherwise, get the 'canonical' actor (non-synthetic) + return game.actors.get(t.actorId) + }) || [] - if (!!m[4]) list = game.actors.entities // ! means check all actors, not just ones on scene - let a = list.filter(a => a?.name?.match(pat)) - let msg = i18n('GURPS.chatMoreThanOneActor') + " '" + m[3] + "': " + a.map(e => e.name).join(', ') - if (a.length == 0 || a.length > 1) { - // No good match on actors, try token names - a = canvas.tokens.placeables.filter(t => t.name.match(pat)) - msg = i18n('GURPS.chatMoreThanOneToken') + " '" + m[3] + "': " + a.map(e => e.name).join(', ') - a = a.map(t => t.actor) - } - if (a.length == 0 || a.length > 1) { - // No good match on token names -- is this a token ID? - a = canvas.tokens.placeables.filter(t => t.id.match(pat)) - a = a.map(t => t.actor) - } - if (a.length == 0) ui.notifications.warn(i18n('GURPS.chatNoActorFound') + " '" + m[3] + "'") - else if (a.length > 1) ui.notifications.warn(msg) - else { - GURPS.SetLastActor(a[0]) - let tokens = canvas.tokens.placeables.filter(t => t.actor == a[0]) - if (tokens.length == 1) tokens[0].control({ releaseOthers: true }) // Foundry 'select' - this.priv('Selecting ' + a[0].displayname) - } - } - } + if (!!m[4]) list = game.actors.entities // ! means check all actors, not just ones on scene + let a = list.filter(a => a?.name?.match(pat)) + let msg = i18n('GURPS.chatMoreThanOneActor') + " '" + m[3] + "': " + a.map(e => e.name).join(', ') + if (a.length == 0 || a.length > 1) { + // No good match on actors, try token names + a = canvas.tokens.placeables.filter(t => t.name.match(pat)) + msg = i18n('GURPS.chatMoreThanOneToken') + " '" + m[3] + "': " + a.map(e => e.name).join(', ') + a = a.map(t => t.actor) + } + if (a.length == 0 || a.length > 1) { + // No good match on token names -- is this a token ID? + a = canvas.tokens.placeables.filter(t => t.id.match(pat)) + a = a.map(t => t.actor) + } + if (a.length == 0) ui.notifications.warn(i18n('GURPS.chatNoActorFound') + " '" + m[3] + "'") + else if (a.length > 1) ui.notifications.warn(msg) + else { + GURPS.SetLastActor(a[0]) + let tokens = canvas.tokens.placeables.filter(t => t.actor == a[0]) + if (tokens.length == 1) tokens[0].control({ releaseOthers: true }) // Foundry 'select' + this.priv('Selecting ' + a[0].displayname) + } + } + } } class RollChatProcessor extends ChatProcessor { - help() { - return '/roll (or /r) [On-the-Fly formula]
    /private (or /pr) [On-the-Fly formula] "Private"
    /sr [On-the-Fly formula] "Selected Roll"
    /psr [On-the-Fly formula] "Private Selected Roll"' - } - matches(line) { - this.match = line.match(/^(\/roll|\/r|\/private|\/pr|\/sr|\/psr) \[(.+)\] *[xX\*]?(\d+)?/) - return !!this.match - } - async process(line) { - let m = this.match - let action = parselink(m[2]) - if (!!action.action) { - if (action.action.type === 'modifier') - // only need to show modifiers, everything else does something. - this.priv(line) - else this.send() // send what we have + help() { + return '/roll (or /r) [On-the-Fly formula]
    /private (or /pr) [On-the-Fly formula] "Private"
    /sr [On-the-Fly formula] "Selected Roll"
    /psr [On-the-Fly formula] "Private Selected Roll"' + } + matches(line) { + this.match = line.match(/^(\/roll|\/r|\/private|\/pr|\/sr|\/psr) \[(.+)\] *[xX\*]?(\d+)?/) + return !!this.match + } + async process(line) { + let m = this.match + let action = parselink(m[2]) + if (!!action.action) { + if (action.action.type === 'modifier') + // only need to show modifiers, everything else does something. + this.priv(line) + else this.send() // send what we have - let actors = [GURPS.LastActor] - if (line.startsWith('/psr') || line.startsWith('/sr')) - actors = canvas.tokens?.controlled.map(t => t.actor) - let atLeastOne = false + let actors = [GURPS.LastActor] + if (line.startsWith('/psr') || line.startsWith('/sr')) actors = canvas.tokens?.controlled.map(t => t.actor) + let atLeastOne = false - let last = GURPS.LastActor - action.action.overridetxt = this.msgs().event?.data?.overridetxt - let ev = { - shiftKey: line.startsWith('/p') || action.action.blindroll, - ctrlKey: false, - data: { - repeat: m[3], - overridetxt: action.action.overridetxt, - private: line.startsWith('/p') - } - } - for (const actor of actors) { - GURPS.LastActor = actor - let result = await GURPS.performAction(action.action, actor, ev) - GURPS.LastActor = last - atLeastOne = atLeastOne || result - } - return atLeastOne - } // Looks like a /roll OtF, but didn't parse as one - else ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '[${m[2]}]'`) - return false - } + let last = GURPS.LastActor + action.action.overridetxt = this.msgs().event?.data?.overridetxt + let ev = { + shiftKey: line.startsWith('/p') || action.action.blindroll, + ctrlKey: false, + data: { + repeat: m[3], + overridetxt: action.action.overridetxt, + private: line.startsWith('/p'), + }, + } + for (const actor of actors) { + GURPS.LastActor = actor + let result = await GURPS.performAction(action.action, actor, ev) + GURPS.LastActor = last + atLeastOne = atLeastOne || result + } + return atLeastOne + } // Looks like a /roll OtF, but didn't parse as one + else ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '[${m[2]}]'`) + return false + } } class UsesChatProcessor extends ChatProcessor { - help() { - return '/uses <formula> <equipment name>' - } - matches(line) { - this.match = line.match(/^\/uses +([\+-=]\w+)?(reset)?(.*)/i) - return !!this.match - } - usagematches(line) { - return line.match(/^[\/\?]uses$/i) - } - usage() { - return i18n("GURPS.chatHelpUses") - } - async process(line) { - let answer = false - let m = this.match - let actor = GURPS.LastActor - if (!actor) ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - else { - var eqt, key - let m2 = m[3].trim().match(/^(o[\.:])?(.*)/i) - let pattern = m2[2].trim() - if (!!pattern) [eqt, key] = actor.findEquipmentByName(pattern, !!m2[1]) - else if (this.msgs().event?.currentTarget) { - pattern = '<current equipment>' - let t = this.msgs().event?.currentTarget - let k = $(t).closest('[data-key]').attr('data-key') - // if we find a data-key, then we assume that we are on the character sheet, and if the target - // is equipment, apply to that equipment. - if (!!k) { - key = k - eqt = getProperty(actor, key) - // if its not equipment, ignore. - if (eqt.count == null) eqt = null - } - } - if (!eqt) ui.notifications.warn(i18n('GURPS.chatNoEquipmentMatched') + " '" + pattern + "'") - else { - if (!m[1] && !m[2]) { // no +-= or reset - ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${line}'`) - } else { - eqt = duplicate(eqt) - let delta = parseInt(m[1]) - if (!!m[2]) { - this.prnt(`${eqt.name} ${i18n('GURPS.chatUsesReset')} (${eqt.maxuses})`) - eqt.uses = eqt.maxuses - await actor.update({ [key]: eqt }) - answer = true - } else if (isNaN(delta)) { - // only happens with '=' - delta = m[1].substr(1) - eqt.uses = delta - await actor.update({ [key]: eqt }) - this.prnt(`${eqt.name} ${i18n('GURPS.chatUsesSet')} ${delta}`) - answer = true - } else { - let q = parseInt(eqt.uses) + delta - let max = parseInt(eqt.maxuses) - if (isNaN(q)) ui.notifications.warn(eqt.name + ' ' + i18n('GURPS.chatUsesIsNaN')) - else if (q < 0) ui.notifications.warn(eqt.name + ' ' + i18n('GURPS.chatDoesNotHaveEnough')) - else if (!isNaN(max) && max > 0 && q > max) - ui.notifications.warn(`${i18n('GURPS.chatExceededMaxUses')} (${max}) ${i18n('GURPS.for')} ` + eqt.name) - else { - this.prnt(`${eqt.name} ${i18n('GURPS.chatUses')} ${m[1]} = ${q}`) - eqt.uses = q - await actor.update({ [key]: eqt }) - answer = true - } - } - } - } - } - return answer - } + help() { + return '/uses <formula> <equipment name>' + } + matches(line) { + this.match = line.match(/^\/uses +([\+-=]\w+)?(reset)?(.*)/i) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?]uses$/i) + } + usage() { + return i18n('GURPS.chatHelpUses') + } + async process(line) { + let answer = false + let m = this.match + let actor = GURPS.LastActor + if (!actor) ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + else { + var eqt, key + let m2 = m[3].trim().match(/^(o[\.:])?(.*)/i) + let pattern = m2[2].trim() + if (!!pattern) [eqt, key] = actor.findEquipmentByName(pattern, !!m2[1]) + else if (this.msgs().event?.currentTarget) { + pattern = '<current equipment>' + let t = this.msgs().event?.currentTarget + let k = $(t).closest('[data-key]').attr('data-key') + // if we find a data-key, then we assume that we are on the character sheet, and if the target + // is equipment, apply to that equipment. + if (!!k) { + key = k + eqt = getProperty(actor, key) + // if its not equipment, ignore. + if (eqt.count == null) eqt = null + } + } + if (!eqt) ui.notifications.warn(i18n('GURPS.chatNoEquipmentMatched') + " '" + pattern + "'") + else { + if (!m[1] && !m[2]) { + // no +-= or reset + ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${line}'`) + } else { + eqt = duplicate(eqt) + let delta = parseInt(m[1]) + if (!!m[2]) { + this.prnt(`${eqt.name} ${i18n('GURPS.chatUsesReset')} (${eqt.maxuses})`) + eqt.uses = eqt.maxuses + await actor.update({ [key]: eqt }) + answer = true + } else if (isNaN(delta)) { + // only happens with '=' + delta = m[1].substr(1) + eqt.uses = delta + await actor.update({ [key]: eqt }) + this.prnt(`${eqt.name} ${i18n('GURPS.chatUsesSet')} ${delta}`) + answer = true + } else { + let q = parseInt(eqt.uses) + delta + let max = parseInt(eqt.maxuses) + if (isNaN(q)) ui.notifications.warn(eqt.name + ' ' + i18n('GURPS.chatUsesIsNaN')) + else if (q < 0) ui.notifications.warn(eqt.name + ' ' + i18n('GURPS.chatDoesNotHaveEnough')) + else if (!isNaN(max) && max > 0 && q > max) + ui.notifications.warn(`${i18n('GURPS.chatExceededMaxUses')} (${max}) ${i18n('GURPS.for')} ` + eqt.name) + else { + this.prnt(`${eqt.name} ${i18n('GURPS.chatUses')} ${m[1]} = ${q}`) + eqt.uses = q + await actor.update({ [key]: eqt }) + answer = true + } + } + } + } + } + return answer + } } class QtyChatProcessor extends ChatProcessor { - help() { - return '/qty <formula> <equipment name>' - } - matches(line) { - this.match = line.match(/^\/qty +([\+-=] *\d+)(.*)/i) - return !!this.match - } - usagematches(line) { - return line.match(/^[\/\?]qty$/i) - } - usage() { - return i18n("GURPS.chatHelpQty") - } - async process(line) { - let answer = false - let m = this.match - let actor = GURPS.LastActor - if (!actor) ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - else { - var eqt, key - let m2 = m[2].trim().match(/^(o[\.:])?(.*)/i) - let pattern = m2[2].trim() - if (!!pattern) [eqt, key] = actor.findEquipmentByName(pattern, !!m2[1]) - else if (this.msgs().event?.currentTarget) { - pattern = '<current equipment>' - let t = this.msgs().event?.currentTarget - let k = $(t).closest('[data-key]').attr('data-key') - // if we find a data-key, then we assume that we are on the character sheet, and if the target - // is equipment, apply to that equipment. - if (!!k) { - key = k - eqt = getProperty(actor, key) - // if its not equipment, try to find equipment with that name - if (eqt.count == null) [eqt, key] = actor.findEquipmentByName((pattern = eqt.name), !!m2[1]) - } - } - if (!eqt) ui.notifications.warn(i18n('GURPS.chatNoEquipmentMatched') + " '" + pattern + "'") - else { - eqt = duplicate(eqt) - let delta = parseInt(m[1].replace(/ /g, '')) - if (isNaN(delta)) { - // only happens with '=' - delta = parseInt(m[1].substr(1).replace(/ /g, '')) - if (isNaN(delta)) ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${m[1]}'`) - else { - answer = true - await actor.updateEqtCount(key, delta) - this.prnt(`${eqt.name} ${i18n('GURPS.chatQtySetTo')} ${delta}`) - } - } else { - let q = parseInt(eqt.count) + delta - if (q < 0) ui.notifications.warn(i18n('GURPS.chatYouDoNotHaveEnough') + " '" + eqt.name + "'") - else { - answer = true - this.prnt(`${eqt.name} ${i18n('GURPS.chatQty')} ${m[1]}`) - await actor.updateEqtCount(key, q) - } - } - } - } - return answer - } + help() { + return '/qty <formula> <equipment name>' + } + matches(line) { + this.match = line.match(/^\/qty +([\+-=] *\d+)(.*)/i) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?]qty$/i) + } + usage() { + return i18n('GURPS.chatHelpQty') + } + async process(line) { + let answer = false + let m = this.match + let actor = GURPS.LastActor + if (!actor) ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + else { + var eqt, key + let m2 = m[2].trim().match(/^(o[\.:])?(.*)/i) + let pattern = m2[2].trim() + if (!!pattern) [eqt, key] = actor.findEquipmentByName(pattern, !!m2[1]) + else if (this.msgs().event?.currentTarget) { + pattern = '<current equipment>' + let t = this.msgs().event?.currentTarget + let k = $(t).closest('[data-key]').attr('data-key') + // if we find a data-key, then we assume that we are on the character sheet, and if the target + // is equipment, apply to that equipment. + if (!!k) { + key = k + eqt = getProperty(actor, key) + // if its not equipment, try to find equipment with that name + if (eqt.count == null) [eqt, key] = actor.findEquipmentByName((pattern = eqt.name), !!m2[1]) + } + } + if (!eqt) ui.notifications.warn(i18n('GURPS.chatNoEquipmentMatched') + " '" + pattern + "'") + else { + eqt = duplicate(eqt) + let delta = parseInt(m[1].replace(/ /g, '')) + if (isNaN(delta)) { + // only happens with '=' + delta = parseInt(m[1].substr(1).replace(/ /g, '')) + if (isNaN(delta)) ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat')} '${m[1]}'`) + else { + answer = true + await actor.updateEqtCount(key, delta) + this.prnt(`${eqt.name} ${i18n('GURPS.chatQtySetTo')} ${delta}`) + } + } else { + let q = parseInt(eqt.count) + delta + if (q < 0) ui.notifications.warn(i18n('GURPS.chatYouDoNotHaveEnough') + " '" + eqt.name + "'") + else { + answer = true + this.prnt(`${eqt.name} ${i18n('GURPS.chatQty')} ${m[1]}`) + await actor.updateEqtCount(key, q) + } + } + } + } + return answer + } } class LightChatProcessor extends ChatProcessor { - help() { - return '/li <dim dist> <bright dist> <angle> <anim>|off ' - } - matches(line) { - // Could be this: - // /^\/(light|li) *(?none|off)? *(?\d+)? *(?\d+)? *(?\d+)? *(?#[0-9a-fA-F]{6})? *(?\w+)? *(?\d+)? *(?\d+)?/i + help() { + return '/li <dim dist> <bright dist> <angle> <anim>|off ' + } + matches(line) { + // Could be this: + // /^\/(light|li) *(?none|off)? *(?\d+)? *(?\d+)? *(?\d+)? *(?#[0-9a-fA-F]{6})? *(?\w+)? *(?\d+)? *(?\d+)?/i - this.match = line.match( - // /^\/(light|li) *(none|off)? *(\d+)? *(\d+)? *(\d+)? *(#\w\w\w\w\w\w)? *(\w+)? *(\d+)? *(\d+)?/i - /^\/(light|li) +(?none|off)? *(?[\d\.]+)? *(?[\d\.]+)? *(?\d+)? *(?#[0-9a-fA-F]{6})? *(?[\d\.]+)? *(?\w+)? *(?\d+)? *(?\d+)?/i - ) - return !!this.match - } - usagematches(line) { - return line.match(/^[\/\?](light|li)$/i) - } - usage() { - return i18n("GURPS.chatHelpLight") - } - async process(line) { - if (canvas.tokens.controlled.length == 0) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return - } - if (line.match(/^\/(light|li) *$/)) { - this.priv('Possible animations: ' + Object.keys(CONFIG.Canvas.lightAnimations).join(', ')) - return - } - let type = this.match.groups.type || '' - if (!!type) { - let pat = new RegExp(makeRegexPatternFrom(type, false), 'i') - let m = Object.keys(CONFIG.Canvas.lightAnimations).find(k => k.match(pat)) - if (!m) { - ui.notifications.warn( - "Unknown light animation '" + type + "'. Expected: " + Object.keys(CONFIG.Canvas.lightAnimations).join(', ') - ) - return - } - type = m - } - let anim = { - type: type, - speed: parseFloat(this.match.groups.speed) || 1, - intensity: parseFloat(this.match.groups.intensity) || 1, - } - let data = { - light: { - dim: 0, - bright: 0, - angle: 360, - animation: anim, - } - } + this.match = line.match( + // /^\/(light|li) *(none|off)? *(\d+)? *(\d+)? *(\d+)? *(#\w\w\w\w\w\w)? *(\w+)? *(\d+)? *(\d+)?/i + /^\/(light|li) +(?none|off)? *(?[\d\.]+)? *(?[\d\.]+)? *(?\d+)? *(?#[0-9a-fA-F]{6})? *(?[\d\.]+)? *(?\w+)? *(?\d+)? *(?\d+)?/i + ) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?](light|li)$/i) + } + usage() { + return i18n('GURPS.chatHelpLight') + } + async process(line) { + if (canvas.tokens.controlled.length == 0) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return + } + if (line.match(/^\/(light|li) *$/)) { + this.priv('Possible animations: ' + Object.keys(CONFIG.Canvas.lightAnimations).join(', ')) + return + } + let type = this.match.groups.type || '' + if (!!type) { + let pat = new RegExp(makeRegexPatternFrom(type, false), 'i') + let m = Object.keys(CONFIG.Canvas.lightAnimations).find(k => k.match(pat)) + if (!m) { + ui.notifications.warn( + "Unknown light animation '" + type + "'. Expected: " + Object.keys(CONFIG.Canvas.lightAnimations).join(', ') + ) + return + } + type = m + } + let anim = { + type: type, + speed: parseFloat(this.match.groups.speed) || 1, + intensity: parseFloat(this.match.groups.intensity) || 1, + } + let data = { + light: { + dim: 0, + bright: 0, + angle: 360, + animation: anim, + }, + } - if (this.match.groups.off) { - data.light['-=color'] = null - } else { - if (this.match.groups.color) data.light.color = this.match.groups.color - if (this.match.groups.colorint) data.light.alpha = parseFloat(this.match.groups.colorint) - data.light.dim = parseFloat(this.match.groups.dim || 0) - data.light.bright = parseFloat(this.match.groups.bright || 0) - data.light.angle = parseFloat(this.match.groups.angle || 360) - } - console.log('Token Light update: ' + GURPS.objToString(data)) - for (const t of canvas.tokens.controlled) await t.document.update(data) - this.priv(line) - } + if (this.match.groups.off) { + data.light['-=color'] = null + } else { + if (this.match.groups.color) data.light.color = this.match.groups.color + if (this.match.groups.colorint) data.light.alpha = parseFloat(this.match.groups.colorint) + data.light.dim = parseFloat(this.match.groups.dim || 0) + data.light.bright = parseFloat(this.match.groups.bright || 0) + data.light.angle = parseFloat(this.match.groups.angle || 360) + } + console.log('Token Light update: ' + GURPS.objToString(data)) + for (const t of canvas.tokens.controlled) await t.document.update(data) + this.priv(line) + } } class ShowChatProcessor extends ChatProcessor { - isGMOnly() { - return true - } - - help() { - return '/show (/showa) <skills or attributes>' - } + isGMOnly() { + return true + } - matches(line) { - this.match = line.match(/^\/(show|sh) (.*)/i) - return !!this.match - } + help() { + return '/show (/showa) <skills or attributes>' + } - usagematches(line) { - return line.match(/^[\/\?](show|sh)$/i) - } - usage() { - return i18n("GURPS.chatHelpShow") - } + matches(line) { + this.match = line.match(/^\/(show|sh) (.*)/i) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?](show|sh)$/i) + } + usage() { + return i18n('GURPS.chatHelpShow') + } - async process(line) { - let args = splitArgs(this.match[2]) - this.priv(line) - let alpha = false - let pc = false - let npc = false - while (args[0].startsWith('-')) { - if (args[0] == '-a') alpha = true - if (args[0] == '-pc') pc = true - if (args[0] == '-npc') npc = true - args.shift() - } - for (const orig of args) { - this.priv('
    ') - if (orig.toLowerCase() == 'move') this.priv("Basic Move / Current Move") - if (orig.toLowerCase() == 'speed') this.priv("Basic Speed") - if (orig.toLowerCase() == 'hp') this.priv("Hit Points: Current / Max") - if (orig.toLowerCase() == 'fp') this.priv("Fatigue Points: Current / Max") - let output = [] - for (const token of canvas.tokens.placeables) { - let arg = orig - let actor = token.actor - let skip = (npc && actor.hasPlayerOwner) || (pc && !actor.hasPlayerOwner) - if (!skip) { - switch (orig.toLowerCase()) { - case 'hp': - output.push({ value: actor.system.HP.value, text: `${actor.name}: ${actor.system.HP.value} / ${actor.system.HP.max}`, name: actor.name }) - continue; - case 'fp': - output.push({ value: actor.system.FP.value, text: `${actor.name}: ${actor.system.FP.value} / ${actor.system.FP.max}`, name: actor.name }) - continue; - case 'move': - output.push({ value: actor.system.currentmove, text: `${actor.name}: ${actor.system.basicmove.value} / ${actor.system.currentmove}`, name: actor.name }) - continue; - case 'speed': - output.push({ value: actor.system.basicspeed.value, text: `${actor.name}: ${actor.system.basicspeed.value}`, name: actor.name }) - continue; - case 'fright': - arg = 'frightcheck' - break - } - if (!GURPS.PARSELINK_MAPPINGS[arg.toUpperCase()]) { - if (arg.includes(' ')) arg = '"' + arg + '"' - arg = 'S:' + arg - } - let action = parselink(arg) - if (!!action.action) { - action.action.calcOnly = true - let ret = await GURPS.performAction(action.action, actor) - if (!!ret.target) { - let lbl = `["${ret.thing} (${ret.target}) : ${actor.name}"!/sel ${token.id}\\\\/r [${arg}]]` - output.push({ value: ret.target, text: lbl, name: actor.name }) - //this.priv(lbl) - } - } - } - } - let sortfunc = (a, b) => { return a.value < b.value ? 1 : -1 } - if (alpha) sortfunc = (a, b) => { return a.name > b.name ? 1 : -1 } - output.sort(sortfunc).forEach(e => this.priv(e.text)) - } - } + async process(line) { + let args = splitArgs(this.match[2]) + this.priv(line) + let alpha = false + let pc = false + let npc = false + while (args[0].startsWith('-')) { + if (args[0] == '-a') alpha = true + if (args[0] == '-pc') pc = true + if (args[0] == '-npc') npc = true + args.shift() + } + for (const orig of args) { + this.priv('
    ') + if (orig.toLowerCase() == 'move') this.priv('Basic Move / Current Move') + if (orig.toLowerCase() == 'speed') this.priv('Basic Speed') + if (orig.toLowerCase() == 'hp') this.priv('Hit Points: Current / Max') + if (orig.toLowerCase() == 'fp') this.priv('Fatigue Points: Current / Max') + let output = [] + for (const token of canvas.tokens.placeables) { + let arg = orig + let actor = token.actor + let skip = (npc && actor.hasPlayerOwner) || (pc && !actor.hasPlayerOwner) + if (!skip) { + switch (orig.toLowerCase()) { + case 'hp': + output.push({ + value: actor.system.HP.value, + text: `${actor.name}: ${actor.system.HP.value} / ${actor.system.HP.max}`, + name: actor.name, + }) + continue + case 'fp': + output.push({ + value: actor.system.FP.value, + text: `${actor.name}: ${actor.system.FP.value} / ${actor.system.FP.max}`, + name: actor.name, + }) + continue + case 'move': + output.push({ + value: actor.system.currentmove, + text: `${actor.name}: ${actor.system.basicmove.value} / ${actor.system.currentmove}`, + name: actor.name, + }) + continue + case 'speed': + output.push({ + value: actor.system.basicspeed.value, + text: `${actor.name}: ${actor.system.basicspeed.value}`, + name: actor.name, + }) + continue + case 'fright': + arg = 'frightcheck' + break + } + if (!GURPS.PARSELINK_MAPPINGS[arg.toUpperCase()]) { + if (arg.includes(' ')) arg = '"' + arg + '"' + arg = 'S:' + arg + } + let action = parselink(arg) + if (!!action.action) { + action.action.calcOnly = true + let ret = await GURPS.performAction(action.action, actor) + if (!!ret.target) { + let lbl = `["${ret.thing} (${ret.target}) : ${actor.name}"!/sel ${token.id}\\\\/r [${arg}]]` + output.push({ value: ret.target, text: lbl, name: actor.name }) + //this.priv(lbl) + } + } + } + } + let sortfunc = (a, b) => { + return a.value < b.value ? 1 : -1 + } + if (alpha) + sortfunc = (a, b) => { + return a.name > b.name ? 1 : -1 + } + output.sort(sortfunc).forEach(e => this.priv(e.text)) + } + } } class DevChatProcessor extends ChatProcessor { - isGMOnly() { - return true - } + isGMOnly() { + return true + } - help() { - return null - } + help() { + return null + } - matches(line) { - this.match = line.match(/^\/dev +(.*)/i) - return !!this.match - } - async process(line) { - let m = this.match[1].match(/(\w+)(.*)/) - switch (m[1]) { - case 'open': { // Open the full character sheet for an Actor - let a = game.actors.getName(m[2].trim()) - if (a) a.openSheet('gurps.GurpsActorSheet') - else ui.notifications.warn("Can't find Actor named '" + m[2] + "'") - break - } - case 'clear': { // flush the chat log without confirming - game.messages.documentClass.deleteDocuments([], { deleteAll: true }) - break - } - } - } + matches(line) { + this.match = line.match(/^\/dev +(.*)/i) + return !!this.match + } + async process(line) { + let m = this.match[1].match(/(\w+)(.*)/) + switch (m[1]) { + case 'open': { + // Open the full character sheet for an Actor + let a = game.actors.getName(m[2].trim()) + if (a) a.openSheet('gurps.GurpsActorSheet') + else ui.notifications.warn("Can't find Actor named '" + m[2] + "'") + break + } + case 'clear': { + // flush the chat log without confirming + game.messages.documentClass.deleteDocuments([], { deleteAll: true }) + break + } + } + } } class ManeuverChatProcessor extends ChatProcessor { - help() { - return '/man <maneuver>' - } - - matches(line) { - this.match = line.match(/^\/(maneuver|man) *(.*)/i) - return !!this.match - } + help() { + return '/man <maneuver>' + } - async process(line) { - if (!this.match[2]) { - this.priv(i18n("GURPS.chatHelpManeuver")) - Object.values(Maneuvers.getAll()).map(e => i18n(e.data.label)).forEach(e => this.priv(e)) - return true - } - if (!game.combat) { - ui.notifications.warn(i18n("GURPS.chatNotInCombat")) - return false - } - let r = makeRegexPatternFrom(this.match[2].toLowerCase(), false) - let m = Object.values(Maneuvers.getAll()).find(e => i18n(e.data.label).toLowerCase().match(r)) - if (!GURPS.LastActor) { - ui.notifications.warn(i18n("GURPS.chatYouMustHaveACharacterSelected")) - return false - } - if (!m) { - ui.notifications.warn(i18n("GURPS.chatUnableToFindManeuver") + " '" + this.match[2] + "'") - return false - } - GURPS.LastActor.replaceManeuver(m._data.name) - return true - } + matches(line) { + this.match = line.match(/^\/(maneuver|man) *(.*)/i) + return !!this.match + } + async process(line) { + if (!this.match[2]) { + this.priv(i18n('GURPS.chatHelpManeuver')) + Object.values(Maneuvers.getAll()) + .map(e => i18n(e.data.label)) + .forEach(e => this.priv(e)) + return true + } + if (!game.combat) { + ui.notifications.warn(i18n('GURPS.chatNotInCombat')) + return false + } + let r = makeRegexPatternFrom(this.match[2].toLowerCase(), false) + let m = Object.values(Maneuvers.getAll()).find(e => i18n(e.data.label).toLowerCase().match(r)) + if (!GURPS.LastActor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + if (!m) { + ui.notifications.warn(i18n('GURPS.chatUnableToFindManeuver') + " '" + this.match[2] + "'") + return false + } + GURPS.LastActor.replaceManeuver(m._data.name) + return true + } } class RepeatChatProcessor extends ChatProcessor { - help() { - return '/repeat <anim command>' - } + help() { + return '/repeat <anim command>' + } - matches(line) { - this.match = line.match(/^\/(repeat|rpt) +([\d\.]+) *(.*)/i) - return !!this.match - } + matches(line) { + this.match = line.match(/^\/(repeat|rpt) +([\d\.]+) *(.*)/i) + return !!this.match + } - usagematches(line) { - return line.match(/^\/(repeat|rpt)/i) - } - usage() { - return i18n("GURPS.chatHelpRepeat") - } + usagematches(line) { + return line.match(/^\/(repeat|rpt)/i) + } + usage() { + return i18n('GURPS.chatHelpRepeat') + } - async process(line) { - if (!GURPS.LastActor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - this.repeatLoop(GURPS.LastActor, this.match[3].trim(), this.match[2]) // We are purposefully NOT waiting for this method, so that it can continue in the background - } - async repeatLoop(actor, anim, delay) { - if (delay < 20) delay = delay * 1000 - const t = canvas.tokens.placeables.find(e => e.actor == actor) - if (!t) { - ui.notifications.warn("/repeat only works on 'linked' actors, " + actor.name) - return false - } - actor.RepeatAnimation = true - while (actor.RepeatAnimation) { - let p = { - x: t.position.x + t.w / 2, - y: t.position.y + t.h / 2 - } - let s = anim + ' @' + p.x + ',' + p.y - await GURPS.executeOTF(s) - await GURPS.wait(delay) - } - ui.notifications.info('Stopped annimation for ' + actor.name) - } + async process(line) { + if (!GURPS.LastActor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + this.repeatLoop(GURPS.LastActor, this.match[3].trim(), this.match[2]) // We are purposefully NOT waiting for this method, so that it can continue in the background + } + async repeatLoop(actor, anim, delay) { + if (delay < 20) delay = delay * 1000 + const t = canvas.tokens.placeables.find(e => e.actor == actor) + if (!t) { + ui.notifications.warn("/repeat only works on 'linked' actors, " + actor.name) + return false + } + actor.RepeatAnimation = true + while (actor.RepeatAnimation) { + let p = { + x: t.position.x + t.w / 2, + y: t.position.y + t.h / 2, + } + let s = anim + ' @' + p.x + ',' + p.y + await GURPS.executeOTF(s) + await GURPS.wait(delay) + } + ui.notifications.info('Stopped annimation for ' + actor.name) + } } class StopChatProcessor extends ChatProcessor { - help() { - return '/stop' - } + help() { + return '/stop' + } - matches(line) { - this.match = line.match(/^\/stop/i) - return !!this.match - } + matches(line) { + this.match = line.match(/^\/stop/i) + return !!this.match + } - async process(line) { - if (!GURPS.LastActor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - GURPS.LastActor.RepeatAnimation = false - } + async process(line) { + if (!GURPS.LastActor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + GURPS.LastActor.RepeatAnimation = false + } } diff --git a/module/chat/everything.js b/module/chat/everything.js index 7e3ad42fe..ebde8de13 100644 --- a/module/chat/everything.js +++ b/module/chat/everything.js @@ -5,198 +5,198 @@ import { parselink } from '../../lib/parselink.js' import { isNiceDiceEnabled, splitArgs, i18n } from '../../lib/utilities.js' export class EveryoneAChatProcessor extends ChatProcessor { - help() { - return '/everyone (or /ev) <formula>' - } - isGMOnly() { - return true - } + help() { + return '/everyone (or /ev) <formula>' + } + isGMOnly() { + return true + } - matches(line) { - this.match = line.match(/^\/(everyone|ev) ([fh]p) reset/i) - return !!this.match - } - usagematches(line) { - return line.match(/^[\/\?](everyone|ev)$/i) - } - usage() { - return i18n("GURPS.chatHelpEveryone"); - } + matches(line) { + this.match = line.match(/^\/(everyone|ev) ([fh]p) reset/i) + return !!this.match + } + usagematches(line) { + return line.match(/^[\/\?](everyone|ev)$/i) + } + usage() { + return i18n('GURPS.chatHelpEveryone') + } - async process(line) { - let m = this.match - let any = false - for (const t of canvas.tokens.ownedTokens) { - let actor = t.actor - if (actor.hasPlayerOwner) { - any = true - let attr = m[2].toUpperCase() - let max = actor.system[attr].max - await actor.update({ ['data.' + attr + '.value']: max }) - this.priv(`${actor.displayname} ${attr} reset to ${max}`) - } - } - if (!any) this.priv(`There are no player owned characters!`) - } + async process(line) { + let m = this.match + let any = false + for (const t of canvas.tokens.ownedTokens) { + let actor = t.actor + if (actor.hasPlayerOwner) { + any = true + let attr = m[2].toUpperCase() + let max = actor.system[attr].max + await actor.update({ ['data.' + attr + '.value']: max }) + this.priv(`${actor.displayname} ${attr} reset to ${max}`) + } + } + if (!any) this.priv(`There are no player owned characters!`) + } } export class EveryoneBChatProcessor extends ChatProcessor { - help() { - return null - } // Don't display a help line for this processor. Useful if you have multiple processors for essentially the same command - isGMOnly() { - return true - } + help() { + return null + } // Don't display a help line for this processor. Useful if you have multiple processors for essentially the same command + isGMOnly() { + return true + } - matches(line) { - this.match = line.match(/^\/(everyone|ev) \[(.*)\]/i) - return !!this.match - } + matches(line) { + this.match = line.match(/^\/(everyone|ev) \[(.*)\]/i) + return !!this.match + } - async process(line) { - let m = this.match - let any = false - let action = parselink(m[2].trim()) - if (!!action.action) { - if (!['modifier', 'chat', 'pdf'].includes(action.action.type)) { - for (const t of canvas.tokens.ownedTokens) { - let actor = t.actor - if (actor.hasPlayerOwner) { - any = true - let event = { data: {} } - event.blind = this.msgs().quiet - await GURPS.performAction(action.action, actor, event) - } - } - if (!any) this.priv(`There are no player owned characters!`) - } else this.priv(`Not allowed to execute Modifier, Chat or PDF links [${m[2].trim()}]`) - } else this.priv(`Unable to parse On-the-Fly formula: [${m[2].trim()}]`) - } + async process(line) { + let m = this.match + let any = false + let action = parselink(m[2].trim()) + if (!!action.action) { + if (!['modifier', 'chat', 'pdf'].includes(action.action.type)) { + for (const t of canvas.tokens.ownedTokens) { + let actor = t.actor + if (actor.hasPlayerOwner) { + any = true + let event = { data: {} } + event.blind = this.msgs().quiet + await GURPS.performAction(action.action, actor, event) + } + } + if (!any) this.priv(`There are no player owned characters!`) + } else this.priv(`Not allowed to execute Modifier, Chat or PDF links [${m[2].trim()}]`) + } else this.priv(`Unable to parse On-the-Fly formula: [${m[2].trim()}]`) + } } export class EveryoneCChatProcessor extends ChatProcessor { - help() { - return null - } // Don't display a help line for this processor. Useful if you have multiple processors for essentially the same command - isGMOnly() { - return true - } + help() { + return null + } // Don't display a help line for this processor. Useful if you have multiple processors for essentially the same command + isGMOnly() { + return true + } - matches(line) { - // /everyone +1 fp or /everyone -2d-1 fp - this.match = line.match(/^\/(everyone|ev) ([fh]p) *([+-]\d+d\d*)?([+-=]\d+)?(!)?/i) - return !!this.match - } + matches(line) { + // /everyone +1 fp or /everyone -2d-1 fp + this.match = line.match(/^\/(everyone|ev) ([fh]p) *([+-]\d+d\d*)?([+-=]\d+)?(!)?/i) + return !!this.match + } - async process(line) { - let m = this.match - if (!!m[3] || !!m[4]) { - let any = false - for (const t of canvas.tokens.ownedTokens) { - let actor = t.actor - if (actor.hasPlayerOwner) { - any = true - let mod = m[4] || '' - let value = mod - let dice = m[3] || '' - let txt = '' - let attr = m[2].toUpperCase() + async process(line) { + let m = this.match + if (!!m[3] || !!m[4]) { + let any = false + for (const t of canvas.tokens.ownedTokens) { + let actor = t.actor + if (actor.hasPlayerOwner) { + any = true + let mod = m[4] || '' + let value = mod + let dice = m[3] || '' + let txt = '' + let attr = m[2].toUpperCase() - if (!!dice) { - let sign = dice[0] == '-' ? -1 : 1 - let d = dice.match(/[+-](\d+)d(\d*)/) - let r = d[1] + 'd' + (!!d[2] ? d[2] : '6') + `[/ev ${attr}]` - let roll = Roll.create(r) - await roll.evaluate({ async: true }) - if (isNiceDiceEnabled()) { - let throws = [] - let dc = [] - roll.dice.forEach(die => { - let type = 'd' + die.faces - die.results.forEach(s => - dc.push({ - result: s.result, - resultLabel: s.result, - type: type, - vectors: [], - options: {}, - }) - ) - }) - throws.push({ dice: dc }) - if (dc.length > 0) { - // The user made a "multi-damage" roll... let them see the dice! - // @ts-ignore - game.dice3d.show({ throws: throws }) - } - } - value = roll.total - if (!!mod) - if (isNaN(mod)) { - ui.notifications.warn(`Unrecognized format for '${line}'`) - return - } else value += parseInt(mod) - value = Math.max(value, !!m[5] ? 1 : 0) - value *= sign - txt = `(${value}) ` - } - let cur = actor.system[attr].value - let newval = parseInt(value) - if (isNaN(newval)) { - // only happens on =10 - newval = parseInt(value.substr(1)) - if (isNaN(newval)) { - ui.notifications.warn(`Unrecognized format for '${line}'`) - return - } - } else newval += cur - let mtxt = '' - let max = actor.system[attr].max - if (newval > max) { - newval = max - mtxt = `(max: ${max})` - } - await actor.update({ ['data.' + attr + '.value']: newval }) - this.priv(`${actor.displayname} ${attr} ${dice}${mod} ${txt}${mtxt}`) - } - } - if (!any) this.priv(`There are no player owned characters!`) - } // Didn't provide dice or scalar, so maybe someone else wants to handle it - else ui.notifications.warn(`There was no dice or number formula to apply '${line}'`) - } + if (!!dice) { + let sign = dice[0] == '-' ? -1 : 1 + let d = dice.match(/[+-](\d+)d(\d*)/) + let r = d[1] + 'd' + (!!d[2] ? d[2] : '6') + `[/ev ${attr}]` + let roll = Roll.create(r) + await roll.evaluate({ async: true }) + if (isNiceDiceEnabled()) { + let throws = [] + let dc = [] + roll.dice.forEach(die => { + let type = 'd' + die.faces + die.results.forEach(s => + dc.push({ + result: s.result, + resultLabel: s.result, + type: type, + vectors: [], + options: {}, + }) + ) + }) + throws.push({ dice: dc }) + if (dc.length > 0) { + // The user made a "multi-damage" roll... let them see the dice! + // @ts-ignore + game.dice3d.show({ throws: throws }) + } + } + value = roll.total + if (!!mod) + if (isNaN(mod)) { + ui.notifications.warn(`Unrecognized format for '${line}'`) + return + } else value += parseInt(mod) + value = Math.max(value, !!m[5] ? 1 : 0) + value *= sign + txt = `(${value}) ` + } + let cur = actor.system[attr].value + let newval = parseInt(value) + if (isNaN(newval)) { + // only happens on =10 + newval = parseInt(value.substr(1)) + if (isNaN(newval)) { + ui.notifications.warn(`Unrecognized format for '${line}'`) + return + } + } else newval += cur + let mtxt = '' + let max = actor.system[attr].max + if (newval > max) { + newval = max + mtxt = `(max: ${max})` + } + await actor.update({ ['data.' + attr + '.value']: newval }) + this.priv(`${actor.displayname} ${attr} ${dice}${mod} ${txt}${mtxt}`) + } + } + if (!any) this.priv(`There are no player owned characters!`) + } // Didn't provide dice or scalar, so maybe someone else wants to handle it + else ui.notifications.warn(`There was no dice or number formula to apply '${line}'`) + } } export class RemoteChatProcessor extends ChatProcessor { - help() { - return '/remote [OtF] <user list>' - } - isGMOnly() { - return true - } + help() { + return '/remote [OtF] <user list>' + } + isGMOnly() { + return true + } - matches(line) { - this.match = line.match(/^\/(remote|rem) \[(.*)\](.*)/i) - return !!this.match - } + matches(line) { + this.match = line.match(/^\/(remote|rem) \[(.*)\](.*)/i) + return !!this.match + } - usagematches(line) { - return line.match(/^[\/\?](remote|rem)$/i) - } - usage() { - return i18n("GURPS.chatHelpRemote"); - } + usagematches(line) { + return line.match(/^[\/\?](remote|rem)$/i) + } + usage() { + return i18n('GURPS.chatHelpRemote') + } - async process(line) { - let m = this.match - let action = parselink(m[2].trim()) - if (!!action.action) { - let users = !!m[3] ? splitArgs(m[3]) : [] // empty array means everyone - this.priv(line) - game.socket.emit('system.gurps', { - type: 'executeOtF', - action: action.action, - users: users, - }) - } else this.priv(`Unable to parse On-the-Fly formula: [${m[2].trim()}]`) - } + async process(line) { + let m = this.match + let action = parselink(m[2].trim()) + if (!!action.action) { + let users = !!m[3] ? splitArgs(m[3]) : [] // empty array means everyone + this.priv(line) + game.socket.emit('system.gurps', { + type: 'executeOtF', + action: action.action, + users: users, + }) + } else this.priv(`Unable to parse On-the-Fly formula: [${m[2].trim()}]`) + } } diff --git a/module/chat/frightcheck.js b/module/chat/frightcheck.js index d79f009a7..c02b5eded 100644 --- a/module/chat/frightcheck.js +++ b/module/chat/frightcheck.js @@ -5,157 +5,157 @@ import ChatProcessor from './chat-processor.js' import * as Settings from '../../lib/miscellaneous-settings.js' export class FrightCheckChatProcessor extends ChatProcessor { - help() { - return '/frightcheck (or /fc)' - } - isGMOnly() { - return true - } + help() { + return '/frightcheck (or /fc)' + } + isGMOnly() { + return true + } - matches(line) { - this.match = line.match(/^\/(fc|frightcheck)/) - return !!this.match - } - async process(line) { - if (!GURPS.LastActor) { - ui.notifications.error('Please select a token/character.') - return - } - let actor = GURPS.LastActor - let tblname = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_FRIGHT_CHECK_TABLE) || 'Fright Check' + matches(line) { + this.match = line.match(/^\/(fc|frightcheck)/) + return !!this.match + } + async process(line) { + if (!GURPS.LastActor) { + ui.notifications.error('Please select a token/character.') + return + } + let actor = GURPS.LastActor + let tblname = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_FRIGHT_CHECK_TABLE) || 'Fright Check' - // TODO reskin frightcheck UI - renderTemplate('systems/gurps/templates/frightcheck-macro.html', { tblname: tblname }).then(dialogTemplate => - new Dialog( - { - title: 'Fright Check', - content: dialogTemplate, - buttons: { - rollFrightCheck: { - label: 'Roll Fright Check', - callback: this.rollFrightCheckCallback.bind(this, actor), - }, - close: { - label: 'Close', - }, - }, - }, - { width: 650 }, - ).render(true), - ) - } + // TODO reskin frightcheck UI + renderTemplate('systems/gurps/templates/frightcheck-macro.html', { tblname: tblname }).then(dialogTemplate => + new Dialog( + { + title: 'Fright Check', + content: dialogTemplate, + buttons: { + rollFrightCheck: { + label: 'Roll Fright Check', + callback: this.rollFrightCheckCallback.bind(this, actor), + }, + close: { + label: 'Close', + }, + }, + }, + { width: 650 } + ).render(true) + ) + } - /** - * - * @param {*} html - */ - async rollFrightCheckCallback(actor, html) { - // { mod: -1, desc: 'Fearfulness 1' } - let selectIds = [ - '#mod2', - '#mod3', - '#mod4', - '#mod5', - '#check1', - '#check2', - '#bodies1', - '#bodies2', - '#monster1', - '#monster2', - '#check4a', - '#check4b', - '#check4', - '#check5', - '#check6', - '#check7', - '#check3', - '#check10', - '#check8', - '#check9', - '#mod1', - ] - let targetmods = [] - for (const id of selectIds) { - //console.log(id) - targetmods.push(this._getMod(html, id)) - } - targetmods = targetmods.filter(it => it != null) + /** + * + * @param {*} html + */ + async rollFrightCheckCallback(actor, html) { + // { mod: -1, desc: 'Fearfulness 1' } + let selectIds = [ + '#mod2', + '#mod3', + '#mod4', + '#mod5', + '#check1', + '#check2', + '#bodies1', + '#bodies2', + '#monster1', + '#monster2', + '#check4a', + '#check4b', + '#check4', + '#check5', + '#check6', + '#check7', + '#check3', + '#check10', + '#check8', + '#check9', + '#mod1', + ] + let targetmods = [] + for (const id of selectIds) { + //console.log(id) + targetmods.push(this._getMod(html, id)) + } + targetmods = targetmods.filter(it => it != null) - let totalMod = targetmods.map(it => it.mod).reduce((a, b) => a + b, 0) - let WILLVar = parseInt(actor.system.frightcheck || actor.system.attributes.WILL.value, 10) - let finaltarget = totalMod + WILLVar + let totalMod = targetmods.map(it => it.mod).reduce((a, b) => a + b, 0) + let WILLVar = parseInt(actor.system.frightcheck || actor.system.attributes.WILL.value, 10) + let finaltarget = totalMod + WILLVar - // true or false? - let ruleOf14 = finaltarget > 13 - finaltarget = ruleOf14 ? 13 : finaltarget + // true or false? + let ruleOf14 = finaltarget > 13 + finaltarget = ruleOf14 ? 13 : finaltarget - let tblname = html.find('#tblname')[0].value - game.settings.set(Settings.SYSTEM_NAME, Settings.SETTING_FRIGHT_CHECK_TABLE, tblname) + let tblname = html.find('#tblname')[0].value + game.settings.set(Settings.SYSTEM_NAME, Settings.SETTING_FRIGHT_CHECK_TABLE, tblname) - let roll = Roll.create('3d6[Fright Check]') - await roll.evaluate({ async: true }) + let roll = Roll.create('3d6[Fright Check]') + await roll.evaluate({ async: true }) - let margin = finaltarget - roll.total - let failure = margin < 0 - let table = this._findFrightCheckTable(tblname) + let margin = finaltarget - roll.total + let failure = margin < 0 + let table = this._findFrightCheckTable(tblname) - let content = await renderTemplate('systems/gurps/templates/frightcheck-results.hbs', { - WILLVar: WILLVar, - targetmods: targetmods, - finaltarget: finaltarget, - ruleOf14: ruleOf14, - rtotal: roll.total, - failure: failure, - margin: finaltarget - roll.total, - loaded: roll.isLoaded, - rolls: roll.dice[0].results.map(it => it.result).join(), - }) + let content = await renderTemplate('systems/gurps/templates/frightcheck-results.hbs', { + WILLVar: WILLVar, + targetmods: targetmods, + finaltarget: finaltarget, + ruleOf14: ruleOf14, + rtotal: roll.total, + failure: failure, + margin: finaltarget - roll.total, + loaded: roll.isLoaded, + rolls: roll.dice[0].results.map(it => it.result).join(), + }) - await ChatMessage.create({ - type: CONST.CHAT_MESSAGE_TYPES.ROLL, - speaker: ChatMessage.getSpeaker(actor), - content: content, - roll: JSON.stringify(roll), - rollMode: game.settings.get('core', 'rollMode'), - }).then(async (html) => { - GURPS.setLastTargetedRoll({ margin: -margin }, actor) - if (failure) { - // Draw results using a custom roll formula. use the negated margin for the rolltable only - let tableRoll = Roll.create(`3d6[Fright Check table roll] + @margin`) - table.draw({ roll: tableRoll }).then(() => GURPS.setLastTargetedRoll({ margin: margin }, actor)) // don't evaluate before passing - } - }) - } + await ChatMessage.create({ + type: CONST.CHAT_MESSAGE_TYPES.ROLL, + speaker: ChatMessage.getSpeaker(actor), + content: content, + roll: JSON.stringify(roll), + rollMode: game.settings.get('core', 'rollMode'), + }).then(async html => { + GURPS.setLastTargetedRoll({ margin: -margin }, actor) + if (failure) { + // Draw results using a custom roll formula. use the negated margin for the rolltable only + let tableRoll = Roll.create(`3d6[Fright Check table roll] + @margin`) + table.draw({ roll: tableRoll }).then(() => GURPS.setLastTargetedRoll({ margin: margin }, actor)) // don't evaluate before passing + } + }) + } - _getMod(html, id) { - let mod = html.find(id)[0] - if (mod.type === 'select' || mod.type === 'select-one') { - if (parseInt(mod.value, 10) === 0) return null - return { mod: parseInt(mod.value, 10), desc: mod.options[mod.selectedIndex].text } - } else if (mod.type === 'checkbox') { - if ($(mod).prop('checked')) { - let label = html.find(`label[for="${mod.id}"]`)[0].innerText - return { mod: parseInt(mod.value, 10), desc: label } - } - } else if (mod.type === 'number') { - if (parseInt(mod.value, 10) === 0) return null - let label = html.find(`label[for="${mod.id}"]`)[0].innerText - return { mod: parseInt(mod.value, 10), desc: label } - } + _getMod(html, id) { + let mod = html.find(id)[0] + if (mod.type === 'select' || mod.type === 'select-one') { + if (parseInt(mod.value, 10) === 0) return null + return { mod: parseInt(mod.value, 10), desc: mod.options[mod.selectedIndex].text } + } else if (mod.type === 'checkbox') { + if ($(mod).prop('checked')) { + let label = html.find(`label[for="${mod.id}"]`)[0].innerText + return { mod: parseInt(mod.value, 10), desc: label } + } + } else if (mod.type === 'number') { + if (parseInt(mod.value, 10) === 0) return null + let label = html.find(`label[for="${mod.id}"]`)[0].innerText + return { mod: parseInt(mod.value, 10), desc: label } + } - return null - } + return null + } - _findFrightCheckTable(tblname) { - let pat = new RegExp(makeRegexPatternFrom(tblname, false), 'i') - let tables = game.tables.contents.filter(t => t.name.match(pat)) - if (tables.length == 0) { - ui.notifications.error("No table found for '" + tblname + "'") - } else if (tables.length > 1) { - ui.notifications.error("More than one table matched '" + tblname + "'") - } else { - return tables[0] - } - return null - } + _findFrightCheckTable(tblname) { + let pat = new RegExp(makeRegexPatternFrom(tblname, false), 'i') + let tables = game.tables.contents.filter(t => t.name.match(pat)) + if (tables.length == 0) { + ui.notifications.error("No table found for '" + tblname + "'") + } else if (tables.length > 1) { + ui.notifications.error("More than one table matched '" + tblname + "'") + } else { + return tables[0] + } + return null + } } diff --git a/module/chat/if.js b/module/chat/if.js index f7bf7619d..2d66a927d 100644 --- a/module/chat/if.js +++ b/module/chat/if.js @@ -8,9 +8,10 @@ export class IfChatProcessor extends ChatProcessor { help() { return '/if [OtF] [thenOTF] /else [elseOTF]
    /if [OtF] {thenChatCmd} {elseChatCmd}' } - matches(line) { // Since this can get called recursively, we cannot use an instance variable to save the match status + matches(line) { + // Since this can get called recursively, we cannot use an instance variable to save the match status let m = line.match(/^\/if (! *)?\[([^\]]+)\] (.*)/) - return m + return m } async _handleResult(then) { @@ -24,8 +25,7 @@ export class IfChatProcessor extends ChatProcessor { else this.send() // send what we have await GURPS.performAction(action.action, GURPS.LastActor, this.msgs().event) } - } else - await this.registry.processLines(then) + } else await this.registry.processLines(then) } async process(line) { @@ -34,16 +34,16 @@ export class IfChatProcessor extends ChatProcessor { const otf = m[2] const restOfLine = m[3].trim() const results = { - s: restOfLine // assume what is left if the success result. - } // s, f, cs, cf - if (restOfLine.match(/{.*}/)) { // using the advanced sytax - m = XRegExp.matchRecursive(restOfLine, '{', '}', 'g', { valueNames:['between', null, 'match', null] }); - let needSuccess = true // if we don't get a prefix, assume it is s:{} 'success' + s: restOfLine, // assume what is left if the success result. + } // s, f, cs, cf + if (restOfLine.match(/{.*}/)) { + // using the advanced sytax + m = XRegExp.matchRecursive(restOfLine, '{', '}', 'g', { valueNames: ['between', null, 'match', null] }) + let needSuccess = true // if we don't get a prefix, assume it is s:{} 'success' var next, key - while (next = m.shift()) { + while ((next = m.shift())) { let v = next.value.trim() - if (next.name == 'between' && v.endsWith(':')) - key = v.slice(0, -1) + if (next.name == 'between' && v.endsWith(':')) key = v.slice(0, -1) if (!key || !key.trim()) key = needSuccess ? 's' : 'f' if (key == 's') needSuccess = false if (next.name == 'match') { @@ -58,7 +58,11 @@ export class IfChatProcessor extends ChatProcessor { } let action = parselink(otf) if (!!action.action) { - if (['skill-spell', 'attribute', 'attack', 'controlroll', 'chat', 'test-exists', 'iftest'].includes(action.action.type)) { + if ( + ['skill-spell', 'attribute', 'attack', 'controlroll', 'chat', 'test-exists', 'iftest'].includes( + action.action.type + ) + ) { this.priv(line) this.send() let event = this.msgs().event @@ -68,13 +72,10 @@ export class IfChatProcessor extends ChatProcessor { if (pass) { if (!!results.cs && GURPS.lastTargetedRoll?.isCritSuccess) { await this._handleResult(results.cs) - } - else if (!!results.s) await this._handleResult(results.s) - } else - if (!!results.cf && GURPS.lastTargetedRoll?.isCritFailure) { - await this._handleResult(results.cf) - } - else if (!!results.f) await this._handleResult(results.f) + } else if (!!results.s) await this._handleResult(results.s) + } else if (!!results.cf && GURPS.lastTargetedRoll?.isCritFailure) { + await this._handleResult(results.cf) + } else if (!!results.f) await this._handleResult(results.f) } else this.priv(`${i18n('GURPS.chatMustBeACheck')}: [${otf}]`) } else this.priv(`${i18n('GURPS.chatUnrecognizedFormat', 'Unrecognized format')}: [${otf}]`) } diff --git a/module/chat/slam.js b/module/chat/slam.js index 7d0a2091e..25186cc8e 100644 --- a/module/chat/slam.js +++ b/module/chat/slam.js @@ -9,146 +9,146 @@ import { SlamCalculator } from './slam-calc.js' * Handle the '/slam' command. Must have a selected actor. */ export default class SlamChatProcessor extends ChatProcessor { - static initialize() { - ChatProcessors.registerProcessor(new SlamChatProcessor()) - } + static initialize() { + ChatProcessors.registerProcessor(new SlamChatProcessor()) + } - constructor() { - super() - } + constructor() { + super() + } - help() { - return '/slam' - } + help() { + return '/slam' + } - matches(line) { - return line.startsWith('/slam') - } + matches(line) { + return line.startsWith('/slam') + } - async process(line) { - let actor = GURPS.LastActor - if (!actor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return - } + async process(line) { + let actor = GURPS.LastActor + if (!actor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return + } - // see if there are any targets - let targets = actor.getOwners().flatMap(u => [...u.targets]) + // see if there are any targets + let targets = actor.getOwners().flatMap(u => [...u.targets]) - // try to find the attacker's token - let attacker = actor.token || canvas.tokens.placeables.find(it => it.actor === actor) + // try to find the attacker's token + let attacker = actor.token || canvas.tokens.placeables.find(it => it.actor === actor) - if (targets.length === 1) SlamCalculatorForm.process(attacker, [...targets][0]) - else if (targets.length > 1) selectTarget().then(target => SlamCalculatorForm.process(attacker, target)) - else SlamCalculatorForm.process(attacker, null) + if (targets.length === 1) SlamCalculatorForm.process(attacker, [...targets][0]) + else if (targets.length > 1) selectTarget().then(target => SlamCalculatorForm.process(attacker, target)) + else SlamCalculatorForm.process(attacker, null) - this.privateMessage('Opening Slam Calculator') - } + this.privateMessage('Opening Slam Calculator') + } - privateMessage(text) { - this.priv(text) - } + privateMessage(text) { + this.priv(text) + } } class SlamCalculatorForm extends FormApplication { - static process(actor, target) { - let calc = new SlamCalculatorForm(actor, target) - calc.render(true) - } - - /** - * Construct the dialog. - * @param {Token} attacker - * @param {Token} target - * @param {*} options - */ - constructor(attacker, target, options = {}) { - super(options) - - this._attacker = attacker - this._target = target - - this._calculator = new SlamCalculator() - - this._attackerHp = !!attacker ? attacker.actor.system.HP.max : 10 - this._attackerSpeed = !!attacker ? parseInt(attacker.actor.system.basicmove.value) : 5 - - this._targetHp = !!target ? target.actor.system.HP.max : 10 - this._targetSpeed = 0 - - this._isAoAStrong = false - this._shieldDB = 0 - } - - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - template: 'systems/gurps/templates/slam-calculator.html', - popOut: true, - minimizable: false, - jQuery: true, - resizable: false, - title: game.i18n.localize('GURPS.slamCalculator'), - }) - } - - getData(options) { - const data = super.getData(options) - - data.attackerToken = this._attacker - data.targetToken = this._target || { name: i18n('GURPS.target') } - data.isRealTarget = !!this._target - - data.attackerHp = this._attackerHp - data.attackerSpeed = this._attackerSpeed - - data.targetHp = this._targetHp - data.targetSpeed = this._targetSpeed - - data.relativeSpeed = this.relativeSpeed - - data.isAoAStrong = this._isAoAStrong - data.shieldDB = this._shieldDB - - return data - } - - get relativeSpeed() { - return this._attackerSpeed + this._targetSpeed - } - - _updateObject(event) { - this._calculator.process(this.getData()) - } - - /** @override */ - activateListeners(html) { - super.activateListeners(html) - - html.find('#aoa').click(ev => { - this._isAoAStrong = $(ev.currentTarget).is(':checked') - }) - - html.find('#db').change(ev => { - this._shieldDB = parseInt(ev.currentTarget.value) - }) - - html.find('#attacker-speed').change(ev => { - this._attackerSpeed = parseInt(ev.currentTarget.value) - this.render(false) - }) - - html.find('#target-speed').change(ev => { - this._targetSpeed = parseInt(ev.currentTarget.value) - this.render(false) - }) - - html.find('#attacker-hp').change(ev => { - this._attackerHp = parseInt(ev.currentTarget.value) - }) - - html.find('#target-hp').change(ev => { - this._targetHp = parseInt(ev.currentTarget.value) - }) - } + static process(actor, target) { + let calc = new SlamCalculatorForm(actor, target) + calc.render(true) + } + + /** + * Construct the dialog. + * @param {Token} attacker + * @param {Token} target + * @param {*} options + */ + constructor(attacker, target, options = {}) { + super(options) + + this._attacker = attacker + this._target = target + + this._calculator = new SlamCalculator() + + this._attackerHp = !!attacker ? attacker.actor.system.HP.max : 10 + this._attackerSpeed = !!attacker ? parseInt(attacker.actor.system.basicmove.value) : 5 + + this._targetHp = !!target ? target.actor.system.HP.max : 10 + this._targetSpeed = 0 + + this._isAoAStrong = false + this._shieldDB = 0 + } + + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + template: 'systems/gurps/templates/slam-calculator.html', + popOut: true, + minimizable: false, + jQuery: true, + resizable: false, + title: game.i18n.localize('GURPS.slamCalculator'), + }) + } + + getData(options) { + const data = super.getData(options) + + data.attackerToken = this._attacker + data.targetToken = this._target || { name: i18n('GURPS.target') } + data.isRealTarget = !!this._target + + data.attackerHp = this._attackerHp + data.attackerSpeed = this._attackerSpeed + + data.targetHp = this._targetHp + data.targetSpeed = this._targetSpeed + + data.relativeSpeed = this.relativeSpeed + + data.isAoAStrong = this._isAoAStrong + data.shieldDB = this._shieldDB + + return data + } + + get relativeSpeed() { + return this._attackerSpeed + this._targetSpeed + } + + _updateObject(event) { + this._calculator.process(this.getData()) + } + + /** @override */ + activateListeners(html) { + super.activateListeners(html) + + html.find('#aoa').click(ev => { + this._isAoAStrong = $(ev.currentTarget).is(':checked') + }) + + html.find('#db').change(ev => { + this._shieldDB = parseInt(ev.currentTarget.value) + }) + + html.find('#attacker-speed').change(ev => { + this._attackerSpeed = parseInt(ev.currentTarget.value) + this.render(false) + }) + + html.find('#target-speed').change(ev => { + this._targetSpeed = parseInt(ev.currentTarget.value) + this.render(false) + }) + + html.find('#attacker-hp').change(ev => { + this._attackerHp = parseInt(ev.currentTarget.value) + }) + + html.find('#target-hp').change(ev => { + this._targetHp = parseInt(ev.currentTarget.value) + }) + } } diff --git a/module/chat/status.js b/module/chat/status.js index b84b31fc9..98c4a4b2b 100644 --- a/module/chat/status.js +++ b/module/chat/status.js @@ -41,7 +41,7 @@ export default class StatusChatProcessor extends ChatProcessor { return line.match(/^[\/\?](st|status)$/i) } usage() { - return i18n("GURPS.chatHelpStatus"); + return i18n('GURPS.chatHelpStatus') } /** @@ -58,11 +58,7 @@ export default class StatusChatProcessor extends ChatProcessor { let self = this.match.groups?.target /* this.match[4] */ === '@self' let tokenName = !self && !!this.match.groups?.target ? this.match.groups.target.replace(/^:(.*)$/, '$1') : null - let tokens = !!tokenName - ? this.getTokensFor(tokenName) - : !!self - ? this.getSelfTokens() - : canvas.tokens?.controlled + let tokens = !!tokenName ? this.getTokensFor(tokenName) : !!self ? this.getSelfTokens() : canvas.tokens?.controlled if (!tokens || tokens.length === 0) { ui.notifications.warn(i18n_f('GURPS.chatSelectSelfOrNameTokens', { self: '@self' })) @@ -178,7 +174,9 @@ export default class StatusChatProcessor extends ChatProcessor { let actor = /** @type {GurpsActor} */ (token.actor) // TODO We need to turn this into a single string, instead of multiple i18n strings concatenated. // This assumes an English-like word order, which may not apply to another language. - this.prnt(`${i18n(actionText)} ${effect.id}:'${i18n(effect.label)}' ${i18n('GURPS.for')} ${actor.displayname}`) + this.prnt( + `${i18n(actionText)} ${effect.id}:'${i18n(effect.label)}' ${i18n('GURPS.for')} ${actor.displayname}` + ) } } diff --git a/module/chat/tracker.js b/module/chat/tracker.js index 0dfc09239..656c272a6 100644 --- a/module/chat/tracker.js +++ b/module/chat/tracker.js @@ -2,111 +2,111 @@ import ChatProcessor from './chat-processor.js' import { i18n, zeroFill } from '../../lib/utilities.js' export default class TrackerChatProcessor extends ChatProcessor { - help() { - return '/tr<N> (or /tr (<name>)) <formula>' - } + help() { + return '/tr<N> (or /tr (<name>)) <formula>' + } - matches(line) { - this.match = line.match(/^\/(tracker|tr|rt|resource)([0123])?( *\(([^\)]+)\))? +([+-=] *\d+)?(reset)?(.*)/i) - return !!this.match - } + matches(line) { + this.match = line.match(/^\/(tracker|tr|rt|resource)([0123])?( *\(([^\)]+)\))? +([+-=] *\d+)?(reset)?(.*)/i) + return !!this.match + } - usagematches(line) { - return line.match(/^[\/\?](tracker|tr|rt|resource)([0123])?( *\(([^\)]+)\))?$/i) - } - usage() { - return i18n("GURPS.chatHelpTracker"); - } + usagematches(line) { + return line.match(/^[\/\?](tracker|tr|rt|resource)([0123])?( *\(([^\)]+)\))?$/i) + } + usage() { + return i18n('GURPS.chatHelpTracker') + } - async process(line) { - let m = this.match - let actor = GURPS.LastActor - if (!actor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } + async process(line) { + let m = this.match + let actor = GURPS.LastActor + if (!actor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } - let tracker = parseInt(m[2]) - let display = tracker + let tracker = parseInt(m[2]) + let display = tracker - // find tracker - if (!!m[3]) { - let pattern = '^' + m[3].trim().replace(/\(\)/, '') - tracker = -1 - for (const [key, value] of Object.entries(actor.system.additionalresources.tracker)) { - if (value.name.match(pattern)) { - tracker = key - display = '(' + value.name + ')' - } - } - if (tracker == -1) { - ui.notifications.warn(`${i18n('GURPS.chatNoResourceTracker', 'No Resource Tracker matched')} '${m[3]}'`) - return false - } - } + // find tracker + if (!!m[3]) { + let pattern = '^' + m[3].trim().replace(/\(\)/, '') + tracker = -1 + for (const [key, value] of Object.entries(actor.system.additionalresources.tracker)) { + if (value.name.match(pattern)) { + tracker = key + display = '(' + value.name + ')' + } + } + if (tracker == -1) { + ui.notifications.warn(`${i18n('GURPS.chatNoResourceTracker', 'No Resource Tracker matched')} '${m[3]}'`) + return false + } + } - let theTrackerKey = zeroFill(tracker, 4) - let theTracker = actor.system.additionalresources.tracker[theTrackerKey] - if (!theTracker) { - ui.notifications.warn(`${i18n('GURPS.chatNoResourceTracker', 'No Resource Tracker matched')} 'tr${m[2]}'`) - return false - } + let theTrackerKey = zeroFill(tracker, 4) + let theTracker = actor.system.additionalresources.tracker[theTrackerKey] + if (!theTracker) { + ui.notifications.warn(`${i18n('GURPS.chatNoResourceTracker', 'No Resource Tracker matched')} 'tr${m[2]}'`) + return false + } - if (!!m[6]) { - // reset -- Damage Tracker's reset to zero - let value = !!theTracker.isDamageTracker ? theTracker.min : theTracker.max - //if (!!theTracker.isDamageTracker) max = 0 - await actor.update({ ['data.additionalresources.tracker.' + theTrackerKey + '.value']: value }) - this.prnt( - `${i18n('GURPS.chatResourceTracker', 'Resource Tracker')}${display} ${i18n( - 'GURPS.chatResetTo', - 'reset to' - )} ${value}` - ) - return true - } + if (!!m[6]) { + // reset -- Damage Tracker's reset to zero + let value = !!theTracker.isDamageTracker ? theTracker.min : theTracker.max + //if (!!theTracker.isDamageTracker) max = 0 + await actor.update({ ['data.additionalresources.tracker.' + theTrackerKey + '.value']: value }) + this.prnt( + `${i18n('GURPS.chatResourceTracker', 'Resource Tracker')}${display} ${i18n( + 'GURPS.chatResetTo', + 'reset to' + )} ${value}` + ) + return true + } - if (!m[5]) { - ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat', 'Unrecognized format')} '${line}'`) - return false - } + if (!m[5]) { + ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat', 'Unrecognized format')} '${line}'`) + return false + } - let delta = parseInt(m[5].replace(/ /g, '')) - if (isNaN(delta)) { - // only happens with '=' - let value = parseInt(m[5].substr(1).replace(/ /g, '')) - if (isNaN(value)) { - ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat', 'Unrecognized format')} '${line}'`) - return false - } else { - value = theTracker.isMaximumEnforced && value > theTracker.max ? theTracker.max : value - value = theTracker.isMinimumEnforced && value < theTracker.min ? theTracker.min : value - await actor.update({ ['data.additionalresources.tracker.' + theTrackerKey + '.value']: value }) - this.prnt(`${i18n('GURPS.chatResourceTracker')}${display} set to ${value}`) - return true - } - } else { - let max = theTracker.max == 0 ? Number.MAX_SAFE_INTEGER : theTracker.max - let min = theTracker.min - let value = theTracker.value + delta + let delta = parseInt(m[5].replace(/ /g, '')) + if (isNaN(delta)) { + // only happens with '=' + let value = parseInt(m[5].substr(1).replace(/ /g, '')) + if (isNaN(value)) { + ui.notifications.warn(`${i18n('GURPS.chatUnrecognizedFormat', 'Unrecognized format')} '${line}'`) + return false + } else { + value = theTracker.isMaximumEnforced && value > theTracker.max ? theTracker.max : value + value = theTracker.isMinimumEnforced && value < theTracker.min ? theTracker.min : value + await actor.update({ ['data.additionalresources.tracker.' + theTrackerKey + '.value']: value }) + this.prnt(`${i18n('GURPS.chatResourceTracker')}${display} set to ${value}`) + return true + } + } else { + let max = theTracker.max == 0 ? Number.MAX_SAFE_INTEGER : theTracker.max + let min = theTracker.min + let value = theTracker.value + delta - if (value > max) { - ui.notifications.warn( - `${i18n('GURPS.chatExceededMax', 'Exceeded MAX')}:${max} ${i18n('GURPS.for')} ${i18n( - 'GURPS.chatResourceTracker' - )}${display}` - ) - if (theTracker.isMaximumEnforced) return false - } - if (value < min) { - ui.notifications.warn( - `${i18n('GURPS.chatResultBelowZero', 'Result below zero')}: ${i18n('GURPS.chatResourceTracker')}${display}` - ) - if (theTracker.isMinimumEnforced) return false - } - await actor.update({ ['data.additionalresources.tracker.' + theTrackerKey + '.value']: value }) - this.prnt(`${i18n('GURPS.chatResourceTracker')}${display} ${m[5]} = ${value}`) - return true - } - } + if (value > max) { + ui.notifications.warn( + `${i18n('GURPS.chatExceededMax', 'Exceeded MAX')}:${max} ${i18n('GURPS.for')} ${i18n( + 'GURPS.chatResourceTracker' + )}${display}` + ) + if (theTracker.isMaximumEnforced) return false + } + if (value < min) { + ui.notifications.warn( + `${i18n('GURPS.chatResultBelowZero', 'Result below zero')}: ${i18n('GURPS.chatResourceTracker')}${display}` + ) + if (theTracker.isMinimumEnforced) return false + } + await actor.update({ ['data.additionalresources.tracker.' + theTrackerKey + '.value']: value }) + this.prnt(`${i18n('GURPS.chatResourceTracker')}${display} ${m[5]} = ${value}`) + return true + } + } } diff --git a/module/color-character-sheet/color-character-sheet-settings.js b/module/color-character-sheet/color-character-sheet-settings.js index 217b58c15..13452798d 100644 --- a/module/color-character-sheet/color-character-sheet-settings.js +++ b/module/color-character-sheet/color-character-sheet-settings.js @@ -1,162 +1,162 @@ -/** - * Added to color the rollable parts of the character sheet. - * Rewrote and made file eslint compatible... - * ~Stevil - */ - -import { SYSTEM_NAME } from '../../lib/miscellaneous-settings.js' -import { i18n } from '../../lib/utilities.js' -import ColorCharacterSheetSettings from './color-character-sheet-html.js' - -export const cssSettings = { - findCSS: [ - { - selector: '.rollable', - rule: 'background-color', - }, - { - selector: '.rollable', - rule: 'color', - }, - { - selector: '.rollable:hover', - rule: 'background-color', - }, - { - selector: '.rollable:hover', - rule: 'color', - }, - ], -} - -export const SETTING_COLOR_CHARACTER_SHEET_MENU = 'color-character-sheet-menu' -export const SETTING_COLOR_CHARACTER_SHEET_DATA = 'color-character-sheet-data' -export const SETTING_DEFAULT_COLOR_BACKGROUND = '#ffffbe' -export const SETTING_DEFAULT_COLOR_TEXT = '#000000' -export const SETTING_DEFAULT_COLOR_BACKGROUND_HOVER = '#cccc00' -export const SETTING_DEFAULT_COLOR_TEXT_HOVER = '#000000' -export const SETTING_COLOR_ROLLABLE = [ - 'color-attributes-rollable', - 'color-dodge-rollable', - 'color-damage-rollable', - 'color-block-rollable', - 'color-parry-rollable', - 'color-weapons-rollable', - 'color-skills-rollable', - 'color-spells-rollable', - 'color-otf-notes-rollable', - 'color-gurpslink-rollable', -] - -export const registerColorPickerSettings = function () { - // eslint-disable-next-line no-undef - game.settings.registerMenu(SYSTEM_NAME, SETTING_COLOR_CHARACTER_SHEET_MENU, { - name: i18n('GURPS.settingColorSheetMenuTitle'), - hint: i18n('GURPS.settingColorSheetMenuHint'), - label: i18n('GURPS.settingColorSheetMenuTitle'), - type: ColorCharacterSheetSettings, - restricted: false, - }) - // eslint-disable-next-line no-undef - game.settings.register(SYSTEM_NAME, SETTING_COLOR_CHARACTER_SHEET_DATA, { - name: 'Color Character Sheet - Data', - scope: 'world', - config: false, - type: Object, - default: { - colors: [ - { - color_override: false, - area: 'Attributes', - rollable_css: `${SETTING_COLOR_ROLLABLE[0]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'Dodge', - rollable_css: `${SETTING_COLOR_ROLLABLE[1]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'Damage', - rollable_css: `${SETTING_COLOR_ROLLABLE[2]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'Block', - rollable_css: `${SETTING_COLOR_ROLLABLE[3]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'Parry', - rollable_css: `${SETTING_COLOR_ROLLABLE[4]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'Weapons', - rollable_css: `${SETTING_COLOR_ROLLABLE[5]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'Skills', - rollable_css: `${SETTING_COLOR_ROLLABLE[6]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'Spells', - rollable_css: `${SETTING_COLOR_ROLLABLE[7]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'OtF-Notes', - rollable_css: `${SETTING_COLOR_ROLLABLE[8]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - { - color_override: false, - area: 'Ads/Disads', - rollable_css: `${SETTING_COLOR_ROLLABLE[9]}`, - color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, - color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, - color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, - color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, - }, - ], - }, - // onChange: value => console.log(`Updated Character Sheet Colors: ${JSON.stringify(value)}`), - }) -} +/** + * Added to color the rollable parts of the character sheet. + * Rewrote and made file eslint compatible... + * ~Stevil + */ + +import { SYSTEM_NAME } from '../../lib/miscellaneous-settings.js' +import { i18n } from '../../lib/utilities.js' +import ColorCharacterSheetSettings from './color-character-sheet-html.js' + +export const cssSettings = { + findCSS: [ + { + selector: '.rollable', + rule: 'background-color', + }, + { + selector: '.rollable', + rule: 'color', + }, + { + selector: '.rollable:hover', + rule: 'background-color', + }, + { + selector: '.rollable:hover', + rule: 'color', + }, + ], +} + +export const SETTING_COLOR_CHARACTER_SHEET_MENU = 'color-character-sheet-menu' +export const SETTING_COLOR_CHARACTER_SHEET_DATA = 'color-character-sheet-data' +export const SETTING_DEFAULT_COLOR_BACKGROUND = '#ffffbe' +export const SETTING_DEFAULT_COLOR_TEXT = '#000000' +export const SETTING_DEFAULT_COLOR_BACKGROUND_HOVER = '#cccc00' +export const SETTING_DEFAULT_COLOR_TEXT_HOVER = '#000000' +export const SETTING_COLOR_ROLLABLE = [ + 'color-attributes-rollable', + 'color-dodge-rollable', + 'color-damage-rollable', + 'color-block-rollable', + 'color-parry-rollable', + 'color-weapons-rollable', + 'color-skills-rollable', + 'color-spells-rollable', + 'color-otf-notes-rollable', + 'color-gurpslink-rollable', +] + +export const registerColorPickerSettings = function () { + // eslint-disable-next-line no-undef + game.settings.registerMenu(SYSTEM_NAME, SETTING_COLOR_CHARACTER_SHEET_MENU, { + name: i18n('GURPS.settingColorSheetMenuTitle'), + hint: i18n('GURPS.settingColorSheetMenuHint'), + label: i18n('GURPS.settingColorSheetMenuTitle'), + type: ColorCharacterSheetSettings, + restricted: false, + }) + // eslint-disable-next-line no-undef + game.settings.register(SYSTEM_NAME, SETTING_COLOR_CHARACTER_SHEET_DATA, { + name: 'Color Character Sheet - Data', + scope: 'world', + config: false, + type: Object, + default: { + colors: [ + { + color_override: false, + area: 'Attributes', + rollable_css: `${SETTING_COLOR_ROLLABLE[0]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'Dodge', + rollable_css: `${SETTING_COLOR_ROLLABLE[1]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'Damage', + rollable_css: `${SETTING_COLOR_ROLLABLE[2]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'Block', + rollable_css: `${SETTING_COLOR_ROLLABLE[3]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'Parry', + rollable_css: `${SETTING_COLOR_ROLLABLE[4]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'Weapons', + rollable_css: `${SETTING_COLOR_ROLLABLE[5]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'Skills', + rollable_css: `${SETTING_COLOR_ROLLABLE[6]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'Spells', + rollable_css: `${SETTING_COLOR_ROLLABLE[7]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'OtF-Notes', + rollable_css: `${SETTING_COLOR_ROLLABLE[8]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + { + color_override: false, + area: 'Ads/Disads', + rollable_css: `${SETTING_COLOR_ROLLABLE[9]}`, + color_background: `${SETTING_DEFAULT_COLOR_BACKGROUND}`, + color_text: `${SETTING_DEFAULT_COLOR_TEXT}`, + color_hover: `${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`, + color_hover_text: `${SETTING_DEFAULT_COLOR_TEXT_HOVER}`, + }, + ], + }, + // onChange: value => console.log(`Updated Character Sheet Colors: ${JSON.stringify(value)}`), + }) +} diff --git a/module/color-character-sheet/color-character-sheet.js b/module/color-character-sheet/color-character-sheet.js index b29fe787d..6ca021e50 100644 --- a/module/color-character-sheet/color-character-sheet.js +++ b/module/color-character-sheet/color-character-sheet.js @@ -1,481 +1,481 @@ -/** - * Added to color the rollable parts of the character sheet. - * Rewrote and made file eslint compatible... - * ~Stevil - */ - -import { objectToArray } from '../../lib/utilities.js' -import { SYSTEM_NAME } from '../../lib/miscellaneous-settings.js' -import { - SETTING_COLOR_CHARACTER_SHEET_DATA, - SETTING_DEFAULT_COLOR_BACKGROUND, - SETTING_DEFAULT_COLOR_TEXT, - SETTING_DEFAULT_COLOR_BACKGROUND_HOVER, - SETTING_DEFAULT_COLOR_TEXT_HOVER, - SETTING_COLOR_ROLLABLE, -} from './color-character-sheet-settings.js' - -export function addColorWheelsToSettings() { - $('#color-sheets input[type="checkbox"]').on('click', function () { - const overrideColor = $(this).attr('id') - if ($(this).prop('checked')) { - $(this).attr('checked', 'checked') - } else { - $(`.${overrideColor} .colorInput`).val($(`.default-${overrideColor}`).val()) - $(`.${overrideColor}-text .colorInput`).val($(`.default-${overrideColor}-text `).val()) - $(`.${overrideColor}-hover .colorInput`).val($(`.default-${overrideColor}-hover`).val()) - $(`.${overrideColor}-hover-text .colorInput`).val($(`.default-${overrideColor}-hover-text`).val()) - - $(`.${overrideColor} .color`).css('background-color', $(`.default-${overrideColor}`).val()) - $(`.${overrideColor}-text .color`).css('background-color', $(`.default-${overrideColor}-text`).val()) - $(`.${overrideColor}-hover .color`).css('background-color', $(`.default-${overrideColor}-hover`).val()) - $(`.${overrideColor}-hover-text .color`).css('background-color', $(`.default-${overrideColor}-hover-text`).val()) - $(this).removeAttr('checked') - } - saveColorWheelsToSettings() - }) - - const Zindex = 99999 - const oldTrackZindex = 10000 - const oldColorZindex = 10001 - const newTrackZindex = oldTrackZindex + Zindex - const newColorZindex = oldColorZindex + Zindex - - SETTING_COLOR_ROLLABLE.forEach(function (rollableSheetColors) { - $(`.${rollableSheetColors} #colorPicker`).tinycolorpicker() - $(`.${rollableSheetColors}-text #colorPicker`).tinycolorpicker() - $(`.${rollableSheetColors}-hover #colorPicker`).tinycolorpicker() - $(`.${rollableSheetColors}-hover-text #colorPicker`).tinycolorpicker() - - $(`.${rollableSheetColors} .colorInner`).on('click', function () { - $('#colorPicker .track').hide() - $(`.${rollableSheetColors} #colorPicker .track`).show() - $('#colorPicker .color').css('z-index', oldColorZindex) - $('#colorPicker .track').css('z-index', oldTrackZindex) - $(`.${rollableSheetColors} .color`).css('z-index', newColorZindex) - $(`.${rollableSheetColors} .track`).css('z-index', newTrackZindex) - }) - - $(`.${rollableSheetColors}-text .colorInner`).on('click', function () { - $('#colorPicker .track').hide() - $(`.${rollableSheetColors}-text #colorPicker .track`).show() - $('#colorPicker .color').css('z-index', oldColorZindex) - $('#colorPicker .track').css('z-index', oldTrackZindex) - $(`.${rollableSheetColors}-text .color`).css('z-index', newColorZindex) - $(`.${rollableSheetColors}-text .track`).css('z-index', newTrackZindex) - }) - - $(`.${rollableSheetColors}-hover .colorInner`).on('click', function () { - $('#colorPicker .track').hide() - $(`.${rollableSheetColors}-hover #colorPicker .track`).show() - $('#colorPicker .color').css('z-index', oldColorZindex) - $('#colorPicker .track').css('z-index', oldTrackZindex) - $(`.${rollableSheetColors}-hover .color`).css('z-index', newColorZindex) - $(`.${rollableSheetColors}-hover .track`).css('z-index', newTrackZindex) - }) - - $(`.${rollableSheetColors}-hover-text .colorInner`).on('click', function () { - $('#colorPicker .track').hide() - $(`.${rollableSheetColors}-hover-text #colorPicker .track`).show() - $('#colorPicker .color').css('z-index', oldColorZindex) - $('#colorPicker .track').css('z-index', oldTrackZindex) - $(`.${rollableSheetColors}-hover-text .color`).css('z-index', newColorZindex) - $(`.${rollableSheetColors}-hover-text .track`).css('z-index', newTrackZindex) - }) - - $(`.${rollableSheetColors} #colorPicker .track`).on('click', function () { - $(`#${rollableSheetColors}`).attr('checked', 'checked') - saveColorWheelsToSettings() - }) - $(`.${rollableSheetColors}-text #colorPicker .track`).on('click', function () { - $(`#${rollableSheetColors}`).attr('checked', 'checked') - saveColorWheelsToSettings() - }) - $(`.${rollableSheetColors}-hover #colorPicker .track`).on('click', function () { - $(`#${rollableSheetColors}`).attr('checked', 'checked') - saveColorWheelsToSettings() - }) - $(`.${rollableSheetColors}-hover-text #colorPicker .track`).on('click', function () { - $(`#${rollableSheetColors}`).attr('checked', 'checked') - saveColorWheelsToSettings() - }) - - if ($(`#${rollableSheetColors}`).prop('checked')) { - $(`.${rollableSheetColors} .color`).css('background-color', $(`.${rollableSheetColors} .colorInput`).val()) - $(`.${rollableSheetColors}-text .color`).css( - 'background-color', - $(`.${rollableSheetColors}-text .colorInput`).val() - ) - $(`.${rollableSheetColors}-hover .color`).css( - 'background-color', - $(`.${rollableSheetColors}-hover .colorInput`).val() - ) - $(`.${rollableSheetColors}-hover-text .color`).css( - 'background-color', - $(`.${rollableSheetColors}-hover-text .colorInput`).val() - ) - } else { - $(`.${rollableSheetColors} .color`).css('background-color', SETTING_DEFAULT_COLOR_BACKGROUND) - $(`.${rollableSheetColors}-text .color`).css('background-color', SETTING_DEFAULT_COLOR_TEXT) - $(`.${rollableSheetColors}-hover .color`).css('background-color', SETTING_DEFAULT_COLOR_BACKGROUND_HOVER) - $(`.${rollableSheetColors}-hover-text .color`).css('background-color', SETTING_DEFAULT_COLOR_TEXT_HOVER) - } - }) -} - -export function colorGurpsActorSheet() { - // eslint-disable-next-line no-undef - const colorData = game.settings.get(SYSTEM_NAME, SETTING_COLOR_CHARACTER_SHEET_DATA) - // console.log(`Read Character Sheet Colors: ${JSON.stringify(colorData)}`) - - const theColorData = objectToArray(colorData.colors) - - /** - * Atributes - */ - $('#attributes') - .on('mouseenter', '.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[0].color_hover + ' !important; color:' + theColorData[0].color_hover_text - ) - }) - .on('mouseleave', '.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[0].color_background + ' !important; color:' + theColorData[0].color_text - ) - }) - $('#attributes .rollable').attr( - 'style', - 'background-color: ' + theColorData[0].color_background + ' !important; color:' + theColorData[0].color_text - ) - - /** - * Dodge - */ - $('#encumbrance') - .on('mouseenter', '.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[1].color_hover + ' !important; color:' + theColorData[1].color_hover_text - ) - }) - .on('mouseleave', '.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[1].color_background + ' !important; color:' + theColorData[1].color_text - ) - }) - $('.dodge.rollable').attr( - 'style', - 'background-color: ' + theColorData[1].color_background + ' !important; color:' + theColorData[1].color_text - ) - - /** - * Damage - */ - $('#melee, #ranged') - .on('mouseenter', '.damage.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[2].color_hover + ' !important; color:' + theColorData[2].color_hover_text - ) - }) - .on('mouseleave', '.damage.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[2].color_background + ' !important; color:' + theColorData[2].color_text - ) - }) - $('.damage.rollable').attr( - 'style', - 'background-color: ' + theColorData[2].color_background + ' !important; color:' + theColorData[2].color_text - ) - - /** - * Block - */ - $('#melee, #ranged') - .on('mouseenter', '.block.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[3].color_hover + ' !important; color:' + theColorData[3].color_hover_text - ) - }) - .on('mouseleave', '.block.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[3].color_background + ' !important; color:' + theColorData[3].color_text - ) - }) - $('.block.rollable').attr( - 'style', - 'background-color: ' + theColorData[3].color_background + ' !important; color:' + theColorData[3].color_text - ) - - /** - * Parry - */ - $('#melee, #ranged') - .on('mouseenter', '.parry.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[4].color_hover + ' !important; color:' + theColorData[4].color_hover_text - ) - }) - .on('mouseleave', '.parry.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[4].color_background + ' !important; color:' + theColorData[4].color_text - ) - }) - $('.parry.rollable').attr( - 'style', - 'background-color: ' + theColorData[4].color_background + ' !important; color:' + theColorData[4].color_text - ) - - /** - * Melee / Ranged - */ - $('#melee, #ranged') - .on('mouseenter', '.usage.rollable, .level.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[5].color_hover + ' !important; color:' + theColorData[5].color_hover_text - ) - }) - .on('mouseleave', '.usage.rollable, .level.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[5].color_background + ' !important; color:' + theColorData[5].color_text - ) - }) - $('#melee .usage.rollable, #melee .level.rollable, #ranged .usage.rollable, #ranged .level.rollable').attr( - 'style', - 'background-color: ' + theColorData[5].color_background + ' !important; color:' + theColorData[5].color_text - ) - - /** - * Skills - */ - $('#skills') - .on('mouseenter', '.sl.rollable, .rsl.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[6].color_hover + ' !important; color:' + theColorData[6].color_hover_text - ) - }) - .on('mouseleave', '.sl.rollable, .rsl.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[6].color_background + ' !important; color:' + theColorData[6].color_text - ) - }) - $('#skills .sl.rollable, #skills .rsl.rollable').attr( - 'style', - 'background-color: ' + theColorData[6].color_background + ' !important; color:' + theColorData[6].color_text - ) - - /** - * Spells - */ - $('#spells') - .on('mouseenter', '.sl.rollable, .rsl.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[7].color_hover + ' !important; color:' + theColorData[7].color_hover_text - ) - }) - .on('mouseleave', '.sl.rollable, .rsl.rollable', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[7].color_background + ' !important; color:' + theColorData[7].color_text - ) - }) - $('#spells .sl.rollable, #spells .rsl.rollable').attr( - 'style', - 'background-color: ' + theColorData[7].color_background + ' !important; color:' + theColorData[7].color_text - ) - - /** - * OtF in Qick Notes - */ - $('#qnotes') - .on('mouseenter', '.gurpslink', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[8].color_hover + ' !important; color:' + theColorData[8].color_hover_text - ) - }) - .on('mouseleave', '.gurpslink', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[8].color_background + ' !important; color:' + theColorData[8].color_text - ) - }) - $('#qnotes .gurpslink').attr( - 'style', - 'background-color: ' + theColorData[8].color_background + ' !important; color:' + theColorData[8].color_text - ) - - /** - * Ads / Disads - */ - $('#advantages') - .on('mouseenter', '.gurpslink', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[9].color_hover + ' !important; color:' + theColorData[9].color_hover_text - ) - }) - .on('mouseleave', '.gurpslink', function () { - $(this).attr( - 'style', - 'background-color: ' + theColorData[9].color_background + ' !important; color:' + theColorData[9].color_text - ) - }) - $('#advantages .gurpslink').attr( - 'style', - 'background-color: ' + theColorData[9].color_background + ' !important; color:' + theColorData[9].color_text - ) -} - -export function saveColorWheelsToSettings() { - const html = jQuery($('#color-sheets').html()) - const colorOverride = [] - const colorBackground = [] - const colorText = [] - const colorHover = [] - const colorHoverText = [] - - SETTING_COLOR_ROLLABLE.forEach(function (rollableSheetColors) { - if (html.find('.gurps-sheet-colors').find(`#${rollableSheetColors}`).prop('checked')) { - colorOverride.push(true) - } else { - colorOverride.push(false) - } - - if (html.find('.gurps-sheet-colors').find(`.${rollableSheetColors} .colorInput`).val() === undefined) { - colorBackground.push(`${SETTING_DEFAULT_COLOR_BACKGROUND}`) - } else { - colorBackground.push(html.find('.gurps-sheet-colors').find(`.${rollableSheetColors} .colorInput`).val()) - } - - if (html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-text .colorInput`).val() === undefined) { - colorText.push(`${SETTING_DEFAULT_COLOR_TEXT}`) - } else { - colorText.push(html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-text .colorInput`).val()) - } - - if (html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-hover .colorInput`).val() === undefined) { - colorHover.push(`${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`) - } else { - colorHover.push(html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-hover .colorInput`).val()) - } - - if (html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-hover-text .colorInput`).val() === undefined) { - colorHoverText.push(`${SETTING_DEFAULT_COLOR_TEXT_HOVER}`) - } else { - colorHoverText.push(html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-hover-text .colorInput`).val()) - } - }) - - const data = { - colors: [ - { - color_override: colorOverride[0], - area: 'Attributes', - rollable_css: `${SETTING_COLOR_ROLLABLE[0]}`, - color_background: `${colorBackground[0]}`, - color_text: `${colorText[0]}`, - color_hover: `${colorHover[0]}`, - color_hover_text: `${colorHoverText[0]}`, - }, - { - color_override: colorOverride[1], - area: 'Dodge', - rollable_css: `${SETTING_COLOR_ROLLABLE[1]}`, - color_background: `${colorBackground[1]}`, - color_text: `${colorText[1]}`, - color_hover: `${colorHover[1]}`, - color_hover_text: `${colorHoverText[1]}`, - }, - { - color_override: colorOverride[2], - area: 'Damage', - rollable_css: `${SETTING_COLOR_ROLLABLE[2]}`, - color_background: `${colorBackground[2]}`, - color_text: `${colorText[2]}`, - color_hover: `${colorHover[2]}`, - color_hover_text: `${colorHoverText[2]}`, - }, - { - color_override: colorOverride[3], - area: 'Block', - rollable_css: `${SETTING_COLOR_ROLLABLE[3]}`, - color_background: `${colorBackground[3]}`, - color_text: `${colorText[3]}`, - color_hover: `${colorHover[3]}`, - color_hover_text: `${colorHoverText[3]}`, - }, - { - color_override: colorOverride[4], - area: 'Parry', - rollable_css: `${SETTING_COLOR_ROLLABLE[4]}`, - color_background: `${colorBackground[4]}`, - color_text: `${colorText[4]}`, - color_hover: `${colorHover[4]}`, - color_hover_text: `${colorHoverText[4]}`, - }, - { - color_override: colorOverride[5], - area: 'Weapons', - rollable_css: `${SETTING_COLOR_ROLLABLE[5]}`, - color_background: `${colorBackground[5]}`, - color_text: `${colorText[5]}`, - color_hover: `${colorHover[5]}`, - color_hover_text: `${colorHoverText[5]}`, - }, - { - color_override: colorOverride[6], - area: 'Skills', - rollable_css: `${SETTING_COLOR_ROLLABLE[6]}`, - color_background: `${colorBackground[6]}`, - color_text: `${colorText[6]}`, - color_hover: `${colorHover[6]}`, - color_hover_text: `${colorHoverText[6]}`, - }, - { - color_override: colorOverride[7], - area: 'Spells', - rollable_css: `${SETTING_COLOR_ROLLABLE[7]}`, - color_background: `${colorBackground[7]}`, - color_text: `${colorText[7]}`, - color_hover: `${colorHover[7]}`, - color_hover_text: `${colorHoverText[7]}`, - }, - { - color_override: colorOverride[8], - area: 'OtF-Notes', - rollable_css: `${SETTING_COLOR_ROLLABLE[8]}`, - color_background: `${colorBackground[8]}`, - color_text: `${colorText[8]}`, - color_hover: `${colorHover[8]}`, - color_hover_text: `${colorHoverText[8]}`, - }, - { - color_override: colorOverride[9], - area: 'Ads/Disads', - rollable_css: `${SETTING_COLOR_ROLLABLE[9]}`, - color_background: `${colorBackground[9]}`, - color_text: `${colorText[9]}`, - color_hover: `${colorHover[9]}`, - color_hover_text: `${colorHoverText[9]}`, - }, - ], - } - // eslint-disable-next-line no-undef - game.settings.set(SYSTEM_NAME, SETTING_COLOR_CHARACTER_SHEET_DATA, data) - // console.log(`Saved Character Sheet Colors: ${JSON.stringify(data)}`) -} +/** + * Added to color the rollable parts of the character sheet. + * Rewrote and made file eslint compatible... + * ~Stevil + */ + +import { objectToArray } from '../../lib/utilities.js' +import { SYSTEM_NAME } from '../../lib/miscellaneous-settings.js' +import { + SETTING_COLOR_CHARACTER_SHEET_DATA, + SETTING_DEFAULT_COLOR_BACKGROUND, + SETTING_DEFAULT_COLOR_TEXT, + SETTING_DEFAULT_COLOR_BACKGROUND_HOVER, + SETTING_DEFAULT_COLOR_TEXT_HOVER, + SETTING_COLOR_ROLLABLE, +} from './color-character-sheet-settings.js' + +export function addColorWheelsToSettings() { + $('#color-sheets input[type="checkbox"]').on('click', function () { + const overrideColor = $(this).attr('id') + if ($(this).prop('checked')) { + $(this).attr('checked', 'checked') + } else { + $(`.${overrideColor} .colorInput`).val($(`.default-${overrideColor}`).val()) + $(`.${overrideColor}-text .colorInput`).val($(`.default-${overrideColor}-text `).val()) + $(`.${overrideColor}-hover .colorInput`).val($(`.default-${overrideColor}-hover`).val()) + $(`.${overrideColor}-hover-text .colorInput`).val($(`.default-${overrideColor}-hover-text`).val()) + + $(`.${overrideColor} .color`).css('background-color', $(`.default-${overrideColor}`).val()) + $(`.${overrideColor}-text .color`).css('background-color', $(`.default-${overrideColor}-text`).val()) + $(`.${overrideColor}-hover .color`).css('background-color', $(`.default-${overrideColor}-hover`).val()) + $(`.${overrideColor}-hover-text .color`).css('background-color', $(`.default-${overrideColor}-hover-text`).val()) + $(this).removeAttr('checked') + } + saveColorWheelsToSettings() + }) + + const Zindex = 99999 + const oldTrackZindex = 10000 + const oldColorZindex = 10001 + const newTrackZindex = oldTrackZindex + Zindex + const newColorZindex = oldColorZindex + Zindex + + SETTING_COLOR_ROLLABLE.forEach(function (rollableSheetColors) { + $(`.${rollableSheetColors} #colorPicker`).tinycolorpicker() + $(`.${rollableSheetColors}-text #colorPicker`).tinycolorpicker() + $(`.${rollableSheetColors}-hover #colorPicker`).tinycolorpicker() + $(`.${rollableSheetColors}-hover-text #colorPicker`).tinycolorpicker() + + $(`.${rollableSheetColors} .colorInner`).on('click', function () { + $('#colorPicker .track').hide() + $(`.${rollableSheetColors} #colorPicker .track`).show() + $('#colorPicker .color').css('z-index', oldColorZindex) + $('#colorPicker .track').css('z-index', oldTrackZindex) + $(`.${rollableSheetColors} .color`).css('z-index', newColorZindex) + $(`.${rollableSheetColors} .track`).css('z-index', newTrackZindex) + }) + + $(`.${rollableSheetColors}-text .colorInner`).on('click', function () { + $('#colorPicker .track').hide() + $(`.${rollableSheetColors}-text #colorPicker .track`).show() + $('#colorPicker .color').css('z-index', oldColorZindex) + $('#colorPicker .track').css('z-index', oldTrackZindex) + $(`.${rollableSheetColors}-text .color`).css('z-index', newColorZindex) + $(`.${rollableSheetColors}-text .track`).css('z-index', newTrackZindex) + }) + + $(`.${rollableSheetColors}-hover .colorInner`).on('click', function () { + $('#colorPicker .track').hide() + $(`.${rollableSheetColors}-hover #colorPicker .track`).show() + $('#colorPicker .color').css('z-index', oldColorZindex) + $('#colorPicker .track').css('z-index', oldTrackZindex) + $(`.${rollableSheetColors}-hover .color`).css('z-index', newColorZindex) + $(`.${rollableSheetColors}-hover .track`).css('z-index', newTrackZindex) + }) + + $(`.${rollableSheetColors}-hover-text .colorInner`).on('click', function () { + $('#colorPicker .track').hide() + $(`.${rollableSheetColors}-hover-text #colorPicker .track`).show() + $('#colorPicker .color').css('z-index', oldColorZindex) + $('#colorPicker .track').css('z-index', oldTrackZindex) + $(`.${rollableSheetColors}-hover-text .color`).css('z-index', newColorZindex) + $(`.${rollableSheetColors}-hover-text .track`).css('z-index', newTrackZindex) + }) + + $(`.${rollableSheetColors} #colorPicker .track`).on('click', function () { + $(`#${rollableSheetColors}`).attr('checked', 'checked') + saveColorWheelsToSettings() + }) + $(`.${rollableSheetColors}-text #colorPicker .track`).on('click', function () { + $(`#${rollableSheetColors}`).attr('checked', 'checked') + saveColorWheelsToSettings() + }) + $(`.${rollableSheetColors}-hover #colorPicker .track`).on('click', function () { + $(`#${rollableSheetColors}`).attr('checked', 'checked') + saveColorWheelsToSettings() + }) + $(`.${rollableSheetColors}-hover-text #colorPicker .track`).on('click', function () { + $(`#${rollableSheetColors}`).attr('checked', 'checked') + saveColorWheelsToSettings() + }) + + if ($(`#${rollableSheetColors}`).prop('checked')) { + $(`.${rollableSheetColors} .color`).css('background-color', $(`.${rollableSheetColors} .colorInput`).val()) + $(`.${rollableSheetColors}-text .color`).css( + 'background-color', + $(`.${rollableSheetColors}-text .colorInput`).val() + ) + $(`.${rollableSheetColors}-hover .color`).css( + 'background-color', + $(`.${rollableSheetColors}-hover .colorInput`).val() + ) + $(`.${rollableSheetColors}-hover-text .color`).css( + 'background-color', + $(`.${rollableSheetColors}-hover-text .colorInput`).val() + ) + } else { + $(`.${rollableSheetColors} .color`).css('background-color', SETTING_DEFAULT_COLOR_BACKGROUND) + $(`.${rollableSheetColors}-text .color`).css('background-color', SETTING_DEFAULT_COLOR_TEXT) + $(`.${rollableSheetColors}-hover .color`).css('background-color', SETTING_DEFAULT_COLOR_BACKGROUND_HOVER) + $(`.${rollableSheetColors}-hover-text .color`).css('background-color', SETTING_DEFAULT_COLOR_TEXT_HOVER) + } + }) +} + +export function colorGurpsActorSheet() { + // eslint-disable-next-line no-undef + const colorData = game.settings.get(SYSTEM_NAME, SETTING_COLOR_CHARACTER_SHEET_DATA) + // console.log(`Read Character Sheet Colors: ${JSON.stringify(colorData)}`) + + const theColorData = objectToArray(colorData.colors) + + /** + * Atributes + */ + $('#attributes') + .on('mouseenter', '.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[0].color_hover + ' !important; color:' + theColorData[0].color_hover_text + ) + }) + .on('mouseleave', '.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[0].color_background + ' !important; color:' + theColorData[0].color_text + ) + }) + $('#attributes .rollable').attr( + 'style', + 'background-color: ' + theColorData[0].color_background + ' !important; color:' + theColorData[0].color_text + ) + + /** + * Dodge + */ + $('#encumbrance') + .on('mouseenter', '.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[1].color_hover + ' !important; color:' + theColorData[1].color_hover_text + ) + }) + .on('mouseleave', '.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[1].color_background + ' !important; color:' + theColorData[1].color_text + ) + }) + $('.dodge.rollable').attr( + 'style', + 'background-color: ' + theColorData[1].color_background + ' !important; color:' + theColorData[1].color_text + ) + + /** + * Damage + */ + $('#melee, #ranged') + .on('mouseenter', '.damage.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[2].color_hover + ' !important; color:' + theColorData[2].color_hover_text + ) + }) + .on('mouseleave', '.damage.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[2].color_background + ' !important; color:' + theColorData[2].color_text + ) + }) + $('.damage.rollable').attr( + 'style', + 'background-color: ' + theColorData[2].color_background + ' !important; color:' + theColorData[2].color_text + ) + + /** + * Block + */ + $('#melee, #ranged') + .on('mouseenter', '.block.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[3].color_hover + ' !important; color:' + theColorData[3].color_hover_text + ) + }) + .on('mouseleave', '.block.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[3].color_background + ' !important; color:' + theColorData[3].color_text + ) + }) + $('.block.rollable').attr( + 'style', + 'background-color: ' + theColorData[3].color_background + ' !important; color:' + theColorData[3].color_text + ) + + /** + * Parry + */ + $('#melee, #ranged') + .on('mouseenter', '.parry.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[4].color_hover + ' !important; color:' + theColorData[4].color_hover_text + ) + }) + .on('mouseleave', '.parry.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[4].color_background + ' !important; color:' + theColorData[4].color_text + ) + }) + $('.parry.rollable').attr( + 'style', + 'background-color: ' + theColorData[4].color_background + ' !important; color:' + theColorData[4].color_text + ) + + /** + * Melee / Ranged + */ + $('#melee, #ranged') + .on('mouseenter', '.usage.rollable, .level.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[5].color_hover + ' !important; color:' + theColorData[5].color_hover_text + ) + }) + .on('mouseleave', '.usage.rollable, .level.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[5].color_background + ' !important; color:' + theColorData[5].color_text + ) + }) + $('#melee .usage.rollable, #melee .level.rollable, #ranged .usage.rollable, #ranged .level.rollable').attr( + 'style', + 'background-color: ' + theColorData[5].color_background + ' !important; color:' + theColorData[5].color_text + ) + + /** + * Skills + */ + $('#skills') + .on('mouseenter', '.sl.rollable, .rsl.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[6].color_hover + ' !important; color:' + theColorData[6].color_hover_text + ) + }) + .on('mouseleave', '.sl.rollable, .rsl.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[6].color_background + ' !important; color:' + theColorData[6].color_text + ) + }) + $('#skills .sl.rollable, #skills .rsl.rollable').attr( + 'style', + 'background-color: ' + theColorData[6].color_background + ' !important; color:' + theColorData[6].color_text + ) + + /** + * Spells + */ + $('#spells') + .on('mouseenter', '.sl.rollable, .rsl.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[7].color_hover + ' !important; color:' + theColorData[7].color_hover_text + ) + }) + .on('mouseleave', '.sl.rollable, .rsl.rollable', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[7].color_background + ' !important; color:' + theColorData[7].color_text + ) + }) + $('#spells .sl.rollable, #spells .rsl.rollable').attr( + 'style', + 'background-color: ' + theColorData[7].color_background + ' !important; color:' + theColorData[7].color_text + ) + + /** + * OtF in Qick Notes + */ + $('#qnotes') + .on('mouseenter', '.gurpslink', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[8].color_hover + ' !important; color:' + theColorData[8].color_hover_text + ) + }) + .on('mouseleave', '.gurpslink', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[8].color_background + ' !important; color:' + theColorData[8].color_text + ) + }) + $('#qnotes .gurpslink').attr( + 'style', + 'background-color: ' + theColorData[8].color_background + ' !important; color:' + theColorData[8].color_text + ) + + /** + * Ads / Disads + */ + $('#advantages') + .on('mouseenter', '.gurpslink', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[9].color_hover + ' !important; color:' + theColorData[9].color_hover_text + ) + }) + .on('mouseleave', '.gurpslink', function () { + $(this).attr( + 'style', + 'background-color: ' + theColorData[9].color_background + ' !important; color:' + theColorData[9].color_text + ) + }) + $('#advantages .gurpslink').attr( + 'style', + 'background-color: ' + theColorData[9].color_background + ' !important; color:' + theColorData[9].color_text + ) +} + +export function saveColorWheelsToSettings() { + const html = jQuery($('#color-sheets').html()) + const colorOverride = [] + const colorBackground = [] + const colorText = [] + const colorHover = [] + const colorHoverText = [] + + SETTING_COLOR_ROLLABLE.forEach(function (rollableSheetColors) { + if (html.find('.gurps-sheet-colors').find(`#${rollableSheetColors}`).prop('checked')) { + colorOverride.push(true) + } else { + colorOverride.push(false) + } + + if (html.find('.gurps-sheet-colors').find(`.${rollableSheetColors} .colorInput`).val() === undefined) { + colorBackground.push(`${SETTING_DEFAULT_COLOR_BACKGROUND}`) + } else { + colorBackground.push(html.find('.gurps-sheet-colors').find(`.${rollableSheetColors} .colorInput`).val()) + } + + if (html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-text .colorInput`).val() === undefined) { + colorText.push(`${SETTING_DEFAULT_COLOR_TEXT}`) + } else { + colorText.push(html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-text .colorInput`).val()) + } + + if (html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-hover .colorInput`).val() === undefined) { + colorHover.push(`${SETTING_DEFAULT_COLOR_BACKGROUND_HOVER}`) + } else { + colorHover.push(html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-hover .colorInput`).val()) + } + + if (html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-hover-text .colorInput`).val() === undefined) { + colorHoverText.push(`${SETTING_DEFAULT_COLOR_TEXT_HOVER}`) + } else { + colorHoverText.push(html.find('.gurps-sheet-colors').find(`.${rollableSheetColors}-hover-text .colorInput`).val()) + } + }) + + const data = { + colors: [ + { + color_override: colorOverride[0], + area: 'Attributes', + rollable_css: `${SETTING_COLOR_ROLLABLE[0]}`, + color_background: `${colorBackground[0]}`, + color_text: `${colorText[0]}`, + color_hover: `${colorHover[0]}`, + color_hover_text: `${colorHoverText[0]}`, + }, + { + color_override: colorOverride[1], + area: 'Dodge', + rollable_css: `${SETTING_COLOR_ROLLABLE[1]}`, + color_background: `${colorBackground[1]}`, + color_text: `${colorText[1]}`, + color_hover: `${colorHover[1]}`, + color_hover_text: `${colorHoverText[1]}`, + }, + { + color_override: colorOverride[2], + area: 'Damage', + rollable_css: `${SETTING_COLOR_ROLLABLE[2]}`, + color_background: `${colorBackground[2]}`, + color_text: `${colorText[2]}`, + color_hover: `${colorHover[2]}`, + color_hover_text: `${colorHoverText[2]}`, + }, + { + color_override: colorOverride[3], + area: 'Block', + rollable_css: `${SETTING_COLOR_ROLLABLE[3]}`, + color_background: `${colorBackground[3]}`, + color_text: `${colorText[3]}`, + color_hover: `${colorHover[3]}`, + color_hover_text: `${colorHoverText[3]}`, + }, + { + color_override: colorOverride[4], + area: 'Parry', + rollable_css: `${SETTING_COLOR_ROLLABLE[4]}`, + color_background: `${colorBackground[4]}`, + color_text: `${colorText[4]}`, + color_hover: `${colorHover[4]}`, + color_hover_text: `${colorHoverText[4]}`, + }, + { + color_override: colorOverride[5], + area: 'Weapons', + rollable_css: `${SETTING_COLOR_ROLLABLE[5]}`, + color_background: `${colorBackground[5]}`, + color_text: `${colorText[5]}`, + color_hover: `${colorHover[5]}`, + color_hover_text: `${colorHoverText[5]}`, + }, + { + color_override: colorOverride[6], + area: 'Skills', + rollable_css: `${SETTING_COLOR_ROLLABLE[6]}`, + color_background: `${colorBackground[6]}`, + color_text: `${colorText[6]}`, + color_hover: `${colorHover[6]}`, + color_hover_text: `${colorHoverText[6]}`, + }, + { + color_override: colorOverride[7], + area: 'Spells', + rollable_css: `${SETTING_COLOR_ROLLABLE[7]}`, + color_background: `${colorBackground[7]}`, + color_text: `${colorText[7]}`, + color_hover: `${colorHover[7]}`, + color_hover_text: `${colorHoverText[7]}`, + }, + { + color_override: colorOverride[8], + area: 'OtF-Notes', + rollable_css: `${SETTING_COLOR_ROLLABLE[8]}`, + color_background: `${colorBackground[8]}`, + color_text: `${colorText[8]}`, + color_hover: `${colorHover[8]}`, + color_hover_text: `${colorHoverText[8]}`, + }, + { + color_override: colorOverride[9], + area: 'Ads/Disads', + rollable_css: `${SETTING_COLOR_ROLLABLE[9]}`, + color_background: `${colorBackground[9]}`, + color_text: `${colorText[9]}`, + color_hover: `${colorHover[9]}`, + color_hover_text: `${colorHoverText[9]}`, + }, + ], + } + // eslint-disable-next-line no-undef + game.settings.set(SYSTEM_NAME, SETTING_COLOR_CHARACTER_SHEET_DATA, data) + // console.log(`Saved Character Sheet Colors: ${JSON.stringify(data)}`) +} diff --git a/module/damage/applydamage.js b/module/damage/applydamage.js index 9e9b2ed07..31e4a8fcf 100755 --- a/module/damage/applydamage.js +++ b/module/damage/applydamage.js @@ -2,14 +2,14 @@ import { CompositeDamageCalculator } from './damagecalculator.js' import { - isNiceDiceEnabled, - parseFloatFrom, - parseIntFrom, - generateUniqueId, - objectToArray, - i18n, - displayMod, - locateToken, + isNiceDiceEnabled, + parseFloatFrom, + parseIntFrom, + generateUniqueId, + objectToArray, + i18n, + displayMod, + locateToken, } from '../../lib/utilities.js' import * as settings from '../../lib/miscellaneous-settings.js' import { digitsAndDecimalOnly, digitsOnly } from '../../lib/jquery-helper.js' @@ -34,639 +34,639 @@ const simpleDialogHeight = 130 * } */ export default class ApplyDamageDialog extends Application { - /** - * Create a new ADD. - * - * @param {GurpsActor} actor - * @param {Array} damageData - * @param {*} options - */ - constructor(actor, damageData, options = {}) { - super(options) - - if (!Array.isArray(damageData)) damageData = [damageData] - - this._calculator = new CompositeDamageCalculator(actor, damageData) - this.actor = actor - this.isSimpleDialog = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SIMPLE_DAMAGE) - this.timesToApply = 1 - - let trackers = objectToArray(actor._additionalResources.tracker) - this._resourceLabels = trackers.filter(it => !!it.isDamageType).filter(it => !!it.alias) - - // console.log(this._resourceLabels) - } - - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['boilerplate', 'sheet', 'actor'], - id: 'apply-damage-dialog', - template: 'systems/gurps/templates/apply-damage/apply-damage-dialog.html', - resizable: true, - minimizable: false, - width: 800, - height: game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SIMPLE_DAMAGE) ? simpleDialogHeight : 'auto', - title: game.i18n.localize('GURPS.addApplyDamageDialog'), - }) - } - - getData() { - let data = super.getData() - data.actor = this.actor - data.CALC = this._calculator - data.timesToApply = this.timesToApply - data.isSimpleDialog = this.isSimpleDialog - data.resourceLabels = this._resourceLabels - return data - } - - /* - * Wire the logic to the UI. - */ - activateListeners(html) { - super.activateListeners(html) - - // Activate all PDF links - html.find('.pdflink').on('click', handleOnPdf) - html.find('.digits-only').inputFilter(value => digitsOnly.test(value)) - html.find('.decimal-digits-only').inputFilter(value => digitsAndDecimalOnly.test(value)) - - // ==== Multiple Dice ==== - html.find('#pagination-left').on('click', ev => { - if (this._calculator.viewId === 'all') return - if (this._calculator.viewId === 0) this._calculator.viewId = 'all' - else this._calculator.viewId = this._calculator.viewId - 1 - - this.updateUI() - }) - - html.find('#pagination-right').on('click', ev => { - if (this._calculator.viewId === 'all') this._calculator.viewId = 0 - else { - let index = this._calculator.viewId + 1 - if (index >= this._calculator.length) return - this._calculator.viewId = index - } - this.updateUI() - }) - - for (let index = 0; index < this._calculator.length; index++) { - html.find(`#pagination-${index}`).on('click', ev => { - this._calculator.viewId = index - this.updateUI() - }) - } - - html.find('#pagination-all').on('click', ev => { - this._calculator.viewId = 'all' - this.updateUI() - }) - - // ==== Simple Damage ==== - html - .find('#basicDamage') - .on('change', ev => this._updateModelFromInputText($(ev.currentTarget), 'basicDamage', parseIntFrom)) - - html.find('#apply-publicly').on('click', ev => { - this.submitDirectApply(ev.shiftKey, true) - }) - - html.find('#apply-secretly').on('click', ev => { - let content = html.find('#apply-dropdown') - this._toggleVisibility(content, content.hasClass('invisible')) - this.submitDirectApply(ev.shiftKey, false) - }) - - html.find('#apply-keep').on('click', ev => { - let content = html.find('#apply-dropdown') - this._toggleVisibility(content, content.hasClass('invisible')) - this.submitDirectApply(true, true) - }) - - html.find('#apply-secretly-keep').on('click', ev => { - let content = html.find('#apply-dropdown') - this._toggleVisibility(content, content.hasClass('invisible')) - this.submitDirectApply(true, false) - }) - - html.find('#apply-split').on('click', ev => { - let content = html.find('#apply-dropdown') - this._toggleVisibility(content, content.hasClass('invisible')) - }) - - // When dropdown changes, update the calculator and refresh GUI. - html.find('#apply-to').on('change', ev => { - this._calculator.applyTo = $(ev.currentTarget).find('option:selected').val() - this.updateUI() - }) - - // ==== Hit Location and DR ==== - // When user-entered DR input changes, update the calculator. - html - .find('#override-dr input') - .on('change', ev => this._updateModelFromInputText($(ev.currentTarget), 'userEnteredDR', parseIntFrom)) - - // clear the user override of the Override DR value - html.find('#override-dr button').click(() => { - this._calculator.userEnteredDR = null - this.updateUI() - }) - - html.find('#apply-multiple').on('change', ev => { - let temp = $(ev.currentTarget).val() - temp = parseIntFrom(temp, 1) - this.timesToApply = temp - }) - - // If the current hit location is Random, resolve the die roll and update the hit location. - if (this._calculator.hitLocation === 'Random') this._randomizeHitLocation() - - // When the 'random' button is clicked, update the hit location. - html.find('#random-location').on('click', async () => this._randomizeHitLocation()) - - // When a new Hit Location is selected, calculate the new results and update the UI. - html - .find('input[name="hitlocation"]') - .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'hitLocation')) - - // ==== Type and Wounding Modifiers ==== - html - .find('input[name="woundmodifier"]') - .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'damageType')) - - html - .find('#user-entered-woundmod') - .on('change', ev => - this._updateModelFromInputText($(ev.currentTarget), 'userEnteredWoundModifier', parseFloatFrom) - ) - - // When 'Additional Mods' text changes, save the (numeric) value in this object and - // update the result-addmodifier, if necessary. - html - .find('#addmodifier') - .on('change', ev => - this._updateModelFromInputText($(ev.currentTarget), 'additionalWoundModifier', parseFloatFrom) - ) - - html - .find('#adddamagemodifier') - .on('change', ev => this._updateModelFromInputText($(ev.currentTarget), 'damageModifier', t => t)) - - // ==== Tactical Rules ==== - // use armor divisor rules - html - .find('#tactical-armordivisor') - .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'useArmorDivisor')) - - // armor divisor level - html - .find('select[name="tactical-armordivisor"]') - .on('change', ev => this._updateModelFromSelect($(ev.currentTarget), 'armorDivisor', parseFloat)) - - // use blunt trauma rules - html - .find('#tactical-blunttrauma') - .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'useBluntTrauma')) - - // use hit location wounding modifiers rules - html - .find('#tactical-locationmodifier') - .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'useLocationModifiers')) - - // ==== Other situations ==== - // is a ranged attack and at 1/2 damage or further range - html - .find('#specials-range12D') - .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isRangedHalfDamage')) - - // target is vulnerable to this attack - html.find('#vulnerable').click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isVulnerable')) - - // Vulnerability level - html - .find('input[name="vulnerability"]') - .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'vulnerabilityMultiple', parseFloat)) - - // target has Hardened DR - html.find('#hardened').click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isHardenedDR')) - - // Hardened DR level - html - .find('input[name="hardened"]') - .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'hardenedDRLevel', parseFloat)) - - html - .find('select[name="hardened"]') - .on('change', ev => this._updateModelFromSelect($(ev.currentTarget), 'hardenedDRLevel', parseInt)) - - // target has Injury Tolerance - html - .find('#injury-tolerance') - .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isInjuryTolerance')) - - // type of Injury Tolerance - html - .find('input[name="injury-tolerance"]') - .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'injuryToleranceType')) - - // if checked, target has Injury Tolerance (Damage Reduction) - html.find('#damage-reduction').click(ev => { - if (!$(ev.currentTarget).is(':checked')) { - this._calculator.damageReductionLevel = null - this.updateUI() - } - this._updateModelFromCheckedElement($(ev.currentTarget), 'useDamageReduction') - }) - - // damage reduction level field - html - .find('#damage-reduction-field input') - .on('change', ev => this._updateModelFromInputText($(ev.currentTarget), 'damageReductionLevel', parseIntFrom)) - - // clear the damage reduction level field - html.find('#damage-reduction-field button').click(() => { - this._calculator.damageReductionLevel = null - this.updateUI() - }) - - // if checked, target has flexible armor; check for blunt trauma - html - .find('#flexible-armor') - .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isFlexibleArmor')) - - // Blunt Trauma user override text field - html.find('#blunt-trauma-field input').on('change', ev => { - let currentValue = $(ev.currentTarget).val() - this._calculator.bluntTrauma = - currentValue === '' || currentValue === this._calculator.calculatedBluntTrauma ? null : parseFloat(currentValue) - this.updateUI() - }) - - // clear the user override of the Blunt trauma value - html.find('#blunt-trauma-field button').click(() => { - this._calculator.bluntTrauma = null - this.updateUI() - }) - - // if checked, target has flexible armor; check for blunt trauma - html.find('#explosion-damage').click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isExplosion')) - - html.find('#explosion-yards').on('change', ev => { - let currentValue = $(ev.currentTarget).val() - this._calculator.hexesFromExplosion = currentValue === '' || currentValue === '0' ? 1 : parseInt(currentValue) - this.updateUI() - }) - - html.find('#shotgun-damage').click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isShotgun')) - - html.find('#shotgun-rof-multiplier').on('change', ev => { - let currentValue = $(ev.currentTarget).val() - this._calculator.shotgunRofMultiplier = currentValue === '' || currentValue === '0' ? 1 : parseInt(currentValue) - this.updateUI() - }) - - // ==== Results ==== - html.find('#result-effects button').click(async ev => this._handleEffectButtonClick(ev)) - - html.find('#apply-injury-split').on('click', ev => { - let content = html.find('#apply-injury-dropdown') - this._toggleVisibility(content, content.hasClass('invisible')) - }) - - html.find('#apply-injury-publicly').click(ev => { - this.submitInjuryApply(ev, ev.shiftKey, true) - }) - - html.find('#apply-injury-secretly').on('click', ev => { - let content = html.find('#apply-injury-dropdown') - this._toggleVisibility(content, content.hasClass('invisible')) - this.submitInjuryApply(ev, ev.shiftKey, false) - }) - - html.find('#apply-injury-keep').on('click', ev => { - let content = html.find('#apply-injury-dropdown') - this._toggleVisibility(content, content.hasClass('invisible')) - this.submitInjuryApply(ev, true, true) - }) - - html.find('#apply-injury-secretly-keep').on('click', ev => { - let content = html.find('#apply-injury-dropdown') - this._toggleVisibility(content, content.hasClass('invisible')) - this.submitInjuryApply(ev, true, false) - }) - } - - /** - * - * @param {node} element to get value from - * @param {string} property name in the model to update - * @param {function(string) : any} converter function to covert element value (string) to model data type - */ - _updateModelFromInputText(element, property, converter) { - this._calculator[property] = converter(element.val()) - - // removes leading zeros or replaces blank with zero - if (element.val() !== this._calculator[property].toString()) element.val(this._calculator[property]) - - this.updateUI() - } - - /** - * Update the model based on the property name. - * @param {*} element - * @param {*} property - * @param {*} converter - */ - _updateModelFromRadioValue( - element, - property, - converter = value => { - return value - } - ) { - if (element.is(':checked')) { - this._calculator[property] = converter(element.val()) - this.updateUI() - } - } - - /** - * - * @param {HtmlElement} select - * @param {String} property of model to update - * @param {Function} converter optional converter function; by default its the identity function - */ - _updateModelFromSelect( - select, - property, - converter = value => { - return value - } - ) { - let valueText = select.find('option:selected').val() - this._calculator[property] = converter(valueText) - this.updateUI() - } - - /** - * Update the damage calculator property from a UI element that has the 'checked' attribute - * @param {*} element - * @param {*} property - */ - _updateModelFromCheckedElement(element, property) { - this._calculator[property] = element.is(':checked') - this.updateUI() - } - - /** - * Ask the calculator to randomly select a hit location, and return the roll used. - */ - async _randomizeHitLocation() { - let roll3d = await this._calculator.randomizeHitLocation() - - if (isNiceDiceEnabled()) { - let throws = [] - let dc = [] - roll3d.dice.forEach(die => { - let type = 'd' + die.faces - die.results.forEach(s => - dc.push({ - result: s.result, - resultLabel: s.result, - type: type, - vectors: [], - options: {}, - }) - ) - }) - throws.push({ dice: dc }) - if (dc.length > 0) { - // The user made a "multi-damage" roll... let them see the dice! - // @ts-ignore - game.dice3d.show({ throws: throws }).then(display => this.updateUI()) - } - } else { - AudioHelper.play({ src: CONFIG.sounds.dice }) - this.updateUI() - } - } - - _toggleVisibility(element, isVisible) { - if (isVisible) { - element.removeClass('invisible') - } else { - element.addClass('invisible') - } - } - - /** - * Updates the UI based on the current state of the _calculator. - */ - updateUI() { - this.render(false) - } - - async _renderTemplate(template, data) { - return renderTemplate('systems/gurps/templates/apply-damage/' + template, data) - } - - /** - * Create and show the chat message for the Effect. - * @param {*} ev - */ - async _handleEffectButtonClick(ev) { - // TODO allow click to automatically apply effect to a selected target - let stringified = ev.currentTarget.attributes['data-struct'].value - let object = JSON.parse(stringified) - - let token = null - if (this.actor.isToken) { - token = this.actor?.token - } else { - let tokens = locateToken(this.actor.id) - token = tokens.length === 1 ? tokens[0] : null - } - - let message = '' - - if (object.type === 'shock') { - let button = `/st + shock${object.amount}` - if (!!token) button = `/sel ${token.id} \\\\ ${button}` - - message = await this._renderTemplate('chat-shock.html', { - name: !!token ? token.name : this.actor.name, - modifier: object.amount, - doubled: object.amount * 2, - button: button, - }) - } - - if (object.type === 'majorwound') { - let htCheck = - object.modifier === 0 ? 'HT' : object.modifier < 0 ? `HT+${-object.modifier}` : `HT-${object.modifier}` - let button = `/if ! [${htCheck}] {/st + stun \\\\ /st + prone}` - if (!!token) button = `/sel ${token.id} \\\\ ${button}` - - message = await this._renderTemplate('chat-majorwound.html', { - name: !!token ? token.name : this.actor.name, - button: button, - htCheck: htCheck.replace('HT', i18n('GURPS.attributesHT')), - }) - } - - if (object.type === 'headvitalshit') { - let htCheck = - object.modifier === 0 ? 'HT' : object.modifier < 0 ? `HT+${-object.modifier}` : `HT-${object.modifier}` - let button = `/if ! [${htCheck}] {/st + stun \\\\ /st + prone}` - if (!!token) button = `/sel ${token.id} \\\\ ${button}` - - message = await this._renderTemplate('chat-headvitalshit.html', { - name: !!token ? token.name : this.actor.name, - button: button, - location: object.detail, - htCheck: htCheck.replace('HT', i18n('GURPS.attributesHT')), - }) - } - - if (object.type === 'knockback') { - let dx = i18n('GURPS.attributesDX') - let dxCheck = object.modifier === 0 ? dx : `${dx}-${object.modifier}` - let acro = i18n('GURPS.skillAcrobatics') - let acroCheck = object.modifier === 0 ? acro : `${acro}-${object.modifier}` - let judo = i18n('GURPS.skillJudo') - let judoCheck = object.modifier === 0 ? judo : `${judo}-${object.modifier}` - - let button = `/if ! [${dxCheck}|Sk:${acroCheck}|Sk:${judoCheck}] /st + prone` - if (!!token) button = `/sel ${token.id} \\\\ ${button}` - - let templateData = { - name: !!token ? token.name : this.actor.name, - button: button, - yards: object.amount, - pdfref: i18n('GURPS.pdfKnockback'), - unit: object.amount > 1 ? i18n('GURPS.yards') : i18n('GURPS.yard'), - dx: dxCheck.replace('-', '−'), - acrobatics: acroCheck.replace('-', '−'), - judo: judoCheck.replace('-', '−'), - classStart: '', - classEnd: '', - } - - message = await this._renderTemplate('chat-knockback.html', templateData) - } - - if (object.type === 'crippling') { - message = await this._renderTemplate('chat-crippling.html', { - name: this.actor.name, - location: object.detail, - groundModifier: 'DX-1', - swimFlyModifer: 'DX-2', - pdfref: i18n('GURPS.pdfCrippling'), - classStart: '', - classEnd: '', - }) - } - - let msgData = { - content: message, - user: game.user.id, - type: CONST.CHAT_MESSAGE_TYPES.OOC, - } - if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_WHISPER_STATUS_EFFECTS)) { - let users = this.actor.getOwners() - let ids = users.map(it => it.id) - msgData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER - msgData.whisper = ids - } - - ChatMessage.create(msgData) - } - - _getModifierText(value) { - let result = displayMod(value) - if (result === '0') result = '' - return result - } - - /** - * Handle clicking on the Apply (Publicly or Secretly) buttons. - * @param {boolean} publicly - if true, display to everyone; else display to GM and owner. - */ - submitDirectApply(keepOpen, publicly) { - let injury = this._calculator.basicDamage - this.resolveInjury(keepOpen, injury, publicly) - } - - /** - * Handle clicking on the Apply Injury (public or secret) buttons. - * @param {boolean} publicly - if true, display to everyone; else display to GM and owner. - */ - async submitInjuryApply(ev, keepOpen, publicly) { - let injury = this._calculator.pointsToApply - - let dialog = $(ev.currentTarget).parents('.gga-app') - let results = $(dialog).find('.results-table') - let clone = results.clone().html() - - for (let index = 0; index < this.timesToApply; index++) { - await this.resolveInjury(keepOpen, injury, publicly, clone) - } - } - - /** - * Handle the actual loss of HP or FP on the actor and display the results in the chat. - * @param {boolean} keepOpen - if true, apply the damage and keep this window open. - * @param {int} injury - * @param {String} type - a valid damage type (including resources) - * @param {boolean} publicly - if true, display to everyone; else display to GM and owner. - */ - async resolveInjury(keepOpen, injury, publicly, results = null) { - let [resource, path] = this._calculator.resource - - if (!resource || !path) { - ui.notifications.warn( - `Actor ${this.actor.name} does not have a resource named "${this._calculator.damageType}"!!` - ) - return - } - - let attackingActor = game.actors.get(this._calculator.attacker) - - let data = { - id: generateUniqueId(), - injury: injury, - defender: this.actor.name, - current: resource.value, - location: this._calculator.resourceType === 'HP' ? this._calculator.hitLocation : null, - type: this._calculator.resourceType, - resultsTable: results, - } - - let newValue = resource.isDamageTracker ? resource.value + injury : resource.value - injury - - let update = {} - update[`${path}.value`] = newValue - await this.actor.update(update) - - this._renderTemplate('chat-damage-results.html', data).then(html => { - let speaker = ChatMessage.getSpeaker(game.user) - if (!!attackingActor) speaker = ChatMessage.getSpeaker(attackingActor) - let messageData = { - user: game.user.id, - speaker: speaker, - content: html, - type: CONST.CHAT_MESSAGE_TYPES.OTHER, - } - - if (!publicly) { - let users = this.actor.getOwners() - let ids = users.map(it => it.id) - messageData.whisper = ids - messageData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER - } - - ChatMessage.create(messageData) - if (!keepOpen) this.close() - }) - } + /** + * Create a new ADD. + * + * @param {GurpsActor} actor + * @param {Array} damageData + * @param {*} options + */ + constructor(actor, damageData, options = {}) { + super(options) + + if (!Array.isArray(damageData)) damageData = [damageData] + + this._calculator = new CompositeDamageCalculator(actor, damageData) + this.actor = actor + this.isSimpleDialog = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SIMPLE_DAMAGE) + this.timesToApply = 1 + + let trackers = objectToArray(actor._additionalResources.tracker) + this._resourceLabels = trackers.filter(it => !!it.isDamageType).filter(it => !!it.alias) + + // console.log(this._resourceLabels) + } + + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['boilerplate', 'sheet', 'actor'], + id: 'apply-damage-dialog', + template: 'systems/gurps/templates/apply-damage/apply-damage-dialog.html', + resizable: true, + minimizable: false, + width: 800, + height: game.settings.get(settings.SYSTEM_NAME, settings.SETTING_SIMPLE_DAMAGE) ? simpleDialogHeight : 'auto', + title: game.i18n.localize('GURPS.addApplyDamageDialog'), + }) + } + + getData() { + let data = super.getData() + data.actor = this.actor + data.CALC = this._calculator + data.timesToApply = this.timesToApply + data.isSimpleDialog = this.isSimpleDialog + data.resourceLabels = this._resourceLabels + return data + } + + /* + * Wire the logic to the UI. + */ + activateListeners(html) { + super.activateListeners(html) + + // Activate all PDF links + html.find('.pdflink').on('click', handleOnPdf) + html.find('.digits-only').inputFilter(value => digitsOnly.test(value)) + html.find('.decimal-digits-only').inputFilter(value => digitsAndDecimalOnly.test(value)) + + // ==== Multiple Dice ==== + html.find('#pagination-left').on('click', ev => { + if (this._calculator.viewId === 'all') return + if (this._calculator.viewId === 0) this._calculator.viewId = 'all' + else this._calculator.viewId = this._calculator.viewId - 1 + + this.updateUI() + }) + + html.find('#pagination-right').on('click', ev => { + if (this._calculator.viewId === 'all') this._calculator.viewId = 0 + else { + let index = this._calculator.viewId + 1 + if (index >= this._calculator.length) return + this._calculator.viewId = index + } + this.updateUI() + }) + + for (let index = 0; index < this._calculator.length; index++) { + html.find(`#pagination-${index}`).on('click', ev => { + this._calculator.viewId = index + this.updateUI() + }) + } + + html.find('#pagination-all').on('click', ev => { + this._calculator.viewId = 'all' + this.updateUI() + }) + + // ==== Simple Damage ==== + html + .find('#basicDamage') + .on('change', ev => this._updateModelFromInputText($(ev.currentTarget), 'basicDamage', parseIntFrom)) + + html.find('#apply-publicly').on('click', ev => { + this.submitDirectApply(ev.shiftKey, true) + }) + + html.find('#apply-secretly').on('click', ev => { + let content = html.find('#apply-dropdown') + this._toggleVisibility(content, content.hasClass('invisible')) + this.submitDirectApply(ev.shiftKey, false) + }) + + html.find('#apply-keep').on('click', ev => { + let content = html.find('#apply-dropdown') + this._toggleVisibility(content, content.hasClass('invisible')) + this.submitDirectApply(true, true) + }) + + html.find('#apply-secretly-keep').on('click', ev => { + let content = html.find('#apply-dropdown') + this._toggleVisibility(content, content.hasClass('invisible')) + this.submitDirectApply(true, false) + }) + + html.find('#apply-split').on('click', ev => { + let content = html.find('#apply-dropdown') + this._toggleVisibility(content, content.hasClass('invisible')) + }) + + // When dropdown changes, update the calculator and refresh GUI. + html.find('#apply-to').on('change', ev => { + this._calculator.applyTo = $(ev.currentTarget).find('option:selected').val() + this.updateUI() + }) + + // ==== Hit Location and DR ==== + // When user-entered DR input changes, update the calculator. + html + .find('#override-dr input') + .on('change', ev => this._updateModelFromInputText($(ev.currentTarget), 'userEnteredDR', parseIntFrom)) + + // clear the user override of the Override DR value + html.find('#override-dr button').click(() => { + this._calculator.userEnteredDR = null + this.updateUI() + }) + + html.find('#apply-multiple').on('change', ev => { + let temp = $(ev.currentTarget).val() + temp = parseIntFrom(temp, 1) + this.timesToApply = temp + }) + + // If the current hit location is Random, resolve the die roll and update the hit location. + if (this._calculator.hitLocation === 'Random') this._randomizeHitLocation() + + // When the 'random' button is clicked, update the hit location. + html.find('#random-location').on('click', async () => this._randomizeHitLocation()) + + // When a new Hit Location is selected, calculate the new results and update the UI. + html + .find('input[name="hitlocation"]') + .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'hitLocation')) + + // ==== Type and Wounding Modifiers ==== + html + .find('input[name="woundmodifier"]') + .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'damageType')) + + html + .find('#user-entered-woundmod') + .on('change', ev => + this._updateModelFromInputText($(ev.currentTarget), 'userEnteredWoundModifier', parseFloatFrom) + ) + + // When 'Additional Mods' text changes, save the (numeric) value in this object and + // update the result-addmodifier, if necessary. + html + .find('#addmodifier') + .on('change', ev => + this._updateModelFromInputText($(ev.currentTarget), 'additionalWoundModifier', parseFloatFrom) + ) + + html + .find('#adddamagemodifier') + .on('change', ev => this._updateModelFromInputText($(ev.currentTarget), 'damageModifier', t => t)) + + // ==== Tactical Rules ==== + // use armor divisor rules + html + .find('#tactical-armordivisor') + .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'useArmorDivisor')) + + // armor divisor level + html + .find('select[name="tactical-armordivisor"]') + .on('change', ev => this._updateModelFromSelect($(ev.currentTarget), 'armorDivisor', parseFloat)) + + // use blunt trauma rules + html + .find('#tactical-blunttrauma') + .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'useBluntTrauma')) + + // use hit location wounding modifiers rules + html + .find('#tactical-locationmodifier') + .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'useLocationModifiers')) + + // ==== Other situations ==== + // is a ranged attack and at 1/2 damage or further range + html + .find('#specials-range12D') + .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isRangedHalfDamage')) + + // target is vulnerable to this attack + html.find('#vulnerable').click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isVulnerable')) + + // Vulnerability level + html + .find('input[name="vulnerability"]') + .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'vulnerabilityMultiple', parseFloat)) + + // target has Hardened DR + html.find('#hardened').click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isHardenedDR')) + + // Hardened DR level + html + .find('input[name="hardened"]') + .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'hardenedDRLevel', parseFloat)) + + html + .find('select[name="hardened"]') + .on('change', ev => this._updateModelFromSelect($(ev.currentTarget), 'hardenedDRLevel', parseInt)) + + // target has Injury Tolerance + html + .find('#injury-tolerance') + .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isInjuryTolerance')) + + // type of Injury Tolerance + html + .find('input[name="injury-tolerance"]') + .click(ev => this._updateModelFromRadioValue($(ev.currentTarget), 'injuryToleranceType')) + + // if checked, target has Injury Tolerance (Damage Reduction) + html.find('#damage-reduction').click(ev => { + if (!$(ev.currentTarget).is(':checked')) { + this._calculator.damageReductionLevel = null + this.updateUI() + } + this._updateModelFromCheckedElement($(ev.currentTarget), 'useDamageReduction') + }) + + // damage reduction level field + html + .find('#damage-reduction-field input') + .on('change', ev => this._updateModelFromInputText($(ev.currentTarget), 'damageReductionLevel', parseIntFrom)) + + // clear the damage reduction level field + html.find('#damage-reduction-field button').click(() => { + this._calculator.damageReductionLevel = null + this.updateUI() + }) + + // if checked, target has flexible armor; check for blunt trauma + html + .find('#flexible-armor') + .click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isFlexibleArmor')) + + // Blunt Trauma user override text field + html.find('#blunt-trauma-field input').on('change', ev => { + let currentValue = $(ev.currentTarget).val() + this._calculator.bluntTrauma = + currentValue === '' || currentValue === this._calculator.calculatedBluntTrauma ? null : parseFloat(currentValue) + this.updateUI() + }) + + // clear the user override of the Blunt trauma value + html.find('#blunt-trauma-field button').click(() => { + this._calculator.bluntTrauma = null + this.updateUI() + }) + + // if checked, target has flexible armor; check for blunt trauma + html.find('#explosion-damage').click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isExplosion')) + + html.find('#explosion-yards').on('change', ev => { + let currentValue = $(ev.currentTarget).val() + this._calculator.hexesFromExplosion = currentValue === '' || currentValue === '0' ? 1 : parseInt(currentValue) + this.updateUI() + }) + + html.find('#shotgun-damage').click(ev => this._updateModelFromCheckedElement($(ev.currentTarget), 'isShotgun')) + + html.find('#shotgun-rof-multiplier').on('change', ev => { + let currentValue = $(ev.currentTarget).val() + this._calculator.shotgunRofMultiplier = currentValue === '' || currentValue === '0' ? 1 : parseInt(currentValue) + this.updateUI() + }) + + // ==== Results ==== + html.find('#result-effects button').click(async ev => this._handleEffectButtonClick(ev)) + + html.find('#apply-injury-split').on('click', ev => { + let content = html.find('#apply-injury-dropdown') + this._toggleVisibility(content, content.hasClass('invisible')) + }) + + html.find('#apply-injury-publicly').click(ev => { + this.submitInjuryApply(ev, ev.shiftKey, true) + }) + + html.find('#apply-injury-secretly').on('click', ev => { + let content = html.find('#apply-injury-dropdown') + this._toggleVisibility(content, content.hasClass('invisible')) + this.submitInjuryApply(ev, ev.shiftKey, false) + }) + + html.find('#apply-injury-keep').on('click', ev => { + let content = html.find('#apply-injury-dropdown') + this._toggleVisibility(content, content.hasClass('invisible')) + this.submitInjuryApply(ev, true, true) + }) + + html.find('#apply-injury-secretly-keep').on('click', ev => { + let content = html.find('#apply-injury-dropdown') + this._toggleVisibility(content, content.hasClass('invisible')) + this.submitInjuryApply(ev, true, false) + }) + } + + /** + * + * @param {node} element to get value from + * @param {string} property name in the model to update + * @param {function(string) : any} converter function to covert element value (string) to model data type + */ + _updateModelFromInputText(element, property, converter) { + this._calculator[property] = converter(element.val()) + + // removes leading zeros or replaces blank with zero + if (element.val() !== this._calculator[property].toString()) element.val(this._calculator[property]) + + this.updateUI() + } + + /** + * Update the model based on the property name. + * @param {*} element + * @param {*} property + * @param {*} converter + */ + _updateModelFromRadioValue( + element, + property, + converter = value => { + return value + } + ) { + if (element.is(':checked')) { + this._calculator[property] = converter(element.val()) + this.updateUI() + } + } + + /** + * + * @param {HtmlElement} select + * @param {String} property of model to update + * @param {Function} converter optional converter function; by default its the identity function + */ + _updateModelFromSelect( + select, + property, + converter = value => { + return value + } + ) { + let valueText = select.find('option:selected').val() + this._calculator[property] = converter(valueText) + this.updateUI() + } + + /** + * Update the damage calculator property from a UI element that has the 'checked' attribute + * @param {*} element + * @param {*} property + */ + _updateModelFromCheckedElement(element, property) { + this._calculator[property] = element.is(':checked') + this.updateUI() + } + + /** + * Ask the calculator to randomly select a hit location, and return the roll used. + */ + async _randomizeHitLocation() { + let roll3d = await this._calculator.randomizeHitLocation() + + if (isNiceDiceEnabled()) { + let throws = [] + let dc = [] + roll3d.dice.forEach(die => { + let type = 'd' + die.faces + die.results.forEach(s => + dc.push({ + result: s.result, + resultLabel: s.result, + type: type, + vectors: [], + options: {}, + }) + ) + }) + throws.push({ dice: dc }) + if (dc.length > 0) { + // The user made a "multi-damage" roll... let them see the dice! + // @ts-ignore + game.dice3d.show({ throws: throws }).then(display => this.updateUI()) + } + } else { + AudioHelper.play({ src: CONFIG.sounds.dice }) + this.updateUI() + } + } + + _toggleVisibility(element, isVisible) { + if (isVisible) { + element.removeClass('invisible') + } else { + element.addClass('invisible') + } + } + + /** + * Updates the UI based on the current state of the _calculator. + */ + updateUI() { + this.render(false) + } + + async _renderTemplate(template, data) { + return renderTemplate('systems/gurps/templates/apply-damage/' + template, data) + } + + /** + * Create and show the chat message for the Effect. + * @param {*} ev + */ + async _handleEffectButtonClick(ev) { + // TODO allow click to automatically apply effect to a selected target + let stringified = ev.currentTarget.attributes['data-struct'].value + let object = JSON.parse(stringified) + + let token = null + if (this.actor.isToken) { + token = this.actor?.token + } else { + let tokens = locateToken(this.actor.id) + token = tokens.length === 1 ? tokens[0] : null + } + + let message = '' + + if (object.type === 'shock') { + let button = `/st + shock${object.amount}` + if (!!token) button = `/sel ${token.id} \\\\ ${button}` + + message = await this._renderTemplate('chat-shock.html', { + name: !!token ? token.name : this.actor.name, + modifier: object.amount, + doubled: object.amount * 2, + button: button, + }) + } + + if (object.type === 'majorwound') { + let htCheck = + object.modifier === 0 ? 'HT' : object.modifier < 0 ? `HT+${-object.modifier}` : `HT-${object.modifier}` + let button = `/if ! [${htCheck}] {/st + stun \\\\ /st + prone}` + if (!!token) button = `/sel ${token.id} \\\\ ${button}` + + message = await this._renderTemplate('chat-majorwound.html', { + name: !!token ? token.name : this.actor.name, + button: button, + htCheck: htCheck.replace('HT', i18n('GURPS.attributesHT')), + }) + } + + if (object.type === 'headvitalshit') { + let htCheck = + object.modifier === 0 ? 'HT' : object.modifier < 0 ? `HT+${-object.modifier}` : `HT-${object.modifier}` + let button = `/if ! [${htCheck}] {/st + stun \\\\ /st + prone}` + if (!!token) button = `/sel ${token.id} \\\\ ${button}` + + message = await this._renderTemplate('chat-headvitalshit.html', { + name: !!token ? token.name : this.actor.name, + button: button, + location: object.detail, + htCheck: htCheck.replace('HT', i18n('GURPS.attributesHT')), + }) + } + + if (object.type === 'knockback') { + let dx = i18n('GURPS.attributesDX') + let dxCheck = object.modifier === 0 ? dx : `${dx}-${object.modifier}` + let acro = i18n('GURPS.skillAcrobatics') + let acroCheck = object.modifier === 0 ? acro : `${acro}-${object.modifier}` + let judo = i18n('GURPS.skillJudo') + let judoCheck = object.modifier === 0 ? judo : `${judo}-${object.modifier}` + + let button = `/if ! [${dxCheck}|Sk:${acroCheck}|Sk:${judoCheck}] /st + prone` + if (!!token) button = `/sel ${token.id} \\\\ ${button}` + + let templateData = { + name: !!token ? token.name : this.actor.name, + button: button, + yards: object.amount, + pdfref: i18n('GURPS.pdfKnockback'), + unit: object.amount > 1 ? i18n('GURPS.yards') : i18n('GURPS.yard'), + dx: dxCheck.replace('-', '−'), + acrobatics: acroCheck.replace('-', '−'), + judo: judoCheck.replace('-', '−'), + classStart: '', + classEnd: '', + } + + message = await this._renderTemplate('chat-knockback.html', templateData) + } + + if (object.type === 'crippling') { + message = await this._renderTemplate('chat-crippling.html', { + name: this.actor.name, + location: object.detail, + groundModifier: 'DX-1', + swimFlyModifer: 'DX-2', + pdfref: i18n('GURPS.pdfCrippling'), + classStart: '', + classEnd: '', + }) + } + + let msgData = { + content: message, + user: game.user.id, + type: CONST.CHAT_MESSAGE_TYPES.OOC, + } + if (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_WHISPER_STATUS_EFFECTS)) { + let users = this.actor.getOwners() + let ids = users.map(it => it.id) + msgData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER + msgData.whisper = ids + } + + ChatMessage.create(msgData) + } + + _getModifierText(value) { + let result = displayMod(value) + if (result === '0') result = '' + return result + } + + /** + * Handle clicking on the Apply (Publicly or Secretly) buttons. + * @param {boolean} publicly - if true, display to everyone; else display to GM and owner. + */ + submitDirectApply(keepOpen, publicly) { + let injury = this._calculator.basicDamage + this.resolveInjury(keepOpen, injury, publicly) + } + + /** + * Handle clicking on the Apply Injury (public or secret) buttons. + * @param {boolean} publicly - if true, display to everyone; else display to GM and owner. + */ + async submitInjuryApply(ev, keepOpen, publicly) { + let injury = this._calculator.pointsToApply + + let dialog = $(ev.currentTarget).parents('.gga-app') + let results = $(dialog).find('.results-table') + let clone = results.clone().html() + + for (let index = 0; index < this.timesToApply; index++) { + await this.resolveInjury(keepOpen, injury, publicly, clone) + } + } + + /** + * Handle the actual loss of HP or FP on the actor and display the results in the chat. + * @param {boolean} keepOpen - if true, apply the damage and keep this window open. + * @param {int} injury + * @param {String} type - a valid damage type (including resources) + * @param {boolean} publicly - if true, display to everyone; else display to GM and owner. + */ + async resolveInjury(keepOpen, injury, publicly, results = null) { + let [resource, path] = this._calculator.resource + + if (!resource || !path) { + ui.notifications.warn( + `Actor ${this.actor.name} does not have a resource named "${this._calculator.damageType}"!!` + ) + return + } + + let attackingActor = game.actors.get(this._calculator.attacker) + + let data = { + id: generateUniqueId(), + injury: injury, + defender: this.actor.name, + current: resource.value, + location: this._calculator.resourceType === 'HP' ? this._calculator.hitLocation : null, + type: this._calculator.resourceType, + resultsTable: results, + } + + let newValue = resource.isDamageTracker ? resource.value + injury : resource.value - injury + + let update = {} + update[`${path}.value`] = newValue + await this.actor.update(update) + + this._renderTemplate('chat-damage-results.html', data).then(html => { + let speaker = ChatMessage.getSpeaker(game.user) + if (!!attackingActor) speaker = ChatMessage.getSpeaker(attackingActor) + let messageData = { + user: game.user.id, + speaker: speaker, + content: html, + type: CONST.CHAT_MESSAGE_TYPES.OTHER, + } + + if (!publicly) { + let users = this.actor.getOwners() + let ids = users.map(it => it.id) + messageData.whisper = ids + messageData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER + } + + ChatMessage.create(messageData) + if (!keepOpen) this.close() + }) + } } diff --git a/module/damage/damagecalculator.js b/module/damage/damagecalculator.js index b69cd7e9d..89edf3495 100755 --- a/module/damage/damagecalculator.js +++ b/module/damage/damagecalculator.js @@ -27,913 +27,913 @@ const UNLIVING = 'unliving' const armorDivisorSteps = [-1, 100, 10, 5, 3, 2, 1] export class CompositeDamageCalculator { - /** - * Create a composite damage calculator, which is a damage calculator that - * wraps multiple damage calculators. - * - * The basic assumption made by this composite damage calculation is that - * all variables (hit location, armor divisor, damage type, etc) are the same - * for every damage roll; the only thing that is different is the amount of - * damage. - * - * @param {*} defender - * @param {DamageData[]} damageData - */ - constructor(defender, damageData) { - this._useBluntTrauma = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_BLUNT_TRAUMA) - this._useLocationModifiers = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_LOCATION_MODIFIERS) - this._useArmorDivisor = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_APPLY_DIVISOR) - - this._defender = defender - - // The CompositeDamageCalculator has multiple DamageCalculators -- create one per DamageData - // and give it a back pointer to the Composite. - this._calculators = damagesystem.map(data => new DamageCalculator(this, data)) - - this.viewId = this._calculators.length == 1 ? 0 : 'all' - - this._defaultWoundingModifiers = Object.keys(GURPS.DamageTables.woundModifiers).reduce(function(r, e) { - if (!GURPS.DamageTables.woundModifiers[e].nodisplay) r[e] = GURPS.DamageTables.woundModifiers[e] - return r - }, {}) - - this._attacker = damageData[0].attacker - - if (Object.keys(this._defaultWoundingModifiers).includes(damageData[0].damageType)) - this._damageType = damageData[0].damageType - else { - let temp = GURPS.DamageTables.translate(damageData[0].damageType) - if (temp) this._damageType = temp - else this._damageType = 'none' - } - - if (!!CompositeDamageCalculator.isResourceDamageType(this._damageType)) { - this._applyTo = this._damageType - } else { - this._applyTo = this._damageType === 'fat' ? 'FP' : 'HP' - } - - this._damageModifier = damageData[0].damageModifier - - this._armorDivisor = damageData[0].armorDivisor - if (this._armorDivisor === 0) { - this._useArmorDivisor = false - } - - let hitlocations = objectToArray(this._defender.system.hitlocations) - let wheres = hitlocations.map(it => it.where.toLowerCase()) - let damageLocation = !!damageData[0].hitlocation ? damageData[0].hitlocation.toLowerCase() : '' - let hlIndex = wheres.indexOf(damageLocation) - if (hlIndex >= 0) this._hitLocation = hitlocations[hlIndex].where - else this._hitLocation = this._defender.defaultHitLocation - - this._previousHitLocation = this._hitLocation - this._userEnteredDR = null - - // the wounding modifier selected using the radio buttons - this._userEnteredWoundModifier = 1 - this._additionalWoundModifier = 0 - - this._isRangedHalfDamage = false - this._isFlexibleArmor = false - - this._isVulnerable = false - this._vulnerabilityMultiple = 2 - - this._isHardenedDR = false - this._hardenedDRLevel = 1 - - this._isInjuryTolerance = false - this._injuryToleranceType = null - - // Injury Tolerance (Damage Reduction) is handled separately from other types of IT - this._useDamageReduction = false - this._damageReductionLevel = null - - this._isExplosion = false - this._hexesFromExplosion = 1 - this._explosionDivisor = 1 - - this._isShotgun = false - this._shotgunRofMultiplier = 9 - - // look at defender and automatically set things like Unliving, Diffuse, Homogenous - // if advantage.name === 'Diffuse' -- DFRPG style - // if advantage.name === 'Injury Tolerance' && advantage.notes.startsWith('Diffuse ') -- GCS Basic style - // _isInjuryTolerance = true - // _injuryToleranceType = 'unliving' - let values = Object.values(this._defender.system.ads) - if (this.isUnliving(values, false)) { - this._isInjuryTolerance = true - this._injuryToleranceType = UNLIVING - } - if (this.isHomogenous(values)) { - this._isInjuryTolerance = true - this._injuryToleranceType = HOMOGENOUS - } - if (this.isDiffuse(values)) { - this._isInjuryTolerance = true - this._injuryToleranceType = DIFFUSE - } - } - - isUnliving(values, found) { - if (!found) { - let self = this - found = values.find(value => { - let found = !!( - ['Injury Tolerance (Unliving)', 'Unliving'].includes(value.name) || - (value.name === 'Injury Tolerance' && value.notes.includes('Unliving')) - ) - - if (!found && Object.keys(value.contains).length > 0) { - found = self.isUnliving(Object.values(value.contains), false) - } - return found - }) - } - return !!found - } - - isHomogenous(values, found) { - if (!found) { - let self = this - found = values.find(value => { - let found = !!( - ['Injury Tolerance (Homogenous)', 'Homogenous'].includes(value.name) || - (value.name === 'Injury Tolerance' && value.notes.includes('Homogenous')) - ) - - if (!found && Object.keys(value.contains).length > 0) { - found = self.isHomogenous(Object.values(value.contains), false) - } - return found - }) - } - return !!found - } - - isDiffuse(values, found) { - if (!found) { - let self = this - found = values.find(value => { - let found = !!( - ['Injury Tolerance (Diffuse)', 'Diffuse'].includes(value.name) || - (value.name === 'Injury Tolerance' && value.notes.includes('Diffuse')) - ) - - if (!found && Object.keys(value.contains).length > 0) { - found = self.isDiffuse(Object.values(value.contains), false) - } - return found - }) - } - return !!found - } - - static isResourceDamageType(damageType) { - let modifier = GURPS.DamageTables.woundModifiers[damageType] - return !!modifier && !!GURPS.DamageTables.woundModifiers[damageType].resource - } - - get(viewId) { - if (viewId === 'all') return this - return this._calculators[viewId] - } - - get showApplyAction() { - return ( - game.settings.get(settings.SYSTEM_NAME, settings.SETTING_DEFAULT_ADD_ACTION) == 'apply' || - (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_DEFAULT_ADD_ACTION) == 'target' && - this._defender.hasPlayerOwner) - ) - } - - get additionalWoundModifier() { - return this._additionalWoundModifier - } - - set additionalWoundModifier(value) { - this._additionalWoundModifier = value - } - - get allHitLocations() { - return this._defender.system.hitlocations - } - - get armorDivisor() { - return this._armorDivisor - } - - set armorDivisor(value) { - this._armorDivisor = value - } - - get useArmorDivisor() { - return this._useArmorDivisor - } - - get applyTo() { - return this._applyTo - } - - set applyTo(value) { - this._applyTo = value - } - - get attacker() { - return this._attacker - } - - get attributes() { - return this._defender.system.attributes - } - - /** - * Override at the individual dice roll level. - */ - get basicDamage() { - if (this._viewId === 'all') return this._calculators.reduce((sum, a) => sum + a._basicDamage, 0) - return this._calculators[this._viewId].basicDamage - } - - set basicDamage(value) { - if (this._viewId === 'all') return - this._calculators[this._viewId].basicDamage = value - } - - get calculators() { - return this._calculators - } - - get damageModifier() { - return this._damageModifier - } - - set damageModifier(value) { - this._damageModifier = value - } - - get damageType() { - return this._damageType - } - - set damageType(type) { - this._damageType = type - } - - get defaultWoundModifiers() { - return this._defaultWoundingModifiers - } - - // return the DR indicated by the Hit Location - get DR() { - if (this._userEnteredDR !== null) return this._userEnteredDR - - if (this._hitLocation === 'Random') return 0 - - // if (this._hitLocation === 'User Entered') return this._userEnteredDR - let entries = this._defender.hitLocationsWithDR - - return this._hitLocation === 'Large-Area' - ? HitLocationEntry.getLargeAreaDR(entries) - : HitLocationEntry.findLocation(entries, this._hitLocation).getDR(this.damageType) - } - - get effects() { - // TODO accumulate effects - // each call to _calculator.effects returns an array of effects - // create a flattened array of effects - let effects = [] - this._calculators.map(calculator => calculator.effects).forEach(effect => effects.push(effect)) - effects = effects.flat() - - let results = [] - - // process shock -- shock is the sum of all shock to the maximum of 4 - let shock = effects - .filter(it => it.type === 'shock') - .map(it => it.amount) - .reduce((acc, value, index, array) => { - return acc + value - }, 0) - - if (shock > 0) { - results.push({ - type: 'shock', - amount: Math.min(shock, 4), - }) - } - - // process knockback -- value and modifier is the sum across all hits - let allKnockbacks = effects.filter(it => it.type === 'knockback') - - let knockbackValue = allKnockbacks - .map(it => it.amount) - .reduce((acc, value, index, array) => { - return acc + value - }, 0) - - let knockbackMods = allKnockbacks - .map(it => it.modifier) - .reduce((acc, value, index, array) => { - return acc + value - }, 0) - - if (allKnockbacks.length > 0) { - results.push({ - type: 'knockback', - amount: knockbackValue, - modifier: knockbackMods, - unit: knockbackValue === 1 ? i18n('GURPS.yard') : i18n('GURPS.yards'), - modifierText: !!knockbackMods ? `–${knockbackMods}` : '', - }) - } - - // process crippling -- just keep one of them - let crippling = effects.find(it => it.type === 'crippling') - if (!!crippling) results.push(crippling) - - // process major wound -- just keep one of them - let majorwound = effects.find(it => it.type === 'majorwound') - if (!!majorwound) results.push(majorwound) - - // process headvitalshit -- just keep one of them - let headvitalshit = effects.find(it => it.type === 'headvitalshit') - if (!!headvitalshit) results.push(headvitalshit) - - return results - } - - get effectiveArmorDivisor() { - // Armor Divisors do not apply to Explosions, (B414) - if (this._isExplosion) return 1 - - if ((this._armorDivisor > 1 || this._armorDivisor === -1) && this._isHardenedDR) { - let _divisor = this._armorDivisor == 4 ? 3 : this._armorDivisor //If you're using survivable guns check if it's (4) because it's not part of the regular progression, thus we treat it as 3. - let maxIndex = armorDivisorSteps.length - 1 - let index = armorDivisorSteps.indexOf(_divisor) - index = Math.min(index + this._hardenedDRLevel, maxIndex) - return armorDivisorSteps[index] - } - return this._armorDivisor - } - - get effectiveBluntTrauma() { - if (this._viewId === 'all') return - return this._calculators[this._viewId].effectiveBluntTrauma - } - - get effectiveDamage() { - if (this._viewId === 'all') return - return this._calculators[this._viewId].effectiveDamage - } - - // figure out the current DR modified by armor divisor, if necessary - get effectiveDR() { - let dr = this.DR - - if (this._isShotgun && this._shotgunRofMultiplier > 1) { - dr = dr * this.shotgunDamageMultiplier - } - - if (this._useArmorDivisor && !!this._armorDivisor) { - // -1 divisor means "Ignore DR" - let armorDivisor = this.effectiveArmorDivisor - if (armorDivisor === -1) return 0 - if (armorDivisor < 1 && dr === 0) return 1 - - return Math.floor(dr / armorDivisor) - } - return dr - } - - get effectiveVulnerabilityMultiple() { - return this._isVulnerable ? this._vulnerabilityMultiple : 1 - } - - get effectiveWoundModifiers() { - let table = this.defaultWoundModifiers - - if (this._useLocationModifiers) { - switch (this._hitLocation) { - case 'Vitals': - // only imp, pi*, and burn tbb can target Vitals - table = this._vitalsWoundModifiers - break - - case 'Skull': - case 'Eye': - // only inp, pi*, and burn tbb can target Eye - table = this._skullEyeWoundModifiers - break - - case 'Face': - table = this._faceWoundModifiers - break - - case 'Neck': - table = this._neckWoundModifiers - break - - default: { - if ([hitlocation.EXTREMITY, hitlocation.LIMB].includes(this.hitLocationRole)) - table = this._extremityWoundModifiers - else table = this.defaultWoundModifiers - } - } - } - - if (this._isInjuryTolerance) { - switch (this._injuryToleranceType) { - case UNLIVING: - // if Eye, Skull, or Vitals, don't modify wounding modifier - if (['Eye', 'Skull', 'Vitals'].includes(this._hitLocation)) break - - table = JSON.parse(JSON.stringify(table)) - this._modifyForInjuryTolerance(table['imp'], 1) - this._modifyForInjuryTolerance(table['pi++'], 1) - this._modifyForInjuryTolerance(table['pi+'], 0.5) - this._modifyForInjuryTolerance(table['pi'], 1 / 3) - this._modifyForInjuryTolerance(table['pi-'], 0.2) - break - - // TODO Homogenous includes the benefits of No Brain and No Vitals. - - // No Brain: You may have a head, but a blow to the skull or eye is treated no - // differently than a blow to the face (except that an eye injury can still cripple - // that eye). - - // No Vitals: You have no vital organs (such as a heart or engine) that attackers can - // target for extra damage. Treat hits to the 'vitals' or 'groin' as torso hits. - - case HOMOGENOUS: - // Homogenous: Ignore all wounding modifiers for hit location. - table = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) - this._modifyForInjuryTolerance(table['imp'], 0.5) - this._modifyForInjuryTolerance(table['pi++'], 0.5) - this._modifyForInjuryTolerance(table['pi+'], 1 / 3) - this._modifyForInjuryTolerance(table['pi'], 0.2) - this._modifyForInjuryTolerance(table['pi-'], 0.1) - break - - // TODO Diffuse: This makes you immune to crippling injuries and reduces the damage you - // suffer from most physical blows. Diffuse includes all the benefits of No Blood, - // No Brain, and No Vitals. - case DIFFUSE: - // Diffuse: Ignore all wounding modifiers for hit location. - table = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) - break - - default: - // do nothing - } - } - - return table - } - - get explosionDivisor() { - if (this._isExplosion) { - return this._hexesFromExplosion * 3 - } - return 1 - } - - get FP() { - return this._defender.system.FP - } - - get hardenedDRLevel() { - return this._hardenedDRLevel - } - - set hardenedDRLevel(value) { - this._hardenedDRLevel = value - } - - get hasAdditionalWoundingModifiers() { - return this.additionalWoundModifier > 0 || this.effectiveVulnerabilityMultiple > 0 - } - - get hexesFromExplosion() { - return this._hexesFromExplosion - } - - set hexesFromExplosion(value) { - this._hexesFromExplosion = value - } - - get hitLocation() { - return this._hitLocation - } - - set hitLocation(text) { - this._hitLocation = text - } - - get hitLocationRole() { - let hitLocation = this._defender._hitLocationRolls[this._hitLocation] - if (!!hitLocation?.role) { - return hitLocation.role - } - return null - } - - /** - * HitLocationsWithDR is an array of... - * { - * where: 'Torso', - * dr: 5, - * roll: [9,10,11], - * rollText: '9-11', - * } - */ - get hitLocationsWithDR() { - let locations = this._defender.hitLocationsWithDR - for (let l of locations) l.damageType = this.damageType - return locations - } - - get HP() { - return this._defender.system.HP - } - - get injury() { - if (this._viewId === 'all') return - return this._calculators[this._viewId].injury - } - - get injuryToleranceType() { - return this._injuryToleranceType - } - - set injuryToleranceType(value) { - this._injuryToleranceType = value - } - - get useDamageReduction() { - return this._useDamageReduction - } - - set useDamageReduction(value) { - this._damageReductionLevel = value ? 2 : null - this._useDamageReduction = value - } - - get damageReductionLevel() { - return this._damageReductionLevel - } - - set damageReductionLevel(value) { - if (value === null) this._useDamageReduction = false - if (!!value && value < 2) value = 2 - this._damageReductionLevel = value - } - - get isCrippleableLocation() { - return [hitlocation.EXTREMITY, hitlocation.LIMB].includes(this.hitLocationRole) || this._hitLocation === 'Eye' - } - - get isBluntTraumaInjury() { - if (this._viewId === 'all') return - return this._calculators[this._viewId].isBluntTraumaInjury - } - - get isExplosion() { - return this._isExplosion - } - - set isExplosion(value) { - if (value && !this._isExplosion) { - this._previousHitLocation = this._hitLocation - this._hitLocation = 'Large-Area' - - // Explosion is mutually exclusive with Ranged 1/2D and Shotgun ... ? - this._isShotgun = false - this._isRangedHalfDamage = false - } else if (!value && this._isExplosion) { - this._hitLocation = this._previousHitLocation - } - this._isExplosion = value - } - - get isFlexibleArmor() { - return this._isFlexibleArmor - } - - set isFlexibleArmor(value) { - this._isFlexibleArmor = value - } - - get isHardenedDR() { - return this._isHardenedDR - } - - set isHardenedDR(value) { - this._isHardenedDR = value - } - - get isInjuryReducedByLocation() { - if (this._viewId === 'all') return - return this._calculators[this._viewId].isInjuryReducedByLocation - } - - get isInjuryTolerance() { - return this._isInjuryTolerance - } - - set isInjuryTolerance(value) { - this._isInjuryTolerance = value - } - - get isRangedHalfDamage() { - return this._isRangedHalfDamage - } - - set isRangedHalfDamage(value) { - this._isRangedHalfDamage = value - if (value) { - this._isShotgun = false - this._isExplosion = false - } - } - - get isShotgun() { - return this._isShotgun - } - - set isShotgun(value) { - this._isShotgun = value - if (value) { - this._isRangedHalfDamage = false - this._isExplosion = false - } - } - - get shotgunRofMultiplier() { - return this._shotgunRofMultiplier - } - - set shotgunRofMultiplier(value) { - this._shotgunRofMultiplier = value - } - - get isVulnerable() { - return this._isVulnerable - } - - set isVulnerable(value) { - this._isVulnerable = value - } - - get isWoundModifierAdjustedForInjuryTolerance() { - let table = this.effectiveWoundModifiers - let entries = Object.keys(table).filter(key => table[key].changed === 'injury-tolerance') - return entries.length > 0 - } - - get isWoundModifierAdjustedForLocation() { - let table = this.effectiveWoundModifiers - let entries = Object.keys(table).filter(key => table[key].changed === 'hitlocation') - return entries.length > 0 - } - - get isWoundModifierAdjustedForDamageType() { - let table = this.effectiveWoundModifiers - let entries = Object.keys(table).filter(key => table[key].changed === 'damagemodifier') - return entries.length > 0 - } - - get length() { - return this._calculators.length - } - - get locationMaxHP() { - if (this.hitLocationRole === hitlocation.LIMB) return this.HP.max / 2 + 1 - if (this.hitLocationRole === hitlocation.EXTREMITY) return this.HP.max / 3 + 1 - if (this.hitLocation === 'Eye') return this.HP.max / 10 + 1 - return this.HP.max - } - - get locationMaxHPAsInt() { - return Math.floor(this.locationMaxHP) - } - - get maxInjuryForDiffuse() { - if (this._viewId === 'all') return - return this._calculators[this._viewId].maxInjuryForDiffuse - } - - get penetratingDamage() { - if (this._viewId === 'all') return - return this._calculators[this._viewId].penetratingDamage - } - - get pointsToApply() { - return this._calculators.map(it => it.pointsToApply).reduce((acc, value) => acc + value) - } - - get resource() { - // if (CompositeDamageCalculator.isResourceDamageType(this._applyTo)) { - let trackers = objectToArray(this._defender.system.additionalresources.tracker) - let tracker = null - let index = null - trackers.forEach((t, i) => { - if (t.alias === this._applyTo) { - index = i - tracker = t - return - } - }) - if (!!tracker) return [tracker, `system.additionalresources.tracker.${zeroFill(index, 4)}`] - // } - - if (this._applyTo === 'FP') return [this._defender.system.FP, 'system.FP'] - return [this._defender.system.HP, 'system.HP'] - } - - get resourceType() { - // if (CompositeDamageCalculator.isResourceDamageType(this._applyTo)) { - let trackers = objectToArray(this._defender.system.additionalresources.tracker) - let tracker = trackers.find(it => it.alias === this._applyTo) - if (!!tracker) return tracker.name - // } - - if (this._applyTo === 'FP') return 'FP' - return 'HP' - } - - get shotgunDamageMultiplier() { - if (this._isShotgun && this._shotgunRofMultiplier > 1) { - return Math.floor(this._shotgunRofMultiplier / 2) - } - return 1 - } - - get totalBasicDamage() { - return this._calculators.map(it => it.basicDamage).reduce((acc, value) => acc + value) - } - - get totalWoundingModifier() { - return (this.woundingModifier + this._additionalWoundModifier) * this.effectiveVulnerabilityMultiple - } - - get useArmorDivisor() { - return this._useArmorDivisor - } - - set useArmorDivisor(value) { - this._useArmorDivisor = value - } - - get useBluntTrauma() { - return this._useBluntTrauma - } - - set useBluntTrauma(value) { - this._useBluntTrauma = value - } - - get useLocationModifiers() { - return this._useLocationModifiers - } - - set useLocationModifiers(value) { - this._useLocationModifiers = value - } - - get userEnteredDR() { - return this._userEnteredDR - } - - set userEnteredDR(value) { - this._userEnteredDR = value - } - - get userEnteredWoundModifier() { - return this._userEnteredWoundModifier - } - - set userEnteredWoundModifier(value) { - this._userEnteredWoundModifier = value - } - - get viewId() { - return this._viewId - } - - set viewId(value) { - this._viewId = value - } - - get vulnerabilityMultiple() { - return this._vulnerabilityMultiple - } - - set vulnerabilityMultiple(value) { - this._vulnerabilityMultiple = value - } - - get woundingModifier() { - if (this._damageType === 'none') return 1 - if (this._damageType === 'User Entered') return this._userEnteredWoundModifier - - return this.effectiveWoundModifiers[this._damageType].multiplier - } - - get _extremityWoundModifiers() { - // copy the properties into my local variable - let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) - - // update the ones that need it - Object.keys(results) - .filter(key => ['imp', 'pi+', 'pi++'].includes(key)) - .forEach(key => { - results[key].multiplier = 1 - results[key].changed = 'hitlocation' - }) - return results - } - - get _faceWoundModifiers() { - // copy the properties into my local variable - let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) - - // update the ones that need it - results['cor'].multiplier = 1.5 - results['cor'].changed = 'hitlocation' - return results - } - - get _neckWoundModifiers() { - // copy the properties into my local variable - let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) - - // update the ones that need it - results['cr'].multiplier = 1.5 - results['cor'].multiplier = 1.5 - results['cut'].multiplier = 2 - results['cr'].changed = 'hitlocation' - results['cor'].changed = 'hitlocation' - results['cut'].changed = 'hitlocation' - return results - } - - get _skullEyeWoundModifiers() { - // copy the properties into my local variable - let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) - - // update the ones that need it - Object.keys(results) - .filter(key => key !== 'tox') // everything EXCEPT toxic - .forEach(key => { - results[key].multiplier = 4 - results[key].changed = 'hitlocation' - }) - return results - } - - get _vitalsWoundModifiers() { - // copy the properties into my local variable - let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) - - // update the ones that need it - Object.keys(results) - .filter(key => ['imp', ...piercing].includes(key)) - .forEach(key => { - results[key].multiplier = 3 - results[key].changed = 'hitlocation' - }) - - // update for [burn tbb] - if (this._damageType === 'burn' && this._damageModifier === 'tbb') { - results['burn'].multiplier = 2 - results['burn'].changed = 'damagemodifier' - } - - return results - } - - _modifyForInjuryTolerance(result, value) { - let m = Math.min(result.multiplier, value) - - if (m <= result.multiplier) { - result.multiplier = m - result.changed = 'injury-tolerance' - } - } - - async randomizeHitLocation() { - let roll3d = Roll.create('3d6[Hit Location]') - await roll3d.roll({ async: true }) - let total = roll3d.total - - let loc = this._defender.hitLocationsWithDR.filter(it => it.roll.includes(total)) - if (!!loc && loc.length > 0) this._hitLocation = loc[0].where - else ui.notifications.warn(`There are no hit locations defined for #${total}`) - return roll3d - } + /** + * Create a composite damage calculator, which is a damage calculator that + * wraps multiple damage calculators. + * + * The basic assumption made by this composite damage calculation is that + * all variables (hit location, armor divisor, damage type, etc) are the same + * for every damage roll; the only thing that is different is the amount of + * damage. + * + * @param {*} defender + * @param {DamageData[]} damageData + */ + constructor(defender, damageData) { + this._useBluntTrauma = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_BLUNT_TRAUMA) + this._useLocationModifiers = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_LOCATION_MODIFIERS) + this._useArmorDivisor = game.settings.get(settings.SYSTEM_NAME, settings.SETTING_APPLY_DIVISOR) + + this._defender = defender + + // The CompositeDamageCalculator has multiple DamageCalculators -- create one per DamageData + // and give it a back pointer to the Composite. + this._calculators = damagesystem.map(data => new DamageCalculator(this, data)) + + this.viewId = this._calculators.length == 1 ? 0 : 'all' + + this._defaultWoundingModifiers = Object.keys(GURPS.DamageTables.woundModifiers).reduce(function (r, e) { + if (!GURPS.DamageTables.woundModifiers[e].nodisplay) r[e] = GURPS.DamageTables.woundModifiers[e] + return r + }, {}) + + this._attacker = damageData[0].attacker + + if (Object.keys(this._defaultWoundingModifiers).includes(damageData[0].damageType)) + this._damageType = damageData[0].damageType + else { + let temp = GURPS.DamageTables.translate(damageData[0].damageType) + if (temp) this._damageType = temp + else this._damageType = 'none' + } + + if (!!CompositeDamageCalculator.isResourceDamageType(this._damageType)) { + this._applyTo = this._damageType + } else { + this._applyTo = this._damageType === 'fat' ? 'FP' : 'HP' + } + + this._damageModifier = damageData[0].damageModifier + + this._armorDivisor = damageData[0].armorDivisor + if (this._armorDivisor === 0) { + this._useArmorDivisor = false + } + + let hitlocations = objectToArray(this._defender.system.hitlocations) + let wheres = hitlocations.map(it => it.where.toLowerCase()) + let damageLocation = !!damageData[0].hitlocation ? damageData[0].hitlocation.toLowerCase() : '' + let hlIndex = wheres.indexOf(damageLocation) + if (hlIndex >= 0) this._hitLocation = hitlocations[hlIndex].where + else this._hitLocation = this._defender.defaultHitLocation + + this._previousHitLocation = this._hitLocation + this._userEnteredDR = null + + // the wounding modifier selected using the radio buttons + this._userEnteredWoundModifier = 1 + this._additionalWoundModifier = 0 + + this._isRangedHalfDamage = false + this._isFlexibleArmor = false + + this._isVulnerable = false + this._vulnerabilityMultiple = 2 + + this._isHardenedDR = false + this._hardenedDRLevel = 1 + + this._isInjuryTolerance = false + this._injuryToleranceType = null + + // Injury Tolerance (Damage Reduction) is handled separately from other types of IT + this._useDamageReduction = false + this._damageReductionLevel = null + + this._isExplosion = false + this._hexesFromExplosion = 1 + this._explosionDivisor = 1 + + this._isShotgun = false + this._shotgunRofMultiplier = 9 + + // look at defender and automatically set things like Unliving, Diffuse, Homogenous + // if advantage.name === 'Diffuse' -- DFRPG style + // if advantage.name === 'Injury Tolerance' && advantage.notes.startsWith('Diffuse ') -- GCS Basic style + // _isInjuryTolerance = true + // _injuryToleranceType = 'unliving' + let values = Object.values(this._defender.system.ads) + if (this.isUnliving(values, false)) { + this._isInjuryTolerance = true + this._injuryToleranceType = UNLIVING + } + if (this.isHomogenous(values)) { + this._isInjuryTolerance = true + this._injuryToleranceType = HOMOGENOUS + } + if (this.isDiffuse(values)) { + this._isInjuryTolerance = true + this._injuryToleranceType = DIFFUSE + } + } + + isUnliving(values, found) { + if (!found) { + let self = this + found = values.find(value => { + let found = !!( + ['Injury Tolerance (Unliving)', 'Unliving'].includes(value.name) || + (value.name === 'Injury Tolerance' && value.notes.includes('Unliving')) + ) + + if (!found && Object.keys(value.contains).length > 0) { + found = self.isUnliving(Object.values(value.contains), false) + } + return found + }) + } + return !!found + } + + isHomogenous(values, found) { + if (!found) { + let self = this + found = values.find(value => { + let found = !!( + ['Injury Tolerance (Homogenous)', 'Homogenous'].includes(value.name) || + (value.name === 'Injury Tolerance' && value.notes.includes('Homogenous')) + ) + + if (!found && Object.keys(value.contains).length > 0) { + found = self.isHomogenous(Object.values(value.contains), false) + } + return found + }) + } + return !!found + } + + isDiffuse(values, found) { + if (!found) { + let self = this + found = values.find(value => { + let found = !!( + ['Injury Tolerance (Diffuse)', 'Diffuse'].includes(value.name) || + (value.name === 'Injury Tolerance' && value.notes.includes('Diffuse')) + ) + + if (!found && Object.keys(value.contains).length > 0) { + found = self.isDiffuse(Object.values(value.contains), false) + } + return found + }) + } + return !!found + } + + static isResourceDamageType(damageType) { + let modifier = GURPS.DamageTables.woundModifiers[damageType] + return !!modifier && !!GURPS.DamageTables.woundModifiers[damageType].resource + } + + get(viewId) { + if (viewId === 'all') return this + return this._calculators[viewId] + } + + get showApplyAction() { + return ( + game.settings.get(settings.SYSTEM_NAME, settings.SETTING_DEFAULT_ADD_ACTION) == 'apply' || + (game.settings.get(settings.SYSTEM_NAME, settings.SETTING_DEFAULT_ADD_ACTION) == 'target' && + this._defender.hasPlayerOwner) + ) + } + + get additionalWoundModifier() { + return this._additionalWoundModifier + } + + set additionalWoundModifier(value) { + this._additionalWoundModifier = value + } + + get allHitLocations() { + return this._defender.system.hitlocations + } + + get armorDivisor() { + return this._armorDivisor + } + + set armorDivisor(value) { + this._armorDivisor = value + } + + get useArmorDivisor() { + return this._useArmorDivisor + } + + get applyTo() { + return this._applyTo + } + + set applyTo(value) { + this._applyTo = value + } + + get attacker() { + return this._attacker + } + + get attributes() { + return this._defender.system.attributes + } + + /** + * Override at the individual dice roll level. + */ + get basicDamage() { + if (this._viewId === 'all') return this._calculators.reduce((sum, a) => sum + a._basicDamage, 0) + return this._calculators[this._viewId].basicDamage + } + + set basicDamage(value) { + if (this._viewId === 'all') return + this._calculators[this._viewId].basicDamage = value + } + + get calculators() { + return this._calculators + } + + get damageModifier() { + return this._damageModifier + } + + set damageModifier(value) { + this._damageModifier = value + } + + get damageType() { + return this._damageType + } + + set damageType(type) { + this._damageType = type + } + + get defaultWoundModifiers() { + return this._defaultWoundingModifiers + } + + // return the DR indicated by the Hit Location + get DR() { + if (this._userEnteredDR !== null) return this._userEnteredDR + + if (this._hitLocation === 'Random') return 0 + + // if (this._hitLocation === 'User Entered') return this._userEnteredDR + let entries = this._defender.hitLocationsWithDR + + return this._hitLocation === 'Large-Area' + ? HitLocationEntry.getLargeAreaDR(entries) + : HitLocationEntry.findLocation(entries, this._hitLocation).getDR(this.damageType) + } + + get effects() { + // TODO accumulate effects + // each call to _calculator.effects returns an array of effects + // create a flattened array of effects + let effects = [] + this._calculators.map(calculator => calculator.effects).forEach(effect => effects.push(effect)) + effects = effects.flat() + + let results = [] + + // process shock -- shock is the sum of all shock to the maximum of 4 + let shock = effects + .filter(it => it.type === 'shock') + .map(it => it.amount) + .reduce((acc, value, index, array) => { + return acc + value + }, 0) + + if (shock > 0) { + results.push({ + type: 'shock', + amount: Math.min(shock, 4), + }) + } + + // process knockback -- value and modifier is the sum across all hits + let allKnockbacks = effects.filter(it => it.type === 'knockback') + + let knockbackValue = allKnockbacks + .map(it => it.amount) + .reduce((acc, value, index, array) => { + return acc + value + }, 0) + + let knockbackMods = allKnockbacks + .map(it => it.modifier) + .reduce((acc, value, index, array) => { + return acc + value + }, 0) + + if (allKnockbacks.length > 0) { + results.push({ + type: 'knockback', + amount: knockbackValue, + modifier: knockbackMods, + unit: knockbackValue === 1 ? i18n('GURPS.yard') : i18n('GURPS.yards'), + modifierText: !!knockbackMods ? `–${knockbackMods}` : '', + }) + } + + // process crippling -- just keep one of them + let crippling = effects.find(it => it.type === 'crippling') + if (!!crippling) results.push(crippling) + + // process major wound -- just keep one of them + let majorwound = effects.find(it => it.type === 'majorwound') + if (!!majorwound) results.push(majorwound) + + // process headvitalshit -- just keep one of them + let headvitalshit = effects.find(it => it.type === 'headvitalshit') + if (!!headvitalshit) results.push(headvitalshit) + + return results + } + + get effectiveArmorDivisor() { + // Armor Divisors do not apply to Explosions, (B414) + if (this._isExplosion) return 1 + + if ((this._armorDivisor > 1 || this._armorDivisor === -1) && this._isHardenedDR) { + let _divisor = this._armorDivisor == 4 ? 3 : this._armorDivisor //If you're using survivable guns check if it's (4) because it's not part of the regular progression, thus we treat it as 3. + let maxIndex = armorDivisorSteps.length - 1 + let index = armorDivisorSteps.indexOf(_divisor) + index = Math.min(index + this._hardenedDRLevel, maxIndex) + return armorDivisorSteps[index] + } + return this._armorDivisor + } + + get effectiveBluntTrauma() { + if (this._viewId === 'all') return + return this._calculators[this._viewId].effectiveBluntTrauma + } + + get effectiveDamage() { + if (this._viewId === 'all') return + return this._calculators[this._viewId].effectiveDamage + } + + // figure out the current DR modified by armor divisor, if necessary + get effectiveDR() { + let dr = this.DR + + if (this._isShotgun && this._shotgunRofMultiplier > 1) { + dr = dr * this.shotgunDamageMultiplier + } + + if (this._useArmorDivisor && !!this._armorDivisor) { + // -1 divisor means "Ignore DR" + let armorDivisor = this.effectiveArmorDivisor + if (armorDivisor === -1) return 0 + if (armorDivisor < 1 && dr === 0) return 1 + + return Math.floor(dr / armorDivisor) + } + return dr + } + + get effectiveVulnerabilityMultiple() { + return this._isVulnerable ? this._vulnerabilityMultiple : 1 + } + + get effectiveWoundModifiers() { + let table = this.defaultWoundModifiers + + if (this._useLocationModifiers) { + switch (this._hitLocation) { + case 'Vitals': + // only imp, pi*, and burn tbb can target Vitals + table = this._vitalsWoundModifiers + break + + case 'Skull': + case 'Eye': + // only inp, pi*, and burn tbb can target Eye + table = this._skullEyeWoundModifiers + break + + case 'Face': + table = this._faceWoundModifiers + break + + case 'Neck': + table = this._neckWoundModifiers + break + + default: { + if ([hitlocation.EXTREMITY, hitlocation.LIMB].includes(this.hitLocationRole)) + table = this._extremityWoundModifiers + else table = this.defaultWoundModifiers + } + } + } + + if (this._isInjuryTolerance) { + switch (this._injuryToleranceType) { + case UNLIVING: + // if Eye, Skull, or Vitals, don't modify wounding modifier + if (['Eye', 'Skull', 'Vitals'].includes(this._hitLocation)) break + + table = JSON.parse(JSON.stringify(table)) + this._modifyForInjuryTolerance(table['imp'], 1) + this._modifyForInjuryTolerance(table['pi++'], 1) + this._modifyForInjuryTolerance(table['pi+'], 0.5) + this._modifyForInjuryTolerance(table['pi'], 1 / 3) + this._modifyForInjuryTolerance(table['pi-'], 0.2) + break + + // TODO Homogenous includes the benefits of No Brain and No Vitals. + + // No Brain: You may have a head, but a blow to the skull or eye is treated no + // differently than a blow to the face (except that an eye injury can still cripple + // that eye). + + // No Vitals: You have no vital organs (such as a heart or engine) that attackers can + // target for extra damage. Treat hits to the 'vitals' or 'groin' as torso hits. + + case HOMOGENOUS: + // Homogenous: Ignore all wounding modifiers for hit location. + table = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) + this._modifyForInjuryTolerance(table['imp'], 0.5) + this._modifyForInjuryTolerance(table['pi++'], 0.5) + this._modifyForInjuryTolerance(table['pi+'], 1 / 3) + this._modifyForInjuryTolerance(table['pi'], 0.2) + this._modifyForInjuryTolerance(table['pi-'], 0.1) + break + + // TODO Diffuse: This makes you immune to crippling injuries and reduces the damage you + // suffer from most physical blows. Diffuse includes all the benefits of No Blood, + // No Brain, and No Vitals. + case DIFFUSE: + // Diffuse: Ignore all wounding modifiers for hit location. + table = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) + break + + default: + // do nothing + } + } + + return table + } + + get explosionDivisor() { + if (this._isExplosion) { + return this._hexesFromExplosion * 3 + } + return 1 + } + + get FP() { + return this._defender.system.FP + } + + get hardenedDRLevel() { + return this._hardenedDRLevel + } + + set hardenedDRLevel(value) { + this._hardenedDRLevel = value + } + + get hasAdditionalWoundingModifiers() { + return this.additionalWoundModifier > 0 || this.effectiveVulnerabilityMultiple > 0 + } + + get hexesFromExplosion() { + return this._hexesFromExplosion + } + + set hexesFromExplosion(value) { + this._hexesFromExplosion = value + } + + get hitLocation() { + return this._hitLocation + } + + set hitLocation(text) { + this._hitLocation = text + } + + get hitLocationRole() { + let hitLocation = this._defender._hitLocationRolls[this._hitLocation] + if (!!hitLocation?.role) { + return hitLocation.role + } + return null + } + + /** + * HitLocationsWithDR is an array of... + * { + * where: 'Torso', + * dr: 5, + * roll: [9,10,11], + * rollText: '9-11', + * } + */ + get hitLocationsWithDR() { + let locations = this._defender.hitLocationsWithDR + for (let l of locations) l.damageType = this.damageType + return locations + } + + get HP() { + return this._defender.system.HP + } + + get injury() { + if (this._viewId === 'all') return + return this._calculators[this._viewId].injury + } + + get injuryToleranceType() { + return this._injuryToleranceType + } + + set injuryToleranceType(value) { + this._injuryToleranceType = value + } + + get useDamageReduction() { + return this._useDamageReduction + } + + set useDamageReduction(value) { + this._damageReductionLevel = value ? 2 : null + this._useDamageReduction = value + } + + get damageReductionLevel() { + return this._damageReductionLevel + } + + set damageReductionLevel(value) { + if (value === null) this._useDamageReduction = false + if (!!value && value < 2) value = 2 + this._damageReductionLevel = value + } + + get isCrippleableLocation() { + return [hitlocation.EXTREMITY, hitlocation.LIMB].includes(this.hitLocationRole) || this._hitLocation === 'Eye' + } + + get isBluntTraumaInjury() { + if (this._viewId === 'all') return + return this._calculators[this._viewId].isBluntTraumaInjury + } + + get isExplosion() { + return this._isExplosion + } + + set isExplosion(value) { + if (value && !this._isExplosion) { + this._previousHitLocation = this._hitLocation + this._hitLocation = 'Large-Area' + + // Explosion is mutually exclusive with Ranged 1/2D and Shotgun ... ? + this._isShotgun = false + this._isRangedHalfDamage = false + } else if (!value && this._isExplosion) { + this._hitLocation = this._previousHitLocation + } + this._isExplosion = value + } + + get isFlexibleArmor() { + return this._isFlexibleArmor + } + + set isFlexibleArmor(value) { + this._isFlexibleArmor = value + } + + get isHardenedDR() { + return this._isHardenedDR + } + + set isHardenedDR(value) { + this._isHardenedDR = value + } + + get isInjuryReducedByLocation() { + if (this._viewId === 'all') return + return this._calculators[this._viewId].isInjuryReducedByLocation + } + + get isInjuryTolerance() { + return this._isInjuryTolerance + } + + set isInjuryTolerance(value) { + this._isInjuryTolerance = value + } + + get isRangedHalfDamage() { + return this._isRangedHalfDamage + } + + set isRangedHalfDamage(value) { + this._isRangedHalfDamage = value + if (value) { + this._isShotgun = false + this._isExplosion = false + } + } + + get isShotgun() { + return this._isShotgun + } + + set isShotgun(value) { + this._isShotgun = value + if (value) { + this._isRangedHalfDamage = false + this._isExplosion = false + } + } + + get shotgunRofMultiplier() { + return this._shotgunRofMultiplier + } + + set shotgunRofMultiplier(value) { + this._shotgunRofMultiplier = value + } + + get isVulnerable() { + return this._isVulnerable + } + + set isVulnerable(value) { + this._isVulnerable = value + } + + get isWoundModifierAdjustedForInjuryTolerance() { + let table = this.effectiveWoundModifiers + let entries = Object.keys(table).filter(key => table[key].changed === 'injury-tolerance') + return entries.length > 0 + } + + get isWoundModifierAdjustedForLocation() { + let table = this.effectiveWoundModifiers + let entries = Object.keys(table).filter(key => table[key].changed === 'hitlocation') + return entries.length > 0 + } + + get isWoundModifierAdjustedForDamageType() { + let table = this.effectiveWoundModifiers + let entries = Object.keys(table).filter(key => table[key].changed === 'damagemodifier') + return entries.length > 0 + } + + get length() { + return this._calculators.length + } + + get locationMaxHP() { + if (this.hitLocationRole === hitlocation.LIMB) return this.HP.max / 2 + 1 + if (this.hitLocationRole === hitlocation.EXTREMITY) return this.HP.max / 3 + 1 + if (this.hitLocation === 'Eye') return this.HP.max / 10 + 1 + return this.HP.max + } + + get locationMaxHPAsInt() { + return Math.floor(this.locationMaxHP) + } + + get maxInjuryForDiffuse() { + if (this._viewId === 'all') return + return this._calculators[this._viewId].maxInjuryForDiffuse + } + + get penetratingDamage() { + if (this._viewId === 'all') return + return this._calculators[this._viewId].penetratingDamage + } + + get pointsToApply() { + return this._calculators.map(it => it.pointsToApply).reduce((acc, value) => acc + value) + } + + get resource() { + // if (CompositeDamageCalculator.isResourceDamageType(this._applyTo)) { + let trackers = objectToArray(this._defender.system.additionalresources.tracker) + let tracker = null + let index = null + trackers.forEach((t, i) => { + if (t.alias === this._applyTo) { + index = i + tracker = t + return + } + }) + if (!!tracker) return [tracker, `system.additionalresources.tracker.${zeroFill(index, 4)}`] + // } + + if (this._applyTo === 'FP') return [this._defender.system.FP, 'system.FP'] + return [this._defender.system.HP, 'system.HP'] + } + + get resourceType() { + // if (CompositeDamageCalculator.isResourceDamageType(this._applyTo)) { + let trackers = objectToArray(this._defender.system.additionalresources.tracker) + let tracker = trackers.find(it => it.alias === this._applyTo) + if (!!tracker) return tracker.name + // } + + if (this._applyTo === 'FP') return 'FP' + return 'HP' + } + + get shotgunDamageMultiplier() { + if (this._isShotgun && this._shotgunRofMultiplier > 1) { + return Math.floor(this._shotgunRofMultiplier / 2) + } + return 1 + } + + get totalBasicDamage() { + return this._calculators.map(it => it.basicDamage).reduce((acc, value) => acc + value) + } + + get totalWoundingModifier() { + return (this.woundingModifier + this._additionalWoundModifier) * this.effectiveVulnerabilityMultiple + } + + get useArmorDivisor() { + return this._useArmorDivisor + } + + set useArmorDivisor(value) { + this._useArmorDivisor = value + } + + get useBluntTrauma() { + return this._useBluntTrauma + } + + set useBluntTrauma(value) { + this._useBluntTrauma = value + } + + get useLocationModifiers() { + return this._useLocationModifiers + } + + set useLocationModifiers(value) { + this._useLocationModifiers = value + } + + get userEnteredDR() { + return this._userEnteredDR + } + + set userEnteredDR(value) { + this._userEnteredDR = value + } + + get userEnteredWoundModifier() { + return this._userEnteredWoundModifier + } + + set userEnteredWoundModifier(value) { + this._userEnteredWoundModifier = value + } + + get viewId() { + return this._viewId + } + + set viewId(value) { + this._viewId = value + } + + get vulnerabilityMultiple() { + return this._vulnerabilityMultiple + } + + set vulnerabilityMultiple(value) { + this._vulnerabilityMultiple = value + } + + get woundingModifier() { + if (this._damageType === 'none') return 1 + if (this._damageType === 'User Entered') return this._userEnteredWoundModifier + + return this.effectiveWoundModifiers[this._damageType].multiplier + } + + get _extremityWoundModifiers() { + // copy the properties into my local variable + let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) + + // update the ones that need it + Object.keys(results) + .filter(key => ['imp', 'pi+', 'pi++'].includes(key)) + .forEach(key => { + results[key].multiplier = 1 + results[key].changed = 'hitlocation' + }) + return results + } + + get _faceWoundModifiers() { + // copy the properties into my local variable + let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) + + // update the ones that need it + results['cor'].multiplier = 1.5 + results['cor'].changed = 'hitlocation' + return results + } + + get _neckWoundModifiers() { + // copy the properties into my local variable + let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) + + // update the ones that need it + results['cr'].multiplier = 1.5 + results['cor'].multiplier = 1.5 + results['cut'].multiplier = 2 + results['cr'].changed = 'hitlocation' + results['cor'].changed = 'hitlocation' + results['cut'].changed = 'hitlocation' + return results + } + + get _skullEyeWoundModifiers() { + // copy the properties into my local variable + let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) + + // update the ones that need it + Object.keys(results) + .filter(key => key !== 'tox') // everything EXCEPT toxic + .forEach(key => { + results[key].multiplier = 4 + results[key].changed = 'hitlocation' + }) + return results + } + + get _vitalsWoundModifiers() { + // copy the properties into my local variable + let results = JSON.parse(JSON.stringify(this.defaultWoundModifiers)) + + // update the ones that need it + Object.keys(results) + .filter(key => ['imp', ...piercing].includes(key)) + .forEach(key => { + results[key].multiplier = 3 + results[key].changed = 'hitlocation' + }) + + // update for [burn tbb] + if (this._damageType === 'burn' && this._damageModifier === 'tbb') { + results['burn'].multiplier = 2 + results['burn'].changed = 'damagemodifier' + } + + return results + } + + _modifyForInjuryTolerance(result, value) { + let m = Math.min(result.multiplier, value) + + if (m <= result.multiplier) { + result.multiplier = m + result.changed = 'injury-tolerance' + } + } + + async randomizeHitLocation() { + let roll3d = Roll.create('3d6[Hit Location]') + await roll3d.roll({ async: true }) + let total = roll3d.total + + let loc = this._defender.hitLocationsWithDR.filter(it => it.roll.includes(total)) + if (!!loc && loc.length > 0) this._hitLocation = loc[0].where + else ui.notifications.warn(`There are no hit locations defined for #${total}`) + return roll3d + } } /** @@ -941,289 +941,289 @@ export class CompositeDamageCalculator { * damage roll. */ class DamageCalculator { - /** - * - * @param {CompositeDamageCalculator} parent - * @param {*} damageData - */ - constructor(parent, damageData) { - this._parent = parent - this._basicDamage = damagesystem.damage - this._maxInjuryForDiffuse = null - this._bluntTrauma = null - } - - // --- DAMAGE CALCULATION --- - // Basic Damage = Number rolled on the dice. - // - // Effective Damage = Basic Damage, unless this is a ranged attack and the range is beyond - // the weapon's 1/2D range, in which case we divide the damage by 2. - // - // DR = The DR of the Hit Location. - // - // Effective DR = DR adjusted for armor divisor, if any. Hardened DR reduces the effects of the - // armor divisor. - // - // Penetrating Damage = Effective Damage - Effective DR. - // - // Wounding Modifier = wounding modifier based on damage type. - // - // Total Wounding Modifier = Wounding Modifier + any additional (user entered) wounding modifier - // + any Vulnerability multiplier. - // - // Injury = Penetrating Damage x Total Wounding Modifier x Explosion divider. - // - // Calculated Blunt Trauma = 1/10 of the Effective Damage for the appropriate damage types; 1/5 of - // Effective Damage if damage type is 'cr' (Crushing). - // - // Effective Blunt Trauma = Calculated Blunt Trauma, unless the user overrode it with his own value, - // in which case the user entered value is used. - // - // Unmodified Points to Apply = Injury, unless injury is zero and there was potential - // Effective Blunt Trauma, in which case the value is equal to the Effective Blunt Trauma. - // - // Points to Apply = Unmodified Points to Apply, adjusted by the maximum amount of damage per hit - // location if using "Hit Location Wounding Modifiers". - - get effectiveDamage() { - if (this._parent.isRangedHalfDamage) { - return Math.floor(this._basicDamage / 2) - } else if (this._parent.isShotgun) { - return this._basicDamage * this._parent.shotgunDamageMultiplier - } else if (this._parent.isExplosion) { - return Math.floor(this._basicDamage / this._parent.explosionDivisor) - } else { - return this._basicDamage - } - } - - get penetratingDamage() { - return Math.max(0, this.effectiveDamage - this._parent.effectiveDR) - } - - /** - * Injury is equal to penetrating damage x total wounding modifiers. - */ - get injury() { - this._maxInjuryForDiffuse = null - let injury = Math.floor(this.penetratingDamage * this._parent.totalWoundingModifier) - - if (this._parent._damageReductionLevel !== null && this._parent._damageReductionLevel != 0) { - // Injury Tolerance (Damage Reduction) can't reduce damage below 1 - injury = Math.max(1, Math.floor(injury / this._parent._damageReductionLevel)) - } - - // B380: A target with Injury Tolerance (Diffuse) is even harder to damage! - if (this._parent.isInjuryTolerance && this._parent.injuryToleranceType === DIFFUSE) { - // ...Impaling and piercing attacks (of any size) never do more than 1 HP of injury, - // regardless of penetrating damage! - if (['imp', ...piercing].includes(this._parent.damageType)) { - this._maxInjuryForDiffuse = Math.min(1, injury) - return this._maxInjuryForDiffuse - } - - // ...Other attacks can never do more than 2 HP of injury. - this._maxInjuryForDiffuse = Math.min(2, injury) - return this._maxInjuryForDiffuse - } - - return injury - } - - get calculatedBluntTrauma() { - if (this.effectiveDamage === 0 || this.penetratingDamage > 0) return 0 - if (!bluntTraumaTypes.includes(this._parent.damageType)) return 0 - if (this._parent.damageType === 'cr') return Math.floor(this.effectiveDamage / 5) - return Math.floor(this.effectiveDamage / 10) - } - - get effectiveBluntTrauma() { - return this._bluntTrauma === null ? this.calculatedBluntTrauma : this._bluntTrauma - } - - /** - * This is the damage to apply before applying any maximum based on Location. - */ - get unmodifiedPointsToApply() { - let injury = this.injury - let pointsToApply = - injury === 0 - ? this._parent.isFlexibleArmor && this._parent.useBluntTrauma - ? this.effectiveBluntTrauma - : 0 - : injury - return pointsToApply - } - - /** - * Actual number of HP to apply is the amount of injury, or the effective blunt trauma. - * Set a limit of the max needed to cripple the location. - */ - get pointsToApply() { - let pointsToApply = this.unmodifiedPointsToApply - if (this._parent.useLocationModifiers) { - if ([hitlocation.EXTREMITY, hitlocation.LIMB].includes(this._parent.hitLocationRole)) { - return Math.min(pointsToApply, Math.floor(this._parent.locationMaxHP)) - } - } - return pointsToApply - } - - get bonusToHTRollForHalfDamage() { - return this._parent.isRangedHalfDamage ? 3 : 0 - } - - get calculatedShock() { - let factor = Math.max(1, Math.floor(this._parent.HP.max / 10)) - let shock = Math.min(4, Math.floor(this.pointsToApply / factor)) - return shock - } - - get isMajorWound() { - return this.pointsToApply > this._parent.HP.max / 2 - } - - get penaltyToHTRollForStunning() { - // Diffuse or Homogenous: Ignore all knockdown modifiers for hit location. - let isInjuryTolerance = this._parent.isInjuryTolerance - let injuryToleranceType = this._parent.injuryToleranceType - if (isInjuryTolerance && (injuryToleranceType === DIFFUSE || injuryToleranceType === HOMOGENOUS)) { - return 0 - } - - // B420 "Knockdown and Stunning" states that HT-5 is a modifier for Major Wound on Face/Vitals/Groin - // B399 says "damage rolls at HT-5" but that's just a simplified summary on could happen, should follow B420 - if (this.isMajorWound && ['Face', 'Vitals', 'Groin'].includes(this._parent.hitLocation)) return 5 - - // No Brain (Diffuse or Homogenous) - blows to skull or eye are no different than a blow to - // the face, except that eyes can still be crippled. (Handled earlier in this method.) - if (this.isMajorWound && ['Eye', 'Skull', 'Brain'].includes(this._parent.hitLocation)) return 10 - - return 0 - } - - get isCripplingInjury() { - if (this._parent.useLocationModifiers && this._parent.isCrippleableLocation) { - return this.unmodifiedPointsToApply > this._parent.locationMaxHP - } - return false - } - - get isInjuryReducedByLocation() { - return this._parent.useLocationModifiers && this.isCripplingInjury && this._parent.hitLocation !== 'Eye' - } - - get isKnockbackEligible() { - // Damage modifier 'nkb' means no knockback - if (this._parent._damageModifier === 'nkb') return false - - if ((this._parent.damageType === 'cr' || this._parent.damageType === 'kb') && this._basicDamage > 0) return true - - return this._parent.damageType === 'cut' && this._basicDamage > 0 && this.penetratingDamage === 0 - } - - /* - * let effect = { - * type: , - * modifier: , - * amount: , - * detail: - * } - */ - get effects() { - let _effects = [] - let shock = this.calculatedShock - - if (this.pointsToApply > 0) { - if (shock > 0) { - _effects.push({ - type: 'shock', - amount: shock, - }) - } - - let isMajorWound = false - if ([...head, 'Vitals'].includes(this._parent.hitLocation) && shock > 0) { - _effects.push({ - type: 'headvitalshit', - detail: this._parent.hitLocation, - modifier: this.penaltyToHTRollForStunning - this.bonusToHTRollForHalfDamage, - }) - } else if (this.isMajorWound) { - isMajorWound = true - _effects.push({ - type: 'majorwound', - modifier: this.penaltyToHTRollForStunning - this.bonusToHTRollForHalfDamage, - }) - } - - // Crippling Injury is also a Major Wound - if (this.isCripplingInjury) { - _effects.push({ - type: 'crippling', - amount: Math.floor(this._parent.locationMaxHP), - detail: this._parent.hitLocation, - reduceInjury: this.isInjuryReducedByLocation, - }) - if (!isMajorWound) - _effects.push({ - type: 'majorwound', - modifier: this.penaltyToHTRollForStunning - this.bonusToHTRollForHalfDamage, - }) - } - } - - if (this.isKnockbackEligible) { - let st = this._parent.attributes.ST.value - let hp = this._parent._defender.system.HP.max - - // if the target has no ST score, use its HPs instead (B378) - let knockbackResistance = !st || st == 0 ? hp - 2 : st - 2 - // if ST or HP is less than 3, knockback is one yard per point of damage (B378) - knockbackResistance = Math.max(knockbackResistance, 1) - // For every full multiple of the target’s ST-2 rolled, move the target one yard away from the attacker. (B378) - let knockback = Math.floor(this.effectiveDamage / knockbackResistance) - - // TODO this is the default behavior; implement Powers P101 (in which knockback is calculated based on double basic damage) - // This lets a crushing or cutting attack inflict twice as much knockback as usual. (B104) - if (this._parent._damageModifier === 'dkb') knockback *= 2 - - if (knockback > 0) { - let modifier = knockback - 1 - _effects.push({ - type: 'knockback', - amount: knockback, - modifier: modifier, - }) - } - } - - return _effects - } - - get basicDamage() { - return this._basicDamage - } - - set basicDamage(value) { - this._basicDamage = value - } - - get bluntTrauma() { - return this._bluntTrauma - } - - set bluntTrauma(value) { - this._bluntTrauma = value - } - - get isBluntTraumaInjury() { - return ( - this.injury === 0 && this._parent.isFlexibleArmor && this._parent.useBluntTrauma && this.effectiveBluntTrauma > 0 - ) - } - - get maxInjuryForDiffuse() { - return this._maxInjuryForDiffuse - } + /** + * + * @param {CompositeDamageCalculator} parent + * @param {*} damageData + */ + constructor(parent, damageData) { + this._parent = parent + this._basicDamage = damagesystem.damage + this._maxInjuryForDiffuse = null + this._bluntTrauma = null + } + + // --- DAMAGE CALCULATION --- + // Basic Damage = Number rolled on the dice. + // + // Effective Damage = Basic Damage, unless this is a ranged attack and the range is beyond + // the weapon's 1/2D range, in which case we divide the damage by 2. + // + // DR = The DR of the Hit Location. + // + // Effective DR = DR adjusted for armor divisor, if any. Hardened DR reduces the effects of the + // armor divisor. + // + // Penetrating Damage = Effective Damage - Effective DR. + // + // Wounding Modifier = wounding modifier based on damage type. + // + // Total Wounding Modifier = Wounding Modifier + any additional (user entered) wounding modifier + // + any Vulnerability multiplier. + // + // Injury = Penetrating Damage x Total Wounding Modifier x Explosion divider. + // + // Calculated Blunt Trauma = 1/10 of the Effective Damage for the appropriate damage types; 1/5 of + // Effective Damage if damage type is 'cr' (Crushing). + // + // Effective Blunt Trauma = Calculated Blunt Trauma, unless the user overrode it with his own value, + // in which case the user entered value is used. + // + // Unmodified Points to Apply = Injury, unless injury is zero and there was potential + // Effective Blunt Trauma, in which case the value is equal to the Effective Blunt Trauma. + // + // Points to Apply = Unmodified Points to Apply, adjusted by the maximum amount of damage per hit + // location if using "Hit Location Wounding Modifiers". + + get effectiveDamage() { + if (this._parent.isRangedHalfDamage) { + return Math.floor(this._basicDamage / 2) + } else if (this._parent.isShotgun) { + return this._basicDamage * this._parent.shotgunDamageMultiplier + } else if (this._parent.isExplosion) { + return Math.floor(this._basicDamage / this._parent.explosionDivisor) + } else { + return this._basicDamage + } + } + + get penetratingDamage() { + return Math.max(0, this.effectiveDamage - this._parent.effectiveDR) + } + + /** + * Injury is equal to penetrating damage x total wounding modifiers. + */ + get injury() { + this._maxInjuryForDiffuse = null + let injury = Math.floor(this.penetratingDamage * this._parent.totalWoundingModifier) + + if (this._parent._damageReductionLevel !== null && this._parent._damageReductionLevel != 0) { + // Injury Tolerance (Damage Reduction) can't reduce damage below 1 + injury = Math.max(1, Math.floor(injury / this._parent._damageReductionLevel)) + } + + // B380: A target with Injury Tolerance (Diffuse) is even harder to damage! + if (this._parent.isInjuryTolerance && this._parent.injuryToleranceType === DIFFUSE) { + // ...Impaling and piercing attacks (of any size) never do more than 1 HP of injury, + // regardless of penetrating damage! + if (['imp', ...piercing].includes(this._parent.damageType)) { + this._maxInjuryForDiffuse = Math.min(1, injury) + return this._maxInjuryForDiffuse + } + + // ...Other attacks can never do more than 2 HP of injury. + this._maxInjuryForDiffuse = Math.min(2, injury) + return this._maxInjuryForDiffuse + } + + return injury + } + + get calculatedBluntTrauma() { + if (this.effectiveDamage === 0 || this.penetratingDamage > 0) return 0 + if (!bluntTraumaTypes.includes(this._parent.damageType)) return 0 + if (this._parent.damageType === 'cr') return Math.floor(this.effectiveDamage / 5) + return Math.floor(this.effectiveDamage / 10) + } + + get effectiveBluntTrauma() { + return this._bluntTrauma === null ? this.calculatedBluntTrauma : this._bluntTrauma + } + + /** + * This is the damage to apply before applying any maximum based on Location. + */ + get unmodifiedPointsToApply() { + let injury = this.injury + let pointsToApply = + injury === 0 + ? this._parent.isFlexibleArmor && this._parent.useBluntTrauma + ? this.effectiveBluntTrauma + : 0 + : injury + return pointsToApply + } + + /** + * Actual number of HP to apply is the amount of injury, or the effective blunt trauma. + * Set a limit of the max needed to cripple the location. + */ + get pointsToApply() { + let pointsToApply = this.unmodifiedPointsToApply + if (this._parent.useLocationModifiers) { + if ([hitlocation.EXTREMITY, hitlocation.LIMB].includes(this._parent.hitLocationRole)) { + return Math.min(pointsToApply, Math.floor(this._parent.locationMaxHP)) + } + } + return pointsToApply + } + + get bonusToHTRollForHalfDamage() { + return this._parent.isRangedHalfDamage ? 3 : 0 + } + + get calculatedShock() { + let factor = Math.max(1, Math.floor(this._parent.HP.max / 10)) + let shock = Math.min(4, Math.floor(this.pointsToApply / factor)) + return shock + } + + get isMajorWound() { + return this.pointsToApply > this._parent.HP.max / 2 + } + + get penaltyToHTRollForStunning() { + // Diffuse or Homogenous: Ignore all knockdown modifiers for hit location. + let isInjuryTolerance = this._parent.isInjuryTolerance + let injuryToleranceType = this._parent.injuryToleranceType + if (isInjuryTolerance && (injuryToleranceType === DIFFUSE || injuryToleranceType === HOMOGENOUS)) { + return 0 + } + + // B420 "Knockdown and Stunning" states that HT-5 is a modifier for Major Wound on Face/Vitals/Groin + // B399 says "damage rolls at HT-5" but that's just a simplified summary on could happen, should follow B420 + if (this.isMajorWound && ['Face', 'Vitals', 'Groin'].includes(this._parent.hitLocation)) return 5 + + // No Brain (Diffuse or Homogenous) - blows to skull or eye are no different than a blow to + // the face, except that eyes can still be crippled. (Handled earlier in this method.) + if (this.isMajorWound && ['Eye', 'Skull', 'Brain'].includes(this._parent.hitLocation)) return 10 + + return 0 + } + + get isCripplingInjury() { + if (this._parent.useLocationModifiers && this._parent.isCrippleableLocation) { + return this.unmodifiedPointsToApply > this._parent.locationMaxHP + } + return false + } + + get isInjuryReducedByLocation() { + return this._parent.useLocationModifiers && this.isCripplingInjury && this._parent.hitLocation !== 'Eye' + } + + get isKnockbackEligible() { + // Damage modifier 'nkb' means no knockback + if (this._parent._damageModifier === 'nkb') return false + + if ((this._parent.damageType === 'cr' || this._parent.damageType === 'kb') && this._basicDamage > 0) return true + + return this._parent.damageType === 'cut' && this._basicDamage > 0 && this.penetratingDamage === 0 + } + + /* + * let effect = { + * type: , + * modifier: , + * amount: , + * detail: + * } + */ + get effects() { + let _effects = [] + let shock = this.calculatedShock + + if (this.pointsToApply > 0) { + if (shock > 0) { + _effects.push({ + type: 'shock', + amount: shock, + }) + } + + let isMajorWound = false + if ([...head, 'Vitals'].includes(this._parent.hitLocation) && shock > 0) { + _effects.push({ + type: 'headvitalshit', + detail: this._parent.hitLocation, + modifier: this.penaltyToHTRollForStunning - this.bonusToHTRollForHalfDamage, + }) + } else if (this.isMajorWound) { + isMajorWound = true + _effects.push({ + type: 'majorwound', + modifier: this.penaltyToHTRollForStunning - this.bonusToHTRollForHalfDamage, + }) + } + + // Crippling Injury is also a Major Wound + if (this.isCripplingInjury) { + _effects.push({ + type: 'crippling', + amount: Math.floor(this._parent.locationMaxHP), + detail: this._parent.hitLocation, + reduceInjury: this.isInjuryReducedByLocation, + }) + if (!isMajorWound) + _effects.push({ + type: 'majorwound', + modifier: this.penaltyToHTRollForStunning - this.bonusToHTRollForHalfDamage, + }) + } + } + + if (this.isKnockbackEligible) { + let st = this._parent.attributes.ST.value + let hp = this._parent._defender.system.HP.max + + // if the target has no ST score, use its HPs instead (B378) + let knockbackResistance = !st || st == 0 ? hp - 2 : st - 2 + // if ST or HP is less than 3, knockback is one yard per point of damage (B378) + knockbackResistance = Math.max(knockbackResistance, 1) + // For every full multiple of the target’s ST-2 rolled, move the target one yard away from the attacker. (B378) + let knockback = Math.floor(this.effectiveDamage / knockbackResistance) + + // TODO this is the default behavior; implement Powers P101 (in which knockback is calculated based on double basic damage) + // This lets a crushing or cutting attack inflict twice as much knockback as usual. (B104) + if (this._parent._damageModifier === 'dkb') knockback *= 2 + + if (knockback > 0) { + let modifier = knockback - 1 + _effects.push({ + type: 'knockback', + amount: knockback, + modifier: modifier, + }) + } + } + + return _effects + } + + get basicDamage() { + return this._basicDamage + } + + set basicDamage(value) { + this._basicDamage = value + } + + get bluntTrauma() { + return this._bluntTrauma + } + + set bluntTrauma(value) { + this._bluntTrauma = value + } + + get isBluntTraumaInjury() { + return ( + this.injury === 0 && this._parent.isFlexibleArmor && this._parent.useBluntTrauma && this.effectiveBluntTrauma > 0 + ) + } + + get maxInjuryForDiffuse() { + return this._maxInjuryForDiffuse + } } diff --git a/module/damage/damagechat.js b/module/damage/damagechat.js index e54157d54..3d4224a3d 100755 --- a/module/damage/damagechat.js +++ b/module/damage/damagechat.js @@ -29,476 +29,473 @@ import { handleOnPdf } from '../pdf-refs.js' * specific actor. This object takes care of binding the dragstart and dragend events to that div. */ export default class DamageChat { - /** - * @param {{ data: { flags: { transfer: string; }; }; }} app - * @param {JQuery} html - * @param {any} _msg - */ - static async _renderDamageChat(app, html, _msg) { - if (!html.find('.damage-chat-message').length) return // this is not a damage chat message - - let transfer = JSON.parse(app.flags.transfer) - - // for each damage-message, set the drag-and-drop events and data - let damageMessages = html.find('.damage-message') - if (!!damageMessages && damageMessages.length > 0) { - for (let index = 0; index < damageMessages.length; index++) { - let message = damageMessages[index] - let payload = transfer.payload[index] - makeElementDraggable(message, 'damageItem', 'dragging', payload, GURPS.damageDragImage, [30, 30]) - } - } // end-if (!!damageMessages && damageMessages.length) - - // for the damage-all-message, set the drag-and-drop events and data - let allDamageMessage = html.find('.damage-all-message') - if (!!allDamageMessage && allDamageMessage.length == 1) { - let transfer = JSON.parse(app.flags.transfer) - let message = allDamageMessage[0] - - makeElementDraggable(message, 'damageItem', 'dragging', transfer.payload, GURPS.damageDragImage, [30, 30]) - } - - // If there was a target, enable the GM's apply button - let button = html.find(':button.apply-all') - button.hide() - if (!!transfer.userTarget && transfer.userTarget != null) { - if (game.user.isGM) { - button.show() - - button.on('click', ev => { - // get actor from id - let token = canvas.tokens?.get(transfer.userTarget) // ... - // get payload; its either the "all damage" payload or ... - let actor = token?.actor - if (!!actor) actor.handleDamageDrop(transfer.payload) - else ui.notifications?.warn('Unable to find token with ID:' + transfer.userTarget) - }) - } - } - } - - /** - * @param {Canvas} canvas - * @param {{ type: string; x: number; y: number; payload: any; }} dropData - */ - static async _dropCanvasData(canvas, dropData) { - if (dropData.type === 'damageItem' || dropData.type === 'Item' || dropData.type === 'equipment') { - let oldselection = new Set(game.user.targets) // remember current targets (so we can reselect them afterwards) - let grid_size = canvas.scene?.grid - canvas.tokens?.targetObjects( - { - x: dropData.x - grid_size / 2, - y: dropData.y - grid_size / 2, - height: grid_size, - width: grid_size, - }, - { releaseOthers: true } - ) - let targets = [...game.user.targets] - - // Now that we have the list of targets, reset the target selection back to whatever the user had - for (let t of game.user.targets) { - t.setTarget(false, { releaseOthers: false, groupSelection: true }) - } - oldselection.forEach(t => { - t.setTarget(true, { releaseOthers: false, groupSelection: true }) - }) - - let handle = (/** @type {GurpsActor} */ actor) => actor.handleDamageDrop(dropData.payload) - if (dropData.type === 'Item') handle = actor => actor.handleItemDrop(dropData) - if (dropData.type === 'equipment') handle = actor => actor.handleEquipmentDrop(dropData) - - // actual targets are stored in game.user.targets - if (targets.length === 0) return false - if (targets.length === 1) { - handle(targets[0].actor) - return false - } - - let buttons = { - apply: { - icon: '', - label: game.i18n.localize('GURPS.addApply'), - callback: (/** @type {JQuery} */ html) => { - let name = html.find('select option:selected').text().trim() - let target = targets.find(token => token.name === name) - handle(target?.actor) - }, - }, - } - - let d = new Dialog( - { - title: game.i18n.localize('GURPS.selectToken'), - content: await renderTemplate('systems/gurps/templates/apply-damage/select-token.html', { - tokens: targets, - }), - // @ts-ignore - buttons: buttons, - default: 'apply', - tokens: targets, - }, - { width: 300 } - ) - await d.render(true) - - return false - } - } - - static init() { - Hooks.on('renderChatMessage', DamageChat._renderDamageChat) - Hooks.on('dropCanvasData', DamageChat._dropCanvasData) - } - - /** - * Create the damage chat message. - * @param {GurpsActor|User} actor that rolled the damage. - * @param {String} diceText such as '3d-1(2)' - * @param {String} damageType text from DamageTables.damageTypeMap - * @param {JQuery.Event|null} event that triggered this action - * @param {String|null} overrideDiceText ?? - * @param {String[]|undefined} tokenNames - * @param {String|null} extdamagetype - * @param {string|null} hitlocation - * @returns {Promise} - */ - static async create( - actor, - diceText, - damageType, - event, - overrideDiceText, - tokenNames, - extdamagetype = null, - hitlocation = null - ) { - let message = new DamageChat() - - const targetmods = await GURPS.ModifierBucket.applyMods() // append any global mods - - let dice = message._getDiceData(diceText, damageType, targetmods, overrideDiceText, extdamagetype) - if (dice == null) return - - if (!tokenNames) tokenNames = [] - if (!!event && event?.repeat > 1) - for (let i = 0; i < event.repeat; i++) - tokenNames.push('' + i) - - if (tokenNames.length == 0) tokenNames.push('') - - let draggableData = [] - for (const tokenName of tokenNames) { - let data = await message._createDraggableSection(actor, dice, tokenName, targetmods, hitlocation) - draggableData.push(data) - } - - // TODO add hitlocation to Chat message (e.g, something like 'Rolling 3d cut damage to Neck') - message._createChatMessage(actor, dice, targetmods, draggableData, event) - - // Resolve any modifier descriptors (such as *Costs 1FP) - targetmods - .filter(it => !!it.desc) - .map(it => it.desc) - .forEach(it => GURPS.applyModifierDesc(actor, it)) - } - - /** - * This method is all about interpreting the die roll text. - * - * @param {string} originalDiceText - * @param {string} damageType - * @param {string|null} overrideDiceText - * @param {string|null} extdamagetype - * @param {Modifier[]} targetmods - * @returns {diceData|null} - */ - _getDiceData(originalDiceText, damageType, targetmods, overrideDiceText, extdamagetype) { - // format for diceText: - // - // 'd++x()' - // - // - Multipliers are integers that appear after a multiplication sign (x, X, *, or ×). - // - // - Armor divisors are decimal numbers in parentheses: (2), (5), (0.2), (0.5). (Accept - // an optional space between the armor divisor and the preceding characters.) - // - // Examples: 3d, 1d-1, 3dx5, 3d-1x5, 2d(2), 5d-1(0.2), 3dx2(5), 4d-1x4(5). - // - // Added support for a second add, such as: 1d-2+3 -- this was to support damage expressed - // using basic damage syntax, such as "sw+3" (which could translate to '1d-2+3', for exmaple). - - let result = DamageChat.fullRegex.exec(originalDiceText) - - if (!result) { - ui.notifications?.warn(`Invalid Dice formula: "${originalDiceText}"`) - return null - } - - let diceText = result.groups?.roll || '' - - if (originalDiceText.slice(-1) === '!') diceText = diceText + '!' - diceText = diceText.replace('−', '-') // replace minus (−) with hyphen - - let multiplier = !!result.groups?.mult ? parseFloat(result.groups.mult) : 1 - let divisor = !!result.groups?.divisor ? parseFloat(result.groups.divisor) : 0 - - let adds1 = 0 - let temp = !!result.groups?.adds1 ? result.groups.adds1 : '' - if (!!temp && temp !== '') { - let m = temp.match(/([+-])@margin/) - if (!!m) { - let mrg = GURPS.lastTargetedRoll?.margin || 0 - if (m[1] == '+') temp = '' + mrg - else { - if (mrg <= 0) temp = '' + mrg - else temp = '-' + mrg - } - } - temp = temp.startsWith('+') ? temp.slice(1) : temp - adds1 = parseInt(temp) - } - - let adds2 = 0 - temp = !!result.groups?.adds2 ? result.groups.adds2 : '' - if (!!temp && temp !== '') { - temp = temp.startsWith('+') ? temp.slice(1) : temp - adds2 = parseInt(temp) - } - - let formula = diceText - let rolled = false - if (!!result.groups?.D) { - rolled = true - if (result.groups.D === 'd') formula = d6ify(diceText, '[Damage]') // GURPS dice (assume 6) - } - let displayText = overrideDiceText || diceText // overrideDiceText used when actual formula isn't 'pretty' SW+2 vs 1d6+1+2 - let min = 1 - - if (damageType === '' || damageType === null) damageType = 'dmg' - if (damageType === 'cr') min = 0 - - if (formula.slice(-1) === '!') { - formula = formula.slice(0, -1) - min = 1 - } - - let modifier = 0 - for (let m of targetmods) { - modifier += m.modint - } - let additionalText = '' - if (multiplier > 1) { - additionalText += `×${multiplier}` - } - - if (divisor != 0) { - additionalText += ` (${divisor})` - } - - let diceData = { - formula: formula, - rolled: rolled, - modifier: modifier, - diceText: displayText + additionalText, - damageType: damageType, - extdamagetype: extdamagetype, - multiplier: multiplier, - divisor: divisor, - adds1: adds1, - adds2: adds2, - min: min, - } - // console.log(diceData) - return diceData - } - - /** - * This method creates the content of each draggable section with - * damage rolled for a single target. - * @param {GurpsActor|User} actor - * @param {diceData} diceData - * @param {*} tokenName - * @param {*} targetmods - */ - async _createDraggableSection(actor, diceData, tokenName, targetmods, hitlocation) { - let roll = /** @type {GurpsRoll} */ (Roll.create(diceData.formula + `+${diceData.modifier}`)) - await roll.evaluate({ async: true }) - - let diceValue = parseInt(roll.result.split(' ')[0]) // in 0.8.X, result is string, so must make into int - let dicePlusAdds = diceValue + diceData.adds1 + diceData.adds2 - let rollTotal = roll.total || 0 - diceData.loaded = roll.isLoaded - - let b378 = false - if (rollTotal < diceData.min) { - rollTotal = diceData.min - if (diceData.damageType !== 'cr') { - b378 = true - } - } - - let damage = rollTotal * diceData.multiplier - - let explainLineOne = null - if ((roll.dice.length > 0 && roll.dice[0].results.length > 1) || diceData.adds1 !== 0) { - let tempString = '' - if (roll.dice.length > 0) { - tempString = roll.dice[0].results.map(it => it.result).join() - tempString = `Rolled (${tempString})` - } else { - tempString = diceValue.toString() - } - - if (diceData.adds1 !== 0) { - let sign = diceData.adds1 < 0 ? '−' : '+' - let value = Math.abs(diceData.adds1) - tempString = `${tempString} ${sign} ${value}` - } - - if (diceData.adds2 !== 0) { - let sign = diceData.adds2 < 0 ? '-' : '+' - let value = Math.abs(diceData.adds2) - tempString = `${tempString} ${sign} ${value}` - } - explainLineOne = `${tempString} = ${dicePlusAdds}.` - } - - let explainLineTwo = null - let sign = diceData.modifier < 0 ? '−' : '+' - let value = Math.abs(diceData.modifier) - - if (targetmods.length > 0 && diceData.multiplier > 1) { - explainLineTwo = `Total = (${dicePlusAdds} ${sign} ${value}) × ${diceData.multiplier} = ${damage}.` - } else if (targetmods.length > 0) { - explainLineTwo = `Total = ${dicePlusAdds} ${sign} ${value} = ${damage}.` - } else if (diceData.multiplier > 1) { - explainLineTwo = `Total = ${dicePlusAdds} × ${diceData.multiplier} = ${damage}.` - } - - let hasExplanation = explainLineOne || explainLineTwo - - if (hasExplanation && b378) { - if (explainLineTwo) explainLineTwo = `${explainLineTwo}*` - else explainLineOne = `${explainLineOne}*` - } - - let contentData = { - id: generateUniqueId(), - attacker: actor.id, - dice: diceData.diceText, - damageType: diceData.damageType, - damageTypeText: diceData.damageType === 'dmg' ? ' ' : `'${diceData.damageType}' `, - damageModifier: diceData.extdamagetype, - armorDivisor: diceData.divisor, - damage: damage, - hasExplanation: hasExplanation, - explainLineOne: explainLineOne, - explainLineTwo: explainLineTwo, - isB378: b378, - roll: roll, - target: tokenName, - hitlocation: hitlocation, - } - return contentData - } - - /** - * @param {GurpsActor | User } actor - * @param {diceData} diceData - * @param {any[]} targetmods - * @param {any[]} draggableData - * @param {JQuery.Event|null} event - */ - async _createChatMessage(actor, diceData, targetmods, draggableData, event) { - let userTarget = null - if (!!game.user.targets.size) { - userTarget = game.user.targets.values().next().value - } - - let damageType = diceData.damageType === 'dmg' ? '' : diceData.damageType - damageType = !!diceData.extdamagetype ? `${damageType} ${diceData.extdamagetype}` : damageType - - let html = await renderTemplate('systems/gurps/templates/damage-message.hbs', { - draggableData: draggableData, - rolled: diceData.rolled, - dice: diceData.diceText, - loaded: diceData.loaded, - damageTypeText: `${damageType} `, - modifiers: targetmods.map(it => `${it.mod} ${it.desc.replace(/^dmg/, 'damage')}`), - userTarget: userTarget, - hitlocation: draggableData[0].hitlocation, - numtimes: draggableData.length > 1 ? ' x' + draggableData.length : '' - }) - - // @ts-ignore - const speaker = ChatMessage.getSpeaker({ actor: actor }) - /** @type {Record} */ - let messageData = { - user: game.user.id, - speaker: speaker, - content: html, - type: CONST.CHAT_MESSAGE_TYPES.ROLL, - roll: JSON.stringify(draggableData[0].roll), // only need to stringify when sending to chat - } - - if (event?.shiftKey) { - messageData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER; - if (game.user.isGM) { - messageData.whisper = [game.user.id] - } else - messageData.whisper = game.users.filter(u => u.isGM).map(u => u.id) - messageData.blind = true - } - - messageData['flags.transfer'] = JSON.stringify({ - type: 'damageItem', - payload: draggableData, - userTarget: !!userTarget ? userTarget.id : null, - }) - - if (isNiceDiceEnabled()) { - /** @type {GurpsRoll[]} */ - let rolls = draggableData.map(d => d.roll) - let throws = [] - /** - * @type {{ result: any; resultLabel: any; type: string; vectors: never[]; options: {}; }[]} - */ - let dice = [] - - rolls.shift() // The first roll will be handled by DSN's chat handler... the rest we will manually roll - rolls.forEach(roll => { - roll.dice.forEach(die => { - let type = 'd' + die.faces - die.results.forEach(s => - dice.push({ - result: s.result, - resultLabel: s.result, - type: type, - vectors: [], - options: {}, - }) - ) - }) - }) - throws.push({ dice: dice }) - if (dice.length > 0) { - // The user made a "multi-damage" roll... let them see the dice! - // @ts-ignore - game.dice3d.show({ throws: throws }) - } - } else { - messageData.sound = CONFIG.sounds.dice - } - ChatMessage.create(messageData).then(arg => { - let messageId = arg.id // 'qHz1QQuzpJiavH3V' - $(`[data-message-id='${messageId}']`).on('click', handleOnPdf) - }) - } + /** + * @param {{ data: { flags: { transfer: string; }; }; }} app + * @param {JQuery} html + * @param {any} _msg + */ + static async _renderDamageChat(app, html, _msg) { + if (!html.find('.damage-chat-message').length) return // this is not a damage chat message + + let transfer = JSON.parse(app.flags.transfer) + + // for each damage-message, set the drag-and-drop events and data + let damageMessages = html.find('.damage-message') + if (!!damageMessages && damageMessages.length > 0) { + for (let index = 0; index < damageMessages.length; index++) { + let message = damageMessages[index] + let payload = transfer.payload[index] + makeElementDraggable(message, 'damageItem', 'dragging', payload, GURPS.damageDragImage, [30, 30]) + } + } // end-if (!!damageMessages && damageMessages.length) + + // for the damage-all-message, set the drag-and-drop events and data + let allDamageMessage = html.find('.damage-all-message') + if (!!allDamageMessage && allDamageMessage.length == 1) { + let transfer = JSON.parse(app.flags.transfer) + let message = allDamageMessage[0] + + makeElementDraggable(message, 'damageItem', 'dragging', transfer.payload, GURPS.damageDragImage, [30, 30]) + } + + // If there was a target, enable the GM's apply button + let button = html.find(':button.apply-all') + button.hide() + if (!!transfer.userTarget && transfer.userTarget != null) { + if (game.user.isGM) { + button.show() + + button.on('click', ev => { + // get actor from id + let token = canvas.tokens?.get(transfer.userTarget) // ... + // get payload; its either the "all damage" payload or ... + let actor = token?.actor + if (!!actor) actor.handleDamageDrop(transfer.payload) + else ui.notifications?.warn('Unable to find token with ID:' + transfer.userTarget) + }) + } + } + } + + /** + * @param {Canvas} canvas + * @param {{ type: string; x: number; y: number; payload: any; }} dropData + */ + static async _dropCanvasData(canvas, dropData) { + if (dropData.type === 'damageItem' || dropData.type === 'Item' || dropData.type === 'equipment') { + let oldselection = new Set(game.user.targets) // remember current targets (so we can reselect them afterwards) + let grid_size = canvas.scene?.grid + canvas.tokens?.targetObjects( + { + x: dropData.x - grid_size / 2, + y: dropData.y - grid_size / 2, + height: grid_size, + width: grid_size, + }, + { releaseOthers: true } + ) + let targets = [...game.user.targets] + + // Now that we have the list of targets, reset the target selection back to whatever the user had + for (let t of game.user.targets) { + t.setTarget(false, { releaseOthers: false, groupSelection: true }) + } + oldselection.forEach(t => { + t.setTarget(true, { releaseOthers: false, groupSelection: true }) + }) + + let handle = (/** @type {GurpsActor} */ actor) => actor.handleDamageDrop(dropData.payload) + if (dropData.type === 'Item') handle = actor => actor.handleItemDrop(dropData) + if (dropData.type === 'equipment') handle = actor => actor.handleEquipmentDrop(dropData) + + // actual targets are stored in game.user.targets + if (targets.length === 0) return false + if (targets.length === 1) { + handle(targets[0].actor) + return false + } + + let buttons = { + apply: { + icon: '', + label: game.i18n.localize('GURPS.addApply'), + callback: (/** @type {JQuery} */ html) => { + let name = html.find('select option:selected').text().trim() + let target = targets.find(token => token.name === name) + handle(target?.actor) + }, + }, + } + + let d = new Dialog( + { + title: game.i18n.localize('GURPS.selectToken'), + content: await renderTemplate('systems/gurps/templates/apply-damage/select-token.html', { + tokens: targets, + }), + // @ts-ignore + buttons: buttons, + default: 'apply', + tokens: targets, + }, + { width: 300 } + ) + await d.render(true) + + return false + } + } + + static init() { + Hooks.on('renderChatMessage', DamageChat._renderDamageChat) + Hooks.on('dropCanvasData', DamageChat._dropCanvasData) + } + + /** + * Create the damage chat message. + * @param {GurpsActor|User} actor that rolled the damage. + * @param {String} diceText such as '3d-1(2)' + * @param {String} damageType text from DamageTables.damageTypeMap + * @param {JQuery.Event|null} event that triggered this action + * @param {String|null} overrideDiceText ?? + * @param {String[]|undefined} tokenNames + * @param {String|null} extdamagetype + * @param {string|null} hitlocation + * @returns {Promise} + */ + static async create( + actor, + diceText, + damageType, + event, + overrideDiceText, + tokenNames, + extdamagetype = null, + hitlocation = null + ) { + let message = new DamageChat() + + const targetmods = await GURPS.ModifierBucket.applyMods() // append any global mods + + let dice = message._getDiceData(diceText, damageType, targetmods, overrideDiceText, extdamagetype) + if (dice == null) return + + if (!tokenNames) tokenNames = [] + if (!!event && event?.repeat > 1) for (let i = 0; i < event.repeat; i++) tokenNames.push('' + i) + + if (tokenNames.length == 0) tokenNames.push('') + + let draggableData = [] + for (const tokenName of tokenNames) { + let data = await message._createDraggableSection(actor, dice, tokenName, targetmods, hitlocation) + draggableData.push(data) + } + + // TODO add hitlocation to Chat message (e.g, something like 'Rolling 3d cut damage to Neck') + message._createChatMessage(actor, dice, targetmods, draggableData, event) + + // Resolve any modifier descriptors (such as *Costs 1FP) + targetmods + .filter(it => !!it.desc) + .map(it => it.desc) + .forEach(it => GURPS.applyModifierDesc(actor, it)) + } + + /** + * This method is all about interpreting the die roll text. + * + * @param {string} originalDiceText + * @param {string} damageType + * @param {string|null} overrideDiceText + * @param {string|null} extdamagetype + * @param {Modifier[]} targetmods + * @returns {diceData|null} + */ + _getDiceData(originalDiceText, damageType, targetmods, overrideDiceText, extdamagetype) { + // format for diceText: + // + // 'd++x()' + // + // - Multipliers are integers that appear after a multiplication sign (x, X, *, or ×). + // + // - Armor divisors are decimal numbers in parentheses: (2), (5), (0.2), (0.5). (Accept + // an optional space between the armor divisor and the preceding characters.) + // + // Examples: 3d, 1d-1, 3dx5, 3d-1x5, 2d(2), 5d-1(0.2), 3dx2(5), 4d-1x4(5). + // + // Added support for a second add, such as: 1d-2+3 -- this was to support damage expressed + // using basic damage syntax, such as "sw+3" (which could translate to '1d-2+3', for exmaple). + + let result = DamageChat.fullRegex.exec(originalDiceText) + + if (!result) { + ui.notifications?.warn(`Invalid Dice formula: "${originalDiceText}"`) + return null + } + + let diceText = result.groups?.roll || '' + + if (originalDiceText.slice(-1) === '!') diceText = diceText + '!' + diceText = diceText.replace('−', '-') // replace minus (−) with hyphen + + let multiplier = !!result.groups?.mult ? parseFloat(result.groups.mult) : 1 + let divisor = !!result.groups?.divisor ? parseFloat(result.groups.divisor) : 0 + + let adds1 = 0 + let temp = !!result.groups?.adds1 ? result.groups.adds1 : '' + if (!!temp && temp !== '') { + let m = temp.match(/([+-])@margin/) + if (!!m) { + let mrg = GURPS.lastTargetedRoll?.margin || 0 + if (m[1] == '+') temp = '' + mrg + else { + if (mrg <= 0) temp = '' + mrg + else temp = '-' + mrg + } + } + temp = temp.startsWith('+') ? temp.slice(1) : temp + adds1 = parseInt(temp) + } + + let adds2 = 0 + temp = !!result.groups?.adds2 ? result.groups.adds2 : '' + if (!!temp && temp !== '') { + temp = temp.startsWith('+') ? temp.slice(1) : temp + adds2 = parseInt(temp) + } + + let formula = diceText + let rolled = false + if (!!result.groups?.D) { + rolled = true + if (result.groups.D === 'd') formula = d6ify(diceText, '[Damage]') // GURPS dice (assume 6) + } + let displayText = overrideDiceText || diceText // overrideDiceText used when actual formula isn't 'pretty' SW+2 vs 1d6+1+2 + let min = 1 + + if (damageType === '' || damageType === null) damageType = 'dmg' + if (damageType === 'cr') min = 0 + + if (formula.slice(-1) === '!') { + formula = formula.slice(0, -1) + min = 1 + } + + let modifier = 0 + for (let m of targetmods) { + modifier += m.modint + } + let additionalText = '' + if (multiplier > 1) { + additionalText += `×${multiplier}` + } + + if (divisor != 0) { + additionalText += ` (${divisor})` + } + + let diceData = { + formula: formula, + rolled: rolled, + modifier: modifier, + diceText: displayText + additionalText, + damageType: damageType, + extdamagetype: extdamagetype, + multiplier: multiplier, + divisor: divisor, + adds1: adds1, + adds2: adds2, + min: min, + } + // console.log(diceData) + return diceData + } + + /** + * This method creates the content of each draggable section with + * damage rolled for a single target. + * @param {GurpsActor|User} actor + * @param {diceData} diceData + * @param {*} tokenName + * @param {*} targetmods + */ + async _createDraggableSection(actor, diceData, tokenName, targetmods, hitlocation) { + let roll = /** @type {GurpsRoll} */ (Roll.create(diceData.formula + `+${diceData.modifier}`)) + await roll.evaluate({ async: true }) + + let diceValue = parseInt(roll.result.split(' ')[0]) // in 0.8.X, result is string, so must make into int + let dicePlusAdds = diceValue + diceData.adds1 + diceData.adds2 + let rollTotal = roll.total || 0 + diceData.loaded = roll.isLoaded + + let b378 = false + if (rollTotal < diceData.min) { + rollTotal = diceData.min + if (diceData.damageType !== 'cr') { + b378 = true + } + } + + let damage = rollTotal * diceData.multiplier + + let explainLineOne = null + if ((roll.dice.length > 0 && roll.dice[0].results.length > 1) || diceData.adds1 !== 0) { + let tempString = '' + if (roll.dice.length > 0) { + tempString = roll.dice[0].results.map(it => it.result).join() + tempString = `Rolled (${tempString})` + } else { + tempString = diceValue.toString() + } + + if (diceData.adds1 !== 0) { + let sign = diceData.adds1 < 0 ? '−' : '+' + let value = Math.abs(diceData.adds1) + tempString = `${tempString} ${sign} ${value}` + } + + if (diceData.adds2 !== 0) { + let sign = diceData.adds2 < 0 ? '-' : '+' + let value = Math.abs(diceData.adds2) + tempString = `${tempString} ${sign} ${value}` + } + explainLineOne = `${tempString} = ${dicePlusAdds}.` + } + + let explainLineTwo = null + let sign = diceData.modifier < 0 ? '−' : '+' + let value = Math.abs(diceData.modifier) + + if (targetmods.length > 0 && diceData.multiplier > 1) { + explainLineTwo = `Total = (${dicePlusAdds} ${sign} ${value}) × ${diceData.multiplier} = ${damage}.` + } else if (targetmods.length > 0) { + explainLineTwo = `Total = ${dicePlusAdds} ${sign} ${value} = ${damage}.` + } else if (diceData.multiplier > 1) { + explainLineTwo = `Total = ${dicePlusAdds} × ${diceData.multiplier} = ${damage}.` + } + + let hasExplanation = explainLineOne || explainLineTwo + + if (hasExplanation && b378) { + if (explainLineTwo) explainLineTwo = `${explainLineTwo}*` + else explainLineOne = `${explainLineOne}*` + } + + let contentData = { + id: generateUniqueId(), + attacker: actor.id, + dice: diceData.diceText, + damageType: diceData.damageType, + damageTypeText: diceData.damageType === 'dmg' ? ' ' : `'${diceData.damageType}' `, + damageModifier: diceData.extdamagetype, + armorDivisor: diceData.divisor, + damage: damage, + hasExplanation: hasExplanation, + explainLineOne: explainLineOne, + explainLineTwo: explainLineTwo, + isB378: b378, + roll: roll, + target: tokenName, + hitlocation: hitlocation, + } + return contentData + } + + /** + * @param {GurpsActor | User } actor + * @param {diceData} diceData + * @param {any[]} targetmods + * @param {any[]} draggableData + * @param {JQuery.Event|null} event + */ + async _createChatMessage(actor, diceData, targetmods, draggableData, event) { + let userTarget = null + if (!!game.user.targets.size) { + userTarget = game.user.targets.values().next().value + } + + let damageType = diceData.damageType === 'dmg' ? '' : diceData.damageType + damageType = !!diceData.extdamagetype ? `${damageType} ${diceData.extdamagetype}` : damageType + + let html = await renderTemplate('systems/gurps/templates/damage-message.hbs', { + draggableData: draggableData, + rolled: diceData.rolled, + dice: diceData.diceText, + loaded: diceData.loaded, + damageTypeText: `${damageType} `, + modifiers: targetmods.map(it => `${it.mod} ${it.desc.replace(/^dmg/, 'damage')}`), + userTarget: userTarget, + hitlocation: draggableData[0].hitlocation, + numtimes: draggableData.length > 1 ? ' x' + draggableData.length : '', + }) + + // @ts-ignore + const speaker = ChatMessage.getSpeaker({ actor: actor }) + /** @type {Record} */ + let messageData = { + user: game.user.id, + speaker: speaker, + content: html, + type: CONST.CHAT_MESSAGE_TYPES.ROLL, + roll: JSON.stringify(draggableData[0].roll), // only need to stringify when sending to chat + } + + if (event?.shiftKey) { + messageData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER + if (game.user.isGM) { + messageData.whisper = [game.user.id] + } else messageData.whisper = game.users.filter(u => u.isGM).map(u => u.id) + messageData.blind = true + } + + messageData['flags.transfer'] = JSON.stringify({ + type: 'damageItem', + payload: draggableData, + userTarget: !!userTarget ? userTarget.id : null, + }) + + if (isNiceDiceEnabled()) { + /** @type {GurpsRoll[]} */ + let rolls = draggableData.map(d => d.roll) + let throws = [] + /** + * @type {{ result: any; resultLabel: any; type: string; vectors: never[]; options: {}; }[]} + */ + let dice = [] + + rolls.shift() // The first roll will be handled by DSN's chat handler... the rest we will manually roll + rolls.forEach(roll => { + roll.dice.forEach(die => { + let type = 'd' + die.faces + die.results.forEach(s => + dice.push({ + result: s.result, + resultLabel: s.result, + type: type, + vectors: [], + options: {}, + }) + ) + }) + }) + throws.push({ dice: dice }) + if (dice.length > 0) { + // The user made a "multi-damage" roll... let them see the dice! + // @ts-ignore + game.dice3d.show({ throws: throws }) + } + } else { + messageData.sound = CONFIG.sounds.dice + } + ChatMessage.create(messageData).then(arg => { + let messageId = arg.id // 'qHz1QQuzpJiavH3V' + $(`[data-message-id='${messageId}']`).on('click', handleOnPdf) + }) + } } DamageChat.fullRegex = - /^(?\d+(?d\d*)?(?[+-]@?\w+)?(?[+-]\d+)?)(?:[×xX\*](?\d+\.?\d*))?(?: ?\((?-?\d+(?:\.\d+)?)\))?/ + /^(?\d+(?d\d*)?(?[+-]@?\w+)?(?[+-]\d+)?)(?:[×xX\*](?\d+\.?\d*))?(?: ?\((?-?\d+(?:\.\d+)?)\))?/ /* let transfer = { diff --git a/module/dierolls/dieroll.js b/module/dierolls/dieroll.js index 64f078ce2..3859099d1 100755 --- a/module/dierolls/dieroll.js +++ b/module/dierolls/dieroll.js @@ -6,196 +6,197 @@ import * as Settings from '../../lib/miscellaneous-settings.js' // formula="3d6", targetmods="[{ desc:"", mod:+-1 }]", thing="Roll vs 'thing'" or damagetype 'burn', // target=skill level or -1=damage roll export async function doRoll({ - actor, - formula = '3d6', - targetmods = [], - prefix = '', - thing = '', - chatthing = '', - origtarget = -1, - optionalArgs = {}, + actor, + formula = '3d6', + targetmods = [], + prefix = '', + thing = '', + chatthing = '', + origtarget = -1, + optionalArgs = {}, }) { - if (origtarget == 0 || isNaN(origtarget)) return // Target == 0, so no roll. Target == -1 for non-targetted rolls (roll, damage) - let isTargeted = origtarget > 0 // Roll "against" something (true), or just a roll (false) - let failure = false - - let chatdata = { - prefix: prefix.trim(), - chatthing: chatthing, - thing: thing, - origtarget: origtarget, - } - - // TODO Code below is duplicated in damagemessage.mjs (DamageChat) -- make sure it is updated in both places - // Lets collect up the modifiers, they are used differently depending on the type of roll - let modifier = 0 - let maxtarget = null // If not null, then the target cannot be any higher than this. - - targetmods = await GURPS.ModifierBucket.applyMods(targetmods) // append any global mods - - chatdata['targetmods'] = targetmods - let multiples = [] // The roll results (to display the individual dice rolls) - chatdata['multiples'] = multiples - - for (let m of targetmods) { - modifier += m.modint - maxtarget = (await GURPS.applyModifierDesc(actor, m.desc)) || maxtarget - } - - actor = actor || game.user - let speaker = ChatMessage.getSpeaker({ actor: actor }) - let messageData = { - user: game.user.id, - speaker: speaker, - type: CONST.CHAT_MESSAGE_TYPES.ROLL, - } - if (optionalArgs.event?.data?.private) { - messageData.whisper = [game.user.id] - messageData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER - } - - let roll = null // Will be the Roll - if (isTargeted) { - // This is a roll "against a target number", e.g. roll vs skill/attack/attribute/etc. - let finaltarget = parseInt(origtarget) + modifier - if (!!maxtarget && finaltarget > maxtarget) finaltarget = maxtarget - if (!!thing) { - //let flav = thing.replace(/\[.*\] */, '') // Flavor text cannot handle internal [] - const r1 = /\[/g - const r2 = /\]/g - let flav = thing.replaceAll(r1, '').replaceAll(r2, '') // Flavor text cannot handle internal [] - formula = formula.replace(/^(\d+d6)/, `$1[${flav.trim()}]`) - } - roll = Roll.create(formula) // The formula will always be "3d6" for a "targetted" roll - await roll.evaluate({ async: true }) - let rtotal = roll.total - - chatdata['rtotal'] = rtotal - chatdata['loaded'] = !!roll.isLoaded - chatdata['rolls'] = !!roll.dice[0] ? roll.dice[0].results.map(it => it.result.toString()).join(',') : '' - chatdata['modifier'] = modifier - chatdata['finaltarget'] = finaltarget - - // Actually, you aren't allowed to roll if the target is < 3... except for active defenses. So we will just allow it and let the GM decide. - let isCritSuccess = rtotal <= 4 || (rtotal == 5 && finaltarget >= 15) || (rtotal == 6 && finaltarget >= 16) - let isCritFailure = - rtotal >= 18 || (rtotal == 17 && finaltarget <= 15) || (rtotal - finaltarget >= 10 && finaltarget > 0) - let margin = finaltarget - rtotal - let seventeen = rtotal >= 17 - failure = seventeen || margin < 0 - - chatdata['isCritSuccess'] = isCritSuccess - chatdata['isCritFailure'] = isCritFailure - chatdata['margin'] = margin - chatdata['failure'] = failure - chatdata['seventeen'] = seventeen - chatdata['isDraggable'] = !seventeen && margin != 0 - chatdata['otf'] = (margin >= 0 ? '+' + margin : margin) + ' margin for ' + thing - chatdata['followon'] = optionalArgs.followon - - if (margin > 0 && !!optionalArgs.obj && !!optionalArgs.obj.rcl) { - // if the attached obj (see handleRoll()) as Recoil information, do the additional math - let rofrcl = Math.floor(margin / parseInt(optionalArgs.obj.rcl)) + 1 - if (!!optionalArgs.obj.rof) { - let rof = optionalArgs.obj.rof - let m = rof.match(/(\d+)[×xX\*](\d+)/) // Support shotgun RoF (3x9) - if (!!m) rofrcl = Math.min(rofrcl, parseInt(m[1]) * parseInt(m[2])) - else rofrcl = Math.min(rofrcl, parseInt(rof)) - } - - chatdata['rof'] = optionalArgs.obj.rof - chatdata['rcl'] = optionalArgs.obj.rcl - chatdata['rofrcl'] = rofrcl - } - - chatdata['optlabel'] = optionalArgs.text || '' - - if (game.dice3d && !game.dice3d.messageHookDisabled) { // save for after roll animation is complete - if (failure && optionalArgs.obj?.failotf) GURPS.PendingOTFs.unshift(optionalArgs.obj.failotf) - if (!failure && optionalArgs.obj?.passotf) GURPS.PendingOTFs.unshift(optionalArgs.obj.passotf) - } else { - if (failure && optionalArgs.obj?.failotf) GURPS.executeOTF(optionalArgs.obj.failotf, optionalArgs.event) - if (!failure && optionalArgs.obj?.passotf) GURPS.executeOTF(optionalArgs.obj.passotf, optionalArgs.event) - } - let r = {} - r['rtotal'] = rtotal - r['loaded'] = !!roll.isLoaded - r['rolls'] = !!roll.dice[0] ? roll.dice[0].results.map(it => it.result).join() : '' - multiples.push(r) - } else { - // This is non-targeted, non-damage roll where the modifier is added to the roll, not the target - // NOTE: Damage rolls have been moved to damagemessage.js/DamageChat - - let min = 0 - if (formula.slice(-1) === '!') { - formula = formula.slice(0, -1) - min = 1 - } - - let max = +(optionalArgs.event?.data?.repeat) || 1 - if (max > 1) chatdata['chatthing'] = 'x' + max - for (let i = 0; i < max; i++) { - roll = Roll.create(formula + `+${modifier}`) - await roll.evaluate({ async: true }) - - let rtotal = roll.total - if (rtotal < min) { - rtotal = min - } - - // ? if (rtotal == 1) thing = thing.replace('points', 'point') - let r = {} - r['rtotal'] = rtotal - r['loaded'] = !!roll.isLoaded - r['rolls'] = !!roll.dice[0] ? roll.dice[0].results.map(it => it.result).join() : '' - multiples.push(r) - } - chatdata['modifier'] = modifier - } - - if (isTargeted) GURPS.setLastTargetedRoll(chatdata, speaker.actor, speaker.token, true) - - let message = await renderTemplate('systems/gurps/templates/die-roll-chat-message.hbs', chatdata) - - messageData.content = message - messageData.roll = JSON.stringify(roll) - - let whoCanSeeDice = null - if (optionalArgs.event?.shiftKey) { - whoCanSeeDice = [game.user.id] - messageData.whisper = [game.user.id] - } - - let isCtrl = false - let creatOptions = {} - try { - isCtrl = !!optionalArgs.event && game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.CONTROL) - } catch { } - - if ( - !!optionalArgs.blind || - !!optionalArgs.event?.blind || - isCtrl || - (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_SHIFT_CLICK_BLIND) && !!optionalArgs.event?.shiftKey) - ) { - messageData.whisper = ChatMessage.getWhisperRecipients("GM").map(u => u.id); - // messageData.blind = true // possibly not used anymore - creatOptions.rollMode = "blindroll" // new for V9 - } - - messageData.sound = CONFIG.sounds.dice - ChatMessage.create(messageData, creatOptions) - - if (isTargeted && !!optionalArgs.action) { - let users = actor.isSelf ? [] : actor.getOwners() - let ids = users.map(it => it.id) - let messageData = { - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - whisper: ids, - } - if (!failure && !!optionalArgs.action.truetext) messageData.content = optionalArgs.action.truetext - if (failure && !!optionalArgs.action.falsetext) messageData.content = optionalArgs.action.falsetext - if (!!messageData.content) ChatMessage.create(messageData) - } - return !failure + if (origtarget == 0 || isNaN(origtarget)) return // Target == 0, so no roll. Target == -1 for non-targetted rolls (roll, damage) + let isTargeted = origtarget > 0 // Roll "against" something (true), or just a roll (false) + let failure = false + + let chatdata = { + prefix: prefix.trim(), + chatthing: chatthing, + thing: thing, + origtarget: origtarget, + } + + // TODO Code below is duplicated in damagemessage.mjs (DamageChat) -- make sure it is updated in both places + // Lets collect up the modifiers, they are used differently depending on the type of roll + let modifier = 0 + let maxtarget = null // If not null, then the target cannot be any higher than this. + + targetmods = await GURPS.ModifierBucket.applyMods(targetmods) // append any global mods + + chatdata['targetmods'] = targetmods + let multiples = [] // The roll results (to display the individual dice rolls) + chatdata['multiples'] = multiples + + for (let m of targetmods) { + modifier += m.modint + maxtarget = (await GURPS.applyModifierDesc(actor, m.desc)) || maxtarget + } + + actor = actor || game.user + let speaker = ChatMessage.getSpeaker({ actor: actor }) + let messageData = { + user: game.user.id, + speaker: speaker, + type: CONST.CHAT_MESSAGE_TYPES.ROLL, + } + if (optionalArgs.event?.data?.private) { + messageData.whisper = [game.user.id] + messageData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER + } + + let roll = null // Will be the Roll + if (isTargeted) { + // This is a roll "against a target number", e.g. roll vs skill/attack/attribute/etc. + let finaltarget = parseInt(origtarget) + modifier + if (!!maxtarget && finaltarget > maxtarget) finaltarget = maxtarget + if (!!thing) { + //let flav = thing.replace(/\[.*\] */, '') // Flavor text cannot handle internal [] + const r1 = /\[/g + const r2 = /\]/g + let flav = thing.replaceAll(r1, '').replaceAll(r2, '') // Flavor text cannot handle internal [] + formula = formula.replace(/^(\d+d6)/, `$1[${flav.trim()}]`) + } + roll = Roll.create(formula) // The formula will always be "3d6" for a "targetted" roll + await roll.evaluate({ async: true }) + let rtotal = roll.total + + chatdata['rtotal'] = rtotal + chatdata['loaded'] = !!roll.isLoaded + chatdata['rolls'] = !!roll.dice[0] ? roll.dice[0].results.map(it => it.result.toString()).join(',') : '' + chatdata['modifier'] = modifier + chatdata['finaltarget'] = finaltarget + + // Actually, you aren't allowed to roll if the target is < 3... except for active defenses. So we will just allow it and let the GM decide. + let isCritSuccess = rtotal <= 4 || (rtotal == 5 && finaltarget >= 15) || (rtotal == 6 && finaltarget >= 16) + let isCritFailure = + rtotal >= 18 || (rtotal == 17 && finaltarget <= 15) || (rtotal - finaltarget >= 10 && finaltarget > 0) + let margin = finaltarget - rtotal + let seventeen = rtotal >= 17 + failure = seventeen || margin < 0 + + chatdata['isCritSuccess'] = isCritSuccess + chatdata['isCritFailure'] = isCritFailure + chatdata['margin'] = margin + chatdata['failure'] = failure + chatdata['seventeen'] = seventeen + chatdata['isDraggable'] = !seventeen && margin != 0 + chatdata['otf'] = (margin >= 0 ? '+' + margin : margin) + ' margin for ' + thing + chatdata['followon'] = optionalArgs.followon + + if (margin > 0 && !!optionalArgs.obj && !!optionalArgs.obj.rcl) { + // if the attached obj (see handleRoll()) as Recoil information, do the additional math + let rofrcl = Math.floor(margin / parseInt(optionalArgs.obj.rcl)) + 1 + if (!!optionalArgs.obj.rof) { + let rof = optionalArgs.obj.rof + let m = rof.match(/(\d+)[×xX\*](\d+)/) // Support shotgun RoF (3x9) + if (!!m) rofrcl = Math.min(rofrcl, parseInt(m[1]) * parseInt(m[2])) + else rofrcl = Math.min(rofrcl, parseInt(rof)) + } + + chatdata['rof'] = optionalArgs.obj.rof + chatdata['rcl'] = optionalArgs.obj.rcl + chatdata['rofrcl'] = rofrcl + } + + chatdata['optlabel'] = optionalArgs.text || '' + + if (game.dice3d && !game.dice3d.messageHookDisabled) { + // save for after roll animation is complete + if (failure && optionalArgs.obj?.failotf) GURPS.PendingOTFs.unshift(optionalArgs.obj.failotf) + if (!failure && optionalArgs.obj?.passotf) GURPS.PendingOTFs.unshift(optionalArgs.obj.passotf) + } else { + if (failure && optionalArgs.obj?.failotf) GURPS.executeOTF(optionalArgs.obj.failotf, optionalArgs.event) + if (!failure && optionalArgs.obj?.passotf) GURPS.executeOTF(optionalArgs.obj.passotf, optionalArgs.event) + } + let r = {} + r['rtotal'] = rtotal + r['loaded'] = !!roll.isLoaded + r['rolls'] = !!roll.dice[0] ? roll.dice[0].results.map(it => it.result).join() : '' + multiples.push(r) + } else { + // This is non-targeted, non-damage roll where the modifier is added to the roll, not the target + // NOTE: Damage rolls have been moved to damagemessage.js/DamageChat + + let min = 0 + if (formula.slice(-1) === '!') { + formula = formula.slice(0, -1) + min = 1 + } + + let max = +optionalArgs.event?.data?.repeat || 1 + if (max > 1) chatdata['chatthing'] = 'x' + max + for (let i = 0; i < max; i++) { + roll = Roll.create(formula + `+${modifier}`) + await roll.evaluate({ async: true }) + + let rtotal = roll.total + if (rtotal < min) { + rtotal = min + } + + // ? if (rtotal == 1) thing = thing.replace('points', 'point') + let r = {} + r['rtotal'] = rtotal + r['loaded'] = !!roll.isLoaded + r['rolls'] = !!roll.dice[0] ? roll.dice[0].results.map(it => it.result).join() : '' + multiples.push(r) + } + chatdata['modifier'] = modifier + } + + if (isTargeted) GURPS.setLastTargetedRoll(chatdata, speaker.actor, speaker.token, true) + + let message = await renderTemplate('systems/gurps/templates/die-roll-chat-message.hbs', chatdata) + + messageData.content = message + messageData.roll = JSON.stringify(roll) + + let whoCanSeeDice = null + if (optionalArgs.event?.shiftKey) { + whoCanSeeDice = [game.user.id] + messageData.whisper = [game.user.id] + } + + let isCtrl = false + let creatOptions = {} + try { + isCtrl = !!optionalArgs.event && game.keyboard.isModifierActive(KeyboardManager.MODIFIER_KEYS.CONTROL) + } catch {} + + if ( + !!optionalArgs.blind || + !!optionalArgs.event?.blind || + isCtrl || + (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_SHIFT_CLICK_BLIND) && !!optionalArgs.event?.shiftKey) + ) { + messageData.whisper = ChatMessage.getWhisperRecipients('GM').map(u => u.id) + // messageData.blind = true // possibly not used anymore + creatOptions.rollMode = 'blindroll' // new for V9 + } + + messageData.sound = CONFIG.sounds.dice + ChatMessage.create(messageData, creatOptions) + + if (isTargeted && !!optionalArgs.action) { + let users = actor.isSelf ? [] : actor.getOwners() + let ids = users.map(it => it.id) + let messageData = { + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + whisper: ids, + } + if (!failure && !!optionalArgs.action.truetext) messageData.content = optionalArgs.action.truetext + if (failure && !!optionalArgs.action.falsetext) messageData.content = optionalArgs.action.falsetext + if (!!messageData.content) ChatMessage.create(messageData) + } + return !failure } diff --git a/module/effects/active-effect-config.js b/module/effects/active-effect-config.js index 3c74c651b..54d9578a7 100644 --- a/module/effects/active-effect-config.js +++ b/module/effects/active-effect-config.js @@ -15,7 +15,7 @@ export default class GurpsActiveEffectConfig extends ActiveEffectConfig { async _updateObject(event, formData) { // If there is an EndCondition, this is a temporary effect. Signal this by setting the core.statusId value. let newEndCondition = formData.flags?.gurps?.endCondition - formData.flags['core.statusId'] = (!!newEndCondition) ? this.object.data.label : null + formData.flags['core.statusId'] = !!newEndCondition ? this.object.data.label : null let result = await super._updateObject(event, formData) diff --git a/module/effects/active-effect.js b/module/effects/active-effect.js index e26f6e8e0..7bdec439a 100644 --- a/module/effects/active-effect.js +++ b/module/effects/active-effect.js @@ -3,250 +3,250 @@ import { i18n, i18n_f } from '../../lib/utilities.js' export default class GurpsActiveEffect extends ActiveEffect { - static init() { - CONFIG.ActiveEffect.documentClass = GurpsActiveEffect - - Hooks.on('preCreateActiveEffect', GurpsActiveEffect._preCreate) - Hooks.on('createActiveEffect', GurpsActiveEffect._create) - Hooks.on('applyActiveEffect', GurpsActiveEffect._apply) - Hooks.on('updateActiveEffect', GurpsActiveEffect._update) - Hooks.on('deleteActiveEffect', GurpsActiveEffect._delete) - Hooks.on('updateCombat', GurpsActiveEffect._updateCombat) - - Hooks.once('ready', function() { - const oldDuration = Object.getOwnPropertyDescriptor(ActiveEffect.prototype, 'duration') - - Object.defineProperty(ActiveEffect.prototype, 'duration', { - get: function() { - let results = oldDuration?.get?.call(this) - - if (results.type === 'none') { - // check if there is a termination condition - const d = this.data.duration - if (!!d?.termination) { - // TODO add core statusId flag and fix up results to show there is a duration of sorts - results = { - type: 'condition', - duration: null, - remaining: null, - termination: d.termination, - label: d.termination, - } - } - } - return results - }, - }) - }) - } - - /** - * Before adding the ActiveEffect to the Actor/Item -- might be used to augment the data used to create, for example. - * @param {ActiveEffect} _effect - * @param {ActiveEffectData} data - * @param {*} _options - * @param {*} _userId - */ - static _preCreate(_effect, data, _options, _userId) { - if (data.duration && !data.duration.combat && game.combat) data.duration.combat = game.combats?.active?.id - } - - /** - * After creation of the ActiveEffect. - * @param {ActiveEffect} effect - * @param {ActiveEffectData} _data - * @param {*} _userId - */ - static async _create(effect, _data, _userId) { - if (effect.getFlag('gurps', 'requiresConfig') === true) { - let dialog = new ActiveEffectConfig(effect) - await dialog.render(true) - } - } - - /** - * On Actor.applyEffect: Applies only to changes that have mode: CONST.ACTIVE_EFFECT_MODES.CUSTOM. - * @param {Actor|Item} actor - * @param {ChangeData} change - the change to apply - * @param {*} _options - * @param {*} _user - */ - static async _apply(actor, change, _options, _user) { - if (change.key === 'data.conditions.maneuver') actor.replaceManeuver(change.value) - else if (change.key === 'data.conditions.posture') actor.replacePosture(change) - // else if (change.key === 'chat') change.effect.chat(actor, JSON.parse(change.value)) - else console.log(change) - } - - /** - * When updating an ActiveEffect. - * @param {ActiveEffect} _effect - * @param {ActiveEffectData} _data to use to update the effect. - * @param {*} _options - * @param {*} _userId - */ - static _update(_effect, _data, _options, _userId) { - console.log('update ' + _effect) - } - - /** - * When deleting an ActiveEffect. - * @param {ActiveEffect} _effect - * @param {ActiveEffectData} _data - * @param {*} _userId - */ - static _delete(_effect, _data, _userId) { - console.log('delete ' + _effect) - } - - /** - * Called whenever updating a Combat. - * @param {Combat} combat - * @param {CombatData} _data - * @param {*} _options - * @param {*} _userId - */ - static async _updateCombat(combat, _data, _options, _userId) { - // get previous combatant { round: 6, turn: 0, combatantId: 'id', tokenId: 'id' } - let previous = combat.previous - if (previous.tokenId) { - let token = canvas.tokens?.get(previous.tokenId) - - // go through all effects, removing those that have expired - if (token && token.actor) { - for (const effect of token.actor.effects) { - if (await effect.isExpired()) - ui.notifications.info( - `${i18n('GURPS.effectExpired', 'Effect has expired: ')} '[${i18n(effect.data.label)}]'` - ) - } - } - } - } - - /** - * @param {ActiveEffectData} data - * @param {any} context - */ - constructor(data, context) { - super(data, context) - - this.context = context - this.chatmessages = [] - } - - get endCondition() { - return this.getFlag('gurps', 'endCondition') - } - - set endCondition(otf) { - this.setFlag('gurps', 'endCondition', otf) - if (!!otf) { - this.setFlag('core', 'statusId', `${this.name}-endCondition`) - } - } - - get terminateActions() { - let data = this.getFlag('gurps', 'terminateActions') - return data ?? [] - } - - /** - * @param {ActiveEffect} effect - */ - static getName(effect) { - return /** @type {string} */ (effect.getFlag('gurps', 'name')) - } - - static async clearEffectsOnSelectedToken() { - const effect = _token.actor.effects.contents - for (let i = 0; i < effect.length; i++) { - let condition = effect[i].data.label - let status = effect[i].data.disabled - let effect_id = effect[i].data._id - console.log(`Clear Effect: condition: [${condition}] status: [${status}] effect_id: [${effect_id}]`) - if (status === false) { - await _token.actor.deleteEmbeddedDocuments('ActiveEffect', [effect_id]) - } - } - } - - chat(actor, value) { - if (!!value?.frequency && value.frequency === 'once') { - if (this.chatmessages.includes(value.msg)) { - console.log(`Message [${value.msg}] already displayed, do nothing`) - return - } - } - - for (const key in value.args) { - let val = value.args[key] - if (foundry.utils.getType(val) === 'string' && val.startsWith('@')) { - value.args[key] = actor[val.slice(1)] - } else if (foundry.utils.getType(val) === 'string' && val.startsWith('!')) { - value.args[key] = i18n(val.slice(1)) - } - if (key === 'pdfref') value.args.pdfref = i18n(val) - } - - let msg = !!value.args ? i18n_f(value.msg, value.args) : i18n(value.msg) - - let self = this - renderTemplate('systems/gurps/templates/chat-processing.html', { lines: [msg] }).then(content => { - let users = actor.getOwners() - let ids = /** @type {string[] | undefined} */ (users?.map(it => it.id)) - - let messageData = { - content: content, - whisper: ids || null, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - } - ChatMessage.create(messageData) - ui.combat?.render() - self.chatmessages.push(value.msg) - }) - } - - // TODO Any ActiveEffect with a status.core.statusId is by default a temporary effect and will be added as an icon to the token. - - async isExpired() { - if (this.duration && !!this.duration.duration) { - if (this.duration.remaining <= 1) { - return true - } - } - - // if (!!this.endCondition) { - // let action = parselink(this.endCondition) - - // if (!!action.action) { - // if (action.action.type === 'modifier') { - // ui.notifications.warn( - // `${i18n( - // 'GURPS.effectBadEndCondition', - // 'End Condition is not a skill or attribute test: ' - // )} '[${endCondition}]'` - // ) - // return false - // } - - // return await GURPS.performAction(action.action, this.parent, { - // shiftKey: false, - // ctrlKey: false, - // data: {}, - // }) - // } // Looks like a /roll OtF, but didn't parse as one - // else - // ui.notifications.warn( - // `${i18n( - // 'GURPS.effectBadEndCondition', - // 'End Condition is not a skill or attribute test: ' - // )} '[${endCondition}]'` - // ) - // } - - return false - } + static init() { + CONFIG.ActiveEffect.documentClass = GurpsActiveEffect + + Hooks.on('preCreateActiveEffect', GurpsActiveEffect._preCreate) + Hooks.on('createActiveEffect', GurpsActiveEffect._create) + Hooks.on('applyActiveEffect', GurpsActiveEffect._apply) + Hooks.on('updateActiveEffect', GurpsActiveEffect._update) + Hooks.on('deleteActiveEffect', GurpsActiveEffect._delete) + Hooks.on('updateCombat', GurpsActiveEffect._updateCombat) + + Hooks.once('ready', function () { + const oldDuration = Object.getOwnPropertyDescriptor(ActiveEffect.prototype, 'duration') + + Object.defineProperty(ActiveEffect.prototype, 'duration', { + get: function () { + let results = oldDuration?.get?.call(this) + + if (results.type === 'none') { + // check if there is a termination condition + const d = this.data.duration + if (!!d?.termination) { + // TODO add core statusId flag and fix up results to show there is a duration of sorts + results = { + type: 'condition', + duration: null, + remaining: null, + termination: d.termination, + label: d.termination, + } + } + } + return results + }, + }) + }) + } + + /** + * Before adding the ActiveEffect to the Actor/Item -- might be used to augment the data used to create, for example. + * @param {ActiveEffect} _effect + * @param {ActiveEffectData} data + * @param {*} _options + * @param {*} _userId + */ + static _preCreate(_effect, data, _options, _userId) { + if (data.duration && !data.duration.combat && game.combat) data.duration.combat = game.combats?.active?.id + } + + /** + * After creation of the ActiveEffect. + * @param {ActiveEffect} effect + * @param {ActiveEffectData} _data + * @param {*} _userId + */ + static async _create(effect, _data, _userId) { + if (effect.getFlag('gurps', 'requiresConfig') === true) { + let dialog = new ActiveEffectConfig(effect) + await dialog.render(true) + } + } + + /** + * On Actor.applyEffect: Applies only to changes that have mode: CONST.ACTIVE_EFFECT_MODES.CUSTOM. + * @param {Actor|Item} actor + * @param {ChangeData} change - the change to apply + * @param {*} _options + * @param {*} _user + */ + static async _apply(actor, change, _options, _user) { + if (change.key === 'data.conditions.maneuver') actor.replaceManeuver(change.value) + else if (change.key === 'data.conditions.posture') actor.replacePosture(change) + // else if (change.key === 'chat') change.effect.chat(actor, JSON.parse(change.value)) + else console.log(change) + } + + /** + * When updating an ActiveEffect. + * @param {ActiveEffect} _effect + * @param {ActiveEffectData} _data to use to update the effect. + * @param {*} _options + * @param {*} _userId + */ + static _update(_effect, _data, _options, _userId) { + console.log('update ' + _effect) + } + + /** + * When deleting an ActiveEffect. + * @param {ActiveEffect} _effect + * @param {ActiveEffectData} _data + * @param {*} _userId + */ + static _delete(_effect, _data, _userId) { + console.log('delete ' + _effect) + } + + /** + * Called whenever updating a Combat. + * @param {Combat} combat + * @param {CombatData} _data + * @param {*} _options + * @param {*} _userId + */ + static async _updateCombat(combat, _data, _options, _userId) { + // get previous combatant { round: 6, turn: 0, combatantId: 'id', tokenId: 'id' } + let previous = combat.previous + if (previous.tokenId) { + let token = canvas.tokens?.get(previous.tokenId) + + // go through all effects, removing those that have expired + if (token && token.actor) { + for (const effect of token.actor.effects) { + if (await effect.isExpired()) + ui.notifications.info( + `${i18n('GURPS.effectExpired', 'Effect has expired: ')} '[${i18n(effect.data.label)}]'` + ) + } + } + } + } + + /** + * @param {ActiveEffectData} data + * @param {any} context + */ + constructor(data, context) { + super(data, context) + + this.context = context + this.chatmessages = [] + } + + get endCondition() { + return this.getFlag('gurps', 'endCondition') + } + + set endCondition(otf) { + this.setFlag('gurps', 'endCondition', otf) + if (!!otf) { + this.setFlag('core', 'statusId', `${this.name}-endCondition`) + } + } + + get terminateActions() { + let data = this.getFlag('gurps', 'terminateActions') + return data ?? [] + } + + /** + * @param {ActiveEffect} effect + */ + static getName(effect) { + return /** @type {string} */ (effect.getFlag('gurps', 'name')) + } + + static async clearEffectsOnSelectedToken() { + const effect = _token.actor.effects.contents + for (let i = 0; i < effect.length; i++) { + let condition = effect[i].data.label + let status = effect[i].data.disabled + let effect_id = effect[i].data._id + console.log(`Clear Effect: condition: [${condition}] status: [${status}] effect_id: [${effect_id}]`) + if (status === false) { + await _token.actor.deleteEmbeddedDocuments('ActiveEffect', [effect_id]) + } + } + } + + chat(actor, value) { + if (!!value?.frequency && value.frequency === 'once') { + if (this.chatmessages.includes(value.msg)) { + console.log(`Message [${value.msg}] already displayed, do nothing`) + return + } + } + + for (const key in value.args) { + let val = value.args[key] + if (foundry.utils.getType(val) === 'string' && val.startsWith('@')) { + value.args[key] = actor[val.slice(1)] + } else if (foundry.utils.getType(val) === 'string' && val.startsWith('!')) { + value.args[key] = i18n(val.slice(1)) + } + if (key === 'pdfref') value.args.pdfref = i18n(val) + } + + let msg = !!value.args ? i18n_f(value.msg, value.args) : i18n(value.msg) + + let self = this + renderTemplate('systems/gurps/templates/chat-processing.html', { lines: [msg] }).then(content => { + let users = actor.getOwners() + let ids = /** @type {string[] | undefined} */ (users?.map(it => it.id)) + + let messageData = { + content: content, + whisper: ids || null, + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + } + ChatMessage.create(messageData) + ui.combat?.render() + self.chatmessages.push(value.msg) + }) + } + + // TODO Any ActiveEffect with a status.core.statusId is by default a temporary effect and will be added as an icon to the token. + + async isExpired() { + if (this.duration && !!this.duration.duration) { + if (this.duration.remaining <= 1) { + return true + } + } + + // if (!!this.endCondition) { + // let action = parselink(this.endCondition) + + // if (!!action.action) { + // if (action.action.type === 'modifier') { + // ui.notifications.warn( + // `${i18n( + // 'GURPS.effectBadEndCondition', + // 'End Condition is not a skill or attribute test: ' + // )} '[${endCondition}]'` + // ) + // return false + // } + + // return await GURPS.performAction(action.action, this.parent, { + // shiftKey: false, + // ctrlKey: false, + // data: {}, + // }) + // } // Looks like a /roll OtF, but didn't parse as one + // else + // ui.notifications.warn( + // `${i18n( + // 'GURPS.effectBadEndCondition', + // 'End Condition is not a skill or attribute test: ' + // )} '[${endCondition}]'` + // ) + // } + + return false + } } /* diff --git a/module/effects/dae.js b/module/effects/dae.js index 36b752e5f..95630e756 100644 --- a/module/effects/dae.js +++ b/module/effects/dae.js @@ -3,132 +3,136 @@ Mainly for the 'teleport token function. ;-) **/ -export var socketlibSocket = undefined; +export var socketlibSocket = undefined export let setupSocket = () => { if (globalThis.socketlib) { - socketlibSocket = globalThis.socketlib.registerSystem("gurps"); - socketlibSocket.register("recreateToken", _recreateToken); - socketlibSocket.register("createToken", _createToken); - socketlibSocket.register("deleteToken", _deleteToken); - socketlibSocket.register("renameToken", _renameToken); - socketlibSocket.register("setTokenFlag", _setTokenFlag); - socketlibSocket.register("setFlag", _setFlag); - socketlibSocket.register("unsetFlag", _unsetFlag); - socketlibSocket.register("deleteUuid", _deleteUuid); + socketlibSocket = globalThis.socketlib.registerSystem('gurps') + socketlibSocket.register('recreateToken', _recreateToken) + socketlibSocket.register('createToken', _createToken) + socketlibSocket.register('deleteToken', _deleteToken) + socketlibSocket.register('renameToken', _renameToken) + socketlibSocket.register('setTokenFlag', _setTokenFlag) + socketlibSocket.register('setFlag', _setFlag) + socketlibSocket.register('unsetFlag', _unsetFlag) + socketlibSocket.register('deleteUuid', _deleteUuid) } return !!globalThis.socketlib -}; +} function DAEfromUuid(uuid) { - let doc; - try { - let parts = uuid.split("."); - const [docName, docId] = parts.slice(0, 2); - parts = parts.slice(2); - const collection = CONFIG[docName].collection.instance; - doc = collection.get(docId); - // Embedded Documents - while (parts.length > 1) { - const [embeddedName, embeddedId] = parts.slice(0, 2); - doc = doc.getEmbeddedDocument(embeddedName, embeddedId); - parts = parts.slice(2); - } - } /*catch (err) { + let doc + try { + let parts = uuid.split('.') + const [docName, docId] = parts.slice(0, 2) + parts = parts.slice(2) + const collection = CONFIG[docName].collection.instance + doc = collection.get(docId) + // Embedded Documents + while (parts.length > 1) { + const [embeddedName, embeddedId] = parts.slice(0, 2) + doc = doc.getEmbeddedDocument(embeddedName, embeddedId) + parts = parts.slice(2) + } + } finally { + /*catch (err) { error(`dae | could not fetch ${uuid} ${err}`) } */ - finally { - return doc || null; - } + return doc || null + } } function DAEfromActorUuid(uuid) { - let doc = DAEfromUuid(uuid); - if (doc instanceof CONFIG.Token.documentClass) - doc = doc.actor; - return doc || null; + let doc = DAEfromUuid(uuid) + if (doc instanceof CONFIG.Token.documentClass) doc = doc.actor + return doc || null } async function _deleteUuid(data) { - const entity = await fromUuid(data.uuid); - if (entity && entity instanceof Item && !data.uuid.startsWith("Compendium") && !data.uuid.startsWith("Item")) { // only allow deletion of owned items - return await entity.delete(); - } - if (entity && entity instanceof CONFIG.Token.documentClass && !data.uuid.startsWith("Compendium") && !data.uuid.startsWith("Item")) { // only allow deletion of owned items - return await entity.delete(); - } - if (entity && entity instanceof CONFIG.ActiveEffect.documentClass) - return await entity.delete(); - return false; + const entity = await fromUuid(data.uuid) + if (entity && entity instanceof Item && !data.uuid.startsWith('Compendium') && !data.uuid.startsWith('Item')) { + // only allow deletion of owned items + return await entity.delete() + } + if ( + entity && + entity instanceof CONFIG.Token.documentClass && + !data.uuid.startsWith('Compendium') && + !data.uuid.startsWith('Item') + ) { + // only allow deletion of owned items + return await entity.delete() + } + if (entity && entity instanceof CONFIG.ActiveEffect.documentClass) return await entity.delete() + return false } async function _unsetFlag(data) { - return await DAEfromActorUuid(data.actorUuid)?.unsetFlag("dae", data.flagId); + return await DAEfromActorUuid(data.actorUuid)?.unsetFlag('dae', data.flagId) } async function _setFlag(data) { - if (!data.actorUuid) - return await game.actors.get(data.actorId)?.setFlag("dae", data.flagId, data.value); - else - return await DAEfromActorUuid(data.actorUuid)?.setFlag("dae", data.flagId, data.value); + if (!data.actorUuid) return await game.actors.get(data.actorId)?.setFlag('dae', data.flagId, data.value) + else return await DAEfromActorUuid(data.actorUuid)?.setFlag('dae', data.flagId, data.value) } async function _setTokenFlag(data) { - const update = {}; - update[`flags.dae.${data.flagName}`] = data.flagValue; - return await DAEfromUuid(data.tokenUuid)?.update(update); + const update = {} + update[`flags.dae.${data.flagName}`] = data.flagValue + return await DAEfromUuid(data.tokenUuid)?.update(update) } async function _createToken(data) { - let scenes = game.scenes; - let targetScene = scenes.get(data.targetSceneId); - //@ts-ignore - return await targetScene.createEmbeddedDocuments('Token', [mergeObject(duplicate(data.tokenData), { "x": data.x, "y": data.y, hidden: false }, { overwrite: true, inplace: true })]); + let scenes = game.scenes + let targetScene = scenes.get(data.targetSceneId) + //@ts-ignore + return await targetScene.createEmbeddedDocuments('Token', [ + mergeObject(duplicate(data.tokenData), { x: data.x, y: data.y, hidden: false }, { overwrite: true, inplace: true }), + ]) } async function _deleteToken(data) { - return await DAEfromUuid(data.tokenUuid)?.delete(); + return await DAEfromUuid(data.tokenUuid)?.delete() } async function _recreateToken(data) { - await _createToken(data); - return await DAEfromUuid(data.tokenUuid)?.delete(); + await _createToken(data) + return await DAEfromUuid(data.tokenUuid)?.delete() } async function _renameToken(data) { - return await canvas.tokens.placeables.find(t => t.id === data.tokenData._id).update({ "name": data.newName }); + return await canvas.tokens.placeables.find(t => t.id === data.tokenData._id).update({ name: data.newName }) } let tokenScene = (tokenName, sceneName) => { - if (!sceneName) { - for (let scene of game.scenes) { - //@ts-ignore - let found = scene.tokens.getName(tokenName); - if (found) - return { scene, found }; - } - } - else { - //@ts-ignore - let scene = game.scenes.getName(sceneName); - if (scene) { - //@ts-ignore - let found = scene.tokens.getName(tokenName); - if (found) { - return { scene, found }; - } - } - } - return { scene: null, tokenDocument: null }; -}; -export let moveToken = async (token, targetTokenName, xGridOffset = 0, yGridOffset = 0, targetSceneName = "") => { - let { scene, found } = tokenScene(targetTokenName, targetSceneName); - if (!token) { - warn("Dynmaiceffects | moveToken: Token not found"); - return ("Token not found"); + if (!sceneName) { + for (let scene of game.scenes) { + //@ts-ignore + let found = scene.tokens.getName(tokenName) + if (found) return { scene, found } } - if (!found) { - warn("dae | moveToken: Target Not found"); - return `Token ${targetTokenName} not found`; + } else { + //@ts-ignore + let scene = game.scenes.getName(sceneName) + if (scene) { + //@ts-ignore + let found = scene.tokens.getName(tokenName) + if (found) { + return { scene, found } + } } - socketlibSocket.executeAsGM("recreateToken", { - userId: game.user.id, - startSceneId: canvas.scene.id, - tokenUuid: token.uuid, - targetSceneId: scene.id, tokenData: token.data, - x: found.data.x + xGridOffset * canvas.scene.data.grid, - y: found.data.y + yGridOffset * canvas.scene.data.grid - }); - /* + } + return { scene: null, tokenDocument: null } +} +export let moveToken = async (token, targetTokenName, xGridOffset = 0, yGridOffset = 0, targetSceneName = '') => { + let { scene, found } = tokenScene(targetTokenName, targetSceneName) + if (!token) { + warn('Dynmaiceffects | moveToken: Token not found') + return 'Token not found' + } + if (!found) { + warn('dae | moveToken: Target Not found') + return `Token ${targetTokenName} not found` + } + socketlibSocket.executeAsGM('recreateToken', { + userId: game.user.id, + startSceneId: canvas.scene.id, + tokenUuid: token.uuid, + targetSceneId: scene.id, + tokenData: token.data, + x: found.data.x + xGridOffset * canvas.scene.data.grid, + y: found.data.y + yGridOffset * canvas.scene.data.grid, + }) + /* return await requestGMAction(GMAction.actions.recreateToken, { userId: game.user.id, startSceneId: canvas.scene.id, @@ -138,55 +142,68 @@ export let moveToken = async (token, targetTokenName, xGridOffset = 0, yGridOffs y: found.data.y + yGridOffset * canvas.scene.data.grid }); */ -}; +} export async function createToken(tokenData, x, y) { - let targetSceneId = canvas.scene.id; - // requestGMAction(GMAction.actions.createToken, {userId: game.user.id, targetSceneId, tokenData, x, y}) - return socketlibSocket.execuateAsGM("createToken", { userId: game.user.id, targetSceneId, tokenData, x, y }); + let targetSceneId = canvas.scene.id + // requestGMAction(GMAction.actions.createToken, {userId: game.user.id, targetSceneId, tokenData, x, y}) + return socketlibSocket.execuateAsGM('createToken', { userId: game.user.id, targetSceneId, tokenData, x, y }) } export let teleport = async (tokenDocument, targetScene, xpos, ypos) => { - let x = Number(xpos); - let y = parseInt(ypos); - if (isNaN(x) || isNaN(y)) { - console.error("dae| teleport: Invalid co-ords", xpos, ypos); - return `Invalid target co-ordinates (${xpos}, ${ypos})`; - } - if (!tokenDocument) { - console.warn("dae | teleport: No Token"); - return "No active token"; - } - // Hide the current token - if (targetScene.name === canvas.scene.name) { - //@ts-ignore - CanvasAnimation.terminateAnimation(`Token.${tokenDocument.id}.animateMovement`); - let sourceSceneId = canvas.scene.id; - // After the token is transported, we need to store the new token as the active token (and select it, since the original was selected) - GURPS.IgnoreTokenSelect = true - let newDocument = await socketlibSocket.executeAsGM("recreateToken", { userId: game.user.id, tokenUuid: tokenDocument.uuid, startSceneId: sourceSceneId, targetSceneId: targetScene.id, tokenData: tokenDocument.data, x: xpos, y: ypos }); - let actor = newDocument.actor - if (!actor && newDocument.actorId) - actor = game.actors.get(newDocument.actorId) - GURPS.SetLastActor(actor, newDocument) - GURPS.IgnoreTokenSelect = false - setTimeout(() => canvas.pan({ x: xpos, y: ypos }), 200) - return true - } - // deletes and recreates the token - var sourceSceneId = canvas.scene.id; - Hooks.once("canvasReady", async () => { - GURPS.IgnoreTokenSelect = true - let newDocument = await socketlibSocket.executeAsGM("createToken", { userId: game.user.id, startSceneId: sourceSceneId, targetSceneId: targetScene.id, tokenData: tokenDocument.data, x: xpos, y: ypos }); - await socketlibSocket.executeAsGM("deleteToken", { userId: game.user.id, tokenUuid: tokenDocument.uuid }); - if (Array.isArray(newDocument)) newDocument = newDocument[0] - let actor = newDocument.actor - if (!actor && newDocument.actorId) - actor = game.actors.get(newDocument.actorId) - GURPS.SetLastActor(actor, newDocument) - GURPS.IgnoreTokenSelect = false - setTimeout(() => canvas.pan({ x: xpos, y: ypos }), 200) - }); - // Need to stop animation since we are going to delete the token and if that happens before the animation completes we get an error + let x = Number(xpos) + let y = parseInt(ypos) + if (isNaN(x) || isNaN(y)) { + console.error('dae| teleport: Invalid co-ords', xpos, ypos) + return `Invalid target co-ordinates (${xpos}, ${ypos})` + } + if (!tokenDocument) { + console.warn('dae | teleport: No Token') + return 'No active token' + } + // Hide the current token + if (targetScene.name === canvas.scene.name) { //@ts-ignore - CanvasAnimation.terminateAnimation(`Token.${tokenDocument.id}.animateMovement`); - return await targetScene.view(); -}; \ No newline at end of file + CanvasAnimation.terminateAnimation(`Token.${tokenDocument.id}.animateMovement`) + let sourceSceneId = canvas.scene.id + // After the token is transported, we need to store the new token as the active token (and select it, since the original was selected) + GURPS.IgnoreTokenSelect = true + let newDocument = await socketlibSocket.executeAsGM('recreateToken', { + userId: game.user.id, + tokenUuid: tokenDocument.uuid, + startSceneId: sourceSceneId, + targetSceneId: targetScene.id, + tokenData: tokenDocument.data, + x: xpos, + y: ypos, + }) + let actor = newDocument.actor + if (!actor && newDocument.actorId) actor = game.actors.get(newDocument.actorId) + GURPS.SetLastActor(actor, newDocument) + GURPS.IgnoreTokenSelect = false + setTimeout(() => canvas.pan({ x: xpos, y: ypos }), 200) + return true + } + // deletes and recreates the token + var sourceSceneId = canvas.scene.id + Hooks.once('canvasReady', async () => { + GURPS.IgnoreTokenSelect = true + let newDocument = await socketlibSocket.executeAsGM('createToken', { + userId: game.user.id, + startSceneId: sourceSceneId, + targetSceneId: targetScene.id, + tokenData: tokenDocument.data, + x: xpos, + y: ypos, + }) + await socketlibSocket.executeAsGM('deleteToken', { userId: game.user.id, tokenUuid: tokenDocument.uuid }) + if (Array.isArray(newDocument)) newDocument = newDocument[0] + let actor = newDocument.actor + if (!actor && newDocument.actorId) actor = game.actors.get(newDocument.actorId) + GURPS.SetLastActor(actor, newDocument) + GURPS.IgnoreTokenSelect = false + setTimeout(() => canvas.pan({ x: xpos, y: ypos }), 200) + }) + // Need to stop animation since we are going to delete the token and if that happens before the animation completes we get an error + //@ts-ignore + CanvasAnimation.terminateAnimation(`Token.${tokenDocument.id}.animateMovement`) + return await targetScene.view() +} diff --git a/module/effects/effects.js b/module/effects/effects.js index 55277a640..996e9fd3c 100644 --- a/module/effects/effects.js +++ b/module/effects/effects.js @@ -581,7 +581,7 @@ export class StatusEffect { id: 'bad-5', label: 'GURPS.STATUSBad', }, - 'disarmed': { + disarmed: { icon: 'systems/gurps/icons/statuses/disarmed.webp', id: 'disarmed', label: 'GURPS.STATUSDisarmed', diff --git a/module/effects/triggerhappy.js b/module/effects/triggerhappy.js index b0b8d8f91..18d6493fb 100644 --- a/module/effects/triggerhappy.js +++ b/module/effects/triggerhappy.js @@ -6,16 +6,13 @@ const OTF = 'OTF' Hooks.once('setup', function () { if (game.triggers) { console.log('Registering Trigger Happy support') - game.triggers.registerEffect(Teleport); - game.triggers.registerEffect(OTF); - if (!setupSocket()) - uconsole.log("Unable to set up socket lib for DAE") + game.triggers.registerEffect(Teleport) + game.triggers.registerEffect(OTF) + if (!setupSocket()) uconsole.log('Unable to set up socket lib for DAE') } }) - export default class TriggerHappySupport { - static init() { new TriggerHappySupport()._init() } @@ -23,45 +20,46 @@ export default class TriggerHappySupport { _init() { Hooks.on('TriggerHappy', async (key, args) => { switch (key) { - case Teleport: + case Teleport: this._teleport(args) - break; + break case OTF: this.otf(args) - break; + break default: - console.log("Unknown key: " + key) + console.log('Unknown key: ' + key) } - }) + }) } _teleport(args) { if (!GURPS.LastTokenDocument) { - ui.notifications.warn("No last token") - return + ui.notifications.warn('No last token') + return } args = args.join(' ').split('/') let sn = args[0] let tn = args[1] - if (!tn) { // If only one name, assume the current scene + if (!tn) { + // If only one name, assume the current scene tn = sn - sn = canvas.scene.name + sn = canvas.scene.name } if (!tn) { ui.notifications.warn('Requires scene name / drawing (or token) name or just drawing (or token) name ') - return + return } let scene = game.scenes.contents.find(s => s.name == sn) if (!scene) { - ui.notifications.warn("Unable to find scene " + sn) + ui.notifications.warn('Unable to find scene ' + sn) return } - let target = scene.drawings.contents.find(d => d.data.text == tn) + let target = scene.drawings.contents.find(d => d.data.text == tn) if (!target) target = scene.tokens.find(t => t.name == tn) if (!target) { - ui.notifications.warn("Unable to find drawing or token " + tn + " in scene " + sn) + ui.notifications.warn('Unable to find drawing or token ' + tn + ' in scene ' + sn) return } - + teleport(GURPS.LastTokenDocument, scene, target.data.x, target.data.y) } otf(args) { @@ -69,4 +67,3 @@ export default class TriggerHappySupport { GURPS.executeOTF(args) } } - diff --git a/module/file-handlers/chrome-file-handler.js b/module/file-handlers/chrome-file-handler.js index 564641a5f..2981cd538 100644 --- a/module/file-handlers/chrome-file-handler.js +++ b/module/file-handlers/chrome-file-handler.js @@ -1,86 +1,84 @@ class ChromiumFile { constructor(handle, path) { - this.handle = handle; - this.name = handle.name; - this.path = path !== "" ? path : handle.name; + this.handle = handle + this.name = handle.name + this.path = path !== '' ? path : handle.name } async text() { - return (await this.handle.getFile()).text(); + return (await this.handle.getFile()).text() } } class ChromiumFolder { constructor(handle) { - this.handle = handle; - this.name = handle.name; + this.handle = handle + this.name = handle.name } async files(extensions) { - const allFiles = await this._getFiles(); - return extensions ? allFiles.filter(f => extensions.some(ext => f.name.endsWith(ext))) : allFiles; + const allFiles = await this._getFiles() + return extensions ? allFiles.filter(f => extensions.some(ext => f.name.endsWith(ext))) : allFiles } async _getFiles(handle, path) { - path = path ?? this.name; - handle = handle ?? this.handle; - const files = []; - for await (let [name, child] of handle.entries()) { - if (child instanceof FileSystemDirectoryHandle) { - files.push(...await this._getFiles(child, `${path}/${name}`)); - } - else { - files.push(new ChromiumFile(child, `${path}/${child.name}`)); - } + path = path ?? this.name + handle = handle ?? this.handle + const files = [] + for await (let [name, child] of handle.entries()) { + if (child instanceof FileSystemDirectoryHandle) { + files.push(...(await this._getFiles(child, `${path}/${name}`))) + } else { + files.push(new ChromiumFile(child, `${path}/${child.name}`)) } - return files; + } + return files } } export class ChromiumFileHandler { - static File = ChromiumFile - static Folder = ChromiumFolder - static async _getFileOrFolder({template, templateOptions={}, mode, extensions=[]}) { - const promise = new Promise((resolve, reject) => { - const inputElement = ` -
    no ${mode} chosen
    `; - const content = template({ inputElement, ...templateOptions }); - let handle; - Dialog.prompt( { - title: `Import Data`, - content, - label: "Import", - callback: () => { - handle ? resolve(handle) : reject(`no ${mode} were chosen`); + static File = ChromiumFile + static Folder = ChromiumFolder + static async _getFileOrFolder({ template, templateOptions = {}, mode, extensions = [] }) { + const promise = new Promise((resolve, reject) => { + const inputElement = ` +
    no ${mode} chosen
    ` + const content = template({ inputElement, ...templateOptions }) + let handle + Dialog.prompt({ + title: `Import Data`, + content, + label: 'Import', + callback: () => { + handle ? resolve(handle) : reject(`no ${mode} were chosen`) + }, + rejectClose: false, + }) + Hooks.once('renderDialog', dialog => { + const fileChosenDiv = dialog.element.find('#selectedFile') + dialog.element.find('#importButton').on('click', async () => { + const pickerOpts = { + types: [ + { + description: 'Allowed', + accept: { + 'custom/custom': extensions, }, - rejectClose: false, - } ); - Hooks.once('renderDialog', (dialog) => { - const fileChosenDiv = dialog.element.find('#selectedFile'); - dialog.element.find('#importButton').on('click', async () => { - const pickerOpts = { - types: [ - { - description: 'Allowed', - accept: { - 'custom/custom': extensions - } - }, - ], - excludeAcceptAllOption: true, - multiple: false - }; - if (mode === 'file') { - handle = (await (extensions.length > 0 ? showOpenFilePicker(pickerOpts) : showOpenFilePicker()))[0]; - } - else { - handle = await showDirectoryPicker(); - } - fileChosenDiv[0].innerText = handle.name; - }); - }); - }); - return promise; - } - static async getFile({template, templateOptions={}, extensions=[]}) { - return new ChromiumFile(await this._getFileOrFolder({template, templateOptions, mode:'file', extensions}), ""); - } - static async getFolder({template, templateOptions={}}) { - return new ChromiumFolder(await this._getFileOrFolder({template, templateOptions, mode:'folder'})); - } + }, + ], + excludeAcceptAllOption: true, + multiple: false, + } + if (mode === 'file') { + handle = (await (extensions.length > 0 ? showOpenFilePicker(pickerOpts) : showOpenFilePicker()))[0] + } else { + handle = await showDirectoryPicker() + } + fileChosenDiv[0].innerText = handle.name + }) + }) + }) + return promise + } + static async getFile({ template, templateOptions = {}, extensions = [] }) { + return new ChromiumFile(await this._getFileOrFolder({ template, templateOptions, mode: 'file', extensions }), '') + } + static async getFolder({ template, templateOptions = {} }) { + return new ChromiumFolder(await this._getFileOrFolder({ template, templateOptions, mode: 'folder' })) + } } diff --git a/module/file-handlers/fallback-file-handler.js b/module/file-handlers/fallback-file-handler.js index 0e54c2e75..bbb01437d 100644 --- a/module/file-handlers/fallback-file-handler.js +++ b/module/file-handlers/fallback-file-handler.js @@ -1,52 +1,53 @@ class FallbackFile { constructor(file) { - this.file = file; - this.name = file.name; - this.path = file.webkitRelativePath !== "" ? file.webkitRelativePath : file.name; + this.file = file + this.name = file.name + this.path = file.webkitRelativePath !== '' ? file.webkitRelativePath : file.name } async text() { - return this.file.text(); + return this.file.text() } } class FallbackFolder { constructor(files, name) { - this._files = files; - this.name = name ?? files[0].path.split('/')[0]; + this._files = files + this.name = name ?? files[0].path.split('/')[0] } async files(extensions) { - return extensions ? this._files.filter(f => extensions.some(ext => f.name.endsWith(ext))) : this._files; + return extensions ? this._files.filter(f => extensions.some(ext => f.name.endsWith(ext))) : this._files } } export class FallbackFileHandler { - static async _getFileOrFolder({template, templateOptions={}, mode, extensions=[]}) { - const inputElement = mode === 'file' ? - `` : - ``; - const content = template({ inputElement, ...templateOptions }); - const promise = new Promise((resolve, reject) => { - Dialog.prompt( { - title: `Import Data`, - content, - label: "Import", - callback: (html) => { - const inputElementObject = html.find('#inputFiles')[0]; - if (!(inputElementObject instanceof HTMLInputElement)) - return reject(`can't find input element`); - if (!inputElementObject.files) - return reject(`input element isn't file input`); - let files = Array.from(inputElementObject.files); - files = extensions.length > 0 ? files.filter(f => extensions.some(ext => f.name.endsWith(ext))) : files; - files.length !== 0 ? resolve(files.map(f => new FallbackFile(f))) : reject('no files with the correct extensions were chosen'); - }, - rejectClose: false, - } ); - }); - return promise; + static async _getFileOrFolder({ template, templateOptions = {}, mode, extensions = [] }) { + const inputElement = + mode === 'file' + ? `` + : `` + const content = template({ inputElement, ...templateOptions }) + const promise = new Promise((resolve, reject) => { + Dialog.prompt({ + title: `Import Data`, + content, + label: 'Import', + callback: html => { + const inputElementObject = html.find('#inputFiles')[0] + if (!(inputElementObject instanceof HTMLInputElement)) return reject(`can't find input element`) + if (!inputElementObject.files) return reject(`input element isn't file input`) + let files = Array.from(inputElementObject.files) + files = extensions.length > 0 ? files.filter(f => extensions.some(ext => f.name.endsWith(ext))) : files + files.length !== 0 + ? resolve(files.map(f => new FallbackFile(f))) + : reject('no files with the correct extensions were chosen') + }, + rejectClose: false, + }) + }) + return promise } - static async getFile({template, templateOptions={}, extensions=[]}) { - return (await this._getFileOrFolder({template, templateOptions, mode:'file', extensions}))[0]; + static async getFile({ template, templateOptions = {}, extensions = [] }) { + return (await this._getFileOrFolder({ template, templateOptions, mode: 'file', extensions }))[0] } - static async getFolder({template, templateOptions={}}) { - return new FallbackFolder(await this._getFileOrFolder({template, templateOptions, mode:'folder'})); + static async getFolder({ template, templateOptions = {} }) { + return new FallbackFolder(await this._getFileOrFolder({ template, templateOptions, mode: 'folder' })) } } diff --git a/module/file-handlers/universal-file-handler.js b/module/file-handlers/universal-file-handler.js index 56c9cfe57..d4b09e4d1 100644 --- a/module/file-handlers/universal-file-handler.js +++ b/module/file-handlers/universal-file-handler.js @@ -1,42 +1,35 @@ -import { ChromiumFileHandler } from './chrome-file-handler.js'; -import { FallbackFileHandler } from './fallback-file-handler.js'; +import { ChromiumFileHandler } from './chrome-file-handler.js' +import { FallbackFileHandler } from './fallback-file-handler.js' function getBrowser() { // the code is based on https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator - const sUsrAg = navigator.userAgent; + const sUsrAg = navigator.userAgent // The order matters here, and this may report false positives for unlisted browsers. - if (sUsrAg.indexOf("Firefox") > -1) { - return "Mozilla Firefox"; - } - else if (sUsrAg.indexOf("SamsungBrowser") > -1) { - return "Samsung Internet"; - } - else if (sUsrAg.indexOf("Opera") > -1 || sUsrAg.indexOf("OPR") > -1) { - return "Opera"; - } - else if (sUsrAg.indexOf("Trident") > -1) { - return "Microsoft Internet Explorer"; - } - else if (sUsrAg.indexOf("Edge") > -1) { - return "Microsoft Edge"; - } - else if (sUsrAg.indexOf("Chrome") > -1) { - return "Chromium"; - } - else if (sUsrAg.indexOf("Safari") > -1) { - return "Apple Safari"; - } - else { - return "unknown"; + if (sUsrAg.indexOf('Firefox') > -1) { + return 'Mozilla Firefox' + } else if (sUsrAg.indexOf('SamsungBrowser') > -1) { + return 'Samsung Internet' + } else if (sUsrAg.indexOf('Opera') > -1 || sUsrAg.indexOf('OPR') > -1) { + return 'Opera' + } else if (sUsrAg.indexOf('Trident') > -1) { + return 'Microsoft Internet Explorer' + } else if (sUsrAg.indexOf('Edge') > -1) { + return 'Microsoft Edge' + } else if (sUsrAg.indexOf('Chrome') > -1) { + return 'Chromium' + } else if (sUsrAg.indexOf('Safari') > -1) { + return 'Apple Safari' + } else { + return 'unknown' } } -const IS_CHROMIUM = ['Chromium', 'Microsoft Edge', 'Opera'].includes(getBrowser()); +const IS_CHROMIUM = ['Chromium', 'Microsoft Edge', 'Opera'].includes(getBrowser()) const FileHandler = IS_CHROMIUM ? ChromiumFileHandler : FallbackFileHandler export class UniversalFileHandler { - static async getFile({template, templateOptions={}, extensions=[]}) { - extensions = typeof extensions === 'string' ? [extensions] : extensions; - return FileHandler.getFile({template, templateOptions, extensions}); - } - static async getFolder({template, templateOptions={}}) { - return FileHandler.getFolder({template, templateOptions}); - } -} \ No newline at end of file + static async getFile({ template, templateOptions = {}, extensions = [] }) { + extensions = typeof extensions === 'string' ? [extensions] : extensions + return FileHandler.getFile({ template, templateOptions, extensions }) + } + static async getFolder({ template, templateOptions = {} }) { + return FileHandler.getFolder({ template, templateOptions }) + } +} diff --git a/module/game-utils.js b/module/game-utils.js index 3413ed1ab..10dd94b6f 100644 --- a/module/game-utils.js +++ b/module/game-utils.js @@ -1,3 +1,3 @@ export function isConfigurationAllowed(actor) { return game.user.isGM || actor.isOwner -} \ No newline at end of file +} diff --git a/module/global-references.js b/module/global-references.js index 871bda00b..4ac16c163 100644 --- a/module/global-references.js +++ b/module/global-references.js @@ -41,7 +41,7 @@ import GURPSConditionalInjury from './injury/foundry/conditional-injury.js' * damagetype?: string; // if [damage|deriveddamage], the string value of the damage type (i.e., 'cut', 'pi', 'burn', etc...) * extdamagetype?: string; // if [damage], any extra damage modifier, like 'ex'. * derivedformula?: string; // if [deriveddamage], a damage formula like 'sw+2' or 'thr-1'. If [derivedroll], 'sw' or 'thr'. - * calcOnly?: boolean; // if [skill-spell|attribute], then return the target calculated for this skill-spell|attribute instead of rolling against it. + * calcOnly?: boolean; // if [skill-spell|attribute], then return the target calculated for this skill-spell|attribute instead of rolling against it. * attrkey?: string; * melee?: string; * attribute?: string; diff --git a/module/gurps.js b/module/gurps.js index 6e0e19540..66fdfd933 100755 --- a/module/gurps.js +++ b/module/gurps.js @@ -5,28 +5,28 @@ import { GurpsActor } from './actor/actor.js' import { GurpsItem } from './item.js' import { GurpsItemSheet } from './item-sheet.js' import { - GurpsActorCombatSheet, - GurpsActorSheet, - GurpsActorEditorSheet, - GurpsActorSimplifiedSheet, - GurpsActorNpcSheet, - GurpsInventorySheet, - GurpsActorTabSheet, + GurpsActorCombatSheet, + GurpsActorSheet, + GurpsActorEditorSheet, + GurpsActorSimplifiedSheet, + GurpsActorNpcSheet, + GurpsInventorySheet, + GurpsActorTabSheet, } from './actor/actor-sheet.js' import { ModifierBucket } from './modifier-bucket/bucket-app.js' import { ChangeLogWindow } from '../lib/change-log.js' import { SemanticVersion } from '../lib/semver.js' import { - d6ify, - recurselist, - atou, - utoa, - makeRegexPatternFrom, - i18n, - zeroFill, - wait, - quotedAttackName, - requestFpHp, + d6ify, + recurselist, + atou, + utoa, + makeRegexPatternFrom, + i18n, + zeroFill, + wait, + quotedAttackName, + requestFpHp, } from '../lib/utilities.js' import { doRoll } from '../module/dierolls/dieroll.js' import { ResourceTrackerManager } from './actor/resource-tracker-manager.js' @@ -74,11 +74,11 @@ import { gurpslink } from './utilities/gurpslink.js' let GURPS = undefined if (!globalThis.GURPS) { - GURPS = {} - globalThis.GURPS = GURPS // Make GURPS global! - GURPS.DEBUG = true - GURPS.Migration = Migration - GURPS.BANNER = ` + GURPS = {} + globalThis.GURPS = GURPS // Make GURPS global! + GURPS.DEBUG = true + GURPS.Migration = Migration + GURPS.BANNER = ` __ ____ _____ _____ _____ _____ ____ __ / /_____|_____|_____|_____|_____|_____\\ \\ / / ____ _ _ ____ ____ ____ \\ \\ @@ -89,1004 +89,1003 @@ if (!globalThis.GURPS) { \\ \\ _____ _____ _____ _____ _____ ____ / / \\_|_____|_____|_____|_____|_____|____|_/ ` - GURPS.LEGAL = `GURPS is a trademark of Steve Jackson Games, and its rules and art are copyrighted by Steve Jackson Games. All rights are reserved by Steve Jackson Games. This game aid is the original creation of Chris Normand/Nose66 and is released for free distribution, and not for resale, under the permissions granted by http://www.sjgames.com/general/online_policy.html` - - if (GURPS.DEBUG) { - GURPS.parseDecimalNumber = parseDecimalNumber - } - - AddChatHooks() - JQueryHelpers() - MoustacheWax() - Settings.initializeSettings() - GURPS.EffectModifierControl = new EffectModifierControl() - - // CONFIG.debug.hooks = true - - // Expose Maneuvers to make them easier to use in modules - GURPS.Maneuvers = Maneuvers - - // Use the target d6 icon for rolltable entries - //CONFIG.RollTable.resultIcon = 'systems/gurps/icons/single-die.webp' - CONFIG.time.roundTime = 1 - - GURPS.StatusEffect = new StatusEffect() - - // Hack to remember the last Actor sheet that was accessed... for the Modifier Bucket to work - GURPS.LastActor = null - GURPS.SJGProductMappings = SJGProductMappings - GURPS.clearActiveEffects = GurpsActiveEffect.clearEffectsOnSelectedToken - - GURPS.SetLastActor = function(actor, tokenDocument) { - if (actor != GURPS.LastActor) console.log('Setting Last Actor:' + actor?.name) - GURPS.LastActor = actor - GURPS.LastTokenDocument = tokenDocument - setTimeout(() => GURPS.ModifierBucket.refresh(), 100) // Need to make certain the mod bucket refresh occurs later - } - - GURPS.ClearLastActor = function(actor) { - if (GURPS.LastActor == actor) { - console.log('Clearing Last Actor:' + GURPS.LastActor?.name) - GURPS.LastActor = null - GURPS.ModifierBucket.refresh() - const tokens = canvas.tokens - if (tokens && tokens.controlled.length > 0) { - GURPS.SetLastActor(tokens.controlled[0].actor) - } // There may still be tokens selected... if so, select one of them - } - } - - /** - * This object literal holds the results of the last targeted roll by an actor. - * The property key is the actor's ID. The value is literally the chatdata from - * the doRoll() function, which has close to anything anyone would want. - */ - GURPS.lastTargetedRoll = {} - GURPS.lastTargetedRolls = {} // mapped by both actor and token id - - GURPS.setLastTargetedRoll = function(chatdata, actorid, tokenid, updateOtherClients = false) { - let tmp = { ...chatdata } - if (!!actorid) GURPS.lastTargetedRolls[actorid] = tmp - if (!!tokenid) GURPS.lastTargetedRolls[tokenid] = tmp - GURPS.lastTargetedRoll = tmp // keep the local copy - // Interesting fields: GURPS.lastTargetedRoll.margin .isCritSuccess .IsCritFailure .thing - - if (updateOtherClients) - game.socket.emit('system.gurps', { - type: 'setLastTargetedRoll', - chatdata: tmp, - actorid: actorid, - tokenid: tokenid, - }) - } - - // TODO Why are these global? - GURPS.ChatCommandsInProcess = [] // Taking advantage of synchronous nature of JS arrays - GURPS.PendingOTFs = [] - GURPS.IgnoreTokenSelect = false - GURPS.wait = wait - - GURPS.attributepaths = { - ST: 'attributes.ST.value', - DX: 'attributes.DX.value', - IQ: 'attributes.IQ.value', - HT: 'attributes.HT.value', - QN: 'attributes.QN.value', - WILL: 'attributes.WILL.value', - Will: 'attributes.WILL.value', - PER: 'attributes.PER.value', - Per: 'attributes.PER.value', - } - - // Map stuff back to translation keys... don't know if useful yet - GURPS.attributes = { - ST: 'GURPS.attributesST', - DX: 'GURPS.attributesDX', - IQ: 'GURPS.attributesIQ', - HT: 'GURPS.attributesHT', - QN: 'GURPS.attributesQN', - Will: 'GURPS.attributesWILL', - Per: 'GURPS.attributesPER', - } - - GURPS.attributeNames = { - ST: 'GURPS.attributesSTNAME', - DX: 'GURPS.attributesDXNAME', - IQ: 'GURPS.attributesIQNAME', - HT: 'GURPS.attributesHTNAME', - QN: 'GURPS.attributesQNNAME', - Will: 'GURPS.attributesWILLNAME', - Per: 'GURPS.attributesPERNAME', - } - - GURPS.skillTypes = { - 'DX/E': 'GURPS.SkillDXE', - 'DX/A': 'GURPS.SkillDXA', - 'DX/H': 'GURPS.SkillDXH', - 'DX/VH': 'GURPS.SkillDXVH', - - 'IQ/E': 'GURPS.SkillIQE', - 'IQ/A': 'GURPS.SkillIQA', - 'IQ/H': 'GURPS.SkillIQH', - 'IQ/VH': 'GURPS.SkillIQVH', - - 'HT/E': 'GURPS.SkillHTE', - 'HT/A': 'GURPS.SkillHTA', - 'HT/H': 'GURPS.SkillHTH', - 'HT/VH': 'GURPS.SkillHTVH', - - 'QN/E': 'GURPS.SkillQNE', - 'QN/A': 'GURPS.SkillQNA', - 'QN/H': 'GURPS.SkillQNH', - 'QN/VH': 'GURPS.SkillQNVH', - - 'Will/E': 'GURPS.SkillWillE', - 'Will/A': 'GURPS.SkillWillA', - 'Will/H': 'GURPS.SkillWillH', - 'Will/VH': 'GURPS.SkillWillVH', - - 'Per/E': 'GURPS.SkillPerE', - 'Per/A': 'GURPS.SkillPerA', - 'Per/H': 'GURPS.SkillPerH', - 'Per/VH': 'GURPS.SkillPerVH', - } - - GURPS.PARSELINK_MAPPINGS = { - ST: 'attributes.ST.value', - DX: 'attributes.DX.value', - IQ: 'attributes.IQ.value', - HT: 'attributes.HT.value', - QN: 'attributes.QN.value', - WILL: 'attributes.WILL.value', - PER: 'attributes.PER.value', - VISION: 'vision', - FRIGHTCHECK: 'frightcheck', - 'FRIGHT CHECK': 'frightcheck', - HEARING: 'hearing', - TASTESMELL: 'tastesmell', - 'TASTE SMELL': 'tastesmell', - TASTE: 'tastesmell', - SMELL: 'tastesmell', - TOUCH: 'touch', - DODGE: 'currentdodge', - Parry: 'equippedparry', - PARRY: 'equippedparry', - BLOCK: 'equippedblock', - } - - GURPS.SJGProductMappings = SJGProductMappings - GURPS.USER_GUIDE_URL = 'https://bit.ly/2JaSlQd' - - /** - * @param {string} str - */ - function escapeUnicode(str) { - return str.replace(/[^\0-~]/g, function(ch) { - return '&#x' + ('0000' + ch.charCodeAt(0).toString(16).toUpperCase()).slice(-4) + ';' - }) - } - GURPS.escapeUnicode = escapeUnicode - - /** - * Read text data from a user provided File object - * Stolen from Foundry, and replaced 'readAsText' with 'readAsBinaryString' to save unicode characters. - * @param {File} file A File object - * @return {Promise.} A Promise which resolves to the loaded text data - */ - async function readTextFromFile(file) { - const reader = new FileReader() - return new Promise((resolve, reject) => { - // @ts-ignore - reader.onload = ev => { - resolve(reader.result) - } - // @ts-ignore - reader.onerror = ev => { - reader.abort() - reject() - } - if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_IMPORT_FILE_ENCODING) == 1) - reader.readAsText(file, 'UTF-8') - else reader.readAsText(file, 'ISO-8859-1') - }) - } - GURPS.readTextFromFile = readTextFromFile - - // This is an ugly hack to clean up the "formatted text" output from GCS FG XML. - // First we have to remove non-printing characters, and then we want to replace - // all

    ...

    with .../n before we try to convert to JSON. Also, for some reason, - // the DOMParser doesn't like some of the stuff in the formatted text sections, so - // we will base64 encode it, and the decode it in the Named subclass setNotes() - /** - * @param {string} xml - */ - function cleanUpP(xml) { - // First, remove non-ascii characters - // xml = xml.replace(/[^ -~]+/g, '') - xml = GURPS.escapeUnicode(xml) - - // Now try to remove any lone " & " in names, etc. Will only occur in GCA output - xml = xml.replace(/ & /g, ' & ') - let swap = function(/** @type {string} */ xml, /** @type {string} */ tagin, /** @type {string} */ tagout) { - let s = xml.indexOf(tagin) - while (s > 0) { - let e = xml.indexOf(tagout, s) - if (e > s) { - let t1 = xml.substring(0, s) - let t2 = xml.substring(s + 3, e) - t2 = '@@@@' + utoa(t2) + '\n' - let t3 = xml.substr(e + 4) - xml = t1 + t2 + t3 - s = xml.indexOf(tagin, s + t2.length) - } - } - return xml - } - xml = swap(xml, '<p>', '</p>') - xml = swap(xml, '

    ', '

    ') - xml = xml.replace(/
    /g, '\n') - return xml - } - GURPS.cleanUpP = cleanUpP - - /** - * A utility function to "deep" print an object - * @param {Object | null} obj - * @param {number} ndeep - * @returns {string} - */ - function objToString(obj, ndeep = 1) { - if (obj == null) { - return String(obj) - } - if (ndeep > 10) return '(stopping due to depth): ' + obj.toString() - switch (typeof obj) { - case 'string': - return '"' + obj + '"' - case 'function': - return obj.name || obj.toString() - case 'object': - var indent = Array(ndeep || 1).join('\t'), - isArray = Array.isArray(obj) - return ( - '{['[+isArray] + - Object.keys(obj) - .map(function(key) { - // @ts-ignore - return '\n\t' + indent + key + ': ' + objToString(obj[key], (ndeep || 1) + 1) - }) - .join(',') + - '\n' + - indent + - '}]'[+isArray] - ) - default: - return obj.toString() - } - } - GURPS.objToString = objToString - - /** - * @param {string} s - */ - function trim(s) { - return s.replace(/^\s*$(?:\r\n?|\n)/gm, '').trim() // /^\s*[\r\n]/gm - } - GURPS.trim = trim - - // Needed for external modules like Token Action HUD and Nordlond Bestiary - GURPS.gurpslink = gurpslink - - /** - * @param {string} string - * @param {boolean} priv - * @param {JQuery.Event|null} event - * @returns {Promise} - */ - async function executeOTF(string, priv = false, event = null) { - if (!string) return false - string = string.trim() - if (string[0] == '[' && string[string.length - 1] == ']') string = string.substring(1, string.length - 1) - let action = parselink(string) - let answer = false - if (!!action.action) { - if (!event) event = { shiftKey: priv, ctrlKey: false, data: {} } - let result = await GURPS.performAction(action.action, GURPS.LastActor, event) - answer = !!result - } else ui.notifications.warn(`"${string}" did not parse into a valid On-the-Fly formula`) - return answer - } - GURPS.executeOTF = executeOTF - - function processSkillSpell({ action, actor }) { - let actordata = actor?.data - - // skill - var skill - if (!!action.target) { - // Skill-12 - skill = { - name: action.name, - // @ts-ignore - level: parseInt(action.target), - } - } - // @ts-ignore - else skill = GURPS.findSkillSpell(actor?.data?.data, action.name, !!action.isSkillOnly, !!action.isSpellOnly) - if (!skill) { - return 0 - } - let skillLevel = skill.level - // @ts-ignore - action.obj = skill - - // on a floating skill check, we want the skill with the highest relative skill level - if (!!action.floatingAttribute) { - if (!!actor) { - let value = GURPS.resolve(action.floatingAttribute, actordata.data) - let rsl = skill.relativelevel // this is something like 'IQ-2' or 'Touch+3' - console.log(rsl) - let valueText = rsl.replace(/^.*([+-]\d+)$/g, '$1') - console.log(valueText) - skillLevel = valueText === rsl ? parseInt(value) : parseInt(valueText) + parseInt(value) - } else { - ui.notifications?.warn('You must have a character selected to use a "Based" Skill') - } - } - - //if (!!action.mod) skillLevel += parseInt(action.mod) - - return skillLevel - } - - const actionFuncs = { - /** - * @param {Object} data - * @param {Object} data.action - * @param {string} data.action.link - */ - pdf({ action }) { - if (!action.link) { - ui.notifications?.warn('no link was parsed for the pdf') - return false // if there's no link action fails - } - handlePdf(action.link) - return true - }, - - // - iftest({ action }) { - if (!GURPS.lastTargetedRoll) return false - if (action.name == 'isCritSuccess') return !!GURPS.lastTargetedRoll.isCritSuccess - if (action.name == 'isCritFailure') return !!GURPS.lastTargetedRoll.isCritFailure - if (!action.equation) - // if [@margin] tests for >=0 - return GURPS.lastTargetedRoll.margin >= 0 - else { - let m = action.equation.match(/ *([=<>]+) *([+-]?[\d\.]+)/) - let value = Number(m[2]) - switch (m[1]) { - case '=': - case '==': - return GURPS.lastTargetedRoll.margin == value - case '>': - return GURPS.lastTargetedRoll.margin > value - case '>=': - return GURPS.lastTargetedRoll.margin >= value - case '<': - return GURPS.lastTargetedRoll.margin < value - case '<=': - return GURPS.lastTargetedRoll.margin <= value - default: - return false - } - } - }, - - /** - * @param {Object} data - * @param {Object} data.action - * @param {string} data.action.mod - * @param {string} data.action.desc - * @param {Object} data.action.next - */ - modifier({ action }) { - GURPS.ModifierBucket.addModifier(action.mod, action.desc) - if (action.next && action.next.type === 'modifier') { - return this.modifier({ action: action.next }) // recursion, but you need to wrap the next action in an object using the 'action' attribute - } - return true - }, - /** - * @param {Object} data - * @param {Object} data.action - * @param {string} data.action.orig - * @param {boolean} data.action.quiet - * @param {JQuery.Event|null} data.event - */ - async chat({ action, event }) { - // @ts-ignore - const chat = `/setEventFlags ${!!action.quiet} ${!!event?.shiftKey} ${game.keyboard.isModifierActive( - KeyboardManager.MODIFIER_KEYS.CONTROL - )}\n${action.orig}` - - if (!!action.overridetxt) { - if (!event.data) - event.data = {} - event.data.overridetxt = action.overridetxt - } - // @ts-ignore - someone somewhere must have added chatmsgData to the MouseEvent. - return await GURPS.ChatProcessors.startProcessingLines(chat, event?.chatmsgData, event) - }, - /** - * @param {Object} data - * @param {Object} data.action - * @param {string} data.action.link - * @param {string} data.action.id - */ - dragdrop({ action }) { - switch (action.link) { - case 'JournalEntry': - let j = game.journal?.get(action.id) - if (j) { - if (j.data.flags.pdfoundry) { - handlePdf(j.data.flags.pdfoundry.PDFData.code) - } else - j.sheet?.render(true) - } - return true - case 'Actor': - game.actors?.get(action.id)?.sheet?.render(true) - return true - case 'RollTable': - game.tables?.get(action.id)?.sheet?.render(true) - return true - case 'Item': - game.items?.get(action.id)?.sheet?.render(true) - return true - default: - ui.notifications.warn(`unknown entity type: ${action.link}`) - return false - } - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.mod - * @param {string} data.action.desc - * @param {string} data.action.formula - * @param {string} data.action.damagetype - * @param {string} data.action.extdamagetype - * @param {string} data.action.hitlocation - * @param {string} data.action.costs - * @param {boolean} data.action.accumulate - * - * @param {JQuery.Event|null} data.event - * @param {GurpsActor|null} data.actor - * @param {string[]} data.targets - */ - damage({ action, event, actor, targets }) { - // accumulate action fails if there's no selected actor - if (action.accumulate && !actor) { - ui.notifications?.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - - if (action.accumulate) { - // store/increment value on GurpsActor - actor.accumulateDamageRoll(action) - return true - } - - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - - if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc) // special case where Damage comes from [D:attack + mod] - - DamageChat.create( - actor || game.user, - action.formula, - action.damagetype, - event, - null, - targets, - action.extdamagetype, - action.hitlocation - ) - if (action.next) { - return GURPS.performAction(action.next, actor, event, targets) - } - - return true - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.damagetype - * @param {string} data.action.formula - * @param {string} data.action.costs - * @param {string} data.action.derivedformula - * @param {string} data.action.extdamagetype - * @param {string} data.action.hitlocation - * @param {boolean} data.action.accumulate - * - * @param {JQuery.Event|null} data.event - * @param {GurpsActor|null} data.actor - * @param {string[]} data.targets - */ - deriveddamage({ action, event, actor, targets }) { - // action fails if there's no selected actor - if (!actor) { - ui.notifications?.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - let df = action.derivedformula.match(/sw/i) ? actor.system.swing : actor.system.thrust - // action fails if there's no formula - if (!df) { - ui.notifications?.warn(`${actor.name} does not have a ${action.derivedformula.toUpperCase()} formula`) - return false - } - let formula = df + action.formula - - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - DamageChat.create( - actor || game.user, - formula, - action.damagetype, - event, - action.derivedformula + action.formula.replace(/([+-]\d+).*/g, '$1'), // Just keep the +/- mod - targets, - action.extdamagetype, - action.hitlocation - ) - if (action.next) { - return GURPS.performAction(action.next, actor, event, targets) - } - return true - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.name - * @param {boolean} data.action.isMelee - * @param {boolean} data.action.isRanged - * @param {string} data.action.costs - * @param {string} data.action.mod - * @param {string} data.action.desc - * - * @param {JQuery.Event|null} data.event - * @param {GurpsActor|null} data.actor - * @param {string[]} data.targets - */ - attackdamage({ action, event, actor, targets }) { - // action fails if there's no selected actor - if (!actor) { - ui.notifications?.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - if (!action.name) { - ui.notifications?.warn('attack damage action has no name') - return false - } - let att = null - att = GURPS.findAttack(actor.system, action.name, !!action.isMelee, !!action.isRanged) // find attack possibly using wildcards - if (!att) { - ui.notifications.warn( - `No melee or ranged attack named '${action.name.replace('<', '<')}' found on ${actor.name}` - ) - return false - } - if (action.calcOnly) return att.damage - - let dam = parseForRollOrDamage(att.damage) - if (!dam) { - ui.notifications?.warn('Damage is not rollable') - return false - } - dam.action.costs = action.costs - dam.action.mod = action.mod - dam.action.desc = action.desc - return performAction(dam.action, actor, event, targets) - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} [data.action.displayformula] - * @param {string} data.action.formula - * @param {string} data.action.desc - * @param {string} data.action.costs - * @param {boolean} data.action.blindroll - * - * @param {GurpsActor|null} data.actor - * @param {JQuery.Event|null} data.event - */ - roll({ action, actor, event }) { - const prefix = `Rolling [${!!action.displayformula ? action.displayformula : action.formula}${!!action.desc ? ' ' + action.desc : '' - }]` - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - return doRoll({ - actor, - formula: action.formula, - prefix, - optionalArgs: { blind: action.blindroll, event }, - }) - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.target - * @param {string} data.action.desc - * @param {boolean} data.action.blindroll - * - * @param {GurpsActor|null} data.actor - * @param {JQuery.Event|null} data.event - */ - controlroll({ action, actor, event }) { - const target = parseInt(action.target) - let thing - let chatthing - if (!!action.desc) { - thing = action.desc - chatthing = `["Control Roll, ${thing}"CR:${target} ${thing}]` - } else { - chatthing = `[CR:${target}]` - } - return doRoll({ - actor, - thing, - chatthing, - origtarget: target, - optionalArgs: { blind: action.blindroll, event }, - }) - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.derivedformula - * @param {string} data.action.desc - * @param {string} data.action.costs - * @param {string} data.action.formula - * @param {boolean} data.action.blindroll - * - * @param {GurpsActor|null} data.actor - * @param {JQuery.Event|null} data.event - */ - derivedroll({ action, actor, event }) { - if (!action.derivedformula) { - ui.notifications.warn('derived roll with no derived formula') - return false - } - if (!actor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - let df = action.derivedformula.match(/[Ss][Ww]/) ? actor.system.swing : actor.system.thrust - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - const originalFormula = action.derivedformula + action.formula - return doRoll({ - actor, - formula: d6ify(df + action.formula), - prefix: `Rolling [${action.derivedformula}${action.formula}] ${action.desc}`, - optionalArgs: { blind: action.blindroll, event }, - }) - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.desc - * @param {string} data.action.costs - * @param {string} data.action.name - * @param {string} data.action.mod - * @param {boolean} data.action.isMelee - * @param {boolean} data.action.isRanged - * @param {boolean} data.action.calcOnly - * @param {boolean} data.action.blindroll - * - * @param {GurpsActor|null} data.actor - * @param {JQuery.Event|null} data.event - */ - async attack({ action, actor, event }) { - if (!actor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - if (!action.name) { - ui.notifications.warn('attack action without name') - return false - } - let att = GURPS.findAttack(actor.system, action.name, !!action.isMelee, !!action.isRanged) // find attack possibly using wildcards - if (!att) { - if (!action.calcOnly) { - ui.notifications.warn(`No melee attack named '${action.name.replace('<', '<')}' found on ${actor.name}`) - } - return false - } - let p = 'A:' - if (!!action.isMelee && !action.isRanged) p = 'M:' - if (!action.isMelee && !!action.isRanged) p = 'R:' - // Need to finagle chatthing to allow for attack names that include OtFs - let thing = att.name - .replace(/\[.*\]/, '') - .replace(/ +/g, ' ') - .trim() - const chatthing = `[${p}${quotedAttackName({ name: thing, mode: att.mode })}]` - const followon = `[D:${quotedAttackName({ name: thing, mode: att.mode })}]` - let target = att.level - if (!target) { - ui.notifications.warn(`attack named ${thing} has level of 0 or NaN`) - return false - } - if (action.calcOnly) { - let modifier = parseInt(action.mod) ?? 0 - if (isNaN(modifier)) modifier = 0 - return { target: target + modifier, thing: thing } - } - const opt = { - blind: action.blindroll, - event, - obj: att, // save the attack in the optional parameters, in case it has rcl/rof - followon: followon, - text: '' - } - let targetmods = [] - if (opt.obj.checkotf && !(await GURPS.executeOTF(opt.obj.checkotf, false, event))) return false - if (opt.obj.duringotf) await GURPS.executeOTF(opt.obj.duringotf, false, event) - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) - if (action.overridetxt) opt.text += "" + action.overridetxt + '' - - return doRoll({ - actor, - targetmods, - thing, - chatthing, - origtarget: target, - optionalArgs: opt, - }) - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.desc - * @param {string} data.action.costs - * @param {string} data.action.name - * @param {string} data.action.mod - * @param {boolean} data.action.isMelee - * @param {boolean} data.action.calcOnly - * @param {boolean} data.action.blindroll - * - * @param {GurpsActor|null} data.actor - * @param {JQuery.Event|null} data.event - */ - ['weapon-block']({ action, actor, event }) { - if (!actor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - let att = GURPS.findAttack(actor.system, action.name, !!action.isMelee, false) // find attack possibly using wildcards - if (!att) { - ui.notifications.warn(`No melee attack named '${action.name.replace('<', '<')}' found on ${actor.name}`) - return false - } - let mode = att.mode ? ` (${att.mode})` : '' - const target = parseInt(att.block) - if (isNaN(target) || target == 0) { - ui.notifications.warn(`No Block for '${action.name.replace('<', '<')}' found on ${actor.name}`) - return false - } - const thing = att.name - .replace(/\[.*\]/, '') - .replace(/ +/g, ' ') - .trim() - if (action.calcOnly) { - let modifier = parseInt(action.mod) ?? 0 - if (isNaN(modifier)) modifier = 0 - return { target: target + modifier, thing: thing } - } - let targetmods = [] - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) - const chatthing = thing === '' ? att.name + mode : `[B:"${thing}${mode}"]` - - return doRoll({ - actor, - targetmods, - prefix: 'Block: ', - thing, - chatthing, - origtarget: target, - optionalArgs: { blind: action.blindroll, event }, - }) - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.desc - * @param {string} data.action.costs - * @param {string} data.action.name - * @param {string} data.action.mod - * @param {boolean} data.action.isMelee - * @param {boolean} data.action.calcOnly - * @param {boolean} data.action.blindroll - * - * @param {GurpsActor|null} data.actor - * @param {JQuery.Event|null} data.event - * @param {boolean} data.calcOnly - */ - ['weapon-parry']({ action, actor, event, calcOnly }) { - if (!actor) { - ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) - return false - } - let att = GURPS.findAttack(actor.system, action.name, !!action.isMelee, false) // find attack possibly using wildcards - if (!att) { - ui.notifications.warn(`No melee attack named '${action.name.replace('<', '<')}' found on ${actor.name}`) - return false - } - let mode = att.mode ? ` (${att.mode})` : '' - const target = parseInt(att.parry) - if (isNaN(target) || target == 0) { - ui.notifications.warn(`No Parry for '${action.name.replace('<', '<')}' found on ${actor.name}`) - return false - } - const thing = att.name - .replace(/\[.*\]/, '') - .replace(/ +/g, ' ') - .trim() - if (action.calcOnly) { - let modifier = parseInt(action.mod) ?? 0 - if (isNaN(modifier)) modifier = 0 - return { target: target + modifier, thing: thing } - } - let targetmods = [] - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) - const chatthing = thing === '' ? att.name + mode : `[P:"${thing}${mode}"]` - - return doRoll({ - actor, - targetmods, - prefix: 'Parry: ', - thing, - chatthing, - origtarget: target, - optionalArgs: { blind: action.blindroll, event }, - }) - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.desc - * @param {string} data.action.costs - * @param {string} data.action.name - * @param {string} data.action.mod - * @param {boolean} data.action.isMelee - * @param {boolean} data.action.blindroll - * @param {string} [data.action.target] - * - * @param {GurpsActor|null} data.actor - * @param {JQuery.Event|null} data.event - * @param {string} data.originalOtf - * @param {boolean} data.calcOnly - */ - async attribute({ action, actor, event, originalOtf, calcOnly }) { - // This can be complicated because Attributes (and Skills) can be pre-targeted (meaning we don't need an actor) - if (!actor && (!action || !action.target)) { - ui.notifications?.warn('You must have a character selected') - return false - } - let target = parseInt(action.target) // is it pre-targeted (ST12) - if (!target && !!actor) { - if (!!action.melee) { - // Is it trying to match to an attack name (should only occur with Parry: & Block: - let meleeAttack = GURPS.findAttack(actor.system, action.melee) - if (!!meleeAttack) { - target = parseInt(meleeAttack[action.attribute.toLowerCase()]) // should only occur with parry & block - } - } else { - target = parseInt(GURPS.resolve(action.path, actor.system)) - } - } - const thing = action.name - if (!target) { - return false - } - if (calcOnly) { - let modifier = parseInt(action.mod) ?? 0 - if (isNaN(modifier)) modifier = 0 - return { target: target + modifier, thing: thing } - } - let targetmods = [] - const chatthing = originalOtf ? `[${originalOtf}]` : `[${thing}]` - let opt = { - blind: action.blindroll, - event: event, - action: action, - obj: action.obj, - text: '' - } - if (opt.obj?.checkotf && !(await GURPS.executeOTF(opt.obj.checkotf, false, event))) return false - if (opt.obj?.duringotf) await GURPS.executeOTF(opt.obj.duringotf, false, event) - opt.text = '' - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) - else if (!!action.desc) opt.text = "" + action.desc + '' - if (action.overridetxt) opt.text += "" + action.overridetxt + '' - - return doRoll({ - actor, - targetmods, - prefix: 'Roll vs ', - thing, - chatthing, - origtarget: target, - optionalArgs: opt, - }) - }, - /** - * @param {Object} data - * - * @param {Object} data.action - * @param {string} data.action.desc - * @param {string} data.action.costs - * @param {string} data.action.name - * @param {string} data.action.mod - * @param {boolean} data.action.blindroll - * @param {string} [data.action.target] - * - * @param {GurpsActor|null} data.actor - * @param {JQuery.Event|null} data.event - * @param {string} data.originalOtf - * @param {boolean} data.calcOnly - */ - async ['skill-spell']({ action, actor, event, originalOtf, calcOnly }) { - if (!actor && (!action || !action.target)) { - ui.notifications?.warn('You must have a character selected') - return false - } - const target = processSkillSpell({ action, actor }) - if (!action) { - return false - } - let thing = action.name - .replace(/\[.*\]/, '') - .replace(/ +/g, ' ') - .trim() - if (calcOnly) { - let modifier = parseInt(action.mod) ?? 0 - if (isNaN(modifier)) modifier = 0 - return { target: target + modifier, thing: thing } - } - let targetmods = [] - let chatthing = originalOtf ? `[${originalOtf}]` : `[S:"${thing}"]` - let opt = { - blind: action.blindroll, - event, - action, - obj: action.obj, - text: '' - } - if (opt.obj?.checkotf && !(await GURPS.executeOTF(opt.obj.checkotf, false, event))) return false - if (opt.obj?.duringotf) await GURPS.executeOTF(opt.obj.duringotf, false, event) - - if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) - if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) - else if (!!action.desc) opt.text = "" + action.desc + '' - if (action.overridetxt) opt.text += "" + action.overridetxt + '' - - return doRoll({ actor, targetmods, thing, chatthing, origtarget: target, optionalArgs: opt }) - }, - - /* + GURPS.LEGAL = `GURPS is a trademark of Steve Jackson Games, and its rules and art are copyrighted by Steve Jackson Games. All rights are reserved by Steve Jackson Games. This game aid is the original creation of Chris Normand/Nose66 and is released for free distribution, and not for resale, under the permissions granted by http://www.sjgames.com/general/online_policy.html` + + if (GURPS.DEBUG) { + GURPS.parseDecimalNumber = parseDecimalNumber + } + + AddChatHooks() + JQueryHelpers() + MoustacheWax() + Settings.initializeSettings() + GURPS.EffectModifierControl = new EffectModifierControl() + + // CONFIG.debug.hooks = true + + // Expose Maneuvers to make them easier to use in modules + GURPS.Maneuvers = Maneuvers + + // Use the target d6 icon for rolltable entries + //CONFIG.RollTable.resultIcon = 'systems/gurps/icons/single-die.webp' + CONFIG.time.roundTime = 1 + + GURPS.StatusEffect = new StatusEffect() + + // Hack to remember the last Actor sheet that was accessed... for the Modifier Bucket to work + GURPS.LastActor = null + GURPS.SJGProductMappings = SJGProductMappings + GURPS.clearActiveEffects = GurpsActiveEffect.clearEffectsOnSelectedToken + + GURPS.SetLastActor = function (actor, tokenDocument) { + if (actor != GURPS.LastActor) console.log('Setting Last Actor:' + actor?.name) + GURPS.LastActor = actor + GURPS.LastTokenDocument = tokenDocument + setTimeout(() => GURPS.ModifierBucket.refresh(), 100) // Need to make certain the mod bucket refresh occurs later + } + + GURPS.ClearLastActor = function (actor) { + if (GURPS.LastActor == actor) { + console.log('Clearing Last Actor:' + GURPS.LastActor?.name) + GURPS.LastActor = null + GURPS.ModifierBucket.refresh() + const tokens = canvas.tokens + if (tokens && tokens.controlled.length > 0) { + GURPS.SetLastActor(tokens.controlled[0].actor) + } // There may still be tokens selected... if so, select one of them + } + } + + /** + * This object literal holds the results of the last targeted roll by an actor. + * The property key is the actor's ID. The value is literally the chatdata from + * the doRoll() function, which has close to anything anyone would want. + */ + GURPS.lastTargetedRoll = {} + GURPS.lastTargetedRolls = {} // mapped by both actor and token id + + GURPS.setLastTargetedRoll = function (chatdata, actorid, tokenid, updateOtherClients = false) { + let tmp = { ...chatdata } + if (!!actorid) GURPS.lastTargetedRolls[actorid] = tmp + if (!!tokenid) GURPS.lastTargetedRolls[tokenid] = tmp + GURPS.lastTargetedRoll = tmp // keep the local copy + // Interesting fields: GURPS.lastTargetedRoll.margin .isCritSuccess .IsCritFailure .thing + + if (updateOtherClients) + game.socket.emit('system.gurps', { + type: 'setLastTargetedRoll', + chatdata: tmp, + actorid: actorid, + tokenid: tokenid, + }) + } + + // TODO Why are these global? + GURPS.ChatCommandsInProcess = [] // Taking advantage of synchronous nature of JS arrays + GURPS.PendingOTFs = [] + GURPS.IgnoreTokenSelect = false + GURPS.wait = wait + + GURPS.attributepaths = { + ST: 'attributes.ST.value', + DX: 'attributes.DX.value', + IQ: 'attributes.IQ.value', + HT: 'attributes.HT.value', + QN: 'attributes.QN.value', + WILL: 'attributes.WILL.value', + Will: 'attributes.WILL.value', + PER: 'attributes.PER.value', + Per: 'attributes.PER.value', + } + + // Map stuff back to translation keys... don't know if useful yet + GURPS.attributes = { + ST: 'GURPS.attributesST', + DX: 'GURPS.attributesDX', + IQ: 'GURPS.attributesIQ', + HT: 'GURPS.attributesHT', + QN: 'GURPS.attributesQN', + Will: 'GURPS.attributesWILL', + Per: 'GURPS.attributesPER', + } + + GURPS.attributeNames = { + ST: 'GURPS.attributesSTNAME', + DX: 'GURPS.attributesDXNAME', + IQ: 'GURPS.attributesIQNAME', + HT: 'GURPS.attributesHTNAME', + QN: 'GURPS.attributesQNNAME', + Will: 'GURPS.attributesWILLNAME', + Per: 'GURPS.attributesPERNAME', + } + + GURPS.skillTypes = { + 'DX/E': 'GURPS.SkillDXE', + 'DX/A': 'GURPS.SkillDXA', + 'DX/H': 'GURPS.SkillDXH', + 'DX/VH': 'GURPS.SkillDXVH', + + 'IQ/E': 'GURPS.SkillIQE', + 'IQ/A': 'GURPS.SkillIQA', + 'IQ/H': 'GURPS.SkillIQH', + 'IQ/VH': 'GURPS.SkillIQVH', + + 'HT/E': 'GURPS.SkillHTE', + 'HT/A': 'GURPS.SkillHTA', + 'HT/H': 'GURPS.SkillHTH', + 'HT/VH': 'GURPS.SkillHTVH', + + 'QN/E': 'GURPS.SkillQNE', + 'QN/A': 'GURPS.SkillQNA', + 'QN/H': 'GURPS.SkillQNH', + 'QN/VH': 'GURPS.SkillQNVH', + + 'Will/E': 'GURPS.SkillWillE', + 'Will/A': 'GURPS.SkillWillA', + 'Will/H': 'GURPS.SkillWillH', + 'Will/VH': 'GURPS.SkillWillVH', + + 'Per/E': 'GURPS.SkillPerE', + 'Per/A': 'GURPS.SkillPerA', + 'Per/H': 'GURPS.SkillPerH', + 'Per/VH': 'GURPS.SkillPerVH', + } + + GURPS.PARSELINK_MAPPINGS = { + ST: 'attributes.ST.value', + DX: 'attributes.DX.value', + IQ: 'attributes.IQ.value', + HT: 'attributes.HT.value', + QN: 'attributes.QN.value', + WILL: 'attributes.WILL.value', + PER: 'attributes.PER.value', + VISION: 'vision', + FRIGHTCHECK: 'frightcheck', + 'FRIGHT CHECK': 'frightcheck', + HEARING: 'hearing', + TASTESMELL: 'tastesmell', + 'TASTE SMELL': 'tastesmell', + TASTE: 'tastesmell', + SMELL: 'tastesmell', + TOUCH: 'touch', + DODGE: 'currentdodge', + Parry: 'equippedparry', + PARRY: 'equippedparry', + BLOCK: 'equippedblock', + } + + GURPS.SJGProductMappings = SJGProductMappings + GURPS.USER_GUIDE_URL = 'https://bit.ly/2JaSlQd' + + /** + * @param {string} str + */ + function escapeUnicode(str) { + return str.replace(/[^\0-~]/g, function (ch) { + return '&#x' + ('0000' + ch.charCodeAt(0).toString(16).toUpperCase()).slice(-4) + ';' + }) + } + GURPS.escapeUnicode = escapeUnicode + + /** + * Read text data from a user provided File object + * Stolen from Foundry, and replaced 'readAsText' with 'readAsBinaryString' to save unicode characters. + * @param {File} file A File object + * @return {Promise.} A Promise which resolves to the loaded text data + */ + async function readTextFromFile(file) { + const reader = new FileReader() + return new Promise((resolve, reject) => { + // @ts-ignore + reader.onload = ev => { + resolve(reader.result) + } + // @ts-ignore + reader.onerror = ev => { + reader.abort() + reject() + } + if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_IMPORT_FILE_ENCODING) == 1) + reader.readAsText(file, 'UTF-8') + else reader.readAsText(file, 'ISO-8859-1') + }) + } + GURPS.readTextFromFile = readTextFromFile + + // This is an ugly hack to clean up the "formatted text" output from GCS FG XML. + // First we have to remove non-printing characters, and then we want to replace + // all

    ...

    with .../n before we try to convert to JSON. Also, for some reason, + // the DOMParser doesn't like some of the stuff in the formatted text sections, so + // we will base64 encode it, and the decode it in the Named subclass setNotes() + /** + * @param {string} xml + */ + function cleanUpP(xml) { + // First, remove non-ascii characters + // xml = xml.replace(/[^ -~]+/g, '') + xml = GURPS.escapeUnicode(xml) + + // Now try to remove any lone " & " in names, etc. Will only occur in GCA output + xml = xml.replace(/ & /g, ' & ') + let swap = function (/** @type {string} */ xml, /** @type {string} */ tagin, /** @type {string} */ tagout) { + let s = xml.indexOf(tagin) + while (s > 0) { + let e = xml.indexOf(tagout, s) + if (e > s) { + let t1 = xml.substring(0, s) + let t2 = xml.substring(s + 3, e) + t2 = '@@@@' + utoa(t2) + '\n' + let t3 = xml.substr(e + 4) + xml = t1 + t2 + t3 + s = xml.indexOf(tagin, s + t2.length) + } + } + return xml + } + xml = swap(xml, '<p>', '</p>') + xml = swap(xml, '

    ', '

    ') + xml = xml.replace(/
    /g, '\n') + return xml + } + GURPS.cleanUpP = cleanUpP + + /** + * A utility function to "deep" print an object + * @param {Object | null} obj + * @param {number} ndeep + * @returns {string} + */ + function objToString(obj, ndeep = 1) { + if (obj == null) { + return String(obj) + } + if (ndeep > 10) return '(stopping due to depth): ' + obj.toString() + switch (typeof obj) { + case 'string': + return '"' + obj + '"' + case 'function': + return obj.name || obj.toString() + case 'object': + var indent = Array(ndeep || 1).join('\t'), + isArray = Array.isArray(obj) + return ( + '{['[+isArray] + + Object.keys(obj) + .map(function (key) { + // @ts-ignore + return '\n\t' + indent + key + ': ' + objToString(obj[key], (ndeep || 1) + 1) + }) + .join(',') + + '\n' + + indent + + '}]'[+isArray] + ) + default: + return obj.toString() + } + } + GURPS.objToString = objToString + + /** + * @param {string} s + */ + function trim(s) { + return s.replace(/^\s*$(?:\r\n?|\n)/gm, '').trim() // /^\s*[\r\n]/gm + } + GURPS.trim = trim + + // Needed for external modules like Token Action HUD and Nordlond Bestiary + GURPS.gurpslink = gurpslink + + /** + * @param {string} string + * @param {boolean} priv + * @param {JQuery.Event|null} event + * @returns {Promise} + */ + async function executeOTF(string, priv = false, event = null) { + if (!string) return false + string = string.trim() + if (string[0] == '[' && string[string.length - 1] == ']') string = string.substring(1, string.length - 1) + let action = parselink(string) + let answer = false + if (!!action.action) { + if (!event) event = { shiftKey: priv, ctrlKey: false, data: {} } + let result = await GURPS.performAction(action.action, GURPS.LastActor, event) + answer = !!result + } else ui.notifications.warn(`"${string}" did not parse into a valid On-the-Fly formula`) + return answer + } + GURPS.executeOTF = executeOTF + + function processSkillSpell({ action, actor }) { + let actordata = actor?.data + + // skill + var skill + if (!!action.target) { + // Skill-12 + skill = { + name: action.name, + // @ts-ignore + level: parseInt(action.target), + } + } + // @ts-ignore + else skill = GURPS.findSkillSpell(actor?.data?.data, action.name, !!action.isSkillOnly, !!action.isSpellOnly) + if (!skill) { + return 0 + } + let skillLevel = skill.level + // @ts-ignore + action.obj = skill + + // on a floating skill check, we want the skill with the highest relative skill level + if (!!action.floatingAttribute) { + if (!!actor) { + let value = GURPS.resolve(action.floatingAttribute, actordata.data) + let rsl = skill.relativelevel // this is something like 'IQ-2' or 'Touch+3' + console.log(rsl) + let valueText = rsl.replace(/^.*([+-]\d+)$/g, '$1') + console.log(valueText) + skillLevel = valueText === rsl ? parseInt(value) : parseInt(valueText) + parseInt(value) + } else { + ui.notifications?.warn('You must have a character selected to use a "Based" Skill') + } + } + + //if (!!action.mod) skillLevel += parseInt(action.mod) + + return skillLevel + } + + const actionFuncs = { + /** + * @param {Object} data + * @param {Object} data.action + * @param {string} data.action.link + */ + pdf({ action }) { + if (!action.link) { + ui.notifications?.warn('no link was parsed for the pdf') + return false // if there's no link action fails + } + handlePdf(action.link) + return true + }, + + // + iftest({ action }) { + if (!GURPS.lastTargetedRoll) return false + if (action.name == 'isCritSuccess') return !!GURPS.lastTargetedRoll.isCritSuccess + if (action.name == 'isCritFailure') return !!GURPS.lastTargetedRoll.isCritFailure + if (!action.equation) + // if [@margin] tests for >=0 + return GURPS.lastTargetedRoll.margin >= 0 + else { + let m = action.equation.match(/ *([=<>]+) *([+-]?[\d\.]+)/) + let value = Number(m[2]) + switch (m[1]) { + case '=': + case '==': + return GURPS.lastTargetedRoll.margin == value + case '>': + return GURPS.lastTargetedRoll.margin > value + case '>=': + return GURPS.lastTargetedRoll.margin >= value + case '<': + return GURPS.lastTargetedRoll.margin < value + case '<=': + return GURPS.lastTargetedRoll.margin <= value + default: + return false + } + } + }, + + /** + * @param {Object} data + * @param {Object} data.action + * @param {string} data.action.mod + * @param {string} data.action.desc + * @param {Object} data.action.next + */ + modifier({ action }) { + GURPS.ModifierBucket.addModifier(action.mod, action.desc) + if (action.next && action.next.type === 'modifier') { + return this.modifier({ action: action.next }) // recursion, but you need to wrap the next action in an object using the 'action' attribute + } + return true + }, + /** + * @param {Object} data + * @param {Object} data.action + * @param {string} data.action.orig + * @param {boolean} data.action.quiet + * @param {JQuery.Event|null} data.event + */ + async chat({ action, event }) { + // @ts-ignore + const chat = `/setEventFlags ${!!action.quiet} ${!!event?.shiftKey} ${game.keyboard.isModifierActive( + KeyboardManager.MODIFIER_KEYS.CONTROL + )}\n${action.orig}` + + if (!!action.overridetxt) { + if (!event.data) event.data = {} + event.data.overridetxt = action.overridetxt + } + // @ts-ignore - someone somewhere must have added chatmsgData to the MouseEvent. + return await GURPS.ChatProcessors.startProcessingLines(chat, event?.chatmsgData, event) + }, + /** + * @param {Object} data + * @param {Object} data.action + * @param {string} data.action.link + * @param {string} data.action.id + */ + dragdrop({ action }) { + switch (action.link) { + case 'JournalEntry': + let j = game.journal?.get(action.id) + if (j) { + if (j.data.flags.pdfoundry) { + handlePdf(j.data.flags.pdfoundry.PDFData.code) + } else j.sheet?.render(true) + } + return true + case 'Actor': + game.actors?.get(action.id)?.sheet?.render(true) + return true + case 'RollTable': + game.tables?.get(action.id)?.sheet?.render(true) + return true + case 'Item': + game.items?.get(action.id)?.sheet?.render(true) + return true + default: + ui.notifications.warn(`unknown entity type: ${action.link}`) + return false + } + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.mod + * @param {string} data.action.desc + * @param {string} data.action.formula + * @param {string} data.action.damagetype + * @param {string} data.action.extdamagetype + * @param {string} data.action.hitlocation + * @param {string} data.action.costs + * @param {boolean} data.action.accumulate + * + * @param {JQuery.Event|null} data.event + * @param {GurpsActor|null} data.actor + * @param {string[]} data.targets + */ + damage({ action, event, actor, targets }) { + // accumulate action fails if there's no selected actor + if (action.accumulate && !actor) { + ui.notifications?.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + + if (action.accumulate) { + // store/increment value on GurpsActor + actor.accumulateDamageRoll(action) + return true + } + + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + + if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc) // special case where Damage comes from [D:attack + mod] + + DamageChat.create( + actor || game.user, + action.formula, + action.damagetype, + event, + null, + targets, + action.extdamagetype, + action.hitlocation + ) + if (action.next) { + return GURPS.performAction(action.next, actor, event, targets) + } + + return true + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.damagetype + * @param {string} data.action.formula + * @param {string} data.action.costs + * @param {string} data.action.derivedformula + * @param {string} data.action.extdamagetype + * @param {string} data.action.hitlocation + * @param {boolean} data.action.accumulate + * + * @param {JQuery.Event|null} data.event + * @param {GurpsActor|null} data.actor + * @param {string[]} data.targets + */ + deriveddamage({ action, event, actor, targets }) { + // action fails if there's no selected actor + if (!actor) { + ui.notifications?.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + let df = action.derivedformula.match(/sw/i) ? actor.system.swing : actor.system.thrust + // action fails if there's no formula + if (!df) { + ui.notifications?.warn(`${actor.name} does not have a ${action.derivedformula.toUpperCase()} formula`) + return false + } + let formula = df + action.formula + + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + DamageChat.create( + actor || game.user, + formula, + action.damagetype, + event, + action.derivedformula + action.formula.replace(/([+-]\d+).*/g, '$1'), // Just keep the +/- mod + targets, + action.extdamagetype, + action.hitlocation + ) + if (action.next) { + return GURPS.performAction(action.next, actor, event, targets) + } + return true + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.name + * @param {boolean} data.action.isMelee + * @param {boolean} data.action.isRanged + * @param {string} data.action.costs + * @param {string} data.action.mod + * @param {string} data.action.desc + * + * @param {JQuery.Event|null} data.event + * @param {GurpsActor|null} data.actor + * @param {string[]} data.targets + */ + attackdamage({ action, event, actor, targets }) { + // action fails if there's no selected actor + if (!actor) { + ui.notifications?.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + if (!action.name) { + ui.notifications?.warn('attack damage action has no name') + return false + } + let att = null + att = GURPS.findAttack(actor.system, action.name, !!action.isMelee, !!action.isRanged) // find attack possibly using wildcards + if (!att) { + ui.notifications.warn( + `No melee or ranged attack named '${action.name.replace('<', '<')}' found on ${actor.name}` + ) + return false + } + if (action.calcOnly) return att.damage + + let dam = parseForRollOrDamage(att.damage) + if (!dam) { + ui.notifications?.warn('Damage is not rollable') + return false + } + dam.action.costs = action.costs + dam.action.mod = action.mod + dam.action.desc = action.desc + return performAction(dam.action, actor, event, targets) + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} [data.action.displayformula] + * @param {string} data.action.formula + * @param {string} data.action.desc + * @param {string} data.action.costs + * @param {boolean} data.action.blindroll + * + * @param {GurpsActor|null} data.actor + * @param {JQuery.Event|null} data.event + */ + roll({ action, actor, event }) { + const prefix = `Rolling [${!!action.displayformula ? action.displayformula : action.formula}${ + !!action.desc ? ' ' + action.desc : '' + }]` + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + return doRoll({ + actor, + formula: action.formula, + prefix, + optionalArgs: { blind: action.blindroll, event }, + }) + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.target + * @param {string} data.action.desc + * @param {boolean} data.action.blindroll + * + * @param {GurpsActor|null} data.actor + * @param {JQuery.Event|null} data.event + */ + controlroll({ action, actor, event }) { + const target = parseInt(action.target) + let thing + let chatthing + if (!!action.desc) { + thing = action.desc + chatthing = `["Control Roll, ${thing}"CR:${target} ${thing}]` + } else { + chatthing = `[CR:${target}]` + } + return doRoll({ + actor, + thing, + chatthing, + origtarget: target, + optionalArgs: { blind: action.blindroll, event }, + }) + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.derivedformula + * @param {string} data.action.desc + * @param {string} data.action.costs + * @param {string} data.action.formula + * @param {boolean} data.action.blindroll + * + * @param {GurpsActor|null} data.actor + * @param {JQuery.Event|null} data.event + */ + derivedroll({ action, actor, event }) { + if (!action.derivedformula) { + ui.notifications.warn('derived roll with no derived formula') + return false + } + if (!actor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + let df = action.derivedformula.match(/[Ss][Ww]/) ? actor.system.swing : actor.system.thrust + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + const originalFormula = action.derivedformula + action.formula + return doRoll({ + actor, + formula: d6ify(df + action.formula), + prefix: `Rolling [${action.derivedformula}${action.formula}] ${action.desc}`, + optionalArgs: { blind: action.blindroll, event }, + }) + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.desc + * @param {string} data.action.costs + * @param {string} data.action.name + * @param {string} data.action.mod + * @param {boolean} data.action.isMelee + * @param {boolean} data.action.isRanged + * @param {boolean} data.action.calcOnly + * @param {boolean} data.action.blindroll + * + * @param {GurpsActor|null} data.actor + * @param {JQuery.Event|null} data.event + */ + async attack({ action, actor, event }) { + if (!actor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + if (!action.name) { + ui.notifications.warn('attack action without name') + return false + } + let att = GURPS.findAttack(actor.system, action.name, !!action.isMelee, !!action.isRanged) // find attack possibly using wildcards + if (!att) { + if (!action.calcOnly) { + ui.notifications.warn(`No melee attack named '${action.name.replace('<', '<')}' found on ${actor.name}`) + } + return false + } + let p = 'A:' + if (!!action.isMelee && !action.isRanged) p = 'M:' + if (!action.isMelee && !!action.isRanged) p = 'R:' + // Need to finagle chatthing to allow for attack names that include OtFs + let thing = att.name + .replace(/\[.*\]/, '') + .replace(/ +/g, ' ') + .trim() + const chatthing = `[${p}${quotedAttackName({ name: thing, mode: att.mode })}]` + const followon = `[D:${quotedAttackName({ name: thing, mode: att.mode })}]` + let target = att.level + if (!target) { + ui.notifications.warn(`attack named ${thing} has level of 0 or NaN`) + return false + } + if (action.calcOnly) { + let modifier = parseInt(action.mod) ?? 0 + if (isNaN(modifier)) modifier = 0 + return { target: target + modifier, thing: thing } + } + const opt = { + blind: action.blindroll, + event, + obj: att, // save the attack in the optional parameters, in case it has rcl/rof + followon: followon, + text: '', + } + let targetmods = [] + if (opt.obj.checkotf && !(await GURPS.executeOTF(opt.obj.checkotf, false, event))) return false + if (opt.obj.duringotf) await GURPS.executeOTF(opt.obj.duringotf, false, event) + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) + if (action.overridetxt) opt.text += "" + action.overridetxt + '' + + return doRoll({ + actor, + targetmods, + thing, + chatthing, + origtarget: target, + optionalArgs: opt, + }) + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.desc + * @param {string} data.action.costs + * @param {string} data.action.name + * @param {string} data.action.mod + * @param {boolean} data.action.isMelee + * @param {boolean} data.action.calcOnly + * @param {boolean} data.action.blindroll + * + * @param {GurpsActor|null} data.actor + * @param {JQuery.Event|null} data.event + */ + ['weapon-block']({ action, actor, event }) { + if (!actor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + let att = GURPS.findAttack(actor.system, action.name, !!action.isMelee, false) // find attack possibly using wildcards + if (!att) { + ui.notifications.warn(`No melee attack named '${action.name.replace('<', '<')}' found on ${actor.name}`) + return false + } + let mode = att.mode ? ` (${att.mode})` : '' + const target = parseInt(att.block) + if (isNaN(target) || target == 0) { + ui.notifications.warn(`No Block for '${action.name.replace('<', '<')}' found on ${actor.name}`) + return false + } + const thing = att.name + .replace(/\[.*\]/, '') + .replace(/ +/g, ' ') + .trim() + if (action.calcOnly) { + let modifier = parseInt(action.mod) ?? 0 + if (isNaN(modifier)) modifier = 0 + return { target: target + modifier, thing: thing } + } + let targetmods = [] + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) + const chatthing = thing === '' ? att.name + mode : `[B:"${thing}${mode}"]` + + return doRoll({ + actor, + targetmods, + prefix: 'Block: ', + thing, + chatthing, + origtarget: target, + optionalArgs: { blind: action.blindroll, event }, + }) + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.desc + * @param {string} data.action.costs + * @param {string} data.action.name + * @param {string} data.action.mod + * @param {boolean} data.action.isMelee + * @param {boolean} data.action.calcOnly + * @param {boolean} data.action.blindroll + * + * @param {GurpsActor|null} data.actor + * @param {JQuery.Event|null} data.event + * @param {boolean} data.calcOnly + */ + ['weapon-parry']({ action, actor, event, calcOnly }) { + if (!actor) { + ui.notifications.warn(i18n('GURPS.chatYouMustHaveACharacterSelected')) + return false + } + let att = GURPS.findAttack(actor.system, action.name, !!action.isMelee, false) // find attack possibly using wildcards + if (!att) { + ui.notifications.warn(`No melee attack named '${action.name.replace('<', '<')}' found on ${actor.name}`) + return false + } + let mode = att.mode ? ` (${att.mode})` : '' + const target = parseInt(att.parry) + if (isNaN(target) || target == 0) { + ui.notifications.warn(`No Parry for '${action.name.replace('<', '<')}' found on ${actor.name}`) + return false + } + const thing = att.name + .replace(/\[.*\]/, '') + .replace(/ +/g, ' ') + .trim() + if (action.calcOnly) { + let modifier = parseInt(action.mod) ?? 0 + if (isNaN(modifier)) modifier = 0 + return { target: target + modifier, thing: thing } + } + let targetmods = [] + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) + const chatthing = thing === '' ? att.name + mode : `[P:"${thing}${mode}"]` + + return doRoll({ + actor, + targetmods, + prefix: 'Parry: ', + thing, + chatthing, + origtarget: target, + optionalArgs: { blind: action.blindroll, event }, + }) + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.desc + * @param {string} data.action.costs + * @param {string} data.action.name + * @param {string} data.action.mod + * @param {boolean} data.action.isMelee + * @param {boolean} data.action.blindroll + * @param {string} [data.action.target] + * + * @param {GurpsActor|null} data.actor + * @param {JQuery.Event|null} data.event + * @param {string} data.originalOtf + * @param {boolean} data.calcOnly + */ + async attribute({ action, actor, event, originalOtf, calcOnly }) { + // This can be complicated because Attributes (and Skills) can be pre-targeted (meaning we don't need an actor) + if (!actor && (!action || !action.target)) { + ui.notifications?.warn('You must have a character selected') + return false + } + let target = parseInt(action.target) // is it pre-targeted (ST12) + if (!target && !!actor) { + if (!!action.melee) { + // Is it trying to match to an attack name (should only occur with Parry: & Block: + let meleeAttack = GURPS.findAttack(actor.system, action.melee) + if (!!meleeAttack) { + target = parseInt(meleeAttack[action.attribute.toLowerCase()]) // should only occur with parry & block + } + } else { + target = parseInt(GURPS.resolve(action.path, actor.system)) + } + } + const thing = action.name + if (!target) { + return false + } + if (calcOnly) { + let modifier = parseInt(action.mod) ?? 0 + if (isNaN(modifier)) modifier = 0 + return { target: target + modifier, thing: thing } + } + let targetmods = [] + const chatthing = originalOtf ? `[${originalOtf}]` : `[${thing}]` + let opt = { + blind: action.blindroll, + event: event, + action: action, + obj: action.obj, + text: '', + } + if (opt.obj?.checkotf && !(await GURPS.executeOTF(opt.obj.checkotf, false, event))) return false + if (opt.obj?.duringotf) await GURPS.executeOTF(opt.obj.duringotf, false, event) + opt.text = '' + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) + else if (!!action.desc) opt.text = "" + action.desc + '' + if (action.overridetxt) opt.text += "" + action.overridetxt + '' + + return doRoll({ + actor, + targetmods, + prefix: 'Roll vs ', + thing, + chatthing, + origtarget: target, + optionalArgs: opt, + }) + }, + /** + * @param {Object} data + * + * @param {Object} data.action + * @param {string} data.action.desc + * @param {string} data.action.costs + * @param {string} data.action.name + * @param {string} data.action.mod + * @param {boolean} data.action.blindroll + * @param {string} [data.action.target] + * + * @param {GurpsActor|null} data.actor + * @param {JQuery.Event|null} data.event + * @param {string} data.originalOtf + * @param {boolean} data.calcOnly + */ + async ['skill-spell']({ action, actor, event, originalOtf, calcOnly }) { + if (!actor && (!action || !action.target)) { + ui.notifications?.warn('You must have a character selected') + return false + } + const target = processSkillSpell({ action, actor }) + if (!action) { + return false + } + let thing = action.name + .replace(/\[.*\]/, '') + .replace(/ +/g, ' ') + .trim() + if (calcOnly) { + let modifier = parseInt(action.mod) ?? 0 + if (isNaN(modifier)) modifier = 0 + return { target: target + modifier, thing: thing } + } + let targetmods = [] + let chatthing = originalOtf ? `[${originalOtf}]` : `[S:"${thing}"]` + let opt = { + blind: action.blindroll, + event, + action, + obj: action.obj, + text: '', + } + if (opt.obj?.checkotf && !(await GURPS.executeOTF(opt.obj.checkotf, false, event))) return false + if (opt.obj?.duringotf) await GURPS.executeOTF(opt.obj.duringotf, false, event) + + if (!!action.costs) GURPS.ModifierBucket.addModifier(0, action.costs) + if (!!action.mod) GURPS.ModifierBucket.addModifier(action.mod, action.desc, targetmods) + else if (!!action.desc) opt.text = "" + action.desc + '' + if (action.overridetxt) opt.text += "" + action.overridetxt + '' + + return doRoll({ actor, targetmods, thing, chatthing, origtarget: target, optionalArgs: opt }) + }, + + /* [AMRS][DPK] A: ads & attack (melee & range) AD: ads @@ -1097,697 +1096,698 @@ if (!globalThis.GURPS) { SK: skills SP: spells */ - ['test-exists']({ action, actor, event, originalOtf, calcOnly }) { - switch (action.prefix) { - case 'A': - if (!!findAdDisad(actor, action.name)) return true - if (!!findAttack(actor, action.name, true, true)) return true - return false - case 'AD': - if (!!findAdDisad(actor, action.name)) return true - return false - case 'AT': - if (!!findAttack(actor, action.name, true, true)) return true - return false - case 'M': - if (!!findAttack(actor, action.name, true, false)) return true - return false - case 'R': - if (!!findAttack(actor, action.name, false, true)) return true - return false - case 'S': - if (!!findSkillSpell(actor, action.name, false, false)) return true - return false - case 'SK': - if (!!findSkillSpell(actor, action.name, true, false)) return true - return false - case 'SP': - if (!!findSkillSpell(actor, action.name, false, true)) return true - return false - } - return false - }, - href({ action, actor, event, originalOtf, calcOnly }) { - window.open(action.orig, action.label) - } - } - GURPS.actionFuncs = actionFuncs - - async function findBestActionInChain({ action, actor, event, targets, originalOtf }) { - const actions = [] - let overridetxt = action.overridetxt - while (action) { - action.overridetxt = overridetxt - actions.push(action) - action = action.next - } - const calculations = await Promise.all( - actions.map(a => GURPS.actionFuncs[a.type]({ action: a, actor, event, targets, originalOtf, calcOnly: true })) - ) - const levels = calculations.map(result => (result ? result.target : 0)) - if (!levels.some(level => level > 0)) return null // actor does not have any of these skills - const bestLevel = Math.max(...levels) - return actions[levels.indexOf(bestLevel)] - } - - /** - * @param {Action} action - * @param {GurpsActor|null} actor - * @param {JQuery.Event|null} [event] - * @param {string[] } [targets] - * @returns {Promise} - */ - async function performAction(action, actor, event = null, targets = []) { - if (!action || !(action.type in actionFuncs)) return false - const origAction = action - const originalOtf = action.orig - const calcOnly = action.calcOnly - if (['attribute', 'skill-spell'].includes(action.type)) { - action = await findBestActionInChain({ action, event, actor, targets, originalOtf }) - } - return !action - ? false - : await GURPS.actionFuncs[action.type]({ action, actor, event, targets, originalOtf, calcOnly }) - } - GURPS.performAction = performAction - - /** - * Find the skill or spell. if isSkillOnly or isSpellOnly set, only check that list. - * @param {GurpsActor|GurpsActorData} actor - * @param {string} sname - */ - function findSkillSpell(actor, sname, isSkillOnly = false, isSpellOnly = false) { - const removeOtf = '^ *(\\[ ?["\'])?' // pattern to remove some of the OtF syntax from search name so attacks start start with an OtF can be matched - var t - if (!actor) return t - if (actor instanceof GurpsActor) actor = actor.system - let skillRegExp = new RegExp(removeOtf + makeRegexPatternFrom(sname, false, false), 'i') - let best = 0 - if (!isSpellOnly) - recurselist(actor.skills, s => { - if (s.name?.match(skillRegExp) && s.level > best) { - t = s - best = parseInt(s.level) - } - }) - if (!t) - if (!isSkillOnly) - recurselist(actor.spells, s => { - if (s.name?.match(skillRegExp) && s.level > best) { - t = s - best = parseInt(s.level) - } - }) - return t - } - GURPS.findSkillSpell = findSkillSpell - - /** - * @param {GurpsActor | GurpsActorData} actor - * @param {string} sname - * @returns {any} - */ - function findAdDisad(actor, sname) { - var t - if (!actor) return t - if (actor instanceof GurpsActor) actor = actor.system - sname = makeRegexPatternFrom(sname, false) - let regex = new RegExp(sname, 'i') - recurselist(actor.ads, s => { - if (s.name.match(regex)) { - t = s - } - }) - return t - } - GURPS.findAdDisad = findAdDisad - - /** - * @param {GurpsActor | GurpsActorData} actor - * @param {string} sname - */ - function findAttack(actor, sname, isMelee = true, isRanged = true) { - const removeOtf = '^ *(\\[ ?["\'])?' // pattern to remove some of the OtF syntax from search name so attacks start start with an OtF can be matched - var t - if (!actor) return t - if (actor instanceof GurpsActor) actor = actor.system - let fullregex = new RegExp(removeOtf + makeRegexPatternFrom(sname, false, false), 'i') - let smode = '' - let m = XRegExp.matchRecursive(sname, '\\(', '\\)', 'g', { - unbalanced: 'skip-lazy', - valueNames: ['between', null, 'match', null], - }) - if (m.length == 2) { - // Found a mode "(xxx)" in the search name - sname = m[0].value.trim() - smode = m[1].value.trim().toLowerCase() - } - let nameregex = new RegExp(removeOtf + makeRegexPatternFrom(sname, false, false), 'i') - if (isMelee) - // @ts-ignore - recurselist(actor.melee, (e, k, d) => { - if (!t) { - let full = e.name - if (!!e.mode) full += ' (' + e.mode + ')' - let em = !!e.mode ? e.mode.toLowerCase() : '' - if (e.name.match(nameregex) && (smode == '' || em == smode)) t = e - else if (e.name.match(fullregex)) t = e - else if (full.match(fullregex)) t = e - } - }) - // t = Object.values(actor.melee).find(a => (a.name + (!!a.mode ? ' (' + a.mode + ')' : '')).match(nameregex)) - if (isRanged && !t) - // @ts-ignore - recurselist(actor.ranged, (e, k, d) => { - if (!t) { - let full = e.name - if (!!e.mode) full += ' (' + e.mode + ')' - let em = !!e.mode ? e.mode.toLowerCase() : '' - if (e.name.match(nameregex) && (smode == '' || em == smode)) t = e - else if (e.name.match(fullregex)) t = e - else if (full.match(fullregex)) t = e - } - }) - // t = Object.values(actor.ranged).find(a => (a.name + (!!a.mode ? ' (' + a.mode + ')' : '')).match(nameregex)) - return t - } - GURPS.findAttack = findAttack - - /** - * The user clicked on a field that would allow a dice roll. Use the element - * information to try to determine what type of roll. - * @param {JQuery.MouseEventBase} event - * @param {GurpsActor | null} actor - * @param {string[]} targets - labels for multiple Damage rolls - */ - async function handleRoll(event, actor, options) { - event.preventDefault() - let formula = '' - let targetmods = null - let element = event.currentTarget - let prefix = '' - let thing = '' - var chatthing - /** @type {Record} */ - let opt = { event: event } - let target = 0 // -1 == damage roll, target = 0 is NO ROLL. - if (!!actor) GURPS.SetLastActor(actor) - - if ('damage' in element.dataset) { - // expect text like '2d+1 cut' or '1d+1 cut,1d-1 ctrl' (linked damage) - let f = !!element.dataset.otf ? element.dataset.otf : element.innerText.trim() - - let parts = f.includes(',') ? f.split(',') : [f] - for (let part of parts) { - //let result = parseForRollOrDamage(part.trim()) - let result = parselink(part.trim()) - if (result?.action) { - if (options?.combined && result.action.type == 'damage') result.action.formula = multiplyDice(result.action.formula, options.combined) - performAction(result.action, actor, event, options?.targets) - } - } - return - } else if ('path' in element.dataset) { - prefix = 'Roll vs ' - thing = GURPS._mapAttributePath(element.dataset.path) - formula = '3d6' - target = parseInt(element.innerText) - if ('otf' in element.dataset) - if (thing.toUpperCase() != element.dataset.otf.toUpperCase()) - chatthing = thing + '/[' + element.dataset.otf + ']' - else chatthing = '[' + element.dataset.otf + ']' - } else if ('otf' in element.dataset) { - // strip out any inner OtFs when coming from the UI. Mainly attack names - let otf = element.dataset.otf.trim() - // But there is a special case where the OtF is the first thing - // "M:"["Quarterstaff"A:"Quarterstaff (Thrust)"] (Thrust)"" - let m = otf.match(/^([sSmMrRaA]):"\["([^"]+)([^\]]+)]( *\(\w*\))?/) - if (!!m) otf = m[1] + ':' + m[2] + (!!m[4] ? m[4] : '') - otf = otf.replace(/\[.*\]/, '') - otf = otf.replace(/ +/g, ' ') // remove duplicate blanks - return GURPS.executeOTF(otf) - } else if ('name' in element.dataset) { - prefix = '' // "Attempting "; - let text = /** @type {string} */ (element.dataset.name || element.dataset.otf) - text = text.replace(/ \(\)$/g, '') // sent as "name (mode)", and mode is empty - thing = text.replace(/(.*?)\(.*\)/g, '$1') - - // opt.text = text.replace(/.*?\((.*)\)/g, "
     ($1)"); - opt.text = text.replace(/.*?\((.*)\)/g, '$1') - - if (opt.text === text) opt.text = '' - else opt.text = "(" + opt.text + ')' - let k = $(element).closest('[data-key]').attr('data-key') - if (!k) k = element.dataset.key - if (!!k) { - if (actor) opt.obj = getProperty(actor, k) // During the roll, we may want to extract something from the object - if (opt.obj.checkotf && !(await GURPS.executeOTF(opt.obj.checkotf, false, event))) return - if (opt.obj.duringotf) await GURPS.executeOTF(opt.obj.duringotf, false, event) - } - formula = '3d6' - let t = element.innerText - if (!!t) { - let a = t.trim().split(' ') - t = a[0] - if (!!t) target = parseInt(t) - if (isNaN(target)) target = 0 - // Can't roll against a non-integer - else { - a.shift() - let m = a.join(' ') - if (!!m) GURPS.ModifierBucket.addModifier(0, m) - } - } - } else if ('roll' in element.dataset) { - target = -1 // Set flag to indicate a non-targeted roll - formula = element.innerText - prefix = 'Rolling ' + formula - formula = d6ify(formula) - } - - doRoll({ actor, formula, targetmods, prefix, thing, chatthing, origtarget: target, optionalArgs: opt }) - } - GURPS.handleRoll = handleRoll - - /** - * If the desc contains *Cost ?FP or *Max:9 then perform action - * @param {GurpsActor|User} actor - * @param {string} desc - */ - async function applyModifierDesc(actor, desc) { - if (!desc) return null - let m = desc.match(COSTS_REGEX) - - if (!!m && !!actor && !actor.isSelf) { - let delta = parseInt(m.groups.cost) - let target = m.groups.type - if (target.match(/^[hf]p/i)) { - let k = target.toUpperCase() - // @ts-ignore - delta = actor.system[k].value - delta - await actor.update({ ['data.' + k + '.value']: delta }) - } - if (target.match(/^tr/i)) { - await GURPS.ChatProcessors.startProcessingLines('/setEventFlags true false false\\\\/' + target + ' -' + delta) // Make the tracker command quiet - return null - } - } - - let parse = desc.replace(/.*\*max: ?(\d+).*/gi, '$1') - if (parse != desc) { - return parseInt(parse) - } - return null // indicating no overriding MAX value - } - GURPS.applyModifierDesc = applyModifierDesc - - /** - * Return html for text, parsing GURPS "links" into XXX. - * @param {string | null | undefined} str - * @param {boolean} [clrdmods=true] - */ - // function gurpslink(str, clrdmods = true, returnActions = false) { - // if (str === undefined || str == null) return '!!UNDEFINED' - // let found = -1 - // let depth = 0 - // let output = '' - // let actions = [] - // for (let i = 0; i < str.length; i++) { - // if (str[i] == '[') { - // if (depth == 0) found = ++i - // depth++ - // } - // if (str[i] == ']') { - // depth-- - // if (depth == 0 && found >= 0) { - // output += str.substring(0, found - 1) - // let action = parselink(str.substring(found, i), '', clrdmods) - // if (!!action.action) actions.push(action) - // if (!action.action) output += '[' - // output += action.text - // if (!action.action) output += ']' - // str = str.substr(i + 1) - // i = -1 - // found = -1 - // } - // } - // } - // if (returnActions === true) return actions - // output += str - // return output - // } - //GURPS.gurpslink = gurpslink - - /** - * Return the i18n string for this data path (note en.json must match up to the data paths). - * special case, drop ".value" from end of path (and append "NAME"), usually used for attributes. - * @param {string} path - * @param {any} _suffix - */ - function _mapAttributePath(path, suffix) { - let i = path.indexOf('.value') - if (i >= 0) { - path = path.substr(0, i) + 'NAME' // used for the attributes - } - path = path.replace(/\./g, '') // remove periods - return game.i18n.localize('GURPS.' + path) - } - GURPS._mapAttributePath = _mapAttributePath - - /** - * Given a string path "x.y.z", use it to resolve down an object heiracrhy - * @param {string | string[]} path - * @param {any} obj - * @deprecated - Just use Foundry's getProperty and setPrpoerty methods - */ - function resolve(path, obj = self, separator = '.') { - var properties = Array.isArray(path) ? path : path.split(separator) - return properties.reduce((prev, curr) => prev && prev[curr], obj) - } - GURPS.resolve = resolve - - /** - * A user has clicked on a "gurpslink", so we can assume that it previously qualified as a "gurpslink" - * and followed the On-the-Fly formulas. As such, we may already have an action block (base 64 encoded so we can handle - * any text). If not, we will just re-parse the text looking for the action block. - * - * @param {JQuery.MouseEventBase} event - * @param {GurpsActor | null} actor - * @param {string | null} desc - * @param {string[] | undefined} targets - */ - // function handleGurpslink(event, actor, desc, targets) { - // event.preventDefault() - // let element = event.currentTarget - // let action = element.dataset.action // If we have already parsed - // if (!!action) action = JSON.parse(atou(action)) - // else action = parselink(element.innerText, desc).action - // GURPS.performAction(action, actor, event, targets) - // } - // GURPS.handleGurpslink = handleGurpslink - - // So it can be called from a script macro - GURPS.genkey = zeroFill - - /** - * Add the value as a property to obj. The key will be a generated value equal - * to a five-digit string equal to the index, padded to the left with zeros; e.g: - * if index is 12, the property key will be "00012". - * - * If index is equal to -1, then the existing properties of the object are examined - * and the index set to the next available (i.e, no property exists) key of the same - * form. - * - * TODO should be moved to lib/utilities.js. - * - * @param {Record} obj - * @param {any} value - * @param {number} index - */ - function put(obj, value, index = -1) { - if (index == -1) { - index = 0 - while (obj.hasOwnProperty(zeroFill(index))) index++ - } - let k = zeroFill(index) - obj[k] = value - return k - } - GURPS.put = put - - /** - * Convolutions to remove a key from an object and fill in the gaps, necessary - * because the default add behavior just looks for the first open gap - * @param {GurpsActor} actor - * @param {string} path - */ - async function removeKey(actor, path) { - let i = path.lastIndexOf('.') - let objpath = path.substring(0, i) - let key = path.substr(i + 1) - i = objpath.lastIndexOf('.') - let parentpath = objpath.substring(0, i) - let objkey = objpath.substr(i + 1) - let object = GURPS.decode(actor, objpath) - let t = parentpath + '.-=' + objkey - let oldRender = actor.ignoreRender - actor.ignoreRender = true - await actor.internalUpdate({ [t]: null }) // Delete the whole object - delete object[key] - i = parseInt(key) - - i = i + 1 - while (object.hasOwnProperty(zeroFill(i))) { - let k = zeroFill(i) - object[key] = object[k] - delete object[k] - key = k - i++ - } - let sorted = Object.keys(object) - .sort() - .reduce((a, v) => { - // @ts-ignore - a[v] = object[v] - return a - }, {}) // Enforced key order - actor.ignoreRender = oldRender - await actor.internalUpdate({ [objpath]: sorted }, { diff: false }) - } - GURPS.removeKey = removeKey - - /** - * Because the DB just merges keys, the best way to insert is to delete the whole colleciton object, fix it up, and then re-add it. - * @param {Actor} actor - * @param {string} path - * @param {any} newobj - */ - async function insertBeforeKey(actor, path, newobj) { - let i = path.lastIndexOf('.') - let objpath = path.substring(0, i) - let key = path.substr(i + 1) - i = objpath.lastIndexOf('.') - let parentpath = objpath.substring(0, i) - let objkey = objpath.substr(i + 1) - let object = GURPS.decode(actor, objpath) - let t = parentpath + '.-=' + objkey - await actor.internalUpdate({ [t]: null }) // Delete the whole object - let start = parseInt(key) - - i = start + 1 - while (object.hasOwnProperty(zeroFill(i))) i++ - i = i - 1 - for (let z = i; z >= start; z--) { - object[zeroFill(z + 1)] = object[zeroFill(z)] - } - object[key] = newobj - let sorted = Object.keys(object) - .sort() - .reduce((a, v) => { - // @ts-ignore - a[v] = object[v] - return a - }, {}) // Enforced key order - await actor.internalUpdate({ [objpath]: sorted }, { diff: false }) - } - GURPS.insertBeforeKey = insertBeforeKey - - // TODO replace Record with { [key: string]: any } - /** - * @param {Record} obj - * @param {string} path - */ - function decode(obj, path, all = true) { - let p = path.split('.') - let end = p.length - if (!all) end = end - 1 - for (let i = 0; i < end; i++) { - let q = p[i] - obj = obj[q] - } - return obj - } - GURPS.decode = decode - - /** - * Funky helper function to be able to list hierarchical equipment in a linear list (with appropriate keys for editing) - * @param {Record} eqts - * @param {{ fn: (arg0: any, arg1: { data: any; }) => string; }} options - * @param {number} level - * @param {{ indent: any; key: string; count: any; }} data - * @param {string=} parentkey - * @param {{ equipment: { carried: Object; }; }|null} src - */ - function listeqtrecurse(eqts, options, level, data, parentkey = '', src = null) { - if (!eqts) return '' - let ret = '' - let i = 0 - for (let key in eqts) { - let eqt = eqts[key] - if (data) { - data.indent = level - data.key = parentkey + key - data.count = eqt.count - } - let display = true - if (!!src && game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_REMOVE_UNEQUIPPED)) { - // if an optional src is provided (which == actor.system) assume we are checking attacks to see if they are equipped - recurselist(src.equipment.carried, e => { - if (eqt.name.startsWith(e.name) && !e.equipped) display = false - }) - } - if (display) { - let fragment = options.fn(eqt, { data: data }) - // if (!!eqt?.equipped) console.log(fragment) - ret = ret + fragment - } - ret = ret + listeqtrecurse(eqt.contains, options, level + 1, data, parentkey + key + '.contains.') - } - return ret - } - GURPS.listeqtrecurse = listeqtrecurse - - GURPS.whisperOtfToOwner = function(otf, overridetxt, event, blindcheck, actor) { - if (!otf) return - if (!game.user.isGM) { - // If not the GM, just send the text to the chat input window (so the user can copy it) - $(document) - .find('#chat-message') - .val('[' + otf + ']') - return - } - otf = otf.replace(/ \(\)/g, '') // sent as "name (mode)", and mode is empty (only necessary for attacks) - let canblind = false - if (!!blindcheck) { - canblind = blindcheck == true || blindcheck.hasOwnProperty('blindroll') - // @ts-ignore - blindcheck is either boolean or an object with a blindroll property - if (canblind && blindcheck.blindroll) { - otf = '!' + otf - canblind = false - } - } - if (!!overridetxt) { - if (overridetxt.includes('"')) overridetxt = "'" + overridetxt + "'" - else overridetxt = '"' + overridetxt + '"' - } else overridetxt = '' - let users = actor?.getOwners()?.filter(u => !u.isGM) || [] - let botf = '[' + overridetxt + '!' + otf + ']' - otf = '[' + overridetxt + otf + ']' - - /** @type Record */ - let buttons = {} - buttons.one = { - icon: '', - label: 'To Everyone', - callback: () => GURPS.sendOtfMessage(otf, false), - } - if (canblind) - buttons.two = { - icon: '', - label: 'Blindroll to Everyone', - callback: () => GURPS.sendOtfMessage(botf, true), - } - if (users.length > 0) { - let nms = users.map(u => u.name).join(' ') - buttons.three = { - icon: '', - label: 'Whisper to ' + nms, - callback: () => GURPS.sendOtfMessage(otf, false, users), - } - if (canblind) - buttons.four = { - icon: '', - label: 'Whisper Blindroll to ' + nms, - callback: () => GURPS.sendOtfMessage(botf, true, users), - } - } - buttons.def = { - icon: '', - label: 'Copy to chat input', - callback: () => { - $(document).find('#chat-message').val(otf) - }, - } - - let d = new Dialog( - { - title: "GM 'Send Formula'", - content: `
    How would you like to send the formula:

    ${otf}
     
    `, - buttons: buttons, - default: 'def', - }, - { width: 700 } - ) - d.render(true) - } - - GURPS.sendOtfMessage = function(content, blindroll, users = null) { - /** @type {import('@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/chatMessageData').ChatMessageDataConstructorData} */ - let msgData = { - content: content, - user: game.user.id, - blind: blindroll, - } - if (!!users) { - msgData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER - msgData.whisper = users.map(it => it.id || '') - } else { - msgData.type = CONST.CHAT_MESSAGE_TYPES.OOC - } - ChatMessage.create(msgData) - } - - GURPS.resolveDamageRoll = function(event, actor, otf, overridetxt, isGM, isOtf = false) { - let title = game.i18n.localize('GURPS.RESOLVEDAMAGETitle') - let prompt = game.i18n.localize('GURPS.RESOLVEDAMAGEPrompt') - let quantity = game.i18n.localize('GURPS.RESOLVEDAMAGEQuantity') - let sendTo = game.i18n.localize('GURPS.RESOLVEDAMAGESendTo') - let multiple = game.i18n.localize('GURPS.RESOLVEDAMAGEMultiple') - - /** @type {Record} */ - let buttons = {} - - if (isGM) { - buttons.send = { - icon: '', - label: `${sendTo}`, - callback: () => GURPS.whisperOtfToOwner(otf, overridetxt, event, false, actor), // Can't blind roll damages (yet) - } - } - - buttons.multiple = { - icon: '', - label: `${multiple}`, - callback: html => { - // @ts-ignore - let text = /** @type {string} */ (html.find('#number-rolls').val()) - let number = parseInt(text) - let targets = [] - for (let index = 0; index < number; index++) { - targets[index] = `${index + 1}` - } - if (isOtf) GurpsWiring.handleGurpslink(event, actor, null, { targets: targets }) - else GURPS.handleRoll(event, actor, { targets: targets }) - }, - } - - buttons.combined = { - icon: '', - label: i18n('GURPS.RESOLVEDAMAGEAdd', 'Combine'), - callback: html => { - let text = /** @type {string} */ (html.find('#number-rolls').val()) - let number = parseInt(text) - - if (isOtf) otf = multiplyDice(otf, number) - - if (isOtf) GurpsWiring.handleGurpslink(event, actor, null, { combined: number }) - else GURPS.handleRoll(event, actor, { combined: number }) - }, - } - let def = GURPS.lastTargetedRoll?.rofrcl || 2 - let dlg = new Dialog({ - title: `${title}`, - content: ` + ['test-exists']({ action, actor, event, originalOtf, calcOnly }) { + switch (action.prefix) { + case 'A': + if (!!findAdDisad(actor, action.name)) return true + if (!!findAttack(actor, action.name, true, true)) return true + return false + case 'AD': + if (!!findAdDisad(actor, action.name)) return true + return false + case 'AT': + if (!!findAttack(actor, action.name, true, true)) return true + return false + case 'M': + if (!!findAttack(actor, action.name, true, false)) return true + return false + case 'R': + if (!!findAttack(actor, action.name, false, true)) return true + return false + case 'S': + if (!!findSkillSpell(actor, action.name, false, false)) return true + return false + case 'SK': + if (!!findSkillSpell(actor, action.name, true, false)) return true + return false + case 'SP': + if (!!findSkillSpell(actor, action.name, false, true)) return true + return false + } + return false + }, + href({ action, actor, event, originalOtf, calcOnly }) { + window.open(action.orig, action.label) + }, + } + GURPS.actionFuncs = actionFuncs + + async function findBestActionInChain({ action, actor, event, targets, originalOtf }) { + const actions = [] + let overridetxt = action.overridetxt + while (action) { + action.overridetxt = overridetxt + actions.push(action) + action = action.next + } + const calculations = await Promise.all( + actions.map(a => GURPS.actionFuncs[a.type]({ action: a, actor, event, targets, originalOtf, calcOnly: true })) + ) + const levels = calculations.map(result => (result ? result.target : 0)) + if (!levels.some(level => level > 0)) return null // actor does not have any of these skills + const bestLevel = Math.max(...levels) + return actions[levels.indexOf(bestLevel)] + } + + /** + * @param {Action} action + * @param {GurpsActor|null} actor + * @param {JQuery.Event|null} [event] + * @param {string[] } [targets] + * @returns {Promise} + */ + async function performAction(action, actor, event = null, targets = []) { + if (!action || !(action.type in actionFuncs)) return false + const origAction = action + const originalOtf = action.orig + const calcOnly = action.calcOnly + if (['attribute', 'skill-spell'].includes(action.type)) { + action = await findBestActionInChain({ action, event, actor, targets, originalOtf }) + } + return !action + ? false + : await GURPS.actionFuncs[action.type]({ action, actor, event, targets, originalOtf, calcOnly }) + } + GURPS.performAction = performAction + + /** + * Find the skill or spell. if isSkillOnly or isSpellOnly set, only check that list. + * @param {GurpsActor|GurpsActorData} actor + * @param {string} sname + */ + function findSkillSpell(actor, sname, isSkillOnly = false, isSpellOnly = false) { + const removeOtf = '^ *(\\[ ?["\'])?' // pattern to remove some of the OtF syntax from search name so attacks start start with an OtF can be matched + var t + if (!actor) return t + if (actor instanceof GurpsActor) actor = actor.system + let skillRegExp = new RegExp(removeOtf + makeRegexPatternFrom(sname, false, false), 'i') + let best = 0 + if (!isSpellOnly) + recurselist(actor.skills, s => { + if (s.name?.match(skillRegExp) && s.level > best) { + t = s + best = parseInt(s.level) + } + }) + if (!t) + if (!isSkillOnly) + recurselist(actor.spells, s => { + if (s.name?.match(skillRegExp) && s.level > best) { + t = s + best = parseInt(s.level) + } + }) + return t + } + GURPS.findSkillSpell = findSkillSpell + + /** + * @param {GurpsActor | GurpsActorData} actor + * @param {string} sname + * @returns {any} + */ + function findAdDisad(actor, sname) { + var t + if (!actor) return t + if (actor instanceof GurpsActor) actor = actor.system + sname = makeRegexPatternFrom(sname, false) + let regex = new RegExp(sname, 'i') + recurselist(actor.ads, s => { + if (s.name.match(regex)) { + t = s + } + }) + return t + } + GURPS.findAdDisad = findAdDisad + + /** + * @param {GurpsActor | GurpsActorData} actor + * @param {string} sname + */ + function findAttack(actor, sname, isMelee = true, isRanged = true) { + const removeOtf = '^ *(\\[ ?["\'])?' // pattern to remove some of the OtF syntax from search name so attacks start start with an OtF can be matched + var t + if (!actor) return t + if (actor instanceof GurpsActor) actor = actor.system + let fullregex = new RegExp(removeOtf + makeRegexPatternFrom(sname, false, false), 'i') + let smode = '' + let m = XRegExp.matchRecursive(sname, '\\(', '\\)', 'g', { + unbalanced: 'skip-lazy', + valueNames: ['between', null, 'match', null], + }) + if (m.length == 2) { + // Found a mode "(xxx)" in the search name + sname = m[0].value.trim() + smode = m[1].value.trim().toLowerCase() + } + let nameregex = new RegExp(removeOtf + makeRegexPatternFrom(sname, false, false), 'i') + if (isMelee) + // @ts-ignore + recurselist(actor.melee, (e, k, d) => { + if (!t) { + let full = e.name + if (!!e.mode) full += ' (' + e.mode + ')' + let em = !!e.mode ? e.mode.toLowerCase() : '' + if (e.name.match(nameregex) && (smode == '' || em == smode)) t = e + else if (e.name.match(fullregex)) t = e + else if (full.match(fullregex)) t = e + } + }) + // t = Object.values(actor.melee).find(a => (a.name + (!!a.mode ? ' (' + a.mode + ')' : '')).match(nameregex)) + if (isRanged && !t) + // @ts-ignore + recurselist(actor.ranged, (e, k, d) => { + if (!t) { + let full = e.name + if (!!e.mode) full += ' (' + e.mode + ')' + let em = !!e.mode ? e.mode.toLowerCase() : '' + if (e.name.match(nameregex) && (smode == '' || em == smode)) t = e + else if (e.name.match(fullregex)) t = e + else if (full.match(fullregex)) t = e + } + }) + // t = Object.values(actor.ranged).find(a => (a.name + (!!a.mode ? ' (' + a.mode + ')' : '')).match(nameregex)) + return t + } + GURPS.findAttack = findAttack + + /** + * The user clicked on a field that would allow a dice roll. Use the element + * information to try to determine what type of roll. + * @param {JQuery.MouseEventBase} event + * @param {GurpsActor | null} actor + * @param {string[]} targets - labels for multiple Damage rolls + */ + async function handleRoll(event, actor, options) { + event.preventDefault() + let formula = '' + let targetmods = null + let element = event.currentTarget + let prefix = '' + let thing = '' + var chatthing + /** @type {Record} */ + let opt = { event: event } + let target = 0 // -1 == damage roll, target = 0 is NO ROLL. + if (!!actor) GURPS.SetLastActor(actor) + + if ('damage' in element.dataset) { + // expect text like '2d+1 cut' or '1d+1 cut,1d-1 ctrl' (linked damage) + let f = !!element.dataset.otf ? element.dataset.otf : element.innerText.trim() + + let parts = f.includes(',') ? f.split(',') : [f] + for (let part of parts) { + //let result = parseForRollOrDamage(part.trim()) + let result = parselink(part.trim()) + if (result?.action) { + if (options?.combined && result.action.type == 'damage') + result.action.formula = multiplyDice(result.action.formula, options.combined) + performAction(result.action, actor, event, options?.targets) + } + } + return + } else if ('path' in element.dataset) { + prefix = 'Roll vs ' + thing = GURPS._mapAttributePath(element.dataset.path) + formula = '3d6' + target = parseInt(element.innerText) + if ('otf' in element.dataset) + if (thing.toUpperCase() != element.dataset.otf.toUpperCase()) + chatthing = thing + '/[' + element.dataset.otf + ']' + else chatthing = '[' + element.dataset.otf + ']' + } else if ('otf' in element.dataset) { + // strip out any inner OtFs when coming from the UI. Mainly attack names + let otf = element.dataset.otf.trim() + // But there is a special case where the OtF is the first thing + // "M:"["Quarterstaff"A:"Quarterstaff (Thrust)"] (Thrust)"" + let m = otf.match(/^([sSmMrRaA]):"\["([^"]+)([^\]]+)]( *\(\w*\))?/) + if (!!m) otf = m[1] + ':' + m[2] + (!!m[4] ? m[4] : '') + otf = otf.replace(/\[.*\]/, '') + otf = otf.replace(/ +/g, ' ') // remove duplicate blanks + return GURPS.executeOTF(otf) + } else if ('name' in element.dataset) { + prefix = '' // "Attempting "; + let text = /** @type {string} */ (element.dataset.name || element.dataset.otf) + text = text.replace(/ \(\)$/g, '') // sent as "name (mode)", and mode is empty + thing = text.replace(/(.*?)\(.*\)/g, '$1') + + // opt.text = text.replace(/.*?\((.*)\)/g, "
     ($1)"); + opt.text = text.replace(/.*?\((.*)\)/g, '$1') + + if (opt.text === text) opt.text = '' + else opt.text = "(" + opt.text + ')' + let k = $(element).closest('[data-key]').attr('data-key') + if (!k) k = element.dataset.key + if (!!k) { + if (actor) opt.obj = getProperty(actor, k) // During the roll, we may want to extract something from the object + if (opt.obj.checkotf && !(await GURPS.executeOTF(opt.obj.checkotf, false, event))) return + if (opt.obj.duringotf) await GURPS.executeOTF(opt.obj.duringotf, false, event) + } + formula = '3d6' + let t = element.innerText + if (!!t) { + let a = t.trim().split(' ') + t = a[0] + if (!!t) target = parseInt(t) + if (isNaN(target)) target = 0 + // Can't roll against a non-integer + else { + a.shift() + let m = a.join(' ') + if (!!m) GURPS.ModifierBucket.addModifier(0, m) + } + } + } else if ('roll' in element.dataset) { + target = -1 // Set flag to indicate a non-targeted roll + formula = element.innerText + prefix = 'Rolling ' + formula + formula = d6ify(formula) + } + + doRoll({ actor, formula, targetmods, prefix, thing, chatthing, origtarget: target, optionalArgs: opt }) + } + GURPS.handleRoll = handleRoll + + /** + * If the desc contains *Cost ?FP or *Max:9 then perform action + * @param {GurpsActor|User} actor + * @param {string} desc + */ + async function applyModifierDesc(actor, desc) { + if (!desc) return null + let m = desc.match(COSTS_REGEX) + + if (!!m && !!actor && !actor.isSelf) { + let delta = parseInt(m.groups.cost) + let target = m.groups.type + if (target.match(/^[hf]p/i)) { + let k = target.toUpperCase() + // @ts-ignore + delta = actor.system[k].value - delta + await actor.update({ ['data.' + k + '.value']: delta }) + } + if (target.match(/^tr/i)) { + await GURPS.ChatProcessors.startProcessingLines('/setEventFlags true false false\\\\/' + target + ' -' + delta) // Make the tracker command quiet + return null + } + } + + let parse = desc.replace(/.*\*max: ?(\d+).*/gi, '$1') + if (parse != desc) { + return parseInt(parse) + } + return null // indicating no overriding MAX value + } + GURPS.applyModifierDesc = applyModifierDesc + + /** + * Return html for text, parsing GURPS "links" into XXX. + * @param {string | null | undefined} str + * @param {boolean} [clrdmods=true] + */ + // function gurpslink(str, clrdmods = true, returnActions = false) { + // if (str === undefined || str == null) return '!!UNDEFINED' + // let found = -1 + // let depth = 0 + // let output = '' + // let actions = [] + // for (let i = 0; i < str.length; i++) { + // if (str[i] == '[') { + // if (depth == 0) found = ++i + // depth++ + // } + // if (str[i] == ']') { + // depth-- + // if (depth == 0 && found >= 0) { + // output += str.substring(0, found - 1) + // let action = parselink(str.substring(found, i), '', clrdmods) + // if (!!action.action) actions.push(action) + // if (!action.action) output += '[' + // output += action.text + // if (!action.action) output += ']' + // str = str.substr(i + 1) + // i = -1 + // found = -1 + // } + // } + // } + // if (returnActions === true) return actions + // output += str + // return output + // } + //GURPS.gurpslink = gurpslink + + /** + * Return the i18n string for this data path (note en.json must match up to the data paths). + * special case, drop ".value" from end of path (and append "NAME"), usually used for attributes. + * @param {string} path + * @param {any} _suffix + */ + function _mapAttributePath(path, suffix) { + let i = path.indexOf('.value') + if (i >= 0) { + path = path.substr(0, i) + 'NAME' // used for the attributes + } + path = path.replace(/\./g, '') // remove periods + return game.i18n.localize('GURPS.' + path) + } + GURPS._mapAttributePath = _mapAttributePath + + /** + * Given a string path "x.y.z", use it to resolve down an object heiracrhy + * @param {string | string[]} path + * @param {any} obj + * @deprecated - Just use Foundry's getProperty and setPrpoerty methods + */ + function resolve(path, obj = self, separator = '.') { + var properties = Array.isArray(path) ? path : path.split(separator) + return properties.reduce((prev, curr) => prev && prev[curr], obj) + } + GURPS.resolve = resolve + + /** + * A user has clicked on a "gurpslink", so we can assume that it previously qualified as a "gurpslink" + * and followed the On-the-Fly formulas. As such, we may already have an action block (base 64 encoded so we can handle + * any text). If not, we will just re-parse the text looking for the action block. + * + * @param {JQuery.MouseEventBase} event + * @param {GurpsActor | null} actor + * @param {string | null} desc + * @param {string[] | undefined} targets + */ + // function handleGurpslink(event, actor, desc, targets) { + // event.preventDefault() + // let element = event.currentTarget + // let action = element.dataset.action // If we have already parsed + // if (!!action) action = JSON.parse(atou(action)) + // else action = parselink(element.innerText, desc).action + // GURPS.performAction(action, actor, event, targets) + // } + // GURPS.handleGurpslink = handleGurpslink + + // So it can be called from a script macro + GURPS.genkey = zeroFill + + /** + * Add the value as a property to obj. The key will be a generated value equal + * to a five-digit string equal to the index, padded to the left with zeros; e.g: + * if index is 12, the property key will be "00012". + * + * If index is equal to -1, then the existing properties of the object are examined + * and the index set to the next available (i.e, no property exists) key of the same + * form. + * + * TODO should be moved to lib/utilities.js. + * + * @param {Record} obj + * @param {any} value + * @param {number} index + */ + function put(obj, value, index = -1) { + if (index == -1) { + index = 0 + while (obj.hasOwnProperty(zeroFill(index))) index++ + } + let k = zeroFill(index) + obj[k] = value + return k + } + GURPS.put = put + + /** + * Convolutions to remove a key from an object and fill in the gaps, necessary + * because the default add behavior just looks for the first open gap + * @param {GurpsActor} actor + * @param {string} path + */ + async function removeKey(actor, path) { + let i = path.lastIndexOf('.') + let objpath = path.substring(0, i) + let key = path.substr(i + 1) + i = objpath.lastIndexOf('.') + let parentpath = objpath.substring(0, i) + let objkey = objpath.substr(i + 1) + let object = GURPS.decode(actor, objpath) + let t = parentpath + '.-=' + objkey + let oldRender = actor.ignoreRender + actor.ignoreRender = true + await actor.internalUpdate({ [t]: null }) // Delete the whole object + delete object[key] + i = parseInt(key) + + i = i + 1 + while (object.hasOwnProperty(zeroFill(i))) { + let k = zeroFill(i) + object[key] = object[k] + delete object[k] + key = k + i++ + } + let sorted = Object.keys(object) + .sort() + .reduce((a, v) => { + // @ts-ignore + a[v] = object[v] + return a + }, {}) // Enforced key order + actor.ignoreRender = oldRender + await actor.internalUpdate({ [objpath]: sorted }, { diff: false }) + } + GURPS.removeKey = removeKey + + /** + * Because the DB just merges keys, the best way to insert is to delete the whole colleciton object, fix it up, and then re-add it. + * @param {Actor} actor + * @param {string} path + * @param {any} newobj + */ + async function insertBeforeKey(actor, path, newobj) { + let i = path.lastIndexOf('.') + let objpath = path.substring(0, i) + let key = path.substr(i + 1) + i = objpath.lastIndexOf('.') + let parentpath = objpath.substring(0, i) + let objkey = objpath.substr(i + 1) + let object = GURPS.decode(actor, objpath) + let t = parentpath + '.-=' + objkey + await actor.internalUpdate({ [t]: null }) // Delete the whole object + let start = parseInt(key) + + i = start + 1 + while (object.hasOwnProperty(zeroFill(i))) i++ + i = i - 1 + for (let z = i; z >= start; z--) { + object[zeroFill(z + 1)] = object[zeroFill(z)] + } + object[key] = newobj + let sorted = Object.keys(object) + .sort() + .reduce((a, v) => { + // @ts-ignore + a[v] = object[v] + return a + }, {}) // Enforced key order + await actor.internalUpdate({ [objpath]: sorted }, { diff: false }) + } + GURPS.insertBeforeKey = insertBeforeKey + + // TODO replace Record with { [key: string]: any } + /** + * @param {Record} obj + * @param {string} path + */ + function decode(obj, path, all = true) { + let p = path.split('.') + let end = p.length + if (!all) end = end - 1 + for (let i = 0; i < end; i++) { + let q = p[i] + obj = obj[q] + } + return obj + } + GURPS.decode = decode + + /** + * Funky helper function to be able to list hierarchical equipment in a linear list (with appropriate keys for editing) + * @param {Record} eqts + * @param {{ fn: (arg0: any, arg1: { data: any; }) => string; }} options + * @param {number} level + * @param {{ indent: any; key: string; count: any; }} data + * @param {string=} parentkey + * @param {{ equipment: { carried: Object; }; }|null} src + */ + function listeqtrecurse(eqts, options, level, data, parentkey = '', src = null) { + if (!eqts) return '' + let ret = '' + let i = 0 + for (let key in eqts) { + let eqt = eqts[key] + if (data) { + data.indent = level + data.key = parentkey + key + data.count = eqt.count + } + let display = true + if (!!src && game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_REMOVE_UNEQUIPPED)) { + // if an optional src is provided (which == actor.system) assume we are checking attacks to see if they are equipped + recurselist(src.equipment.carried, e => { + if (eqt.name.startsWith(e.name) && !e.equipped) display = false + }) + } + if (display) { + let fragment = options.fn(eqt, { data: data }) + // if (!!eqt?.equipped) console.log(fragment) + ret = ret + fragment + } + ret = ret + listeqtrecurse(eqt.contains, options, level + 1, data, parentkey + key + '.contains.') + } + return ret + } + GURPS.listeqtrecurse = listeqtrecurse + + GURPS.whisperOtfToOwner = function (otf, overridetxt, event, blindcheck, actor) { + if (!otf) return + if (!game.user.isGM) { + // If not the GM, just send the text to the chat input window (so the user can copy it) + $(document) + .find('#chat-message') + .val('[' + otf + ']') + return + } + otf = otf.replace(/ \(\)/g, '') // sent as "name (mode)", and mode is empty (only necessary for attacks) + let canblind = false + if (!!blindcheck) { + canblind = blindcheck == true || blindcheck.hasOwnProperty('blindroll') + // @ts-ignore - blindcheck is either boolean or an object with a blindroll property + if (canblind && blindcheck.blindroll) { + otf = '!' + otf + canblind = false + } + } + if (!!overridetxt) { + if (overridetxt.includes('"')) overridetxt = "'" + overridetxt + "'" + else overridetxt = '"' + overridetxt + '"' + } else overridetxt = '' + let users = actor?.getOwners()?.filter(u => !u.isGM) || [] + let botf = '[' + overridetxt + '!' + otf + ']' + otf = '[' + overridetxt + otf + ']' + + /** @type Record */ + let buttons = {} + buttons.one = { + icon: '', + label: 'To Everyone', + callback: () => GURPS.sendOtfMessage(otf, false), + } + if (canblind) + buttons.two = { + icon: '', + label: 'Blindroll to Everyone', + callback: () => GURPS.sendOtfMessage(botf, true), + } + if (users.length > 0) { + let nms = users.map(u => u.name).join(' ') + buttons.three = { + icon: '', + label: 'Whisper to ' + nms, + callback: () => GURPS.sendOtfMessage(otf, false, users), + } + if (canblind) + buttons.four = { + icon: '', + label: 'Whisper Blindroll to ' + nms, + callback: () => GURPS.sendOtfMessage(botf, true, users), + } + } + buttons.def = { + icon: '', + label: 'Copy to chat input', + callback: () => { + $(document).find('#chat-message').val(otf) + }, + } + + let d = new Dialog( + { + title: "GM 'Send Formula'", + content: `
    How would you like to send the formula:

    ${otf}
     
    `, + buttons: buttons, + default: 'def', + }, + { width: 700 } + ) + d.render(true) + } + + GURPS.sendOtfMessage = function (content, blindroll, users = null) { + /** @type {import('@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/chatMessageData').ChatMessageDataConstructorData} */ + let msgData = { + content: content, + user: game.user.id, + blind: blindroll, + } + if (!!users) { + msgData.type = CONST.CHAT_MESSAGE_TYPES.WHISPER + msgData.whisper = users.map(it => it.id || '') + } else { + msgData.type = CONST.CHAT_MESSAGE_TYPES.OOC + } + ChatMessage.create(msgData) + } + + GURPS.resolveDamageRoll = function (event, actor, otf, overridetxt, isGM, isOtf = false) { + let title = game.i18n.localize('GURPS.RESOLVEDAMAGETitle') + let prompt = game.i18n.localize('GURPS.RESOLVEDAMAGEPrompt') + let quantity = game.i18n.localize('GURPS.RESOLVEDAMAGEQuantity') + let sendTo = game.i18n.localize('GURPS.RESOLVEDAMAGESendTo') + let multiple = game.i18n.localize('GURPS.RESOLVEDAMAGEMultiple') + + /** @type {Record} */ + let buttons = {} + + if (isGM) { + buttons.send = { + icon: '', + label: `${sendTo}`, + callback: () => GURPS.whisperOtfToOwner(otf, overridetxt, event, false, actor), // Can't blind roll damages (yet) + } + } + + buttons.multiple = { + icon: '', + label: `${multiple}`, + callback: html => { + // @ts-ignore + let text = /** @type {string} */ (html.find('#number-rolls').val()) + let number = parseInt(text) + let targets = [] + for (let index = 0; index < number; index++) { + targets[index] = `${index + 1}` + } + if (isOtf) GurpsWiring.handleGurpslink(event, actor, null, { targets: targets }) + else GURPS.handleRoll(event, actor, { targets: targets }) + }, + } + + buttons.combined = { + icon: '', + label: i18n('GURPS.RESOLVEDAMAGEAdd', 'Combine'), + callback: html => { + let text = /** @type {string} */ (html.find('#number-rolls').val()) + let number = parseInt(text) + + if (isOtf) otf = multiplyDice(otf, number) + + if (isOtf) GurpsWiring.handleGurpslink(event, actor, null, { combined: number }) + else GURPS.handleRoll(event, actor, { combined: number }) + }, + } + let def = GURPS.lastTargetedRoll?.rofrcl || 2 + let dlg = new Dialog({ + title: `${title}`, + content: `

    ${otf}

    ${prompt}

    @@ -1798,328 +1798,331 @@ if (!globalThis.GURPS) {

    `, - buttons: buttons, - default: 'send', - }) - dlg.render(true) - } - - GURPS.setInitiativeFormula = function(/** @type {boolean} */ broadcast) { - let formula = /** @type {string} */ (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_INITIATIVE_FORMULA)) - if (!formula) { - formula = Initiative.defaultFormula() - if (game.user.isGM) game.settings.set(Settings.SYSTEM_NAME, Settings.SETTING_INITIATIVE_FORMULA, formula) - } - let m = formula.match(/([^:]*):?(\d)?/) - let d = m && !!m[2] ? parseInt(m[2]) : 5 - CONFIG.Combat.initiative = { - // @ts-ignore - technically, m could be null - formula: m[1], - decimals: d, // Important to be able to maintain resolution - } - if (broadcast && m) - game.socket?.emit('system.gurps', { - type: 'initiativeChanged', - formula: m[1], - decimals: d, - }) - } - - GURPS.recurselist = recurselist - GURPS.parselink = parselink - - /* -------------------------------------------- */ - /* Foundry VTT Initialization */ - /* -------------------------------------------- */ - Hooks.once('init', async function() { - console.log(GURPS.BANNER) - console.log(`Initializing GURPS 4e Game Aid`) - console.log(GURPS.LEGAL) - - let src = game.i18n.lang == 'pt_br' ? 'systems/gurps/icons/gurps4e-pt_br.webp' : 'systems/gurps/icons/gurps4e.webp' - - $('#logo').attr('src', src) - $('#logo').attr('height', '32px') - - // set up all hitlocation tables (must be done before MB) - HitLocation.init() - DamageChat.init() - RegisterChatProcessors() - GurpsActiveEffect.init() - GURPSSpeedProvider.init() - - // Modifier Bucket must be defined after hit locations - GURPS.ModifierBucket = new ModifierBucket() - GURPS.ModifierBucket.render(true) - - GURPS.rangeObject = new GURPSRange() - GURPS.initiative = new Initiative() - GURPS.hitpoints = new HitFatPoints() - GURPS.ConditionalInjury = new GURPSConditionalInjury() - - // do this only after we've initialized i18n/localize - GURPS.Maneuvers = Maneuvers - - // Define custom Entity classes - // @ts-ignore - CONFIG.Actor.documentClass = GurpsActor - CONFIG.Item.documentClass = GurpsItem - - // add custom ActiveEffectConfig sheet class - CONFIG.ActiveEffect.sheetClass = GurpsActiveEffectConfig - // ActiveEffectConfig.registerSheet(Document, 'ActiveEffect', GurpsActiveEffectConfig, { makeDefault: true }) - - // preload drag-and-drop image - { - let img = new Image() - img.src = 'systems/gurps/icons/blood-splatter-clipart-small.webp' - GURPS.damageDragImage = img - } - - // LOAD ALL THE THINGS!!! - { - let img = new Image() - img.src = 'systems/gurps/icons/all-the-things-transparent.webp' - GURPS.allTheThingsImage = img - } - - // Register sheet application classes - Actors.unregisterSheet('core', ActorSheet) - // @ts-ignore - Actors.registerSheet('gurps', GurpsActorCombatSheet, { - label: 'Combat', - makeDefault: false, - }) - // @ts-ignore - Actors.registerSheet('gurps', GurpsActorEditorSheet, { - label: 'Editor', - makeDefault: false, - }) - // @ts-ignore - Actors.registerSheet('gurps', GurpsActorSimplifiedSheet, { - label: 'Simple', - makeDefault: false, - }) - // @ts-ignore - Actors.registerSheet('gurps', GurpsActorNpcSheet, { - label: 'NPC/mini', - makeDefault: false, - }) - // @ts-ignore - Actors.registerSheet('gurps', GurpsInventorySheet, { - label: 'Inventory Only', - makeDefault: false, - }) - // @ts-ignore - Actors.registerSheet('gurps', GurpsActorTabSheet, { - label: 'Tabbed Sheet', - makeDefault: false, - }) - // @ts-ignore - Actors.registerSheet('gurps', GurpsActorSheet, { - // Add this sheet last - label: 'Full (GCS)', - makeDefault: true, - }) - - Items.unregisterSheet('core', ItemSheet) - // @ts-ignore - Items.registerSheet('gurps', GurpsItemSheet, { makeDefault: true }) - - // Warning, the very first table will take a refresh before the dice to show up in the dialog. Sorry, can't seem to get around that - // @ts-ignore - Hooks.on('createRollTable', async function(entity, options, userId) { - await entity.update({ img: 'systems/gurps/icons/single-die.webp' }) - entity.data.img = 'systems/gurps/icons/single-die.webp' - }) - - // @ts-ignore - Hooks.on('renderTokenHUD', (...args) => ManeuverHUDButton.prepTokenHUD(...args)) - - // @ts-ignore - Hooks.on('renderSidebarTab', async (app, html) => { - // Add the import equipment button... - if (app.options.id === 'compendium') { - let button = $( - '' - ) - - button.click(function() { - setTimeout(async () => { - new Dialog( - { - title: 'Import Item Compendium', - // @ts-ignore - content: await renderTemplate('systems/gurps/templates/item-import.html'), - buttons: { - import: { - icon: '', - label: 'Import', - callback: html => { - // @ts-ignore - const form = html.find('form')[0] - let files = form.data.files - // @ts-ignore - let file = null - if (!files.length) { - // @ts-ignore - return ui.notifications.error('You did not upload a data file!') - } else { - file = files[0] - console.log(file) - GURPS.readTextFromFile(file).then(text => - ItemImporter.importItems(text, file.name.split('.').slice(0, -1).join('.'), file.path) - ) - } - }, - }, - no: { - icon: '', - label: 'Cancel', - }, - }, - default: 'import', - }, - { - width: 400, - } - ).render(true) - }, 200) - }) - - html.find('.directory-footer').append(button) - } - - // we need a special case to handle the markdown editor module because it changes the chat textarea with an EasyMDEContainer - const hasMeme = game.modules.get('markdown-editor')?.active - const chat = html[0]?.querySelector(hasMeme ? '.EasyMDEContainer' : '#chat-message') - - const dropHandler = function(event, inLog) { - event.preventDefault() - if (event.originalEvent) event = event.originalEvent - const data = JSON.parse(event.dataTransfer.getData('text/plain')) - if (!!data && !!data.otf) { - let cmd = '' - if (!!data.encodedAction) { - let action = JSON.parse(atou(data.encodedAction)) - if (action.quiet) cmd += '!' - } - cmd += data.otf - if (!!data.displayname) { - let q = '"' - if (data.displayname.includes('"')) q = "'" - cmd = q + data.displayname + q + cmd - } - cmd = '[' + cmd + ']' - if (inLog) { - let messageData = { - user: game.user.id, - //speaker: ChatMessage.getSpeaker({ actor: game.user }), - type: CONST.CHAT_MESSAGE_TYPES.OOC, - content: cmd, - } - ChatMessage.create(messageData, {}) - } else $(document).find('#chat-message').val($(document).find('#chat-message').val() + cmd) - } - } - if (!!chat) chat.addEventListener('drop', event => dropHandler(event, false)) - html.find('#chat-log').on('drop', event => dropHandler(event, true)) - }) - - /** - * Added to color the rollable parts of the character sheet. - * Made this part eslint compatible... - * ~Stevil - */ - registerColorPickerSettings() - // eslint-disable-next-line no-undef - Hooks.on('renderActorSheet', (...args) => { - colorGurpsActorSheet() - }) - - // Listen for the Ctrl key and toggle the roll mode (to show the behaviour we currently do anyway) - game.keybindings.register('gurps', 'toggleDiceDisplay', { - name: 'Toggle dice display', - uneditable: [{ key: 'ControlLeft' }, { key: 'ControlRight' }], - onDown: () => { - if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_CTRL_KEY)) { - GURPS.savedRollMode = game.settings.get('core', 'rollMode') - game.settings.set('core', 'rollMode', game.user?.isGM ? 'gmroll' : 'blindroll') - } - }, - onUp: () => { - if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_CTRL_KEY)) - game.settings.set('core', 'rollMode', GURPS.savedRollMode) - }, - precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL, - // "ControlLeft", "ControlRight" - }) - - Hooks.call('gurpsinit', GURPS) - }) - - Hooks.once('ready', async function() { - // reset the TokenHUD to our version - // @ts-ignore - canvas.hud.token = new GURPSTokenHUD() - - // do this only after we've initialized i18n/localize - // GURPS.StatusEffect = new StatusEffect() - // CONFIG.statusEffects = GURPS.StatusEffect.effects() - - GURPS.DamageTables = new DamageTable() - - ResourceTrackerManager.initSettings() - HitLocation.ready() - - // if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_SHOW_3D6)) - // new ThreeD6({ - // popOut: false, - // minimizable: false, - // resizable: false, - // id: 'ThreeD6', - // template: 'systems/gurps/templates/threed6.html', - // classes: [], - // }).render(true) - - // @ts-ignore - GURPS.currentVersion = SemanticVersion.fromString(game.system.version) - // Test for migration - let mv = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_MIGRATION_VERSION) - let quiet = false - if (!mv) { - mv = '0.0.1' - quiet = true - } - // @ts-ignore - console.log('Current Version: ' + GURPS.currentVersion + ', Migration version: ' + mv) - const migrationVersion = SemanticVersion.fromString(mv) - // @ts-ignore - if (migrationVersion.isLowerThan(GURPS.currentVersion)) { - // check which migrations are needed - // @ts-ignore - if (migrationVersion.isLowerThan(Settings.VERSION_096)) await Migration.migrateTo096(quiet) - // @ts-ignore - if (migrationVersion.isLowerThan(Settings.VERSION_097)) await Migration.migrateTo097(quiet) - // @ts-ignore - if (migrationVersion.isLowerThan(Settings.VERSION_0104)) await Migration.migrateTo0104(quiet) - - game.settings.set(Settings.SYSTEM_NAME, Settings.SETTING_MIGRATION_VERSION, game.system.version) - } - - // Show changelog - const v = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_CHANGELOG_VERSION) || '0.0.1' - const changelogVersion = SemanticVersion.fromString(v) - - // @ts-ignore - if (GURPS.currentVersion.isHigherThan(changelogVersion)) { - // @ts-ignore - if ($(ui.chat.element).find('#GURPS-LEGAL').length == 0) - // If it isn't already in the chat log somewhere - ChatMessage.create({ - content: ` + buttons: buttons, + default: 'send', + }) + dlg.render(true) + } + + GURPS.setInitiativeFormula = function (/** @type {boolean} */ broadcast) { + let formula = /** @type {string} */ (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_INITIATIVE_FORMULA)) + if (!formula) { + formula = Initiative.defaultFormula() + if (game.user.isGM) game.settings.set(Settings.SYSTEM_NAME, Settings.SETTING_INITIATIVE_FORMULA, formula) + } + let m = formula.match(/([^:]*):?(\d)?/) + let d = m && !!m[2] ? parseInt(m[2]) : 5 + CONFIG.Combat.initiative = { + // @ts-ignore - technically, m could be null + formula: m[1], + decimals: d, // Important to be able to maintain resolution + } + if (broadcast && m) + game.socket?.emit('system.gurps', { + type: 'initiativeChanged', + formula: m[1], + decimals: d, + }) + } + + GURPS.recurselist = recurselist + GURPS.parselink = parselink + + /* -------------------------------------------- */ + /* Foundry VTT Initialization */ + /* -------------------------------------------- */ + Hooks.once('init', async function () { + console.log(GURPS.BANNER) + console.log(`Initializing GURPS 4e Game Aid`) + console.log(GURPS.LEGAL) + + let src = game.i18n.lang == 'pt_br' ? 'systems/gurps/icons/gurps4e-pt_br.webp' : 'systems/gurps/icons/gurps4e.webp' + + $('#logo').attr('src', src) + $('#logo').attr('height', '32px') + + // set up all hitlocation tables (must be done before MB) + HitLocation.init() + DamageChat.init() + RegisterChatProcessors() + GurpsActiveEffect.init() + GURPSSpeedProvider.init() + + // Modifier Bucket must be defined after hit locations + GURPS.ModifierBucket = new ModifierBucket() + GURPS.ModifierBucket.render(true) + + GURPS.rangeObject = new GURPSRange() + GURPS.initiative = new Initiative() + GURPS.hitpoints = new HitFatPoints() + GURPS.ConditionalInjury = new GURPSConditionalInjury() + + // do this only after we've initialized i18n/localize + GURPS.Maneuvers = Maneuvers + + // Define custom Entity classes + // @ts-ignore + CONFIG.Actor.documentClass = GurpsActor + CONFIG.Item.documentClass = GurpsItem + + // add custom ActiveEffectConfig sheet class + CONFIG.ActiveEffect.sheetClass = GurpsActiveEffectConfig + // ActiveEffectConfig.registerSheet(Document, 'ActiveEffect', GurpsActiveEffectConfig, { makeDefault: true }) + + // preload drag-and-drop image + { + let img = new Image() + img.src = 'systems/gurps/icons/blood-splatter-clipart-small.webp' + GURPS.damageDragImage = img + } + + // LOAD ALL THE THINGS!!! + { + let img = new Image() + img.src = 'systems/gurps/icons/all-the-things-transparent.webp' + GURPS.allTheThingsImage = img + } + + // Register sheet application classes + Actors.unregisterSheet('core', ActorSheet) + // @ts-ignore + Actors.registerSheet('gurps', GurpsActorCombatSheet, { + label: 'Combat', + makeDefault: false, + }) + // @ts-ignore + Actors.registerSheet('gurps', GurpsActorEditorSheet, { + label: 'Editor', + makeDefault: false, + }) + // @ts-ignore + Actors.registerSheet('gurps', GurpsActorSimplifiedSheet, { + label: 'Simple', + makeDefault: false, + }) + // @ts-ignore + Actors.registerSheet('gurps', GurpsActorNpcSheet, { + label: 'NPC/mini', + makeDefault: false, + }) + // @ts-ignore + Actors.registerSheet('gurps', GurpsInventorySheet, { + label: 'Inventory Only', + makeDefault: false, + }) + // @ts-ignore + Actors.registerSheet('gurps', GurpsActorTabSheet, { + label: 'Tabbed Sheet', + makeDefault: false, + }) + // @ts-ignore + Actors.registerSheet('gurps', GurpsActorSheet, { + // Add this sheet last + label: 'Full (GCS)', + makeDefault: true, + }) + + Items.unregisterSheet('core', ItemSheet) + // @ts-ignore + Items.registerSheet('gurps', GurpsItemSheet, { makeDefault: true }) + + // Warning, the very first table will take a refresh before the dice to show up in the dialog. Sorry, can't seem to get around that + // @ts-ignore + Hooks.on('createRollTable', async function (entity, options, userId) { + await entity.update({ img: 'systems/gurps/icons/single-die.webp' }) + entity.data.img = 'systems/gurps/icons/single-die.webp' + }) + + // @ts-ignore + Hooks.on('renderTokenHUD', (...args) => ManeuverHUDButton.prepTokenHUD(...args)) + + // @ts-ignore + Hooks.on('renderSidebarTab', async (app, html) => { + // Add the import equipment button... + if (app.options.id === 'compendium') { + let button = $( + '' + ) + + button.click(function () { + setTimeout(async () => { + new Dialog( + { + title: 'Import Item Compendium', + // @ts-ignore + content: await renderTemplate('systems/gurps/templates/item-import.html'), + buttons: { + import: { + icon: '', + label: 'Import', + callback: html => { + // @ts-ignore + const form = html.find('form')[0] + let files = form.data.files + // @ts-ignore + let file = null + if (!files.length) { + // @ts-ignore + return ui.notifications.error('You did not upload a data file!') + } else { + file = files[0] + console.log(file) + GURPS.readTextFromFile(file).then(text => + ItemImporter.importItems(text, file.name.split('.').slice(0, -1).join('.'), file.path) + ) + } + }, + }, + no: { + icon: '', + label: 'Cancel', + }, + }, + default: 'import', + }, + { + width: 400, + } + ).render(true) + }, 200) + }) + + html.find('.directory-footer').append(button) + } + + // we need a special case to handle the markdown editor module because it changes the chat textarea with an EasyMDEContainer + const hasMeme = game.modules.get('markdown-editor')?.active + const chat = html[0]?.querySelector(hasMeme ? '.EasyMDEContainer' : '#chat-message') + + const dropHandler = function (event, inLog) { + event.preventDefault() + if (event.originalEvent) event = event.originalEvent + const data = JSON.parse(event.dataTransfer.getData('text/plain')) + if (!!data && !!data.otf) { + let cmd = '' + if (!!data.encodedAction) { + let action = JSON.parse(atou(data.encodedAction)) + if (action.quiet) cmd += '!' + } + cmd += data.otf + if (!!data.displayname) { + let q = '"' + if (data.displayname.includes('"')) q = "'" + cmd = q + data.displayname + q + cmd + } + cmd = '[' + cmd + ']' + if (inLog) { + let messageData = { + user: game.user.id, + //speaker: ChatMessage.getSpeaker({ actor: game.user }), + type: CONST.CHAT_MESSAGE_TYPES.OOC, + content: cmd, + } + ChatMessage.create(messageData, {}) + } else + $(document) + .find('#chat-message') + .val($(document).find('#chat-message').val() + cmd) + } + } + if (!!chat) chat.addEventListener('drop', event => dropHandler(event, false)) + html.find('#chat-log').on('drop', event => dropHandler(event, true)) + }) + + /** + * Added to color the rollable parts of the character sheet. + * Made this part eslint compatible... + * ~Stevil + */ + registerColorPickerSettings() + // eslint-disable-next-line no-undef + Hooks.on('renderActorSheet', (...args) => { + colorGurpsActorSheet() + }) + + // Listen for the Ctrl key and toggle the roll mode (to show the behaviour we currently do anyway) + game.keybindings.register('gurps', 'toggleDiceDisplay', { + name: 'Toggle dice display', + uneditable: [{ key: 'ControlLeft' }, { key: 'ControlRight' }], + onDown: () => { + if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_CTRL_KEY)) { + GURPS.savedRollMode = game.settings.get('core', 'rollMode') + game.settings.set('core', 'rollMode', game.user?.isGM ? 'gmroll' : 'blindroll') + } + }, + onUp: () => { + if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_CTRL_KEY)) + game.settings.set('core', 'rollMode', GURPS.savedRollMode) + }, + precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL, + // "ControlLeft", "ControlRight" + }) + + Hooks.call('gurpsinit', GURPS) + }) + + Hooks.once('ready', async function () { + // reset the TokenHUD to our version + // @ts-ignore + canvas.hud.token = new GURPSTokenHUD() + + // do this only after we've initialized i18n/localize + // GURPS.StatusEffect = new StatusEffect() + // CONFIG.statusEffects = GURPS.StatusEffect.effects() + + GURPS.DamageTables = new DamageTable() + + ResourceTrackerManager.initSettings() + HitLocation.ready() + + // if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_SHOW_3D6)) + // new ThreeD6({ + // popOut: false, + // minimizable: false, + // resizable: false, + // id: 'ThreeD6', + // template: 'systems/gurps/templates/threed6.html', + // classes: [], + // }).render(true) + + // @ts-ignore + GURPS.currentVersion = SemanticVersion.fromString(game.system.version) + // Test for migration + let mv = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_MIGRATION_VERSION) + let quiet = false + if (!mv) { + mv = '0.0.1' + quiet = true + } + // @ts-ignore + console.log('Current Version: ' + GURPS.currentVersion + ', Migration version: ' + mv) + const migrationVersion = SemanticVersion.fromString(mv) + // @ts-ignore + if (migrationVersion.isLowerThan(GURPS.currentVersion)) { + // check which migrations are needed + // @ts-ignore + if (migrationVersion.isLowerThan(Settings.VERSION_096)) await Migration.migrateTo096(quiet) + // @ts-ignore + if (migrationVersion.isLowerThan(Settings.VERSION_097)) await Migration.migrateTo097(quiet) + // @ts-ignore + if (migrationVersion.isLowerThan(Settings.VERSION_0104)) await Migration.migrateTo0104(quiet) + + game.settings.set(Settings.SYSTEM_NAME, Settings.SETTING_MIGRATION_VERSION, game.system.version) + } + + // Show changelog + const v = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_CHANGELOG_VERSION) || '0.0.1' + const changelogVersion = SemanticVersion.fromString(v) + + // @ts-ignore + if (GURPS.currentVersion.isHigherThan(changelogVersion)) { + // @ts-ignore + if ($(ui.chat.element).find('#GURPS-LEGAL').length == 0) + // If it isn't already in the chat log somewhere + ChatMessage.create({ + content: `
    @@ -2131,327 +2134,326 @@ if (!globalThis.GURPS) {
    `, - type: CONST.CHAT_MESSAGE_TYPES.WHISPER, - // @ts-ignore - whisper: [game.user], - }) - if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_SHOW_CHANGELOG)) { - const app = new ChangeLogWindow(changelogVersion) - app.render(true) - // @ts-ignore - game.settings.set(Settings.SYSTEM_NAME, Settings.SETTING_CHANGELOG_VERSION, GURPS.currentVersion.toString()) - } - GURPS.executeOTF('/help') - } - - // get all aliases defined in the resource tracker templates and register them as damage types - let resourceTrackers = ResourceTrackerManager.getAllTemplates() - .filter(it => !!it.tracker.isDamageType) - .filter(it => !!it.tracker.alias) - .map(it => it.tracker) - resourceTrackers.forEach(it => (GURPS.DamageTables.damageTypeMap[it.alias] = it.alias)) - resourceTrackers.forEach( - it => - (GURPS.DamageTables.woundModifiers[it.alias] = { - multiplier: 1, - label: it.name, - resource: true, - }) - ) - - // Sorry, removed the ts-ignores during editing. - Hooks.on('hotbarDrop', async (bar, data, slot) => { - if (!data.otf && !data.bucket) return - let name = data.otf || data.bucket.join(' & ') - if (!!data.displayname) name = data.displayname - let cmd = '' - - if (!!data.actor) { - let a = game.actors.get(data.actor) - if (!!a) cmd = `!/select ${a.name}\n` + cmd - name = game.actors.get(data.actor).name + ': ' + name - } - - let otfs = data.bucket || [data.otf] - otfs.forEach(otf => { - if (otf.startsWith('/')) { - if (!!data.encodedAction) { - let action = JSON.parse(atou(data.encodedAction)) - if (action.quiet) cmd += '!' - } - cmd += otf - } else cmd += '/r [' + otf + ']' - cmd += '\n' - }) - let setmacro = async function(name, cmd) { - let macro = await Macro.create({ - name: name, - type: 'chat', - command: cmd, - }) - macro.setFlag('gurps', 'drag-drop-otf', true) - game.user.assignHotbarMacro(macro, slot) - } - - let oldmacro = game.macros.get(game.user.data.hotbar[slot]) - if (!!oldmacro && !!oldmacro.getFlag('gurps', 'drag-drop-otf')) { - let c = (!!data.bucket ? '/clearmb\n' : '') + cmd - new Dialog({ - title: 'Merge or Replace On-the-Fly macro', - content: `Merge both macros into this:

    ${oldmacro.data.command.split('\n').join('
    ')}
    ${cmd - .split('\n') - .join('
    ')}


    Or just replace current macro with:

    ${c - .split('\n') - .join('
    ')}

     
    `, - buttons: { - one: { - icon: '', - label: 'Merge', - callback: () => { - setmacro(oldmacro.data.name, oldmacro.data.command + '\n' + cmd) - }, - }, - two: { - icon: '', - label: 'Replace', - callback: () => setmacro(name, (!!data.bucket ? '/clearmb\n' : '') + cmd), - }, - }, - default: 'one', - }).render(true) - } else setmacro(name, (!!data.bucket ? '/clearmb\n' : '') + cmd) - return false - }) - - // @ts-ignore - Hooks.on('renderCombatTracker', function(a, html, c) { - // use class 'bound' to know if the drop event is already bound - if (!html.hasClass('bound')) { - html.addClass('bound') - // @ts-ignore - html.on('drop', function(ev) { - console.log('Handle drop event on combatTracker') - ev.preventDefault() - ev.stopPropagation() - let elementMouseIsOver = document.elementFromPoint(ev.clientX, ev.clientY) - - // @ts-ignore - let combatant = $(elementMouseIsOver).parents('.combatant').attr('data-combatant-id') - // @ts-ignore - let target = game.combat.combatants.filter(c => c.id === combatant)[0] - - let event = ev.originalEvent - let dropData = JSON.parse(event.dataTransfer.getData('text/plain')) - if (dropData.type === 'damageItem') { - // @ts-ignore - target.actor.handleDamageDrop(dropData.payload) - } - if (dropData.type === 'initiative') { - let target = game.combat.data.combatants.get(combatant) - let src = game.combat.data.combatants.get(dropData.combatant) - let updates = [] - if (!!target && !!src) { - if (target.initiative < src.initiative) - updates.push({ _id: dropData.combatant, initiative: target.initiative - 0.00001 }) - else updates.push({ _id: dropData.combatant, initiative: target.initiative + 0.00001 }) - game.combat.updateEmbeddedDocuments('Combatant', updates) - } - } - }) - } - - if (game.user.isGM) { - html.find('.combatant').each((_, li) => { - li.setAttribute('draggable', true) - li.addEventListener('dragstart', ev => { - let display = '' - if (!!ev.currentTarget.dataset.action) display = ev.currentTarget.innerText - let dragIcon = $(event.currentTarget).find('.token-image')[0] - ev.dataTransfer.setDragImage(dragIcon, 25, 25) - return ev.dataTransfer.setData( - 'text/plain', - JSON.stringify({ - type: 'initiative', - combatant: li.getAttribute('data-combatant-id'), - }) - ) - }) - }) - } - }) - - // @ts-ignore - game.socket.on('system.gurps', async resp => { - if (resp.type == 'updatebucket') { - if (resp.users.includes(game.user.id)) { - if (resp.add) { - resp.bucket.modifierList.forEach(e => GURPS.ModifierBucket.addModifier(e.mod, e.desc)) - } else - GURPS.ModifierBucket.updateModifierBucket(resp.bucket) - } - } - if (resp.type == 'initiativeChanged') { - CONFIG.Combat.initiative = { - formula: resp.formula, - decimals: resp.decimals, - } - } - if (resp.type == 'playerFpHp') { - requestFpHp(resp) - } - if (resp.type == 'executeOtF') { - // @ts-ignore - if (game.users.isGM || (resp.users.length > 0 && !resp.users.includes(game.user.name))) return - // @ts-ignore - GURPS.performAction(resp.action, GURPS.LastActor) - } - if (resp.type == 'setLastTargetedRoll') { - GURPS.setLastTargetedRoll(resp.chatdata, resp.actorid, resp.tokenid, false) - } - if (resp.type == 'dragEquipment1') { - if (resp.destuserid != game.user.id) return - // @ts-ignore - let destactor = game.actors.get(resp.destactorid) - // @ts-ignore - let srcActor = game.actors.get(resp.srcactorid) - Dialog.confirm({ - // @ts-ignore - title: `Gift for ${destactor.name}!`, - // @ts-ignore - content: `

    ${srcActor.name} wants to give you ${resp.itemData.name} (${resp.count}),


    Ok?`, - yes: () => { - // @ts-ignore - let destKey = destactor._findEqtkeyForId('globalid', resp.itemData.data.globalid) - if (!!destKey) { - // already have some - // @ts-ignore - let destEqt = getProperty(destactor, destKey) - // @ts-ignore - destactor.updateEqtCount(destKey, destEqt.count + resp.count) - } else { - resp.itemData.data.equipped = true - // @ts-ignore - destactor.addNewItemData(resp.itemData) - } - // @ts-ignore - game.socket.emit('system.gurps', { - type: 'dragEquipment2', - srckey: resp.srckey, - srcuserid: resp.srcuserid, - srcactorid: resp.srcactorid, - destactorid: resp.destactorid, - itemname: resp.itemData.name, - count: resp.count, - }) - }, - no: () => { - // @ts-ignore - game.socket.emit('system.gurps', { - type: 'dragEquipment3', - srcuserid: resp.srcuserid, - destactorid: resp.destactorid, - itemname: resp.itemData.name, - }) - }, - }) - } - if (resp.type == 'dragEquipment2') { - if (resp.srcuserid != game.user.id) return - // @ts-ignore - let srcActor = game.actors.get(resp.srcactorid) - // @ts-ignore - let eqt = getProperty(srcActor, resp.srckey) - if (resp.count >= eqt.count) { - // @ts-ignore - srcActor.deleteEquipment(resp.srckey) - } else { - // @ts-ignore - srcActor.updateEqtCount(resp.srckey, eqt.count - resp.count) - } - // @ts-ignore - let destActor = game.actors.get(resp.destactorid) - // @ts-ignore - ui.notifications.info(`${destActor.name} accepted ${resp.itemname}`) - } - if (resp.type == 'dragEquipment3') { - if (resp.srcuserid != game.user.id) return - // @ts-ignore - let destActor = game.actors.get(resp.destactorid) - // @ts-ignore - ui.notifications.info(`${destActor.name} did not want ${resp.itemname}`) - } - }) - - // Keep track of which token has been activated, so we can determine the last actor for the Modifier Bucket - // @ts-ignore - Hooks.on('controlToken', (...args) => { - if (GURPS.IgnoreTokenSelect) return - if (args.length > 1) { - let a = args[0]?.actor - if (!!a) { - if (args[1]) GURPS.SetLastActor(a, args[0].document) - else GURPS.ClearLastActor(a) - } - } - }) - - GurpsJournalEntry.ready() - - // define Handlebars partials for ADD: - const __dirname = 'systems/gurps/templates' - loadTemplates([ - __dirname + '/apply-damage/effect-blunttrauma.html', - __dirname + '/apply-damage/effect-crippling.html', - __dirname + '/apply-damage/effect-headvitalshit.html', - __dirname + '/apply-damage/effect-knockback.html', - __dirname + '/apply-damage/effect-majorwound.html', - __dirname + '/apply-damage/effect-shock.html', - ]) - // @ts-ignore - GURPS.setInitiativeFormula() - - // Translate attribute mappings if not in English - if (game.i18n.lang != 'en') { - console.log('Mapping ' + game.i18n.lang + ' translations into PARSELINK_MAPPINGS') - let mappings = /** @type {Record} */ ({}) - for (let k in GURPS.PARSELINK_MAPPINGS) { - let v = GURPS.PARSELINK_MAPPINGS[k] - let i = v.indexOf('.value') - let nk = v - if (i >= 0) { - nk = nk.substr(0, i) - } - nk = nk.replace(/\./g, '') // remove periods - nk = game.i18n.localize('GURPS.' + nk).toUpperCase() - if (!GURPS.PARSELINK_MAPPINGS[nk]) { - console.log(`Mapping '${k}' -> '${nk}'`) - mappings[nk] = GURPS.PARSELINK_MAPPINGS[k] - } - } - mappings = { ...mappings, ...GURPS.PARSELINK_MAPPINGS } - GURPS.PARSELINK_MAPPINGS = mappings - } - - // This system setting must be built AFTER all of the character sheets have been registered - let sheets = /** @type {Record} */ ({}) - Object.values(CONFIG.Actor.sheetClasses['character']).forEach(e => { - if (e.id.toString().startsWith(Settings.SYSTEM_NAME) && e.id != 'gurps.GurpsActorSheet') sheets[e.label] = e.label - }) - game.settings.register(Settings.SYSTEM_NAME, Settings.SETTING_ALT_SHEET, { - name: i18n('GURPS.settingSheetDetail'), - hint: i18n('GURPS.settingHintSheetDetail'), - scope: 'world', - config: true, - type: String, - choices: sheets, - default: 'Tabbed Sheet', - onChange: value => console.log(`${Settings.SETTING_ALT_SHEET}: ${value}`), - }) - - GurpsToken.ready() - TriggerHappySupport.init() - - // End of system "READY" hook. - Hooks.call('gurpsready') - }) + type: CONST.CHAT_MESSAGE_TYPES.WHISPER, + // @ts-ignore + whisper: [game.user], + }) + if (game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_SHOW_CHANGELOG)) { + const app = new ChangeLogWindow(changelogVersion) + app.render(true) + // @ts-ignore + game.settings.set(Settings.SYSTEM_NAME, Settings.SETTING_CHANGELOG_VERSION, GURPS.currentVersion.toString()) + } + GURPS.executeOTF('/help') + } + + // get all aliases defined in the resource tracker templates and register them as damage types + let resourceTrackers = ResourceTrackerManager.getAllTemplates() + .filter(it => !!it.tracker.isDamageType) + .filter(it => !!it.tracker.alias) + .map(it => it.tracker) + resourceTrackers.forEach(it => (GURPS.DamageTables.damageTypeMap[it.alias] = it.alias)) + resourceTrackers.forEach( + it => + (GURPS.DamageTables.woundModifiers[it.alias] = { + multiplier: 1, + label: it.name, + resource: true, + }) + ) + + // Sorry, removed the ts-ignores during editing. + Hooks.on('hotbarDrop', async (bar, data, slot) => { + if (!data.otf && !data.bucket) return + let name = data.otf || data.bucket.join(' & ') + if (!!data.displayname) name = data.displayname + let cmd = '' + + if (!!data.actor) { + let a = game.actors.get(data.actor) + if (!!a) cmd = `!/select ${a.name}\n` + cmd + name = game.actors.get(data.actor).name + ': ' + name + } + + let otfs = data.bucket || [data.otf] + otfs.forEach(otf => { + if (otf.startsWith('/')) { + if (!!data.encodedAction) { + let action = JSON.parse(atou(data.encodedAction)) + if (action.quiet) cmd += '!' + } + cmd += otf + } else cmd += '/r [' + otf + ']' + cmd += '\n' + }) + let setmacro = async function (name, cmd) { + let macro = await Macro.create({ + name: name, + type: 'chat', + command: cmd, + }) + macro.setFlag('gurps', 'drag-drop-otf', true) + game.user.assignHotbarMacro(macro, slot) + } + + let oldmacro = game.macros.get(game.user.data.hotbar[slot]) + if (!!oldmacro && !!oldmacro.getFlag('gurps', 'drag-drop-otf')) { + let c = (!!data.bucket ? '/clearmb\n' : '') + cmd + new Dialog({ + title: 'Merge or Replace On-the-Fly macro', + content: `Merge both macros into this:

    ${oldmacro.data.command.split('\n').join('
    ')}
    ${cmd + .split('\n') + .join('
    ')}


    Or just replace current macro with:

    ${c + .split('\n') + .join('
    ')}

     
    `, + buttons: { + one: { + icon: '', + label: 'Merge', + callback: () => { + setmacro(oldmacro.data.name, oldmacro.data.command + '\n' + cmd) + }, + }, + two: { + icon: '', + label: 'Replace', + callback: () => setmacro(name, (!!data.bucket ? '/clearmb\n' : '') + cmd), + }, + }, + default: 'one', + }).render(true) + } else setmacro(name, (!!data.bucket ? '/clearmb\n' : '') + cmd) + return false + }) + + // @ts-ignore + Hooks.on('renderCombatTracker', function (a, html, c) { + // use class 'bound' to know if the drop event is already bound + if (!html.hasClass('bound')) { + html.addClass('bound') + // @ts-ignore + html.on('drop', function (ev) { + console.log('Handle drop event on combatTracker') + ev.preventDefault() + ev.stopPropagation() + let elementMouseIsOver = document.elementFromPoint(ev.clientX, ev.clientY) + + // @ts-ignore + let combatant = $(elementMouseIsOver).parents('.combatant').attr('data-combatant-id') + // @ts-ignore + let target = game.combat.combatants.filter(c => c.id === combatant)[0] + + let event = ev.originalEvent + let dropData = JSON.parse(event.dataTransfer.getData('text/plain')) + if (dropData.type === 'damageItem') { + // @ts-ignore + target.actor.handleDamageDrop(dropData.payload) + } + if (dropData.type === 'initiative') { + let target = game.combat.data.combatants.get(combatant) + let src = game.combat.data.combatants.get(dropData.combatant) + let updates = [] + if (!!target && !!src) { + if (target.initiative < src.initiative) + updates.push({ _id: dropData.combatant, initiative: target.initiative - 0.00001 }) + else updates.push({ _id: dropData.combatant, initiative: target.initiative + 0.00001 }) + game.combat.updateEmbeddedDocuments('Combatant', updates) + } + } + }) + } + + if (game.user.isGM) { + html.find('.combatant').each((_, li) => { + li.setAttribute('draggable', true) + li.addEventListener('dragstart', ev => { + let display = '' + if (!!ev.currentTarget.dataset.action) display = ev.currentTarget.innerText + let dragIcon = $(event.currentTarget).find('.token-image')[0] + ev.dataTransfer.setDragImage(dragIcon, 25, 25) + return ev.dataTransfer.setData( + 'text/plain', + JSON.stringify({ + type: 'initiative', + combatant: li.getAttribute('data-combatant-id'), + }) + ) + }) + }) + } + }) + + // @ts-ignore + game.socket.on('system.gurps', async resp => { + if (resp.type == 'updatebucket') { + if (resp.users.includes(game.user.id)) { + if (resp.add) { + resp.bucket.modifierList.forEach(e => GURPS.ModifierBucket.addModifier(e.mod, e.desc)) + } else GURPS.ModifierBucket.updateModifierBucket(resp.bucket) + } + } + if (resp.type == 'initiativeChanged') { + CONFIG.Combat.initiative = { + formula: resp.formula, + decimals: resp.decimals, + } + } + if (resp.type == 'playerFpHp') { + requestFpHp(resp) + } + if (resp.type == 'executeOtF') { + // @ts-ignore + if (game.users.isGM || (resp.users.length > 0 && !resp.users.includes(game.user.name))) return + // @ts-ignore + GURPS.performAction(resp.action, GURPS.LastActor) + } + if (resp.type == 'setLastTargetedRoll') { + GURPS.setLastTargetedRoll(resp.chatdata, resp.actorid, resp.tokenid, false) + } + if (resp.type == 'dragEquipment1') { + if (resp.destuserid != game.user.id) return + // @ts-ignore + let destactor = game.actors.get(resp.destactorid) + // @ts-ignore + let srcActor = game.actors.get(resp.srcactorid) + Dialog.confirm({ + // @ts-ignore + title: `Gift for ${destactor.name}!`, + // @ts-ignore + content: `

    ${srcActor.name} wants to give you ${resp.itemData.name} (${resp.count}),


    Ok?`, + yes: () => { + // @ts-ignore + let destKey = destactor._findEqtkeyForId('globalid', resp.itemData.data.globalid) + if (!!destKey) { + // already have some + // @ts-ignore + let destEqt = getProperty(destactor, destKey) + // @ts-ignore + destactor.updateEqtCount(destKey, destEqt.count + resp.count) + } else { + resp.itemData.data.equipped = true + // @ts-ignore + destactor.addNewItemData(resp.itemData) + } + // @ts-ignore + game.socket.emit('system.gurps', { + type: 'dragEquipment2', + srckey: resp.srckey, + srcuserid: resp.srcuserid, + srcactorid: resp.srcactorid, + destactorid: resp.destactorid, + itemname: resp.itemData.name, + count: resp.count, + }) + }, + no: () => { + // @ts-ignore + game.socket.emit('system.gurps', { + type: 'dragEquipment3', + srcuserid: resp.srcuserid, + destactorid: resp.destactorid, + itemname: resp.itemData.name, + }) + }, + }) + } + if (resp.type == 'dragEquipment2') { + if (resp.srcuserid != game.user.id) return + // @ts-ignore + let srcActor = game.actors.get(resp.srcactorid) + // @ts-ignore + let eqt = getProperty(srcActor, resp.srckey) + if (resp.count >= eqt.count) { + // @ts-ignore + srcActor.deleteEquipment(resp.srckey) + } else { + // @ts-ignore + srcActor.updateEqtCount(resp.srckey, eqt.count - resp.count) + } + // @ts-ignore + let destActor = game.actors.get(resp.destactorid) + // @ts-ignore + ui.notifications.info(`${destActor.name} accepted ${resp.itemname}`) + } + if (resp.type == 'dragEquipment3') { + if (resp.srcuserid != game.user.id) return + // @ts-ignore + let destActor = game.actors.get(resp.destactorid) + // @ts-ignore + ui.notifications.info(`${destActor.name} did not want ${resp.itemname}`) + } + }) + + // Keep track of which token has been activated, so we can determine the last actor for the Modifier Bucket + // @ts-ignore + Hooks.on('controlToken', (...args) => { + if (GURPS.IgnoreTokenSelect) return + if (args.length > 1) { + let a = args[0]?.actor + if (!!a) { + if (args[1]) GURPS.SetLastActor(a, args[0].document) + else GURPS.ClearLastActor(a) + } + } + }) + + GurpsJournalEntry.ready() + + // define Handlebars partials for ADD: + const __dirname = 'systems/gurps/templates' + loadTemplates([ + __dirname + '/apply-damage/effect-blunttrauma.html', + __dirname + '/apply-damage/effect-crippling.html', + __dirname + '/apply-damage/effect-headvitalshit.html', + __dirname + '/apply-damage/effect-knockback.html', + __dirname + '/apply-damage/effect-majorwound.html', + __dirname + '/apply-damage/effect-shock.html', + ]) + // @ts-ignore + GURPS.setInitiativeFormula() + + // Translate attribute mappings if not in English + if (game.i18n.lang != 'en') { + console.log('Mapping ' + game.i18n.lang + ' translations into PARSELINK_MAPPINGS') + let mappings = /** @type {Record} */ ({}) + for (let k in GURPS.PARSELINK_MAPPINGS) { + let v = GURPS.PARSELINK_MAPPINGS[k] + let i = v.indexOf('.value') + let nk = v + if (i >= 0) { + nk = nk.substr(0, i) + } + nk = nk.replace(/\./g, '') // remove periods + nk = game.i18n.localize('GURPS.' + nk).toUpperCase() + if (!GURPS.PARSELINK_MAPPINGS[nk]) { + console.log(`Mapping '${k}' -> '${nk}'`) + mappings[nk] = GURPS.PARSELINK_MAPPINGS[k] + } + } + mappings = { ...mappings, ...GURPS.PARSELINK_MAPPINGS } + GURPS.PARSELINK_MAPPINGS = mappings + } + + // This system setting must be built AFTER all of the character sheets have been registered + let sheets = /** @type {Record} */ ({}) + Object.values(CONFIG.Actor.sheetClasses['character']).forEach(e => { + if (e.id.toString().startsWith(Settings.SYSTEM_NAME) && e.id != 'gurps.GurpsActorSheet') sheets[e.label] = e.label + }) + game.settings.register(Settings.SYSTEM_NAME, Settings.SETTING_ALT_SHEET, { + name: i18n('GURPS.settingSheetDetail'), + hint: i18n('GURPS.settingHintSheetDetail'), + scope: 'world', + config: true, + type: String, + choices: sheets, + default: 'Tabbed Sheet', + onChange: value => console.log(`${Settings.SETTING_ALT_SHEET}: ${value}`), + }) + + GurpsToken.ready() + TriggerHappySupport.init() + + // End of system "READY" hook. + Hooks.call('gurpsready') + }) } diff --git a/module/item-import.js b/module/item-import.js index e851dd7b4..4485bace1 100644 --- a/module/item-import.js +++ b/module/item-import.js @@ -1,226 +1,226 @@ import { zeroFill } from '../lib/utilities.js' export class ItemImporter { - constructor() { - this.count = 0 - } + constructor() { + this.count = 0 + } - static async importItems(text, filename, filepath) { - let importer = new ItemImporter() - importer._importItems(text, filename, filepath) - } + static async importItems(text, filename, filepath) { + let importer = new ItemImporter() + importer._importItems(text, filename, filepath) + } - async _importItems(text, filename, filepath) { - let j = {} - try { - j = JSON.parse(text) - } catch { - return ui.notifications.error('The file you uploaded was not of the right format!') - } - if (j.type !== 'equipment_list') { - return ui.notifications.error('The file you uploaded is not a GCS Equipment Library!') - } - if (j.version !== 2) { - return ui.notifications.error('The file you uploaded is not of the right version!') - } - let pack = game.packs.find(p => p.metadata.name === filename) - if (!pack) - pack = await CompendiumCollection.createCompendium({ - entity: 'Item', - label: filename, - name: filename, - package: 'world', - }) - let timestamp = new Date() - ui.notifications.info('Importing Items from ' + filename + '...') - for (let i of j.rows) { - await this._importItem(i, pack, filename, timestamp) - } - ui.notifications.info('Finished Importing ' + this.count + ' Items!') - } + async _importItems(text, filename, filepath) { + let j = {} + try { + j = JSON.parse(text) + } catch { + return ui.notifications.error('The file you uploaded was not of the right format!') + } + if (j.type !== 'equipment_list') { + return ui.notifications.error('The file you uploaded is not a GCS Equipment Library!') + } + if (j.version !== 2) { + return ui.notifications.error('The file you uploaded is not of the right version!') + } + let pack = game.packs.find(p => p.metadata.name === filename) + if (!pack) + pack = await CompendiumCollection.createCompendium({ + entity: 'Item', + label: filename, + name: filename, + package: 'world', + }) + let timestamp = new Date() + ui.notifications.info('Importing Items from ' + filename + '...') + for (let i of j.rows) { + await this._importItem(i, pack, filename, timestamp) + } + ui.notifications.info('Finished Importing ' + this.count + ' Items!') + } - async _importItem(i, pack, filename, timestamp) { - this.count++ - if (i.children?.length) - for (let ch of i.children) { - await this._importItem(ch, pack, filename, timestamp) - } - let itemData = { - name: i.description, - type: 'equipment', - system: { - eqt: { - name: i.description, - notes: i.notes, - pageref: i.reference, - count: i.quantity, - cost: !!i.value ? parseFloat(i.value) : 0, - weight: !!i.weight ? parseFloat(i.weight) : 0, - carried: true, - equipped: true, - techlevel: i.tech_level || '', - categories: i.categories || '', - legalityclass: i.legality_class || '', - costsum: !!i.value ? parseFloat(i.value) : 0, - weightsum: !!i.weight ? parseFloat(i.weight) : 0, - uses: !!i.max_uses ? i.max_uses.toString() : '', - maxuses: !!i.max_uses ? i.max_uses.toString() : 0, - last_import: timestamp, - uuid: i.id, - }, - melee: {}, - ranged: {}, - bonuses: '', - equipped: true, - carried: true, - }, - } - if (i.weapons?.length) - for (let w of i.weapons) { - let otf_list = [] - if (w.defaults) - for (let d of w.defaults) { - let mod = !!d.modifier ? (d.modifier > -1 ? `+${d.modifier}` : d.modifier.toString()) : '' - if (d.type === 'skill') { - //otf_list.push(`S:${d.name.replace(/ /g, "*")}` + (d.specialization ? `*(${d.specialization.replace(/ /g, "*")})` : "") + mod); - otf_list.push(`S:"${d.name}` + (d.specialization ? `*(${d.specialization})` : '') + '"' + mod) - } else if ( - [ - '10', - 'st', - 'dx', - 'iq', - 'ht', - 'per', - 'will', - 'vision', - 'hearing', - 'taste_smell', - 'touch', - 'parry', - 'block', - ].includes(d.type) - ) { - otf_list.push(d.type.replace('_', ' ') + mod) - } - } - if (w.type === 'melee_weapon') { - let wep = { - block: w.block || '', - damage: w.calc.damage || '', - mode: w.usage || '', - name: itemData.name, - notes: itemData.eqt.notes || '', - pageref: itemData.system.eqt.pageref || '', - parry: w.parry || '', - reach: w.reach || '', - st: w.strength || '', - otf: otf_list.join('|') || '', - } - itemData.system.melee[zeroFill(Object.keys(itemData.system.melee).length + 1)] = wep - } else if (w.type === 'ranged_weapon') { - let wep = { - acc: w.accuracy || '', - ammo: '', - bulk: w.bulk || '', - damage: w.calc.damage || '', - mode: w.usage, - name: itemData.name, - notes: itemData.system.eqt.notes || '', - pageref: itemData.system.eqt.pageref || '', - range: w.range, - rcl: w.recoil, - rof: w.rate_of_fire, - shots: w.shots, - st: w.strength, - otf: otf_list.join('|') || '', - } - itemData.system.ranged[zeroFill(Object.keys(itemData.system.ranged).length + 1)] = wep - } - } - let bonus_list = [] - let feat_list = [] - if (i.features?.length) - for (let f of i.features) { - feat_list.push(f) - } - if (i.modifiers?.length) - for (let m of i.modifiers) { - if (!m.disabled && m.features?.length) - for (let f of m.features) { - feat_list.push(f) - } - } - if (feat_list.length) - for (let f of feat_list) { - let bonus = !!f.amount ? (f.amount > -1 ? `+${f.amount}` : f.amount.toString()) : '' - if (f.type === 'attribute_bonus') { - bonus_list.push(`${f.attribute} ${bonus}`) - } else if (f.type === 'dr_bonus') { - bonus_list.push(`DR ${bonus} *${f.location}`) - } else if (f.type === 'skill_bonus') { - if (f.selection_type === 'skills_with_name' && f.name?.compare === 'is') { - if (f.specialization?.compare === 'is') { - bonus_list.push( - `A:${(f.name.qualifier || '').replace(/ /g, '*')}${(f.specialization.qualifier || '').replace( - / /g, - '*' - )} ${bonus}` - ) - } else if (!f.specialization) { - bonus_list.push(`A:${(f.name.qualifier || '').replace(/ /g, '*')} ${bonus}`) - } - } else if (f.selection_type === 'weapons_with_name' && f.name?.compare === 'is') { - if (f.specialization?.compare === 'is') { - bonus_list.push( - `A:${(f.name.qualifier || '').replace(/ /g, '*')}${(f.specialization.qualifier || '').replace( - / /g, - '*' - )} ${bonus}` - ) - } else if (!f.specialization) { - bonus_list.push(`A:${(f.name.qualifier || '').replace(/ /g, '*')} ${bonus}`) - } - } else if (f.selection_type === 'this_weapon') { - bonus_list.push(`A:${itemData.name.replace(/ /g, '*')} ${bonus}`) - } - } else if (f.type === 'spell_bonus') { - if (f.match === 'spell_name' && f.name?.compare === 'is') { - bonus_list.push(`S:${(f.name.qualifier || '').replace(/ /g, '*')} ${bonus}`) - } - } else if (f.type === 'weapon_bonus') { - if (f.selection_type === 'weapons_with_name') { - if (f.specialization?.compare === 'is') { - bonus_list.push( - `D:${(f.name?.qualifier || '').replace(/ /g, '*')}${(f.specialization.qualifier || '').replace( - / /g, - '*' - )} ${bonus}` - ) - } else if (!f.specialization) { - bonus_list.push(`D:${(f.name?.qualifier || '').replace(/ /g, '*')} ${bonus}`) - } - } else if (f.selection_type === 'this_weapon') { - bonus_list.push(`D:${itemData.name.replace(/ /g, '*')} ${bonus}`) - } - } - } - itemData.system.bonuses = bonus_list.join('\n') - const cachedItems = []; - for (let i of pack.index) { - cachedItems.push(await pack.getDocument(i._id)); - } - let oi = await cachedItems.find(p => p.system.eqt.uuid === itemData.eqt.uuid) - if (!!oi) { - let oldData = duplicate(oi.data) - let newData = duplicate(itemData) - delete oldData.eqt.uuid - delete newData.eqt.uuid - if (oldData != newData) { - return oi.update(itemData) - } - } else { - return Item.create(itemData, { pack: `world.${filename}` }) - } - } + async _importItem(i, pack, filename, timestamp) { + this.count++ + if (i.children?.length) + for (let ch of i.children) { + await this._importItem(ch, pack, filename, timestamp) + } + let itemData = { + name: i.description, + type: 'equipment', + system: { + eqt: { + name: i.description, + notes: i.notes, + pageref: i.reference, + count: i.quantity, + cost: !!i.value ? parseFloat(i.value) : 0, + weight: !!i.weight ? parseFloat(i.weight) : 0, + carried: true, + equipped: true, + techlevel: i.tech_level || '', + categories: i.categories || '', + legalityclass: i.legality_class || '', + costsum: !!i.value ? parseFloat(i.value) : 0, + weightsum: !!i.weight ? parseFloat(i.weight) : 0, + uses: !!i.max_uses ? i.max_uses.toString() : '', + maxuses: !!i.max_uses ? i.max_uses.toString() : 0, + last_import: timestamp, + uuid: i.id, + }, + melee: {}, + ranged: {}, + bonuses: '', + equipped: true, + carried: true, + }, + } + if (i.weapons?.length) + for (let w of i.weapons) { + let otf_list = [] + if (w.defaults) + for (let d of w.defaults) { + let mod = !!d.modifier ? (d.modifier > -1 ? `+${d.modifier}` : d.modifier.toString()) : '' + if (d.type === 'skill') { + //otf_list.push(`S:${d.name.replace(/ /g, "*")}` + (d.specialization ? `*(${d.specialization.replace(/ /g, "*")})` : "") + mod); + otf_list.push(`S:"${d.name}` + (d.specialization ? `*(${d.specialization})` : '') + '"' + mod) + } else if ( + [ + '10', + 'st', + 'dx', + 'iq', + 'ht', + 'per', + 'will', + 'vision', + 'hearing', + 'taste_smell', + 'touch', + 'parry', + 'block', + ].includes(d.type) + ) { + otf_list.push(d.type.replace('_', ' ') + mod) + } + } + if (w.type === 'melee_weapon') { + let wep = { + block: w.block || '', + damage: w.calc.damage || '', + mode: w.usage || '', + name: itemData.name, + notes: itemData.eqt.notes || '', + pageref: itemData.system.eqt.pageref || '', + parry: w.parry || '', + reach: w.reach || '', + st: w.strength || '', + otf: otf_list.join('|') || '', + } + itemData.system.melee[zeroFill(Object.keys(itemData.system.melee).length + 1)] = wep + } else if (w.type === 'ranged_weapon') { + let wep = { + acc: w.accuracy || '', + ammo: '', + bulk: w.bulk || '', + damage: w.calc.damage || '', + mode: w.usage, + name: itemData.name, + notes: itemData.system.eqt.notes || '', + pageref: itemData.system.eqt.pageref || '', + range: w.range, + rcl: w.recoil, + rof: w.rate_of_fire, + shots: w.shots, + st: w.strength, + otf: otf_list.join('|') || '', + } + itemData.system.ranged[zeroFill(Object.keys(itemData.system.ranged).length + 1)] = wep + } + } + let bonus_list = [] + let feat_list = [] + if (i.features?.length) + for (let f of i.features) { + feat_list.push(f) + } + if (i.modifiers?.length) + for (let m of i.modifiers) { + if (!m.disabled && m.features?.length) + for (let f of m.features) { + feat_list.push(f) + } + } + if (feat_list.length) + for (let f of feat_list) { + let bonus = !!f.amount ? (f.amount > -1 ? `+${f.amount}` : f.amount.toString()) : '' + if (f.type === 'attribute_bonus') { + bonus_list.push(`${f.attribute} ${bonus}`) + } else if (f.type === 'dr_bonus') { + bonus_list.push(`DR ${bonus} *${f.location}`) + } else if (f.type === 'skill_bonus') { + if (f.selection_type === 'skills_with_name' && f.name?.compare === 'is') { + if (f.specialization?.compare === 'is') { + bonus_list.push( + `A:${(f.name.qualifier || '').replace(/ /g, '*')}${(f.specialization.qualifier || '').replace( + / /g, + '*' + )} ${bonus}` + ) + } else if (!f.specialization) { + bonus_list.push(`A:${(f.name.qualifier || '').replace(/ /g, '*')} ${bonus}`) + } + } else if (f.selection_type === 'weapons_with_name' && f.name?.compare === 'is') { + if (f.specialization?.compare === 'is') { + bonus_list.push( + `A:${(f.name.qualifier || '').replace(/ /g, '*')}${(f.specialization.qualifier || '').replace( + / /g, + '*' + )} ${bonus}` + ) + } else if (!f.specialization) { + bonus_list.push(`A:${(f.name.qualifier || '').replace(/ /g, '*')} ${bonus}`) + } + } else if (f.selection_type === 'this_weapon') { + bonus_list.push(`A:${itemData.name.replace(/ /g, '*')} ${bonus}`) + } + } else if (f.type === 'spell_bonus') { + if (f.match === 'spell_name' && f.name?.compare === 'is') { + bonus_list.push(`S:${(f.name.qualifier || '').replace(/ /g, '*')} ${bonus}`) + } + } else if (f.type === 'weapon_bonus') { + if (f.selection_type === 'weapons_with_name') { + if (f.specialization?.compare === 'is') { + bonus_list.push( + `D:${(f.name?.qualifier || '').replace(/ /g, '*')}${(f.specialization.qualifier || '').replace( + / /g, + '*' + )} ${bonus}` + ) + } else if (!f.specialization) { + bonus_list.push(`D:${(f.name?.qualifier || '').replace(/ /g, '*')} ${bonus}`) + } + } else if (f.selection_type === 'this_weapon') { + bonus_list.push(`D:${itemData.name.replace(/ /g, '*')} ${bonus}`) + } + } + } + itemData.system.bonuses = bonus_list.join('\n') + const cachedItems = [] + for (let i of pack.index) { + cachedItems.push(await pack.getDocument(i._id)) + } + let oi = await cachedItems.find(p => p.system.eqt.uuid === itemData.eqt.uuid) + if (!!oi) { + let oldData = duplicate(oi.data) + let newData = duplicate(itemData) + delete oldData.eqt.uuid + delete newData.eqt.uuid + if (oldData != newData) { + return oi.update(itemData) + } + } else { + return Item.create(itemData, { pack: `world.${filename}` }) + } + } } diff --git a/module/item-sheet.js b/module/item-sheet.js index 8be0e277d..854800d36 100755 --- a/module/item-sheet.js +++ b/module/item-sheet.js @@ -4,172 +4,172 @@ import { digitsAndDecimalOnly, digitsOnly } from '../lib/jquery-helper.js' import { recurselist } from '../lib/utilities.js' export class GurpsItemSheet extends ItemSheet { - /** @override */ - static get defaultOptions() { - return mergeObject(super.defaultOptions, { - classes: ['sheet', 'item'], - template: 'systems/gurps/templates/item-sheet.html', - width: 680, - height: 'auto', - resizable: false, - tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.content', initial: 'melee-tab' }], - dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], - }) - } - - /* -------------------------------------------- */ - - /** @override */ - getData() { - const sheetData = super.getData() - sheetData.data = this.item.system; - sheetData.data.eqt.f_count = this.item.system.eqt.count // hack for Furnace module - sheetData.name = this.item.name - if (!this.item.system.globalid && !this.item.parent) - this.item.update({ 'data.globalid': this.item.id, _id: this.item.id }) - return sheetData - } - - /* -------------------------------------------- */ - - /** @override */ - activateListeners(html) { - this.html = html - super.activateListeners(html) - - html.find('.digits-only').inputFilter(value => digitsOnly.test(value)) - html.find('.decimal-digits-only').inputFilter(value => digitsAndDecimalOnly.test(value)) - html.find('#itemname').change(ev => { - let nm = ev.currentTarget.value - let commit = { - 'data.eqt.name': nm, - name: nm, - } - recurselist(this.item.system.melee, (e, k, d) => { - commit = { ...commit, ...{ ['data.melee.' + k + '.name']: nm } } - }) - recurselist(this.item.system.ranged, (e, k, d) => { - commit = { ...commit, ...{ ['data.melee.' + k + '.name']: nm } } - }) - this.item.update(commit) - }) - // html.find('#quantity').change(ev => this.item.update({ 'data.eqt.count': parseInt(ev.currentTarget.value) })) - - html.find('#add-melee').click(ev => { - ev.preventDefault() - let m = new Melee() - m.name = this.item.name - this._addToList('melee', m) - }) - - html.find('.delete.button').click(this._deleteKey.bind(this)) - - html.find('#add-ranged').click(ev => { - ev.preventDefault() - let r = new Ranged() - r.name = this.item.name - r.legalityclass = 'lc' - this._addToList('ranged', r) - }) - - html.find('#add-skill').click(ev => { - ev.preventDefault() - let r = new Skill() - r.rsl = '-' - this._addToList('skills', r) - }) - - html.find('#add-spell').click(ev => { - ev.preventDefault() - let r = new Spell() - this._addToList('spells', r) - }) - - html.find('#add-ads').click(ev => { - ev.preventDefault() - let r = new Advantage() - this._addToList('ads', r) - }) - - html.find('textarea').on('drop', this.dropFoundryLinks) - html.find('input').on('drop', this.dropFoundryLinks) - - html.find('.itemdraggable').each((_, li) => { - li.setAttribute('draggable', true) - li.addEventListener('dragstart', ev => { - let img = new Image() - img.src = this.item.img - const w = 50 - const h = 50 - const preview = DragDrop.createDragImage(img, w, h) - ev.dataTransfer.setDragImage(preview, 0, 0) - return ev.dataTransfer.setData( - 'text/plain', - JSON.stringify({ - type: 'Item', - id: this.item.id, - pack: this.item.pack, - data: this.item.data, - }) - ) - }) - }) - } - - dropFoundryLinks(ev) { - if (!!ev.originalEvent) ev = ev.originalEvent - let dragData = JSON.parse(ev.dataTransfer.getData('text/plain')) - var n - if (dragData.type == 'JournalEntry') { - n = game.journal.get(dragData.id).name - } - if (dragData.type == 'Actor') { - n = game.actors.get(dragData.id).name - } - if (dragData.type == 'RollTable') { - n = game.tables.get(dragData.id).name - } - if (dragData.type == 'Item') { - n = game.items.get(dragData.id).name - } - if (!!n) { - let add = ` [${dragData.type}[${dragData.id}]` + '{' + n + '}]' - $(ev.currentTarget).val($(ev.currentTarget).val() + add) - } - } - - async _deleteKey(ev) { - let key = ev.currentTarget.getAttribute('name') - let path = ev.currentTarget.getAttribute('data-path') - GURPS.removeKey(this.item, path + '.' + key) - } - - async _onDrop(event) { - let dragData = JSON.parse(event.dataTransfer.getData('text/plain')) - if (!['melee', 'ranged', 'skills', 'spells', 'ads', 'equipment'].includes(dragData.type)) return - let srcActor = game.actors.get(dragData.actorid) - let srcData = getProperty(srcActor, dragData.key) - srcData.contains = {} // don't include any contained/collapsed items from source - srcData.collapsed = {} - if (dragData.type == 'equipment') { - this.item.update({ - name: srcData.name, - 'data.eqt': srcData, - }) - return - } - this._addToList(dragData.type, srcData) - } - - _addToList(key, data) { - let list = this.item.system[key] || {} - GURPS.put(list, data) - this.item.update({ ['data.' + key]: list }) - } - - close() { - super.close() - this.item.update({ 'data.eqt.name': this.item.name }) - if (!!this.object.editingActor) this.object.editingActor.updateItem(this.object) - } + /** @override */ + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + classes: ['sheet', 'item'], + template: 'systems/gurps/templates/item-sheet.html', + width: 680, + height: 'auto', + resizable: false, + tabs: [{ navSelector: '.gurps-sheet-tabs', contentSelector: '.content', initial: 'melee-tab' }], + dragDrop: [{ dragSelector: '.item-list .item', dropSelector: null }], + }) + } + + /* -------------------------------------------- */ + + /** @override */ + getData() { + const sheetData = super.getData() + sheetData.data = this.item.system + sheetData.data.eqt.f_count = this.item.system.eqt.count // hack for Furnace module + sheetData.name = this.item.name + if (!this.item.system.globalid && !this.item.parent) + this.item.update({ 'data.globalid': this.item.id, _id: this.item.id }) + return sheetData + } + + /* -------------------------------------------- */ + + /** @override */ + activateListeners(html) { + this.html = html + super.activateListeners(html) + + html.find('.digits-only').inputFilter(value => digitsOnly.test(value)) + html.find('.decimal-digits-only').inputFilter(value => digitsAndDecimalOnly.test(value)) + html.find('#itemname').change(ev => { + let nm = ev.currentTarget.value + let commit = { + 'data.eqt.name': nm, + name: nm, + } + recurselist(this.item.system.melee, (e, k, d) => { + commit = { ...commit, ...{ ['data.melee.' + k + '.name']: nm } } + }) + recurselist(this.item.system.ranged, (e, k, d) => { + commit = { ...commit, ...{ ['data.melee.' + k + '.name']: nm } } + }) + this.item.update(commit) + }) + // html.find('#quantity').change(ev => this.item.update({ 'data.eqt.count': parseInt(ev.currentTarget.value) })) + + html.find('#add-melee').click(ev => { + ev.preventDefault() + let m = new Melee() + m.name = this.item.name + this._addToList('melee', m) + }) + + html.find('.delete.button').click(this._deleteKey.bind(this)) + + html.find('#add-ranged').click(ev => { + ev.preventDefault() + let r = new Ranged() + r.name = this.item.name + r.legalityclass = 'lc' + this._addToList('ranged', r) + }) + + html.find('#add-skill').click(ev => { + ev.preventDefault() + let r = new Skill() + r.rsl = '-' + this._addToList('skills', r) + }) + + html.find('#add-spell').click(ev => { + ev.preventDefault() + let r = new Spell() + this._addToList('spells', r) + }) + + html.find('#add-ads').click(ev => { + ev.preventDefault() + let r = new Advantage() + this._addToList('ads', r) + }) + + html.find('textarea').on('drop', this.dropFoundryLinks) + html.find('input').on('drop', this.dropFoundryLinks) + + html.find('.itemdraggable').each((_, li) => { + li.setAttribute('draggable', true) + li.addEventListener('dragstart', ev => { + let img = new Image() + img.src = this.item.img + const w = 50 + const h = 50 + const preview = DragDrop.createDragImage(img, w, h) + ev.dataTransfer.setDragImage(preview, 0, 0) + return ev.dataTransfer.setData( + 'text/plain', + JSON.stringify({ + type: 'Item', + id: this.item.id, + pack: this.item.pack, + data: this.item.data, + }) + ) + }) + }) + } + + dropFoundryLinks(ev) { + if (!!ev.originalEvent) ev = ev.originalEvent + let dragData = JSON.parse(ev.dataTransfer.getData('text/plain')) + var n + if (dragData.type == 'JournalEntry') { + n = game.journal.get(dragData.id).name + } + if (dragData.type == 'Actor') { + n = game.actors.get(dragData.id).name + } + if (dragData.type == 'RollTable') { + n = game.tables.get(dragData.id).name + } + if (dragData.type == 'Item') { + n = game.items.get(dragData.id).name + } + if (!!n) { + let add = ` [${dragData.type}[${dragData.id}]` + '{' + n + '}]' + $(ev.currentTarget).val($(ev.currentTarget).val() + add) + } + } + + async _deleteKey(ev) { + let key = ev.currentTarget.getAttribute('name') + let path = ev.currentTarget.getAttribute('data-path') + GURPS.removeKey(this.item, path + '.' + key) + } + + async _onDrop(event) { + let dragData = JSON.parse(event.dataTransfer.getData('text/plain')) + if (!['melee', 'ranged', 'skills', 'spells', 'ads', 'equipment'].includes(dragData.type)) return + let srcActor = game.actors.get(dragData.actorid) + let srcData = getProperty(srcActor, dragData.key) + srcData.contains = {} // don't include any contained/collapsed items from source + srcData.collapsed = {} + if (dragData.type == 'equipment') { + this.item.update({ + name: srcData.name, + 'data.eqt': srcData, + }) + return + } + this._addToList(dragData.type, srcData) + } + + _addToList(key, data) { + let list = this.item.system[key] || {} + GURPS.put(list, data) + this.item.update({ ['data.' + key]: list }) + } + + close() { + super.close() + this.item.update({ 'data.eqt.name': this.item.name }) + if (!!this.object.editingActor) this.object.editingActor.updateItem(this.object) + } } diff --git a/module/item.js b/module/item.js index 55ac24956..e92468c3b 100755 --- a/module/item.js +++ b/module/item.js @@ -1,25 +1,25 @@ export class GurpsItem extends Item { - /** - * @param {Item} item - * @returns {GurpsItem} - */ - static asGurpsItem(item) { - return /** @type {GurpsItem} */ (item) - } + /** + * @param {Item} item + * @returns {GurpsItem} + */ + static asGurpsItem(item) { + return /** @type {GurpsItem} */ (item) + } - async internalUpdate(data, context) { - let ctx = { render: true } - if (!!context) ctx = { ...context, ...ctx } - await this.update(data, ctx) - } + async internalUpdate(data, context) { + let ctx = { render: true } + if (!!context) ctx = { ...context, ...ctx } + await this.update(data, ctx) + } - prepareData() { - super.prepareData() - } + prepareData() { + super.prepareData() + } - async internalUpdate(data, context) { - let ctx = { render: !this.ignoreRender } - if (!!context) ctx = { ...context, ...ctx } - await this.update(data, ctx) - } + async internalUpdate(data, context) { + let ctx = { render: !this.ignoreRender } + if (!!context) ctx = { ...context, ...ctx } + await this.update(data, ctx) + } } diff --git a/module/modifier-bucket/bucket-app.js b/module/modifier-bucket/bucket-app.js index b8f7115fd..3d1553e7b 100755 --- a/module/modifier-bucket/bucket-app.js +++ b/module/modifier-bucket/bucket-app.js @@ -15,7 +15,7 @@ Hooks.once('init', async function () { // @ts-ignore -- Need to look into why a GurpsRoll isn't a Roll CONFIG.Dice.rolls.push(GurpsRoll) - CONFIG.Dice.terms["d"] = GurpsDie // Hack to get Dice so nice working (it checks the terms["d"].name vs the Dice class name + CONFIG.Dice.terms['d'] = GurpsDie // Hack to get Dice so nice working (it checks the terms["d"].name vs the Dice class name // MONKEY_PATCH // Patch DiceTerm.fromMatch to hi-jack the returned Die instances and in turn patch them to @@ -63,7 +63,6 @@ Hooks.once('init', async function () { }, }) } - }) Hooks.once('ready', async function () { @@ -478,7 +477,7 @@ export class ModifierBucket extends Application { * @param {string | null} id */ sendBucketToPlayer(id) { - if ("SHOWALL" == id) return + if ('SHOWALL' == id) return if (!id) { // Only occurs if the GM clicks on 'everyone' let _users = game.users @@ -512,7 +511,7 @@ export class ModifierBucket extends Application { type: 'updatebucket', users: users.map(u => u.id), bucket: GURPS.ModifierBucket.modifierStack, - add: ctrl + add: ctrl, }) } diff --git a/module/modifier-bucket/tooltip-window.js b/module/modifier-bucket/tooltip-window.js index e16c5dae7..2be1f0acd 100644 --- a/module/modifier-bucket/tooltip-window.js +++ b/module/modifier-bucket/tooltip-window.js @@ -10,304 +10,304 @@ import GurpsWiring from '../gurps-wiring.js' * to the current or other actors. */ export default class ModifierBucketEditor extends Application { - constructor(bucket, options = {}) { - super(options) - - console.trace('+++++ Create ModifierBucketEditor +++++') - - this.bucket = bucket // reference to class ModifierBucket, which is the 'button' that opens this window - this.inside = false - this.tabIndex = 0 - } - - static get defaultOptions() { - let scale = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_BUCKET_SCALE) - - return mergeObject(super.defaultOptions, { - id: 'ModifierBucketEditor', - template: 'systems/gurps/templates/modifier-bucket/tooltip-window.html', - minimizable: false, - width: 820, - scale: scale, - classes: ['modifier-bucket'], - }) - } - - /** - * @override - * @param {*} force - * @param {*} options - */ - render(force, options = {}) { - super.render(force, options) - this.bucket.SHOWING = true - } - - /** - * @override - */ - close() { - this.bucket.SHOWING = false - super.close() - } - - get journals() { - let modifierJournalIds = ModifierBucketJournals.getJournalIds() - let journals = Array.from(game.journal) - .filter(it => modifierJournalIds.includes(it.id)) - .map(it => game.journal.get(it.id)) - journals = journals.filter(it => it.testUserPermission(game.user, CONST.ENTITY_PERMISSIONS.OBSERVER)) - return journals - } - - /** - * @override - * @param {*} options - * @returns - */ - getData(options) { - const data = super.getData(options) - - data.isTooltip = !this.options.popOut - data.gmod = this - data.tabIndex = this.tabIndex - data.journals = this.journals - data.stack = this.bucket.modifierStack - data.meleemods = ModifierLiterals.MeleeMods.split('\n') - data.rangedmods = ModifierLiterals.RangedMods.split('\n') - data.defensemods = ModifierLiterals.DefenseMods.split('\n') - data.speedrangemods = ['Speed / Range'].concat(GURPS.rangeObject.modifiers) - data.actorname = !!GURPS.LastActor ? GURPS.LastActor.name : 'No active character!' - data.othermods1 = ModifierLiterals.OtherMods1.split('\n') - data.othermods2 = ModifierLiterals.OtherMods2.split('\n') - data.cansend = game.user?.isGM || game.user?.hasRole('TRUSTED') || game.user?.hasRole('ASSISTANT') - data.users = game.users?.filter(u => u.id != game.user.id) || [] - data.everyone = data.users.length > 1 ? { name: 'Everyone!' } : null - data.taskdificulties = ModifierLiterals.TaskDifficultyModifiers - data.lightingmods = ModifierLiterals.LightingModifiers - data.eqtqualitymods = ModifierLiterals.EqtQualifyModifiers - data.rofmods = ModifierLiterals.RateOfFireModifiers - data.statusmods = ModifierLiterals.StatusModifiers - data.covermods = ModifierLiterals.CoverPostureModifiers - data.sizemods = ModifierLiterals.SizeModifiers - data.hitlocationmods = ModifierLiterals.HitlocationModifiers - data.currentmods = [] - - if (!!GURPS.LastActor) { - let melee = [] - let ranged = [] - let defense = [] - let gen = [] - - let effects = GURPS.LastActor.effects.filter(e => !e.data.disabled) - for (let effect of effects) { - let type = effect.data.flags?.core?.statusId - let m = ModifiersForStatus[type] - if (!!m) { - melee = melee.concat(m.melee) - ranged = ranged.concat(m.ranged) - defense = defense.concat(m.defense) - gen = gen.concat(m.gen) - } - } - if (gen.length > 0) { - data.currentmods.push(horiz('General')) - gen.forEach(e => data.currentmods.push(e)) - } - if (melee.length > 0) { - data.currentmods.push(horiz('Melee')) - melee.forEach(e => data.currentmods.push(e)) - } - if (ranged.length > 0) { - data.currentmods.push(horiz('Ranged')) - ranged.forEach(e => data.currentmods.push(e)) - } - if (defense.length > 0) { - data.currentmods.push(horiz('Defense')) - defense.forEach(e => data.currentmods.push(e)) - } - } - return data - } - - /** - * @override - * @param {*} html - */ - activateListeners(html) { - super.activateListeners(html) - - // if this is a tooltip, scale and position - let scale = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_BUCKET_SCALE) - - if (!this.options.popOut) { - html.css('font-size', `${13 * scale}px`) - - // let height = parseFloat(html.css('height').replace('px', '')) - // let top = window.innerHeight - height - 65 - // html.css('top', `${top}px`) - - // let right = parseFloat(html.css('right').replace('px', '')) - // if (right < 0) { - let x = $('#modifierbucket') - let bucketTop = x.position().top - let bucketLeft = x.position().left - let bucketWidth = 70 - - let width = parseFloat(html.css('width').replace('px', '')) - // ensure that left is not negative - let left = Math.max(bucketLeft + bucketWidth / 2 - width / 2, 10) - // console.log(`bucketLeft: ${bucketLeft}; width: ${width}; left: ${left}`) - html.css('left', `${left}px`) - // } - } - - html.removeClass('overflowy') - this.bringToTop() - - html.find('#modtooltip').off('mouseleave') - html.find('#modtooltip').off('mouseenter') - this.inside = false - html.find('#modtooltip').mouseenter(this._onenter.bind(this)) - - html.find('.removemod').click(this._onClickRemoveMod.bind(this)) - - GurpsWiring.hookupGurps(html) - GurpsWiring.hookupGurpsRightClick(html) - - html.find('.gmbutton').click(this._onGMbutton.bind(this)) - html.find('#modmanualentry').change(this._onManualEntry.bind(this)) - html.find('.collapsible-content .content-inner .selectable').click(this._onSelect.bind(this)) - html.find('.collapsible-wrapper > input').click(this._onClickClose.bind(this)) - - // get the tabs - let tabs = html.find('.tabbedcontent') - this.numberOfTabs = tabs.length - - // make the current tab visible - for (let index = 0; index < tabs.length; index++) { - const element = tabs[index] - if (index === this.tabIndex) { - element.classList.remove('invisible') - } else { - element.classList.add('invisible') - } - } - - // on click, change the current tab - html.find('.tabbed .forward').click(this._clickTabForward.bind(this)) - html.find('.tabbed .back').click(this._clickTabBack.bind(this)) - } - - _clickTabBack() { - if (this.tabIndex === 0) { - this.tabIndex = this.numberOfTabs - 1 - } else { - this.tabIndex-- - } - this.render(false) - } - - _clickTabForward() { - if (this.tabIndex < this.numberOfTabs - 1) { - this.tabIndex++ - } else { - this.tabIndex = 0 - } - this.render(false) - } - - _onClickClose(ev) { - let name = ev.currentTarget.id - if (name === this._currentlyShowing) { - ev.currentTarget.checked = false - this._currentlyShowing = null - } else { - this._currentlyShowing = name - } - } - - /** - * A 'selectable' div in a collapsible was clicked. - * @param {*} ev - */ - _onSelect(ev) { - // find the toggle input above this element and remove the checked property - let div = $(ev.currentTarget).parent().closest('.collapsible-content') - let toggle = div.siblings('input') - $(toggle).prop('checked', false) - this._onSimpleList(ev, '') - } - - _onleave(ev) { - // console.log('onleave') - this.inside = false - this.bucket.SHOWING = false - this.close() - } - - _onenter(ev) { - if (!this.options.popOut) { - if (!this.inside) { - // console.log('onenter') - this.inside = true - $(ev.currentTarget).mouseleave(ev => this._onleave(ev)) - } - } - } - - async _onManualEntry(event) { - event.preventDefault() - event.stopPropagation() - let element = event.currentTarget - let parsed = parselink(element.value) - if (!!parsed.action && parsed.action.type === 'modifier') { - this.bucket.addModifier(parsed.action.mod, parsed.action.desc) - } else { - setTimeout(() => ui.notifications.info("Unable to determine modifier for '" + element.value + "'"), 200) - this.bucket.refresh() // WARNING: REQUIRED! or the world will crash... trust me. - } - } - - async _onList(event) { - this._onSimpleList(event, '') - } - - async _onTaskDifficulty(event) { - this._onSimpleList(event, 'Difficulty: ') - } - - async _onLighting(event) { - this._onSimpleList(event, 'Lighting: ') - } - - async _onSimpleList(event, prefix) { - event.preventDefault() - let element = event.currentTarget - let v = element.value - if (!v) v = element.textContent - v = v.trim() - let i = v.indexOf(' ') - this.SHOWING = true // Firefox seems to need this reset when showing a pulldown - this.bucket.addModifier(v.substring(0, i), prefix + v.substr(i + 1)) - } - - async _onGMbutton(event) { - event.preventDefault() - let element = event.currentTarget - let id = element.dataset.id - this.bucket.sendBucketToPlayer(id) - setTimeout(() => this.bucket.showOthers(), 1000) // Need time for clients to update...and - } - - async _onClickRemoveMod(event) { - event.preventDefault() - let element = event.currentTarget - let index = element.dataset.index - this.bucket.modifierStack.removeIndex(index) - this.bucket.refresh() - // this.render(false) - } + constructor(bucket, options = {}) { + super(options) + + console.trace('+++++ Create ModifierBucketEditor +++++') + + this.bucket = bucket // reference to class ModifierBucket, which is the 'button' that opens this window + this.inside = false + this.tabIndex = 0 + } + + static get defaultOptions() { + let scale = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_BUCKET_SCALE) + + return mergeObject(super.defaultOptions, { + id: 'ModifierBucketEditor', + template: 'systems/gurps/templates/modifier-bucket/tooltip-window.html', + minimizable: false, + width: 820, + scale: scale, + classes: ['modifier-bucket'], + }) + } + + /** + * @override + * @param {*} force + * @param {*} options + */ + render(force, options = {}) { + super.render(force, options) + this.bucket.SHOWING = true + } + + /** + * @override + */ + close() { + this.bucket.SHOWING = false + super.close() + } + + get journals() { + let modifierJournalIds = ModifierBucketJournals.getJournalIds() + let journals = Array.from(game.journal) + .filter(it => modifierJournalIds.includes(it.id)) + .map(it => game.journal.get(it.id)) + journals = journals.filter(it => it.testUserPermission(game.user, CONST.ENTITY_PERMISSIONS.OBSERVER)) + return journals + } + + /** + * @override + * @param {*} options + * @returns + */ + getData(options) { + const data = super.getData(options) + + data.isTooltip = !this.options.popOut + data.gmod = this + data.tabIndex = this.tabIndex + data.journals = this.journals + data.stack = this.bucket.modifierStack + data.meleemods = ModifierLiterals.MeleeMods.split('\n') + data.rangedmods = ModifierLiterals.RangedMods.split('\n') + data.defensemods = ModifierLiterals.DefenseMods.split('\n') + data.speedrangemods = ['Speed / Range'].concat(GURPS.rangeObject.modifiers) + data.actorname = !!GURPS.LastActor ? GURPS.LastActor.name : 'No active character!' + data.othermods1 = ModifierLiterals.OtherMods1.split('\n') + data.othermods2 = ModifierLiterals.OtherMods2.split('\n') + data.cansend = game.user?.isGM || game.user?.hasRole('TRUSTED') || game.user?.hasRole('ASSISTANT') + data.users = game.users?.filter(u => u.id != game.user.id) || [] + data.everyone = data.users.length > 1 ? { name: 'Everyone!' } : null + data.taskdificulties = ModifierLiterals.TaskDifficultyModifiers + data.lightingmods = ModifierLiterals.LightingModifiers + data.eqtqualitymods = ModifierLiterals.EqtQualifyModifiers + data.rofmods = ModifierLiterals.RateOfFireModifiers + data.statusmods = ModifierLiterals.StatusModifiers + data.covermods = ModifierLiterals.CoverPostureModifiers + data.sizemods = ModifierLiterals.SizeModifiers + data.hitlocationmods = ModifierLiterals.HitlocationModifiers + data.currentmods = [] + + if (!!GURPS.LastActor) { + let melee = [] + let ranged = [] + let defense = [] + let gen = [] + + let effects = GURPS.LastActor.effects.filter(e => !e.data.disabled) + for (let effect of effects) { + let type = effect.data.flags?.core?.statusId + let m = ModifiersForStatus[type] + if (!!m) { + melee = melee.concat(m.melee) + ranged = ranged.concat(m.ranged) + defense = defense.concat(m.defense) + gen = gen.concat(m.gen) + } + } + if (gen.length > 0) { + data.currentmods.push(horiz('General')) + gen.forEach(e => data.currentmods.push(e)) + } + if (melee.length > 0) { + data.currentmods.push(horiz('Melee')) + melee.forEach(e => data.currentmods.push(e)) + } + if (ranged.length > 0) { + data.currentmods.push(horiz('Ranged')) + ranged.forEach(e => data.currentmods.push(e)) + } + if (defense.length > 0) { + data.currentmods.push(horiz('Defense')) + defense.forEach(e => data.currentmods.push(e)) + } + } + return data + } + + /** + * @override + * @param {*} html + */ + activateListeners(html) { + super.activateListeners(html) + + // if this is a tooltip, scale and position + let scale = game.settings.get(Settings.SYSTEM_NAME, Settings.SETTING_BUCKET_SCALE) + + if (!this.options.popOut) { + html.css('font-size', `${13 * scale}px`) + + // let height = parseFloat(html.css('height').replace('px', '')) + // let top = window.innerHeight - height - 65 + // html.css('top', `${top}px`) + + // let right = parseFloat(html.css('right').replace('px', '')) + // if (right < 0) { + let x = $('#modifierbucket') + let bucketTop = x.position().top + let bucketLeft = x.position().left + let bucketWidth = 70 + + let width = parseFloat(html.css('width').replace('px', '')) + // ensure that left is not negative + let left = Math.max(bucketLeft + bucketWidth / 2 - width / 2, 10) + // console.log(`bucketLeft: ${bucketLeft}; width: ${width}; left: ${left}`) + html.css('left', `${left}px`) + // } + } + + html.removeClass('overflowy') + this.bringToTop() + + html.find('#modtooltip').off('mouseleave') + html.find('#modtooltip').off('mouseenter') + this.inside = false + html.find('#modtooltip').mouseenter(this._onenter.bind(this)) + + html.find('.removemod').click(this._onClickRemoveMod.bind(this)) + + GurpsWiring.hookupGurps(html) + GurpsWiring.hookupGurpsRightClick(html) + + html.find('.gmbutton').click(this._onGMbutton.bind(this)) + html.find('#modmanualentry').change(this._onManualEntry.bind(this)) + html.find('.collapsible-content .content-inner .selectable').click(this._onSelect.bind(this)) + html.find('.collapsible-wrapper > input').click(this._onClickClose.bind(this)) + + // get the tabs + let tabs = html.find('.tabbedcontent') + this.numberOfTabs = tabs.length + + // make the current tab visible + for (let index = 0; index < tabs.length; index++) { + const element = tabs[index] + if (index === this.tabIndex) { + element.classList.remove('invisible') + } else { + element.classList.add('invisible') + } + } + + // on click, change the current tab + html.find('.tabbed .forward').click(this._clickTabForward.bind(this)) + html.find('.tabbed .back').click(this._clickTabBack.bind(this)) + } + + _clickTabBack() { + if (this.tabIndex === 0) { + this.tabIndex = this.numberOfTabs - 1 + } else { + this.tabIndex-- + } + this.render(false) + } + + _clickTabForward() { + if (this.tabIndex < this.numberOfTabs - 1) { + this.tabIndex++ + } else { + this.tabIndex = 0 + } + this.render(false) + } + + _onClickClose(ev) { + let name = ev.currentTarget.id + if (name === this._currentlyShowing) { + ev.currentTarget.checked = false + this._currentlyShowing = null + } else { + this._currentlyShowing = name + } + } + + /** + * A 'selectable' div in a collapsible was clicked. + * @param {*} ev + */ + _onSelect(ev) { + // find the toggle input above this element and remove the checked property + let div = $(ev.currentTarget).parent().closest('.collapsible-content') + let toggle = div.siblings('input') + $(toggle).prop('checked', false) + this._onSimpleList(ev, '') + } + + _onleave(ev) { + // console.log('onleave') + this.inside = false + this.bucket.SHOWING = false + this.close() + } + + _onenter(ev) { + if (!this.options.popOut) { + if (!this.inside) { + // console.log('onenter') + this.inside = true + $(ev.currentTarget).mouseleave(ev => this._onleave(ev)) + } + } + } + + async _onManualEntry(event) { + event.preventDefault() + event.stopPropagation() + let element = event.currentTarget + let parsed = parselink(element.value) + if (!!parsed.action && parsed.action.type === 'modifier') { + this.bucket.addModifier(parsed.action.mod, parsed.action.desc) + } else { + setTimeout(() => ui.notifications.info("Unable to determine modifier for '" + element.value + "'"), 200) + this.bucket.refresh() // WARNING: REQUIRED! or the world will crash... trust me. + } + } + + async _onList(event) { + this._onSimpleList(event, '') + } + + async _onTaskDifficulty(event) { + this._onSimpleList(event, 'Difficulty: ') + } + + async _onLighting(event) { + this._onSimpleList(event, 'Lighting: ') + } + + async _onSimpleList(event, prefix) { + event.preventDefault() + let element = event.currentTarget + let v = element.value + if (!v) v = element.textContent + v = v.trim() + let i = v.indexOf(' ') + this.SHOWING = true // Firefox seems to need this reset when showing a pulldown + this.bucket.addModifier(v.substring(0, i), prefix + v.substr(i + 1)) + } + + async _onGMbutton(event) { + event.preventDefault() + let element = event.currentTarget + let id = element.dataset.id + this.bucket.sendBucketToPlayer(id) + setTimeout(() => this.bucket.showOthers(), 1000) // Need time for clients to update...and + } + + async _onClickRemoveMod(event) { + event.preventDefault() + let element = event.currentTarget + let index = element.dataset.index + this.bucket.modifierStack.removeIndex(index) + this.bucket.refresh() + // this.render(false) + } } /** @@ -315,119 +315,119 @@ export default class ModifierBucketEditor extends Application { * This allows us to i18n the values. */ const ModifierLiterals = { - _statusModifiers: null, - - get StatusModifiers() { - if (this._statusModifiers === null) { - this._statusModifiers = [ - i18n('GURPS.modifierStatusAffliction'), - '*' + i18n('GURPS.modifierStatus'), - i18n('GURPS.modifierStatusShock1'), - i18n('GURPS.modifierStatusShock2'), - i18n('GURPS.modifierStatusShock3'), - i18n('GURPS.modifierStatusShock4'), - i18n('GURPS.modifierStatusStunned'), - '*' + i18n('GURPS.modifierAffliction'), - i18n('GURPS.modifierAfflictionCough'), - i18n('GURPS.modifierAfflictionCoughIQ'), - i18n('GURPS.modifierAfflictionDrowsy'), - i18n('GURPS.modifierAfflictionDrunk'), - i18n('GURPS.modifierAfflictionDrunkCR'), - i18n('GURPS.modifierAfflictionTipsy'), - i18n('GURPS.modifierAfflictionTipsyCR'), - i18n('GURPS.modifierAfflictionEuphoria'), - i18n('GURPS.modifierAfflictionNausea'), - i18n('GURPS.modifierAfflictionNauseaDef'), - i18n('GURPS.modifierAfflictionModerate'), - i18n('GURPS.modifierAfflictionModerateHPT'), - i18n('GURPS.modifierAfflictionSevere'), - i18n('GURPS.modifierAfflictionSevereHPT'), - i18n('GURPS.modifierAfflictionTerrible'), - i18n('GURPS.modifierAfflictionTerribleHPT'), - i18n('GURPS.modifierAfflictionRetch'), - ] - } - return this._statusModifiers - }, - - get CoverPostureModifiers() { - return [ - i18n('GURPS.modifierCoverPosture'), - '*' + i18n('GURPS.modifierCover'), - i18n('GURPS.modifierCoverHead'), - i18n('GURPS.modifierCoverHeadShoulder'), - i18n('GURPS.modifierCoverHalfExposed'), - i18n('GURPS.modifierCoverLight'), - i18n('GURPS.modifierCoverBehindFigure'), - i18n('GURPS.modifierCoverProne'), - i18n('GURPS.modifierCoverProneHeadUp'), - i18n('GURPS.modifierCoverProneHeadDown'), - i18n('GURPS.modifierCoverCrouch'), - i18n('GURPS.modifierCoverThroughHex'), - '*' + i18n('GURPS.modifierPosture'), - i18n('GURPS.modifierPostureProneMelee'), - i18n('GURPS.modifierPostureProneRanged'), - i18n('GURPS.modifierPostureProneDefend'), - i18n('GURPS.modifierPostureCrouchMelee'), - i18n('GURPS.modifierPostureCrouchRanged'), - i18n('GURPS.modifierPostureKneelMelee'), - i18n('GURPS.modifierPostureKneelDefend'), - ] - }, - - get SizeModifiers() { - return [ - i18n('GURPS.modifierSize'), - '*' + i18n('GURPS.modifierSizeDetail'), - game.i18n.format('GURPS.modifierSizeEntry', { SM: '-10', us: '1.5 inches', metric: '5 cm' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -9', us: ' 2 inches', metric: '7 cm' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -8', us: ' 3 inches', metric: '10 cm' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -7', us: ' 5 inches', metric: '15 cm' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -6', us: ' 8 inches', metric: '20 cm' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -5', us: ' 1 foot', metric: '30 cm' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -4', us: '1.5 feet', metric: '50 cm' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -3', us: ' 2 feet', metric: '70 cm' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -2', us: ' 1 yard/3 feet', metric: '1 meter' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -1', us: '1.5 yards/4.5 feet', metric: '1.5 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +0', us: ' 2 yards/6 feet', metric: '2 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +1', us: ' 3 yards/9 feet', metric: '3 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +2', us: ' 5 yards/15 feet', metric: '5 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +3', us: ' 7 yards/21 feet', metric: '7 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +4', us: ' 10 yards/30 feet', metric: '10 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +5', us: ' 15 yards/45 feet', metric: '15 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +6', us: ' 20 yards/60 feet', metric: '20 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +7', us: ' 30 yards/90 feet', metric: '30 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +8', us: ' 50 yards/150 feet', metric: '50 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +9', us: ' 70 yards/210 feet', metric: '70 meters' }), - game.i18n.format('GURPS.modifierSizeEntry', { SM: '+10', us: '100 yards/300 feet', metric: '100 meters' }), - ] - }, - - _HitLocationModifiers: [], - - get HitlocationModifiers() { - if (this._HitLocationModifiers.length === 0) { - this._HitLocationModifiers.push(i18n('GURPS.modifierHitLocation')) - - for (let loc in HitLocations.hitlocationRolls) { - let hit = HitLocations.hitlocationRolls[loc] - // Only include the items in the menu is skip is false (or empty) - if (!hit.skip) { - let parts = [displayMod(hit.penalty), i18n('GURPS.modifierToHit'), i18n('GURPS.hitLocation' + loc)] - - if (!!hit.desc) { - parts.push(`[${hit.desc.map(it => i18n(it)).join(', ')}]`) - } - this._HitLocationModifiers.push(parts.join(' ')) - } - } - } - return this._HitLocationModifiers - }, - - // Using back quote to allow \n in the string. Will make it easier to edit later (instead of array of strings) - get MeleeMods() { - return `[+4 ${i18n('GURPS.modifierDeterminedAttack')}] [PDF:${i18n('GURPS.pdfDeterminedAttack')}] + _statusModifiers: null, + + get StatusModifiers() { + if (this._statusModifiers === null) { + this._statusModifiers = [ + i18n('GURPS.modifierStatusAffliction'), + '*' + i18n('GURPS.modifierStatus'), + i18n('GURPS.modifierStatusShock1'), + i18n('GURPS.modifierStatusShock2'), + i18n('GURPS.modifierStatusShock3'), + i18n('GURPS.modifierStatusShock4'), + i18n('GURPS.modifierStatusStunned'), + '*' + i18n('GURPS.modifierAffliction'), + i18n('GURPS.modifierAfflictionCough'), + i18n('GURPS.modifierAfflictionCoughIQ'), + i18n('GURPS.modifierAfflictionDrowsy'), + i18n('GURPS.modifierAfflictionDrunk'), + i18n('GURPS.modifierAfflictionDrunkCR'), + i18n('GURPS.modifierAfflictionTipsy'), + i18n('GURPS.modifierAfflictionTipsyCR'), + i18n('GURPS.modifierAfflictionEuphoria'), + i18n('GURPS.modifierAfflictionNausea'), + i18n('GURPS.modifierAfflictionNauseaDef'), + i18n('GURPS.modifierAfflictionModerate'), + i18n('GURPS.modifierAfflictionModerateHPT'), + i18n('GURPS.modifierAfflictionSevere'), + i18n('GURPS.modifierAfflictionSevereHPT'), + i18n('GURPS.modifierAfflictionTerrible'), + i18n('GURPS.modifierAfflictionTerribleHPT'), + i18n('GURPS.modifierAfflictionRetch'), + ] + } + return this._statusModifiers + }, + + get CoverPostureModifiers() { + return [ + i18n('GURPS.modifierCoverPosture'), + '*' + i18n('GURPS.modifierCover'), + i18n('GURPS.modifierCoverHead'), + i18n('GURPS.modifierCoverHeadShoulder'), + i18n('GURPS.modifierCoverHalfExposed'), + i18n('GURPS.modifierCoverLight'), + i18n('GURPS.modifierCoverBehindFigure'), + i18n('GURPS.modifierCoverProne'), + i18n('GURPS.modifierCoverProneHeadUp'), + i18n('GURPS.modifierCoverProneHeadDown'), + i18n('GURPS.modifierCoverCrouch'), + i18n('GURPS.modifierCoverThroughHex'), + '*' + i18n('GURPS.modifierPosture'), + i18n('GURPS.modifierPostureProneMelee'), + i18n('GURPS.modifierPostureProneRanged'), + i18n('GURPS.modifierPostureProneDefend'), + i18n('GURPS.modifierPostureCrouchMelee'), + i18n('GURPS.modifierPostureCrouchRanged'), + i18n('GURPS.modifierPostureKneelMelee'), + i18n('GURPS.modifierPostureKneelDefend'), + ] + }, + + get SizeModifiers() { + return [ + i18n('GURPS.modifierSize'), + '*' + i18n('GURPS.modifierSizeDetail'), + game.i18n.format('GURPS.modifierSizeEntry', { SM: '-10', us: '1.5 inches', metric: '5 cm' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -9', us: ' 2 inches', metric: '7 cm' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -8', us: ' 3 inches', metric: '10 cm' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -7', us: ' 5 inches', metric: '15 cm' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -6', us: ' 8 inches', metric: '20 cm' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -5', us: ' 1 foot', metric: '30 cm' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -4', us: '1.5 feet', metric: '50 cm' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -3', us: ' 2 feet', metric: '70 cm' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -2', us: ' 1 yard/3 feet', metric: '1 meter' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' -1', us: '1.5 yards/4.5 feet', metric: '1.5 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +0', us: ' 2 yards/6 feet', metric: '2 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +1', us: ' 3 yards/9 feet', metric: '3 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +2', us: ' 5 yards/15 feet', metric: '5 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +3', us: ' 7 yards/21 feet', metric: '7 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +4', us: ' 10 yards/30 feet', metric: '10 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +5', us: ' 15 yards/45 feet', metric: '15 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +6', us: ' 20 yards/60 feet', metric: '20 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +7', us: ' 30 yards/90 feet', metric: '30 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +8', us: ' 50 yards/150 feet', metric: '50 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: ' +9', us: ' 70 yards/210 feet', metric: '70 meters' }), + game.i18n.format('GURPS.modifierSizeEntry', { SM: '+10', us: '100 yards/300 feet', metric: '100 meters' }), + ] + }, + + _HitLocationModifiers: [], + + get HitlocationModifiers() { + if (this._HitLocationModifiers.length === 0) { + this._HitLocationModifiers.push(i18n('GURPS.modifierHitLocation')) + + for (let loc in HitLocations.hitlocationRolls) { + let hit = HitLocations.hitlocationRolls[loc] + // Only include the items in the menu is skip is false (or empty) + if (!hit.skip) { + let parts = [displayMod(hit.penalty), i18n('GURPS.modifierToHit'), i18n('GURPS.hitLocation' + loc)] + + if (!!hit.desc) { + parts.push(`[${hit.desc.map(it => i18n(it)).join(', ')}]`) + } + this._HitLocationModifiers.push(parts.join(' ')) + } + } + } + return this._HitLocationModifiers + }, + + // Using back quote to allow \n in the string. Will make it easier to edit later (instead of array of strings) + get MeleeMods() { + return `[+4 ${i18n('GURPS.modifierDeterminedAttack')}] [PDF:${i18n('GURPS.pdfDeterminedAttack')}] [+4 ${i18n('GURPS.modifierTelegraphicAttack')}] [PDF:${i18n('GURPS.pdfTelegraphicAttack')}] [-2 ${i18n('GURPS.modifierDeceptiveAttack')}] [PDF:${i18n('GURPS.pdfDeceptiveAttack')}] [-4 ${i18n('GURPS.modifierMoveAttack')} *Max:9] [PDF:${i18n('GURPS.pdfMoveAttack')}] @@ -435,17 +435,17 @@ const ModifierLiterals = { ${horiz(i18n('GURPS.modifierExtraEffort'))} [PDF:${i18n('GURPS.pdfExtraEffort')}] [+2 ${i18n('GURPS.modifierMightyBlow')} *Cost 1FP] [PDF:${i18n('GURPS.pdfMightyBlow')}] [+0 ${i18n('GURPS.modifierHeroicCharge')} *Cost 1FP] [PDF:${i18n('GURPS.pdfHeroicCharge')}]` - }, + }, - get RangedMods() { - return `[+1 ${i18n('GURPS.aim')}] + get RangedMods() { + return `[+1 ${i18n('GURPS.aim')}] [+1 ${i18n('GURPS.modifierDeterminedAttack')}] [PDF:${i18n('GURPS.pdfDeterminedAttack')}] ${horiz(i18n('GURPS.actions'))} [${i18n('GURPS.modifierWillCheck')}]` - }, + }, - get DefenseMods() { - return `[+2 ${i18n('GURPS.allOutDefense')}] [PDF:${i18n('GURPS.pdfAllOutDefense')}] + get DefenseMods() { + return `[+2 ${i18n('GURPS.allOutDefense')}] [PDF:${i18n('GURPS.pdfAllOutDefense')}] [+1 ${i18n('GURPS.modifierShieldDB')}] [PDF:${i18n('GURPS.pdfShieldDB')}] [+2 ${i18n('GURPS.modifierDodgeAcrobatic')}] [PDF:${i18n('GURPS.pdfDodgeAcrobatic')}] [+3 ${i18n('GURPS.modifierDodgeDive')}] [PDF:${i18n('GURPS.pdfDodgeDive')}] @@ -458,12 +458,12 @@ const ModifierLiterals = { [-3 ${i18n('GURPS.modifierMaintainConcentration')}] ${horiz(i18n('GURPS.modifierExtraEffort'))} [+2 ${i18n('GURPS.modifierFeverishDef')} *Cost 1FP]` - // ${horiz(i18n('GURPS.actions'))} - // [WILL-3 ${i18n('GURPS.concentrationCheck')}]` - }, + // ${horiz(i18n('GURPS.actions'))} + // [WILL-3 ${i18n('GURPS.concentrationCheck')}]` + }, - get OtherMods1() { - return `["  +1    "+1] + get OtherMods1() { + return `["  +1    "+1] ["  +2    "+2] ["  +3    "+3] ["  +4    "+4] @@ -473,12 +473,12 @@ const ModifierLiterals = { ["  –3   "-3] ["  –4   "-4] ["  –5   "-5]` - }, + }, - get OtherMods2() { - return '' - }, - /** + get OtherMods2() { + return '' + }, + /** return `[+1 ${i18n('GURPS.modifierGMSaidSo')}] [-1 ${i18n('GURPS.modifierGMSaidSo')}] [+4 ${i18n('GURPS.modifierGMBlessed')}] @@ -486,216 +486,216 @@ const ModifierLiterals = { }, */ - get TaskDifficultyModifiers() { - return [ - i18n('GURPS.modifierTaskDifficulty'), - `+10 ${i18n('GURPS.modifierAutomatic')}`, - `+8 ${i18n('GURPS.modifierTrivial')}`, - `+6 ${i18n('GURPS.modifierVeryEasy')}`, - `+4 ${i18n('GURPS.modifierEasy')}`, - `+2 ${i18n('GURPS.modifierVeryFavorable')}`, - `+1 ${i18n('GURPS.modifierFavorable')}`, - `-1 ${i18n('GURPS.modifierUnfavorable')}`, - `-2 ${i18n('GURPS.modifierVeryUnfavorable')}`, - `-4 ${i18n('GURPS.modifierHard')}`, - `-6 ${i18n('GURPS.modifierVeryHard')}`, - `-8 ${i18n('GURPS.modifierDangerous')}`, - `-10 ${i18n('GURPS.modifierImpossible')}`, - ] - }, - - get LightingModifiers() { - return [ - i18n('GURPS.lighting'), - `-1 ${i18n('GURPS.modifierLightDim')}`, - `-2 ${i18n('GURPS.modifierLightTwilight')}`, - `-3 ${i18n('GURPS.modifierLightDeepTwilight')}`, - `-4 ${i18n('GURPS.modifierLightFullMoon')}`, - `-5 ${i18n('GURPS.modifierLightHalfMoon')}`, - `-6 ${i18n('GURPS.modifierLightQuarterMoon')}`, - `-7 ${i18n('GURPS.modifierLightStarlight')}`, - `-8 ${i18n('GURPS.modifierLightStarlightClouds')}`, - `-9 ${i18n('GURPS.modifierLightMoonless')}`, - `-10 ${i18n('GURPS.modifierLightNone')}`, - ] - }, - - get RateOfFireModifiers() { - return [ - i18n('GURPS.rateOfFire'), - `+1 ${i18n('GURPS.rof')}: 5-8`, - `+2 ${i18n('GURPS.rof')}: 9-12`, - `+3 ${i18n('GURPS.rof')}: 13-16`, - `+4 ${i18n('GURPS.rof')}: 17-24`, - `+5 ${i18n('GURPS.rof')}: 25-49`, - `+6 ${i18n('GURPS.rof')}: 50-99`, - ] - }, - - get EqtQualifyModifiers() { - return [ - i18n('GURPS.modifierQuality'), - `+4 ${i18n('GURPS.modifierQualityBest')}`, - `+2 ${i18n('GURPS.modifierQualityFine')}`, - `+1 ${i18n('GURPS.modifierQualityGood')}`, - `-2 ${i18n('GURPS.modifierQualityImprovised')}`, - `-5 ${i18n('GURPS.modifierQualityImprovTech')}`, - `-1 ${i18n('GURPS.modifierQualityMissing')}`, - `-5 ${i18n('GURPS.modifierQualityNone')}`, - `-10 ${i18n('GURPS.modifierQualityNoneTech')}`, - ] - }, + get TaskDifficultyModifiers() { + return [ + i18n('GURPS.modifierTaskDifficulty'), + `+10 ${i18n('GURPS.modifierAutomatic')}`, + `+8 ${i18n('GURPS.modifierTrivial')}`, + `+6 ${i18n('GURPS.modifierVeryEasy')}`, + `+4 ${i18n('GURPS.modifierEasy')}`, + `+2 ${i18n('GURPS.modifierVeryFavorable')}`, + `+1 ${i18n('GURPS.modifierFavorable')}`, + `-1 ${i18n('GURPS.modifierUnfavorable')}`, + `-2 ${i18n('GURPS.modifierVeryUnfavorable')}`, + `-4 ${i18n('GURPS.modifierHard')}`, + `-6 ${i18n('GURPS.modifierVeryHard')}`, + `-8 ${i18n('GURPS.modifierDangerous')}`, + `-10 ${i18n('GURPS.modifierImpossible')}`, + ] + }, + + get LightingModifiers() { + return [ + i18n('GURPS.lighting'), + `-1 ${i18n('GURPS.modifierLightDim')}`, + `-2 ${i18n('GURPS.modifierLightTwilight')}`, + `-3 ${i18n('GURPS.modifierLightDeepTwilight')}`, + `-4 ${i18n('GURPS.modifierLightFullMoon')}`, + `-5 ${i18n('GURPS.modifierLightHalfMoon')}`, + `-6 ${i18n('GURPS.modifierLightQuarterMoon')}`, + `-7 ${i18n('GURPS.modifierLightStarlight')}`, + `-8 ${i18n('GURPS.modifierLightStarlightClouds')}`, + `-9 ${i18n('GURPS.modifierLightMoonless')}`, + `-10 ${i18n('GURPS.modifierLightNone')}`, + ] + }, + + get RateOfFireModifiers() { + return [ + i18n('GURPS.rateOfFire'), + `+1 ${i18n('GURPS.rof')}: 5-8`, + `+2 ${i18n('GURPS.rof')}: 9-12`, + `+3 ${i18n('GURPS.rof')}: 13-16`, + `+4 ${i18n('GURPS.rof')}: 17-24`, + `+5 ${i18n('GURPS.rof')}: 25-49`, + `+6 ${i18n('GURPS.rof')}: 50-99`, + ] + }, + + get EqtQualifyModifiers() { + return [ + i18n('GURPS.modifierQuality'), + `+4 ${i18n('GURPS.modifierQualityBest')}`, + `+2 ${i18n('GURPS.modifierQualityFine')}`, + `+1 ${i18n('GURPS.modifierQualityGood')}`, + `-2 ${i18n('GURPS.modifierQualityImprovised')}`, + `-5 ${i18n('GURPS.modifierQualityImprovTech')}`, + `-1 ${i18n('GURPS.modifierQualityMissing')}`, + `-5 ${i18n('GURPS.modifierQualityNone')}`, + `-10 ${i18n('GURPS.modifierQualityNoneTech')}`, + ] + }, } const ModifiersForStatus = { - grapple: { - gen: ['[-4 to DX checks (Grappled)]'], - melee: [], - ranged: [], - defense: [], - }, - aim: { - gen: ['Aiming! Reference weapon ACC mod'], - melee: [], - ranged: [], - defense: [], - }, - retching: { - gen: ['[-5 to IQ/DX/PER checks (Retching)]'], - melee: [], - ranged: [], - defense: [], - }, - mild_pain: { - gen: ['[-1 to IQ/DX/CR rolls (Mild Pain)]'], - melee: [], - ranged: [], - defense: [], - }, - moderate_pain: { - gen: ['[-2 to IQ/DX/CR rolls (Moderate Pain)]'], - melee: [], - ranged: [], - defense: [], - }, - moderate_pain2: { - gen: ['[-3 to IQ/DX/CR rolls (Moderate Pain)]'], - melee: [], - ranged: [], - defense: [], - }, - severe_pain: { - gen: ['[-4 to IQ/DX/CR rolls (Severe Pain)]'], - melee: [], - ranged: [], - defense: [], - }, - severe_pain2: { - gen: ['[-5 to IQ/DX/CR rolls (Severe Pain)]'], - melee: [], - ranged: [], - defense: [], - }, - terrible_pain: { - gen: ['[-6 to IQ/DX/CR rolls (Terrible Pain)]'], - melee: [], - ranged: [], - defense: [], - }, - nauseated: { - gen: ['[-2 to All attributes (Nauseated)]'], - melee: [], - ranged: [], - defense: ['[-1 to active defense (Nauseated)]'], - }, - tipsy: { - gen: ['[-1 to IQ/DX checks (Tipsy)]', '[-2 to CR rolls (Tipsy)]'], - melee: [], - ranged: [], - defense: [], - }, - drunk: { - gen: ['[-2 to IQ/DX checks (Drunk)]', '[-4 to CR rolls (Drunk)]'], - melee: [], - ranged: [], - defense: [], - }, - drowsy: { - gen: ['[-2 to IQ/DX/CR rolls (Drowsy)]'], - melee: [], - ranged: [], - defense: [], - }, - coughing: { - gen: ['[-3 to DX checks (Coughing)]', '[-1 to IQ checks (Coughing)]'], - melee: [], - ranged: [], - defense: [], - }, - euphoria: { - gen: ['[-3 to IQ/DX/CR rolls (Euphoria)]'], - melee: [], - ranged: [], - defense: [], - }, - shock1: { - gen: ['[-1 to IQ/DX checks (Shock)]'], - melee: [], - ranged: [], - defense: [], - }, - shock2: { - gen: ['[-2 to IQ/DX checks (Shock)]'], - melee: [], - ranged: [], - defense: [], - }, - shock3: { - gen: ['[-3 to IQ/DX checks (Shock)]'], - melee: [], - ranged: [], - defense: [], - }, - shock4: { - gen: ['[-4 to IQ/DX checks (Shock)]'], - melee: [], - ranged: [], - defense: [], - }, - prone: { - gen: [], - melee: ['[-4 to hit Melee (Prone)]'], - ranged: ['[-2 to hit Ranged (Prone)]'], - defense: ['[-3 to active defenses (Prone)]'], - }, - stun: { - gen: [], - melee: [], - ranged: [], - defense: ['[-4 to active defenses (Stunned)]'], - }, - kneel: { - gen: [], - melee: ['[-2 to hit Melee (Kneeling)]'], - ranged: [], - defense: ['[-2 to active defenses (Kneeling)]'], - }, - crouch: { - gen: [], - melee: ['[-2 to hit Melee (Crouching)]'], - ranged: ['[-2 to hit Ranged (Crouching)]'], - defense: [], - }, - sit: { - gen: [], - melee: ['[-2 to hit Melee (Sitting)]'], - ranged: [], - defense: ['[-2 to active defenses (Sitting)]'], - }, - blind: { - gen: [], - melee: ['[-10 (Suddenly Blind)]', '[-6 (Blind)]'], - ranged: ['[-10 (Suddenly Blind)]', '[-6 (Blind)]'], - defense: [], - }, + grapple: { + gen: ['[-4 to DX checks (Grappled)]'], + melee: [], + ranged: [], + defense: [], + }, + aim: { + gen: ['Aiming! Reference weapon ACC mod'], + melee: [], + ranged: [], + defense: [], + }, + retching: { + gen: ['[-5 to IQ/DX/PER checks (Retching)]'], + melee: [], + ranged: [], + defense: [], + }, + mild_pain: { + gen: ['[-1 to IQ/DX/CR rolls (Mild Pain)]'], + melee: [], + ranged: [], + defense: [], + }, + moderate_pain: { + gen: ['[-2 to IQ/DX/CR rolls (Moderate Pain)]'], + melee: [], + ranged: [], + defense: [], + }, + moderate_pain2: { + gen: ['[-3 to IQ/DX/CR rolls (Moderate Pain)]'], + melee: [], + ranged: [], + defense: [], + }, + severe_pain: { + gen: ['[-4 to IQ/DX/CR rolls (Severe Pain)]'], + melee: [], + ranged: [], + defense: [], + }, + severe_pain2: { + gen: ['[-5 to IQ/DX/CR rolls (Severe Pain)]'], + melee: [], + ranged: [], + defense: [], + }, + terrible_pain: { + gen: ['[-6 to IQ/DX/CR rolls (Terrible Pain)]'], + melee: [], + ranged: [], + defense: [], + }, + nauseated: { + gen: ['[-2 to All attributes (Nauseated)]'], + melee: [], + ranged: [], + defense: ['[-1 to active defense (Nauseated)]'], + }, + tipsy: { + gen: ['[-1 to IQ/DX checks (Tipsy)]', '[-2 to CR rolls (Tipsy)]'], + melee: [], + ranged: [], + defense: [], + }, + drunk: { + gen: ['[-2 to IQ/DX checks (Drunk)]', '[-4 to CR rolls (Drunk)]'], + melee: [], + ranged: [], + defense: [], + }, + drowsy: { + gen: ['[-2 to IQ/DX/CR rolls (Drowsy)]'], + melee: [], + ranged: [], + defense: [], + }, + coughing: { + gen: ['[-3 to DX checks (Coughing)]', '[-1 to IQ checks (Coughing)]'], + melee: [], + ranged: [], + defense: [], + }, + euphoria: { + gen: ['[-3 to IQ/DX/CR rolls (Euphoria)]'], + melee: [], + ranged: [], + defense: [], + }, + shock1: { + gen: ['[-1 to IQ/DX checks (Shock)]'], + melee: [], + ranged: [], + defense: [], + }, + shock2: { + gen: ['[-2 to IQ/DX checks (Shock)]'], + melee: [], + ranged: [], + defense: [], + }, + shock3: { + gen: ['[-3 to IQ/DX checks (Shock)]'], + melee: [], + ranged: [], + defense: [], + }, + shock4: { + gen: ['[-4 to IQ/DX checks (Shock)]'], + melee: [], + ranged: [], + defense: [], + }, + prone: { + gen: [], + melee: ['[-4 to hit Melee (Prone)]'], + ranged: ['[-2 to hit Ranged (Prone)]'], + defense: ['[-3 to active defenses (Prone)]'], + }, + stun: { + gen: [], + melee: [], + ranged: [], + defense: ['[-4 to active defenses (Stunned)]'], + }, + kneel: { + gen: [], + melee: ['[-2 to hit Melee (Kneeling)]'], + ranged: [], + defense: ['[-2 to active defenses (Kneeling)]'], + }, + crouch: { + gen: [], + melee: ['[-2 to hit Melee (Crouching)]'], + ranged: ['[-2 to hit Ranged (Crouching)]'], + defense: [], + }, + sit: { + gen: [], + melee: ['[-2 to hit Melee (Sitting)]'], + ranged: [], + defense: ['[-2 to active defenses (Sitting)]'], + }, + blind: { + gen: [], + melee: ['[-10 (Suddenly Blind)]', '[-6 (Blind)]'], + ranged: ['[-10 (Suddenly Blind)]', '[-6 (Blind)]'], + defense: [], + }, } diff --git a/module/pdf-refs.js b/module/pdf-refs.js index fda4b6107..27b92c096 100644 --- a/module/pdf-refs.js +++ b/module/pdf-refs.js @@ -98,12 +98,11 @@ export function handlePdf(links) { book = 'BX' page = page - 335 } else page += 2 - } - else if (book === 'BX') { + } else if (book === 'BX') { if (setting === 'Combined') { - book = 'B' - page += 2 - } else page -= 335 + book = 'B' + page += 2 + } else page -= 335 } // @ts-ignore const pdf = ui.PDFoundry.findPDFDataByCode(book) diff --git a/module/smart-importer.js b/module/smart-importer.js index f61a8b16f..d974f68e1 100644 --- a/module/smart-importer.js +++ b/module/smart-importer.js @@ -1,13 +1,20 @@ -import { UniversalFileHandler } from './file-handlers/universal-file-handler.js'; +import { UniversalFileHandler } from './file-handlers/universal-file-handler.js' export class SmartImporter { - static async getFileForActor(actor) { - const file = this.actorToFileMap.get(actor); - const template = await getTemplate('systems/gurps/templates/import-gcs-or-gca.hbs'); - console.log(template) - return file ?? await UniversalFileHandler.getFile({template, templateOptions: {name: actor.name}, extensions: ['.xml', '.txt', '.gcs']}); - } - static setFileForActor(actor, file) { - this.actorToFileMap.set(actor, file); - } + static async getFileForActor(actor) { + const file = this.actorToFileMap.get(actor) + const template = await getTemplate('systems/gurps/templates/import-gcs-or-gca.hbs') + console.log(template) + return ( + file ?? + (await UniversalFileHandler.getFile({ + template, + templateOptions: { name: actor.name }, + extensions: ['.xml', '.txt', '.gcs'], + })) + ) + } + static setFileForActor(actor, file) { + this.actorToFileMap.set(actor, file) + } } -SmartImporter.actorToFileMap = new Map(); \ No newline at end of file +SmartImporter.actorToFileMap = new Map() diff --git a/module/speed-provider.js b/module/speed-provider.js index 3cfafc48a..e4ff380de 100644 --- a/module/speed-provider.js +++ b/module/speed-provider.js @@ -1,28 +1,28 @@ -export const init = function() { - //Add support for the Drag Ruler module: https://foundryvtt.com/packages/drag-ruler - Hooks.once('dragRuler.ready', SpeedProvider => { - class GURPSSpeedProvider extends SpeedProvider { - get colors() { - return [ - { id: 'walk', default: 0x00ff00, name: 'GURPS.dragrulerWalk' }, - { id: 'sprint', default: 0xffff00, name: 'GURPS.dragrulerSprint' }, - { id: 'fly', default: 0xff8000, name: 'GURPS.dragrulerFly' }, - ] - } +export const init = function () { + //Add support for the Drag Ruler module: https://foundryvtt.com/packages/drag-ruler + Hooks.once('dragRuler.ready', SpeedProvider => { + class GURPSSpeedProvider extends SpeedProvider { + get colors() { + return [ + { id: 'walk', default: 0x00ff00, name: 'GURPS.dragrulerWalk' }, + { id: 'sprint', default: 0xffff00, name: 'GURPS.dragrulerSprint' }, + { id: 'fly', default: 0xff8000, name: 'GURPS.dragrulerFly' }, + ] + } - /** - * @param {GurpsToken} token - */ - getRanges(token) { - const actordata = token.actor.system + /** + * @param {GurpsToken} token + */ + getRanges(token) { + const actordata = token.actor.system - const ranges = [ - { range: actordata.currentmove, color: 'walk' }, - { range: actordata.currentsprint, color: 'sprint' }, - ] - return ranges - } - } - dragRuler.registerSystem('gurps', GURPSSpeedProvider) - }) + const ranges = [ + { range: actordata.currentmove, color: 'walk' }, + { range: actordata.currentsprint, color: 'sprint' }, + ] + return ranges + } + } + dragRuler.registerSystem('gurps', GURPSSpeedProvider) + }) } diff --git a/module/token.js b/module/token.js index 1a7a6525d..a2dfca47b 100644 --- a/module/token.js +++ b/module/token.js @@ -4,239 +4,239 @@ import Maneuvers from './actor/maneuver.js' import GurpsActiveEffect from './effects/active-effect.js' import { i18n } from '../lib/utilities.js' -Hooks.once('init', async function() { - game.settings.register(SYSTEM_NAME, 'token-override-refresh-icon', { - name: i18n('GURPS.settingTokenOverrideRefresh', 'Override Token scaling'), - hint: i18n( - 'GURPS.settingHintTokenOverrideRefresh', - 'If "on", try to draw tokens to properly fit the hex grid. Overrides Foundry drawing functionality -- turn this off if there\'s any odd Foundry drawing behavior. Requires reloading the world.' - ), - scope: 'client', - config: true, - type: Boolean, - default: true, - onChange: value => console.log(`Token Override Refresh : ${value}`), - }) +Hooks.once('init', async function () { + game.settings.register(SYSTEM_NAME, 'token-override-refresh-icon', { + name: i18n('GURPS.settingTokenOverrideRefresh', 'Override Token scaling'), + hint: i18n( + 'GURPS.settingHintTokenOverrideRefresh', + 'If "on", try to draw tokens to properly fit the hex grid. Overrides Foundry drawing functionality -- turn this off if there\'s any odd Foundry drawing behavior. Requires reloading the world.' + ), + scope: 'client', + config: true, + type: Boolean, + default: true, + onChange: value => console.log(`Token Override Refresh : ${value}`), + }) }) let overrideRefresh = false -Hooks.once('ready', async function() { - overrideRefresh = game.settings.get(SYSTEM_NAME, 'token-override-refresh-icon') +Hooks.once('ready', async function () { + overrideRefresh = game.settings.get(SYSTEM_NAME, 'token-override-refresh-icon') }) export default class GurpsToken extends Token { - static ready() { - Hooks.on('createToken', GurpsToken._createToken) - } - - /** - * @param {GurpsToken} token - * @param {any} _data - * @param {any} _options - * @param {any} _userId - */ - static async _createToken(token, _data, _options, _userId) { - console.log(`create Token`) - let actor = /** @type {GurpsActor} */ (token.actor) - // data protect against bad tokens - if (!!actor) { - let maneuverText = actor.system.conditions.maneuver - actor.replaceManeuver(maneuverText) - } - } - - /** - * This is a decorator on the standard drawEffects method, that sets the maneuver icons based - * on level of detail and player visibility. - * @override - */ - async drawEffects() { - // get only the Maneuvers - const effects = Maneuvers.getActiveEffectManeuvers(this.actor?.temporaryEffects || []) - - if (effects && effects.length > 0) { - // restore the original token effects in case we've changed them - // @ts-ignore - effects.forEach(it => (it.data.icon = it.getFlag('gurps', 'icon'))) - - // GM and Owner always see the exact maneuver.. Otherwise: - if (!game.user?.isGM && !this.isOwner) { - const detail = game.settings.get(SYSTEM_NAME, SETTING_MANEUVER_DETAIL) - - if (detail !== 'Full') { - // if detail is not 'Full', always replace Feint with Attack - effects - .filter(it => 'feint' === /** @type {string} */ (it.getFlag('gurps', 'name'))) - // @ts-ignore - .forEach(it => (it.data.icon = it.getFlag('gurps', 'alt'))) - - if (detail === 'General') { - // replace every maneuver that has an alternate appearance with it - effects.forEach(it => { - let alt = it.getFlag('gurps', 'alt') - if (alt) it.data.icon = /** @type {string} */ (alt) - }) - } - } - } - - // Remove any icons based on visibility - const visibility = game.settings.get(SYSTEM_NAME, SETTING_MANEUVER_VISIBILITY) - - // set all icons to null - if (visibility === 'NoOne') effects.forEach(it => (it.data.icon = null)) - else if (visibility === 'GMAndOwner') - if (!game.user?.isGM && !this.isOwner) - // set icon to null if neither GM nor owner - effects.forEach(it => (it.data.icon = null)) - } // if (effects) - - // call the original method - const result = await super.drawEffects() - - return result - } - - /** - * @override - * @param {*} effect - * @param {*} options - */ - async toggleEffect(effect, options) { - // is this a Posture ActiveEffect? - if (effect.icon && foundry.utils.getProperty(effect, 'flags.gurps.effect.type') === 'posture') { - // see if there are other Posture ActiveEffects active - let existing = this.actor.effects.filter(e => e.getFlag('gurps', 'effect.type') === 'posture') - existing = existing.filter(e => e.getFlag('core', 'statusId') !== effect.id) - // if so, toggle them off: - for (let e of existing) { - let id = e.getFlag('core', 'statusId') - await super.toggleEffect(GURPS.StatusEffect.lookup(id)) - } - } - await super.toggleEffect(effect, options) - } - - async setEffectActive(name, active) { - // lookup effect - let effect = GURPS.StatusEffect.lookup(name) - - // check to see if it is active - let existing = this.actor.effects.find(e => e.getFlag('core', 'statusId') === name) - - if (active && !!existing) return - if (!active && !existing) return - - this.toggleEffect(effect) - } - - /** - * We use this function because maneuvers are special Active Effects: maneuvers don't apply - * outside of combat, and only one maneuver can be active simultaneously. So we really don't - * deactivate the old maneuver and then activate the new one -- we simply update the singleton - * maneuver data to match the new maneuver's data. - * - * @param {string} maneuverName - */ - async setManeuver(maneuverName) { - // if not in combat, do nothing - if (game.combats && game.combats.active) { - if (!game.combats.active.combatants.some(c => c.token?.id === this.id)) return - - // get the new maneuver's data - let maneuver = Maneuvers.get(maneuverName) - - if (!maneuver) { - this.actor._renderAllApps() - return - } - - // get all current active effects that are also maneuvers - let maneuvers = Maneuvers.getActiveEffectManeuvers(this.actor?.temporaryEffects) - - if (maneuvers && maneuvers.length === 1) { - // if there is a single active effect maneuver, update its data - // @ts-ignore - if (maneuvers[0].getFlag('gurps', 'name') !== maneuverName) maneuvers[0].update(maneuver) - } else if (!maneuvers || maneuvers.length === 0) { - // if there are no active effect maneuvers, add the new one - // @ts-ignore - await this.toggleEffect(maneuver, { active: true }) - } else { - // if there are more than one active effect maneuvers, that's a problem. Let's remove all of them and add the new one. - console.warn(`More than one maneuver found -- try to fix...`) - for (const existing of maneuvers) { - this._toggleManeuverActiveEffect(existing, { active: false }) - } - // @ts-ignore - await this.toggleEffect(maneuver, { active: true }) - } - } - } - - /** - * Assumes that this token is not in combat any more -- if so, updating the manuever will only - * update the actor's data model, and not add/update the active effect that represents that - * Maneuver. - */ - async removeManeuver() { - let actor = /** @type {GurpsActor} */ (this.actor) - - // get all Active Effects that are also Maneuvers - let maneuvers = Maneuvers.getActiveEffectManeuvers(this.actor?.temporaryEffects) - for (const m of maneuvers) { - this._toggleManeuverActiveEffect(m, { active: false }) - } - } - - /** - * @param {ActiveEffect} effect - */ - async _toggleManeuverActiveEffect(effect, options = {}) { - let data = Maneuvers.get(GurpsActiveEffect.getName(effect)) - await this.toggleEffect(data, options) - } - - /** - * Size and display the Token Icon - * - * FIXME This is a horrible hack to fix Foundry's scaling of figures in a hex grid. By default, Foundry - * scales tokens to fit the width of the grid if the "aspect" of the token is equal to or greater - * than 1, where aspect is width/height. For Hex Columns, the width is measured from vertex to vertex; - * for Hex Rows, the width is measured flat-side to flat-side. This results in tokens overflowing - * a Hex Column, taking up space in adjacent hexes. This "fixes" that for a subset of tokens -- where - * the token has an aspect of 1, we scale the token based on the smaller of the two dimensions. - */ - _refreshIcon() { - // let override = game.settings.get(Settings.SYSTEM_NAME, 'token-override-refresh-icon') - if (!overrideRefresh) return super._refreshIcon() - - // Size the texture aspect ratio within the token frame - const tex = this.texture - let aspect = tex.width / tex.height - const scale = this.icon.scale - - if (aspect == 1) { - if (this.w > this.h) aspect = 0.9 // force scaling by height when width is greater than height - } - - if (aspect >= 1) { - this.icon.width = this.w * this.data.scale - scale.y = Number(scale.x) - } else { - this.icon.height = this.h * this.data.scale - scale.x = Number(scale.y) - } - - // Mirror horizontally or vertically - this.icon.scale.x = Math.abs(this.icon.scale.x) * (this.data.mirrorX ? -1 : 1) - this.icon.scale.y = Math.abs(this.icon.scale.y) * (this.data.mirrorY ? -1 : 1) - - // Set rotation, position, and opacity - this.icon.rotation = this.data.lockRotation ? 0 : Math.toRadians(this.data.rotation) - this.icon.position.set(this.w / 2, this.h / 2) - this.icon.alpha = this.data.hidden ? Math.min(this.data.alpha, 0.5) : this.data.alpha - this.icon.visible = true - } + static ready() { + Hooks.on('createToken', GurpsToken._createToken) + } + + /** + * @param {GurpsToken} token + * @param {any} _data + * @param {any} _options + * @param {any} _userId + */ + static async _createToken(token, _data, _options, _userId) { + console.log(`create Token`) + let actor = /** @type {GurpsActor} */ (token.actor) + // data protect against bad tokens + if (!!actor) { + let maneuverText = actor.system.conditions.maneuver + actor.replaceManeuver(maneuverText) + } + } + + /** + * This is a decorator on the standard drawEffects method, that sets the maneuver icons based + * on level of detail and player visibility. + * @override + */ + async drawEffects() { + // get only the Maneuvers + const effects = Maneuvers.getActiveEffectManeuvers(this.actor?.temporaryEffects || []) + + if (effects && effects.length > 0) { + // restore the original token effects in case we've changed them + // @ts-ignore + effects.forEach(it => (it.data.icon = it.getFlag('gurps', 'icon'))) + + // GM and Owner always see the exact maneuver.. Otherwise: + if (!game.user?.isGM && !this.isOwner) { + const detail = game.settings.get(SYSTEM_NAME, SETTING_MANEUVER_DETAIL) + + if (detail !== 'Full') { + // if detail is not 'Full', always replace Feint with Attack + effects + .filter(it => 'feint' === /** @type {string} */ (it.getFlag('gurps', 'name'))) + // @ts-ignore + .forEach(it => (it.data.icon = it.getFlag('gurps', 'alt'))) + + if (detail === 'General') { + // replace every maneuver that has an alternate appearance with it + effects.forEach(it => { + let alt = it.getFlag('gurps', 'alt') + if (alt) it.data.icon = /** @type {string} */ (alt) + }) + } + } + } + + // Remove any icons based on visibility + const visibility = game.settings.get(SYSTEM_NAME, SETTING_MANEUVER_VISIBILITY) + + // set all icons to null + if (visibility === 'NoOne') effects.forEach(it => (it.data.icon = null)) + else if (visibility === 'GMAndOwner') + if (!game.user?.isGM && !this.isOwner) + // set icon to null if neither GM nor owner + effects.forEach(it => (it.data.icon = null)) + } // if (effects) + + // call the original method + const result = await super.drawEffects() + + return result + } + + /** + * @override + * @param {*} effect + * @param {*} options + */ + async toggleEffect(effect, options) { + // is this a Posture ActiveEffect? + if (effect.icon && foundry.utils.getProperty(effect, 'flags.gurps.effect.type') === 'posture') { + // see if there are other Posture ActiveEffects active + let existing = this.actor.effects.filter(e => e.getFlag('gurps', 'effect.type') === 'posture') + existing = existing.filter(e => e.getFlag('core', 'statusId') !== effect.id) + // if so, toggle them off: + for (let e of existing) { + let id = e.getFlag('core', 'statusId') + await super.toggleEffect(GURPS.StatusEffect.lookup(id)) + } + } + await super.toggleEffect(effect, options) + } + + async setEffectActive(name, active) { + // lookup effect + let effect = GURPS.StatusEffect.lookup(name) + + // check to see if it is active + let existing = this.actor.effects.find(e => e.getFlag('core', 'statusId') === name) + + if (active && !!existing) return + if (!active && !existing) return + + this.toggleEffect(effect) + } + + /** + * We use this function because maneuvers are special Active Effects: maneuvers don't apply + * outside of combat, and only one maneuver can be active simultaneously. So we really don't + * deactivate the old maneuver and then activate the new one -- we simply update the singleton + * maneuver data to match the new maneuver's data. + * + * @param {string} maneuverName + */ + async setManeuver(maneuverName) { + // if not in combat, do nothing + if (game.combats && game.combats.active) { + if (!game.combats.active.combatants.some(c => c.token?.id === this.id)) return + + // get the new maneuver's data + let maneuver = Maneuvers.get(maneuverName) + + if (!maneuver) { + this.actor._renderAllApps() + return + } + + // get all current active effects that are also maneuvers + let maneuvers = Maneuvers.getActiveEffectManeuvers(this.actor?.temporaryEffects) + + if (maneuvers && maneuvers.length === 1) { + // if there is a single active effect maneuver, update its data + // @ts-ignore + if (maneuvers[0].getFlag('gurps', 'name') !== maneuverName) maneuvers[0].update(maneuver) + } else if (!maneuvers || maneuvers.length === 0) { + // if there are no active effect maneuvers, add the new one + // @ts-ignore + await this.toggleEffect(maneuver, { active: true }) + } else { + // if there are more than one active effect maneuvers, that's a problem. Let's remove all of them and add the new one. + console.warn(`More than one maneuver found -- try to fix...`) + for (const existing of maneuvers) { + this._toggleManeuverActiveEffect(existing, { active: false }) + } + // @ts-ignore + await this.toggleEffect(maneuver, { active: true }) + } + } + } + + /** + * Assumes that this token is not in combat any more -- if so, updating the manuever will only + * update the actor's data model, and not add/update the active effect that represents that + * Maneuver. + */ + async removeManeuver() { + let actor = /** @type {GurpsActor} */ (this.actor) + + // get all Active Effects that are also Maneuvers + let maneuvers = Maneuvers.getActiveEffectManeuvers(this.actor?.temporaryEffects) + for (const m of maneuvers) { + this._toggleManeuverActiveEffect(m, { active: false }) + } + } + + /** + * @param {ActiveEffect} effect + */ + async _toggleManeuverActiveEffect(effect, options = {}) { + let data = Maneuvers.get(GurpsActiveEffect.getName(effect)) + await this.toggleEffect(data, options) + } + + /** + * Size and display the Token Icon + * + * FIXME This is a horrible hack to fix Foundry's scaling of figures in a hex grid. By default, Foundry + * scales tokens to fit the width of the grid if the "aspect" of the token is equal to or greater + * than 1, where aspect is width/height. For Hex Columns, the width is measured from vertex to vertex; + * for Hex Rows, the width is measured flat-side to flat-side. This results in tokens overflowing + * a Hex Column, taking up space in adjacent hexes. This "fixes" that for a subset of tokens -- where + * the token has an aspect of 1, we scale the token based on the smaller of the two dimensions. + */ + _refreshIcon() { + // let override = game.settings.get(Settings.SYSTEM_NAME, 'token-override-refresh-icon') + if (!overrideRefresh) return super._refreshIcon() + + // Size the texture aspect ratio within the token frame + const tex = this.texture + let aspect = tex.width / tex.height + const scale = this.icon.scale + + if (aspect == 1) { + if (this.w > this.h) aspect = 0.9 // force scaling by height when width is greater than height + } + + if (aspect >= 1) { + this.icon.width = this.w * this.data.scale + scale.y = Number(scale.x) + } else { + this.icon.height = this.h * this.data.scale + scale.x = Number(scale.y) + } + + // Mirror horizontally or vertically + this.icon.scale.x = Math.abs(this.icon.scale.x) * (this.data.mirrorX ? -1 : 1) + this.icon.scale.y = Math.abs(this.icon.scale.y) * (this.data.mirrorY ? -1 : 1) + + // Set rotation, position, and opacity + this.icon.rotation = this.data.lockRotation ? 0 : Math.toRadians(this.data.rotation) + this.icon.position.set(this.w / 2, this.h / 2) + this.icon.alpha = this.data.hidden ? Math.min(this.data.alpha, 0.5) : this.data.alpha + this.icon.visible = true + } } diff --git a/styles/apps.css b/styles/apps.css index 8e77319b1..58e6e4221 100644 --- a/styles/apps.css +++ b/styles/apps.css @@ -168,7 +168,7 @@ body { } .roll-message .aside .aside-text { -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ font-weight: normal; } @@ -399,14 +399,14 @@ ul#result-effects li { background-color: rgba(255, 255, 240, 0.8); border: 1px solid #b5b3a4; border-radius: 3px; -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ padding-left: 5.5px; padding-right: 5.5px; margin-right: 4px; } [name='result-effect'] .with-send-button { -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ } [name='result-effect'] > div { @@ -511,12 +511,12 @@ ul#result-effects li { .damage-message .label-toggle { font-size: small; -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ } .damage-message .label-toggle:after { font: normal normal normal 12px/1; -/* font-family: 'Font Awesome 5 Free' */ + /* font-family: 'Font Awesome 5 Free' */ font-weight: 900; content: '\f078'; float: right; @@ -524,20 +524,20 @@ ul#result-effects li { .damage-message .toggle:checked + .label-toggle:after { font: normal normal normal 12px/1; -/* font-family: 'Font Awesome 5 Free'; */ + /* font-family: 'Font Awesome 5 Free'; */ font-weight: 900; content: '\f077'; float: right; } .gga-chat-message * { -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ font-size: 13px; } .gga-chat-message .label-toggle { font-size: x-small; -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ padding: 0.25em; background-color: rgba(0, 0, 0, 0.5); margin-top: 4px; @@ -552,7 +552,7 @@ ul#result-effects li { .gga-chat-message .label-toggle:after { font: normal normal normal 12px/1; -/* font-family: 'Font Awesome 5 Free'; */ + /* font-family: 'Font Awesome 5 Free'; */ font-weight: 900; content: '\f078'; color: lightgray; @@ -561,7 +561,7 @@ ul#result-effects li { .gga-chat-message .toggle:checked + .label-toggle:after { font: normal normal normal 12px/1; -/* font-family: 'Font Awesome 5 Free'; */ + /* font-family: 'Font Awesome 5 Free'; */ font-weight: 900; content: '\f077'; color: lightgray; @@ -622,7 +622,7 @@ ul#result-effects li { } .gga-app button.with-icon > i > span { -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ font-weight: 400; } @@ -677,7 +677,7 @@ ul#result-effects li { border-bottom: 1px dotted grey; /* text-transform: uppercase; */ /* color: darkgrey */ -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ } /* Tooltip text */ @@ -1150,7 +1150,7 @@ active-effects-list .section h3 { height: 1.2rem; flex: 0; border: 1px solid grey; -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ cursor: pointer; } @@ -1325,7 +1325,7 @@ select#maneuver option { min-width: 0.75rem; flex: 0; /* border: 1px solid grey; */ -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ padding: 0px 0px 0px 0px; cursor: pointer; } @@ -1423,14 +1423,14 @@ button.equipmentbutton:last-child { .navigation-view > span:before { content: '\f101'; /* this is your text. You can also use UTF-8 character codes as I do here */ -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ font-weight: 900; color: white; } .navigate-area input:checked + .navigate-bar > .navigation-view > span:before { content: '\f100'; -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ font-weight: 900; color: white; } @@ -1825,7 +1825,7 @@ button.equipmentbutton:last-child { } .removemod:hover::after { -/* font-family: 'Font Awesome 5 Free'; */ + /* font-family: 'Font Awesome 5 Free'; */ content: '\f1f8'; display: inline-block; padding-right: 3px; @@ -1929,12 +1929,12 @@ button.equipmentbutton:last-child { padding: 0.35em; border-radius: 0.25em; background: rgba(255, 255, 240, 0.8); -/* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ + /* font-family: 'Font Awesome 5 Free', Roboto, sans-serif; */ } .modtooltip .collapsible-wrapper > label:after { font: normal normal normal 12px/1; -/* font-family: 'Font Awesome 5 Free'; */ + /* font-family: 'Font Awesome 5 Free'; */ font-weight: 900; content: '\f078'; float: right; @@ -1947,7 +1947,7 @@ button.equipmentbutton:last-child { .modtooltip .collapsible-wrapper > input:checked + label:after { font: normal normal normal 12px/1; -/* font-family: 'Font Awesome 5 Free'; */ + /* font-family: 'Font Awesome 5 Free'; */ font-weight: 900; content: '\f077'; float: right; diff --git a/styles/color-character-sheet.css b/styles/color-character-sheet.css index 4ad457e07..146ea1a37 100644 --- a/styles/color-character-sheet.css +++ b/styles/color-character-sheet.css @@ -1,31 +1,31 @@ -.gurps-sheet-colors header { - display: inline-block !important; - vertical-align: top; - width: 100px; -} - -.gurps-sheet-colors .override-color { - text-align: center; - display: inline-block !important; - vertical-align: top; - width: 75px; -} - -.gurps-sheet-colors .the-colors { - display: inline-block !important; - vertical-align: top; - width: 428px; - padding-left: 50px; -} - -.gurps-sheet-colors .pick-colors { - display: inline-block !important; - vertical-align: top; -} - -.gurps-sheet-colors .area-colors { - display: inline-block !important; - vertical-align: top; - padding-top: 6px; - padding-left: 50px; -} +.gurps-sheet-colors header { + display: inline-block !important; + vertical-align: top; + width: 100px; +} + +.gurps-sheet-colors .override-color { + text-align: center; + display: inline-block !important; + vertical-align: top; + width: 75px; +} + +.gurps-sheet-colors .the-colors { + display: inline-block !important; + vertical-align: top; + width: 428px; + padding-left: 50px; +} + +.gurps-sheet-colors .pick-colors { + display: inline-block !important; + vertical-align: top; +} + +.gurps-sheet-colors .area-colors { + display: inline-block !important; + vertical-align: top; + padding-top: 6px; + padding-left: 50px; +} diff --git a/styles/css_boilerplate.css b/styles/css_boilerplate.css index ae1e654d6..428283100 100755 --- a/styles/css_boilerplate.css +++ b/styles/css_boilerplate.css @@ -1,16 +1,16 @@ -@import url("https://fonts.googleapis.com/css2?family=Martel:wght@400;800&family=Roboto:wght@300;400;500&display=swap"); +@import url('https://fonts.googleapis.com/css2?family=Martel:wght@400;800&family=Roboto:wght@300;400;500&display=swap'); /* Global styles */ #navigation { - top: 20px; left: 180px; - width: calc(100% - 510px); + top: 20px; + left: 180px; + width: calc(100% - 510px); } .window-app { - font-family: "Roboto", sans-serif; + font-family: 'Roboto', sans-serif; } - .directory-list .directory-item.actor { align-items: center; } @@ -22,7 +22,7 @@ .rollable:hover, .rollable:focus { color: #000; -/* text-shadow: 0 0 10px red; */ + /* text-shadow: 0 0 10px red; */ cursor: pointer; } @@ -132,7 +132,7 @@ /* Styles limited to boilerplate sheets */ .boilerplate .item-form { - font-family: "Roboto", sans-serif; + font-family: 'Roboto', sans-serif; } .boilerplate .sheet-header { @@ -209,8 +209,8 @@ .boilerplate .tabs { height: 40px; - border-top: 1px solid #AAA; - border-bottom: 1px solid #AAA; + border-top: 1px solid #aaa; + border-bottom: 1px solid #aaa; } .boilerplate .tabs .item { @@ -238,7 +238,7 @@ height: 30px; line-height: 24px; padding: 3px 0; - border-bottom: 1px solid #BBB; + border-bottom: 1px solid #bbb; } .boilerplate .items-list .item .item-image { diff --git a/styles/simple.css b/styles/simple.css index 43d6b5245..eefb5573c 100755 --- a/styles/simple.css +++ b/styles/simple.css @@ -2,8 +2,8 @@ --controlhover: 0 0 5px red; --sectiongap: 0.05in; --smallemgap: 0.2em; -/* --fontawesomefamily: 'Font Awesome 5 Free', Roboto, sans-serif; */ - --fontawesomefamily: "fontawesome"; + /* --fontawesomefamily: 'Font Awesome 5 Free', Roboto, sans-serif; */ + --fontawesomefamily: 'fontawesome'; --lightgrey: rgba(0, 0, 0, 0.3); --headerfontsize: 80%; --standardborder: 1px solid black; @@ -2047,7 +2047,7 @@ body { .roll-result .roll-value, .roll-result .roll-detail { /* font-family: var(--fontawesomefamily); */ - font-family: inherit; + font-family: inherit; } .roll-result .roll-detail { diff --git a/styles/simple.less b/styles/simple.less index ba89e194a..be8b99b35 100755 --- a/styles/simple.less +++ b/styles/simple.less @@ -53,8 +53,8 @@ /* Sheet Tabs */ .tabs { height: 40px; - border-top: 1px solid #AAA; - border-bottom: 1px solid #AAA; + border-top: 1px solid #aaa; + border-bottom: 1px solid #aaa; .item { line-height: 40px; @@ -75,7 +75,8 @@ } } - .editor, .editor-content { + .editor, + .editor-content { height: 100%; } @@ -90,7 +91,7 @@ height: 30px; line-height: 24px; padding: 3px 0; - border-bottom: 1px solid #BBB; + border-bottom: 1px solid #bbb; img { flex: 0 0 24px; @@ -112,7 +113,7 @@ padding: 5px; margin: 5px 0; background: rgba(0, 0, 0, 0.05); - border: 1px solid #AAA; + border: 1px solid #aaa; border-radius: 2px; text-align: center; font-weight: bold; @@ -138,7 +139,7 @@ background: transparent; border: none; border-radius: 0; - border-bottom: 1px solid #AAA; + border-bottom: 1px solid #aaa; } a.attribute-control { @@ -155,7 +156,6 @@ min-height: 420px; } - .worldbuilding.sheet.item { min-width: 460px; min-height: 400px; diff --git a/styles/simplified.css b/styles/simplified.css index 24181b905..36fb29248 100755 --- a/styles/simplified.css +++ b/styles/simplified.css @@ -39,7 +39,7 @@ .simple_form { width: 100% !important; height: 100% !important; - } +} .simple_container { width: 100% !important; @@ -49,23 +49,23 @@ grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 0.3fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; gap: 5px 5px; grid-template-areas: - "ST ST-desc ST-desc Portrait Portrait" - "DX DX-desc DX-desc Portrait Portrait" - "IQ IQ-desc IQ-desc Portrait Portrait" - "HT HT-desc HT-desc Portrait Portrait" - "Shield Body Heart Notes Notes" - "Shield Body Heart Notes Notes" - "Shield Body Heart Notes Notes" - "Defend Armor HP Notes Notes" - "Attacks Attacks Attacks Skills Skills" - "Attacks Attacks Attacks Skills Skills" - "Attacks Attacks Attacks Skills Skills" - "Attacks Attacks Attacks Skills Skills" - "Attacks Attacks Attacks Skills Skills" - "Eqt Eqt Eqt Ads Ads" - "Eqt Eqt Eqt Ads Ads" - "Eqt Eqt Eqt Ads Ads" - "Eqt Eqt Eqt Ads Ads"; + 'ST ST-desc ST-desc Portrait Portrait' + 'DX DX-desc DX-desc Portrait Portrait' + 'IQ IQ-desc IQ-desc Portrait Portrait' + 'HT HT-desc HT-desc Portrait Portrait' + 'Shield Body Heart Notes Notes' + 'Shield Body Heart Notes Notes' + 'Shield Body Heart Notes Notes' + 'Defend Armor HP Notes Notes' + 'Attacks Attacks Attacks Skills Skills' + 'Attacks Attacks Attacks Skills Skills' + 'Attacks Attacks Attacks Skills Skills' + 'Attacks Attacks Attacks Skills Skills' + 'Attacks Attacks Attacks Skills Skills' + 'Eqt Eqt Eqt Ads Ads' + 'Eqt Eqt Eqt Ads Ads' + 'Eqt Eqt Eqt Ads Ads' + 'Eqt Eqt Eqt Ads Ads'; } .simple_lrg { @@ -120,7 +120,6 @@ align-content: center; } - .simple_HT-desc { grid-area: HT-desc; display: flex; @@ -134,30 +133,27 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - - border-top: 1px solid #CCCCCC; + + border-top: 1px solid #cccccc; border-right: 1px solid #333333; border-bottom: 1px solid #333333; - border-left: 1px solid #CCCCCC; + border-left: 1px solid #cccccc; border-radius: 5px; - } .simple_container .gurpslink, .simple_container .glinkmod, .simple_container .rollable { - - border-top: 1px solid #CCCCCC; + border-top: 1px solid #cccccc; border-right: 1px solid #333333; border-bottom: 1px solid #333333; - border-left: 1px solid #CCCCCC; + border-left: 1px solid #cccccc; border-radius: 5px; - - } +} .simple_Shield { grid-area: Shield; - background-image: url("../ui/ri_1.webp"); + background-image: url('../ui/ri_1.webp'); background-repeat: no-repeat; background-size: contain; background-position: center; @@ -169,7 +165,7 @@ .simple_Body { grid-area: Body; text-align: center; - background-image: url("../ui/ri_3.webp"); + background-image: url('../ui/ri_3.webp'); background-repeat: no-repeat; background-size: contain; background-position: center; @@ -191,7 +187,7 @@ width: 100%; height: 100%; text-align: center; - background-image: url("../ui/ri_2t.webp"); + background-image: url('../ui/ri_2t.webp'); background-repeat: no-repeat; background-size: cover; background-position: center; @@ -205,7 +201,6 @@ padding: 0px 0px 30px 0px; } - .simple_Defend { grid-area: Defend; font-weight: bold; @@ -317,10 +312,10 @@ text-decoration: none; background-color: rgb(255, 255, 128) !important; color: black; - border-top: 2px solid #CCCCCC; + border-top: 2px solid #cccccc; border-right: 2px solid #333333; border-bottom: 2px solid #333333; - border-left: 2px solid #CCCCCC; + border-left: 2px solid #cccccc; border-radius: 5px; overflow: visible; z-index: 99; @@ -334,10 +329,10 @@ background-color: rgb(255, 255, 128) !important; color: black; padding: 0px 3px 0px 3px; - border-top: 2px solid #CCCCCC; + border-top: 2px solid #cccccc; border-right: 2px solid #333333; border-bottom: 2px solid #333333; - border-left: 2px solid #CCCCCC; + border-left: 2px solid #cccccc; border-radius: 5px; margin: -2px -4px 0px -2px; overflow: visible; @@ -349,16 +344,16 @@ text-decoration: none; color: black; background-color: rgb(255, 255, 128) !important; - border-bottom: 2px solid #CCCCCC; + border-bottom: 2px solid #cccccc; border-left: 2px solid #333333; border-top: 2px solid #333333; - border-right: 2px solid #CCCCCC; + border-right: 2px solid #cccccc; border-radius: 8px; overflow: visible; z-index: 99; margin: 5px; padding: 10px; - } +} .simple_icon:active { cursor: pointer; @@ -366,14 +361,14 @@ color: black; background-color: rgb(255, 255, 128) !important; padding: 0px 3px 0px 3px; - border-bottom: 2px solid #CCCCCC; + border-bottom: 2px solid #cccccc; border-left: 2px solid #333333; border-top: 2px solid #333333; - border-right: 2px solid #CCCCCC; + border-right: 2px solid #cccccc; border-radius: 8px; margin: -2px -5px 0px -3px; overflow: visible; - z-index: 99 + z-index: 99; } .simple_input { @@ -386,5 +381,3 @@ border: 2px solid black; background-color: rgb(255, 255, 175); } - - diff --git a/styles/tinycolorpicker.css b/styles/tinycolorpicker.css index 6441eb397..1b0522c63 100644 --- a/styles/tinycolorpicker.css +++ b/styles/tinycolorpicker.css @@ -1,66 +1,66 @@ -#colorPicker { - width: 30px; - height: 30px; - position: relative; - clear: both; -} - -#colorPicker .track { - background: #000 url('images/hex-colormap.gif') no-repeat 50% 50%; - height: 201px; - width: 236px; - padding: 10px; - position: absolute; - cursor: crosshair; - float: left; - left: 32px; - top: 25px; - display: none; - border: 1px solid #ccc; - z-index: 10000; -} - -#colorPicker .color { - width: 25px; - height: 25px; - padding: 0; - border: 0 solid #ccc; - display: block; - position: relative; - z-index: 10001; - background-color: #efefef; - -webkit-border-radius: 27px; - -moz-border-radius: 27px; - border-radius: 27px; - cursor: pointer; -} - -#colorPicker .colorInner { - width: 25px; - height: 25px; - -webkit-border-radius: 27px; - -moz-border-radius: 27px; - border-radius: 27px; -} - -#colorPicker .dropdown { - list-style: none; - display: none; - width: 27px; - position: absolute; - top: 28px; - border: 1px solid #ccc; - left: 0; - z-index: 1000; -} - -#colorPicker .dropdown li { - height: 25px; - cursor: pointer; -} - -#colorPicker canvas { - left: 0; - top: 0; - position: absolute; -} +#colorPicker { + width: 30px; + height: 30px; + position: relative; + clear: both; +} + +#colorPicker .track { + background: #000 url('images/hex-colormap.gif') no-repeat 50% 50%; + height: 201px; + width: 236px; + padding: 10px; + position: absolute; + cursor: crosshair; + float: left; + left: 32px; + top: 25px; + display: none; + border: 1px solid #ccc; + z-index: 10000; +} + +#colorPicker .color { + width: 25px; + height: 25px; + padding: 0; + border: 0 solid #ccc; + display: block; + position: relative; + z-index: 10001; + background-color: #efefef; + -webkit-border-radius: 27px; + -moz-border-radius: 27px; + border-radius: 27px; + cursor: pointer; +} + +#colorPicker .colorInner { + width: 25px; + height: 25px; + -webkit-border-radius: 27px; + -moz-border-radius: 27px; + border-radius: 27px; +} + +#colorPicker .dropdown { + list-style: none; + display: none; + width: 27px; + position: absolute; + top: 28px; + border: 1px solid #ccc; + left: 0; + z-index: 1000; +} + +#colorPicker .dropdown li { + height: 25px; + cursor: pointer; +} + +#colorPicker canvas { + left: 0; + top: 0; + position: absolute; +} diff --git a/system.json b/system.json index 73144c98a..6a5cc2bce 100755 --- a/system.json +++ b/system.json @@ -1,82 +1,77 @@ { - "id": "gurps", - "title": "GURPS 4th Ed. Game Aid (Unofficial)", - "description": "A game aid to help play GURPS 4e for Foundry VTT", - "version": "0.15.0", - "authors": [ - { - "name": "Chris Normand", - "email": "", - "discord": "Nose66#6689" - }, - { - "name": "M. Jeff Wilson", - "email": "", - "discord": "Nick Coffin, PI#8616" - }, - { - "name": "Mikolaj Tomczynski", - "email": "mikolajtomczynski@gmail.com", - "discord": "neck#0001" - } - ], - "compatibility": { - "minimum": 10, - "verified": "10.283", - "maximum": 10 - }, - "templateVersion": 2, - "esmodules": [ - "module/gurps.js", - "lib/xregexp-all.js" - ], - "scripts": [ - "scripts/jquery.tinycolorpicker.js" - ], - "styles": [ - "styles/simple.css", - "styles/css_boilerplate.css", - "styles/simplified.css", - "styles/npc-input.css", - "styles/apps.css", - "styles/color-character-sheet.css", - "styles/tinycolorpicker.css" - ], - "packs": [], - "languages": [ - { - "lang": "en", - "name": "English", - "path": "lang/en.json" - }, - { - "lang": "pt-BR", - "name": "Português (Brasil)", - "path": "lang/pt_br.json" - }, - { - "lang": "de", - "name": "Deutsch (German)", - "path": "lang/de.json" - }, - { - "lang": "ru", - "name": "русский (Russian)", - "path": "lang/ru.json" - }, - { - "lang": "Fr", - "name": "French", - "path": "lang/fr.json" - } - ], - "gridDistance": 1, - "gridUnits": "yd", - "primaryTokenAttribute": "HP", - "secondaryTokenAttribute": "FP", - "url": "https://github.com/crnormand/gurps", - "manifest": "https://raw.githubusercontent.com/crnormand/gurps/release/system.json", - "download": "https://github.com/crnormand/gurps/archive/0.15.0.zip", - "socket": true, - "license": "LICENSE.txt" + "id": "gurps", + "title": "GURPS 4th Ed. Game Aid (Unofficial)", + "description": "A game aid to help play GURPS 4e for Foundry VTT", + "version": "0.15.0", + "authors": [ + { + "name": "Chris Normand", + "email": "", + "discord": "Nose66#6689" + }, + { + "name": "M. Jeff Wilson", + "email": "", + "discord": "Nick Coffin, PI#8616" + }, + { + "name": "Mikolaj Tomczynski", + "email": "mikolajtomczynski@gmail.com", + "discord": "neck#0001" + } + ], + "compatibility": { + "minimum": 10, + "verified": "10.283", + "maximum": 10 + }, + "templateVersion": 2, + "esmodules": ["module/gurps.js", "lib/xregexp-all.js"], + "scripts": ["scripts/jquery.tinycolorpicker.js"], + "styles": [ + "styles/simple.css", + "styles/css_boilerplate.css", + "styles/simplified.css", + "styles/npc-input.css", + "styles/apps.css", + "styles/color-character-sheet.css", + "styles/tinycolorpicker.css" + ], + "packs": [], + "languages": [ + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + }, + { + "lang": "pt-BR", + "name": "Português (Brasil)", + "path": "lang/pt_br.json" + }, + { + "lang": "de", + "name": "Deutsch (German)", + "path": "lang/de.json" + }, + { + "lang": "ru", + "name": "русский (Russian)", + "path": "lang/ru.json" + }, + { + "lang": "Fr", + "name": "French", + "path": "lang/fr.json" + } + ], + "gridDistance": 1, + "gridUnits": "yd", + "primaryTokenAttribute": "HP", + "secondaryTokenAttribute": "FP", + "url": "https://github.com/crnormand/gurps", + "manifest": "https://raw.githubusercontent.com/crnormand/gurps/release/system.json", + "download": "https://github.com/crnormand/gurps/archive/0.15.0.zip", + "socket": true, + "license": "LICENSE.txt" } diff --git a/templates/active-effects/active-effect-config.html b/templates/active-effects/active-effect-config.html index 75ed0ce59..55c42a38b 100644 --- a/templates/active-effects/active-effect-config.html +++ b/templates/active-effects/active-effect-config.html @@ -1,152 +1,148 @@
    + +
    + +

    {{ system.label }}

    +
    - -
    - -

    {{ system.label }}

    -
    + + - - + +
    +
    + +
    + +
    +
    - -
    +
    + +
    + {{filePicker target="icon" type="image"}} + +
    +
    -
    - -
    - -
    -
    +
    + +
    + + +
    +
    -
    - -
    - {{filePicker target="icon" type="image"}} - -
    -
    +
    + + +
    -
    - -
    - - -
    -
    + {{#if isActorEffect}} +
    + +
    + +
    +
    + {{/if}} {{#if isItemEffect}} +
    + +
    + +
    +
    + {{/if}} +
    -
    - - -
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + + + + +
    +
    +
    + +
    + +
    +
    - {{#if isActorEffect}} -
    - -
    - -
    -
    - {{/if}} +
    + +
    + + + + +
    +
    +
    - {{#if isItemEffect}} -
    - -
    - -
    -
    - {{/if}} -
    + +
    +
    +
    {{ localize "EFFECT.ChangeKey" }}
    +
    {{ localize "EFFECT.ChangeMode" }}
    +
    {{ localize "EFFECT.ChangeValue" }}
    +
    + +
    +
    +
      + {{#each system.changes as |change i|}} +
    1. +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
    2. + {{/each}} +
    +
    - -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - -
    - - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    - - - - -
    -
    -
    - - -
    -
    -
    {{ localize "EFFECT.ChangeKey" }}
    -
    {{ localize "EFFECT.ChangeMode" }}
    -
    {{ localize "EFFECT.ChangeValue" }}
    -
    - -
    -
    -
      - {{#each system.changes as |change i|}} -
    1. -
      - -
      -
      - -
      -
      - -
      -
      - -
      -
    2. - {{/each}} -
    -
    - -
    - -
    +
    + +
    diff --git a/templates/actor/npc-input.hbs b/templates/actor/npc-input.hbs index 616656d6e..8efe196aa 100755 --- a/templates/actor/npc-input.hbs +++ b/templates/actor/npc-input.hbs @@ -2,7 +2,7 @@
    -
    {{i18n "GURPS.identityName"}}:
    +
    {{i18n 'GURPS.identityName'}}:
    -
    {{i18n "GURPS.identityTitle"}}:
    +
    {{i18n 'GURPS.identityTitle'}}:
    Desc:
    @@ -18,16 +18,16 @@
    -
    {{i18n "GURPS.attributesST"}}:
    +
    {{i18n 'GURPS.attributesST'}}:
    -
    {{i18n "GURPS.attributesDX"}}:
    +
    {{i18n 'GURPS.attributesDX'}}:
    -
    {{i18n "GURPS.attributesIQ"}}:
    +
    {{i18n 'GURPS.attributesIQ'}}:
    -
    {{i18n "GURPS.attributesHT"}}:
    +
    {{i18n 'GURPS.attributesHT'}}:
    Th/Sw:
    @@ -37,46 +37,46 @@ type='text' value='{{mook.damage}}' />
    -
    {{i18n "GURPS.HP"}}:
    +
    {{i18n 'GURPS.HP'}}:
    -
    {{i18n "GURPS.attributesWILL"}}:
    +
    {{i18n 'GURPS.attributesWILL'}}:
    -
    {{i18n "GURPS.attributesPER"}}:
    +
    {{i18n 'GURPS.attributesPER'}}:
    -
    {{i18n "GURPS.FP"}}:
    +
    {{i18n 'GURPS.FP'}}:
    -
    {{i18n "GURPS.parry"}}:
    +
    {{i18n 'GURPS.parry'}}:
    -
    {{i18n "GURPS.speed"}}:
    +
    {{i18n 'GURPS.speed'}}:
    -
    {{i18n "GURPS.move"}}:
    +
    {{i18n 'GURPS.move'}}:
    SM:
    -
    {{i18n "GURPS.hitLocationDR"}}:
    +
    {{i18n 'GURPS.hitLocationDR'}}:
    -
    {{i18n "GURPS.dodge"}}:
    +
    {{i18n 'GURPS.dodge'}}:
    {{mook.notes}}
    -
    {{i18n "GURPS.melee"}}
    +
    {{i18n 'GURPS.melee'}}
    -
    {{i18n "GURPS.ranged"}}
    +
    {{i18n 'GURPS.ranged'}}
    -
    {{i18n "GURPS.advDisadvPerkQuirks"}}
    +
    {{i18n 'GURPS.advDisadvPerkQuirks'}}
    -
    {{i18n "GURPS.skills"}}
    +
    {{i18n 'GURPS.skills'}}
    -
    {{i18n "GURPS.equipment"}}
    +
    {{i18n 'GURPS.equipment'}}
    -
    {{i18n "GURPS.pointsSpells"}}
    +
    {{i18n 'GURPS.pointsSpells'}}
    +
    Page Ref
    - +
    -
    \ No newline at end of file +
    diff --git a/templates/apply-damage/chat-damage-results.html b/templates/apply-damage/chat-damage-results.html index b3dfa4d16..7d6c7a9a3 100755 --- a/templates/apply-damage/chat-damage-results.html +++ b/templates/apply-damage/chat-damage-results.html @@ -1,18 +1,16 @@ -
    -
    ... {{i18n "GURPS.chatDamageResultStart" defender=defender injury=injury}} {{pluralize 'point' - injury}} - {{i18n "GURPS.chatDamageResultMiddle" type=type}}{{#if (and (ne 'User Entered' location) (ne 'Large-Area' - location) (defined location))}} {{i18n "GURPS.chatDamageResultEnd" location=location}}{{/if}}. +
    +
    + ... {{i18n "GURPS.chatDamageResultStart" defender=defender injury=injury}} {{pluralize 'point' injury}} {{i18n + "GURPS.chatDamageResultMiddle" type=type}}{{#if (and (ne 'User Entered' location) (ne 'Large-Area' location) + (defined location))}} {{i18n "GURPS.chatDamageResultEnd" location=location}}{{/if}}.
    {{#if resultsTable}} -
    - - -
    -
    - {{{resultsTable}}} -
    -
    +
    + + +
    +
    {{{resultsTable}}}
    +
    {{/if}} -
    \ No newline at end of file +
    diff --git a/templates/apply-damage/chat-headvitalshit.html b/templates/apply-damage/chat-headvitalshit.html index c65e48f30..3bc3f63ed 100755 --- a/templates/apply-damage/chat-headvitalshit.html +++ b/templates/apply-damage/chat-headvitalshit.html @@ -1,13 +1,14 @@ -
    -
    - {{i18n "GURPS.suffersHeadVitalsWound" name=name location=location}} ([PDF:{{i18n "GURPS.pdfKnockdownStun"}}]). +
    +
    + {{i18n "GURPS.suffersHeadVitalsWound" name=name location=location}} + ([PDF:{{i18n "GURPS.pdfKnockdownStun"}}]).

    -
    +
    ["{{i18n "GURPS.majorWoundEffectLabel" name=name htCheck=htCheck}}" {{button}}] -
      +
      • [{{i18n "GURPS.forLowPainThreshold" mod="-4"}}]
      • [{{i18n "GURPS.forHighPainThreshold" mod="+3"}}]
    -
    \ No newline at end of file +
    diff --git a/templates/apply-damage/chat-knockback.html b/templates/apply-damage/chat-knockback.html index 8fe27a06b..f57f47646 100755 --- a/templates/apply-damage/chat-knockback.html +++ b/templates/apply-damage/chat-knockback.html @@ -1,13 +1,11 @@ -
    -
    - {{{i18n_f "GURPS.suffersKnockback" this}}} -
    +
    +
    {{{i18n_f "GURPS.suffersKnockback" this}}}

    -
    +
    ["{{i18n_f "GURPS.knockbackCheck" this}}" {{button}}] Modifiers: -
      +
      • [{{i18n "GURPS.forPerfectBalance" mod="+4"}}]
    -
    \ No newline at end of file +
    diff --git a/templates/apply-damage/chat-majorwound.html b/templates/apply-damage/chat-majorwound.html index 2d46e9698..ac661f8d1 100755 --- a/templates/apply-damage/chat-majorwound.html +++ b/templates/apply-damage/chat-majorwound.html @@ -1,13 +1,11 @@ -
    -
    - {{i18n "GURPS.suffersAMajorWound" name=name}} ([PDF:{{i18n "GURPS.pdfMajorWound"}}]). -
    +
    +
    {{i18n "GURPS.suffersAMajorWound" name=name}} ([PDF:{{i18n "GURPS.pdfMajorWound"}}]).

    -
    +
    ["{{i18n "GURPS.majorWoundEffectLabel" name=name htCheck=htCheck}}" {{button}}] -
      +
      • [{{i18n "GURPS.forLowPainThreshold" mod="-4"}}]
      • [{{i18n "GURPS.forHighPainThreshold" mod="+3"}}]
    -
    \ No newline at end of file +
    diff --git a/templates/apply-damage/chat-shock.html b/templates/apply-damage/chat-shock.html index 10407c274..2423dedf4 100755 --- a/templates/apply-damage/chat-shock.html +++ b/templates/apply-damage/chat-shock.html @@ -1,22 +1,19 @@ -
    -
    - {{i18n "GURPS.chatShock" name=name modifier=modifier}}([PDF:{{i18n - "GURPS.pdfShock"}}]). +
    +
    + {{i18n "GURPS.chatShock" name=name modifier=modifier}}([PDF:{{i18n "GURPS.pdfShock"}}]).

    -
    +
    ["{{i18n "GURPS.shockPenaltyNextTurnOnly" name=name modifier=modifier}}" {{button}}] -
      -
    • {{i18n "GURPS.highPainThresholdNoPenalty"}} ([PDF:{{i18n "GURPS.pdfHPT"}}]). -
    • -
    • {{i18n "GURPS.lowPainThresholdDoubles"}} ([PDF:{{i18n "GURPS.pdfLPT"}}]). -
    • +
        +
      • {{i18n "GURPS.highPainThresholdNoPenalty"}} ([PDF:{{i18n "GURPS.pdfHPT"}}]).
      • +
      • {{i18n "GURPS.lowPainThresholdDoubles"}} ([PDF:{{i18n "GURPS.pdfLPT"}}]).
      -
      - {{i18n "GURPS.effectsNotApplicableDefenses"}} - {{i18n "GURPS.effectsTemporaryAttributePenalties"}} ([PDF:{{i18n "GURPS.pdfTempAttribPenalties"}}]). +
      + {{i18n "GURPS.effectsNotApplicableDefenses"}} {{i18n "GURPS.effectsTemporaryAttributePenalties"}} + ([PDF:{{i18n "GURPS.pdfTempAttribPenalties"}}]).
      -
    \ No newline at end of file +
    diff --git a/templates/apply-damage/effect-blunttrauma.html b/templates/apply-damage/effect-blunttrauma.html index f5da0e966..dc2c3d34c 100755 --- a/templates/apply-damage/effect-blunttrauma.html +++ b/templates/apply-damage/effect-blunttrauma.html @@ -1,9 +1,9 @@ -
  • +
  • {{i18n "GURPS.bluntTrauma"}}
    -
    B379
    +
    B379
    -
  • \ No newline at end of file + diff --git a/templates/apply-damage/effect-crippling.html b/templates/apply-damage/effect-crippling.html index 0f4f92ca1..809ca4f37 100755 --- a/templates/apply-damage/effect-crippling.html +++ b/templates/apply-damage/effect-crippling.html @@ -1,20 +1,20 @@ -
  • +
  • {{i18n "GURPS.crippling"}}{{detail}}
    {{#if (eq detail 'Tail')}} - + {{/if}}
    -
    - +
    +
    -
    {{i18n "GURPS.pdfCrippling"}}
    +
    {{i18n "GURPS.pdfCrippling"}}
    -
  • \ No newline at end of file + diff --git a/templates/apply-damage/effect-headvitalshit.html b/templates/apply-damage/effect-headvitalshit.html index 3c4715c69..e5e74cbd8 100755 --- a/templates/apply-damage/effect-headvitalshit.html +++ b/templates/apply-damage/effect-headvitalshit.html @@ -1,13 +1,15 @@ -
  • +
  • {{i18n "GURPS.headVitalsHit"}}({{detail}})
    - +
    -
    - +
    +
    -
    {{i18n "GURPS.pdfKnockdownStun"}}
    +
    {{i18n "GURPS.pdfKnockdownStun"}}
    -
  • \ No newline at end of file + diff --git a/templates/apply-damage/effect-knockback.html b/templates/apply-damage/effect-knockback.html index e46453377..1ec7c6007 100755 --- a/templates/apply-damage/effect-knockback.html +++ b/templates/apply-damage/effect-knockback.html @@ -1,15 +1,14 @@ -
  • +
  • -
    - {{i18n_f "GURPS.knockback" this}} -
    +
    {{i18n_f "GURPS.knockback" this}}
    -
    - +
    +
    -
    {{i18n "GURPS.pdfKnockback"}}
    +
    {{i18n "GURPS.pdfKnockback"}}
    -
  • \ No newline at end of file + diff --git a/templates/apply-damage/effect-majorwound.html b/templates/apply-damage/effect-majorwound.html index 8cb36e52a..043f55114 100755 --- a/templates/apply-damage/effect-majorwound.html +++ b/templates/apply-damage/effect-majorwound.html @@ -1,13 +1,15 @@ -
  • +
  • {{i18n "GURPS.majorWound"}}
    - +
    -
    - +
    +
    -
    {{i18n "GURPS.pdfMajorWound"}}
    +
    {{i18n "GURPS.pdfMajorWound"}}
    -
  • \ No newline at end of file + diff --git a/templates/apply-damage/effect-shock.html b/templates/apply-damage/effect-shock.html index bcc989db5..cc45388fa 100755 --- a/templates/apply-damage/effect-shock.html +++ b/templates/apply-damage/effect-shock.html @@ -1,12 +1,12 @@ -
  • +
  • {{i18n "GURPS.shock"}}
    -
    - +
    +
    - +
    -
  • \ No newline at end of file + diff --git a/templates/apply-damage/select-token.html b/templates/apply-damage/select-token.html index 0560899bb..37598a974 100644 --- a/templates/apply-damage/select-token.html +++ b/templates/apply-damage/select-token.html @@ -1,8 +1,8 @@ -
    +
    {{i18n "GURPS.addApplyTo"}}: -
    \ No newline at end of file +
    diff --git a/templates/changelog.html b/templates/changelog.html index 6b08d1c21..020436083 100755 --- a/templates/changelog.html +++ b/templates/changelog.html @@ -5,7 +5,5 @@ -->
    -
    - {{{changelog}}} -
    - \ No newline at end of file +
    {{{changelog}}}
    + diff --git a/templates/chat-import-actor-errors.html b/templates/chat-import-actor-errors.html index 28e2fefd2..435bb7684 100644 --- a/templates/chat-import-actor-errors.html +++ b/templates/chat-import-actor-errors.html @@ -1,18 +1,16 @@ -
    -
    - {{i18n "GURPS.warning"}} -
    -
    +
    +
    {{i18n "GURPS.warning"}}
    +
      {{#each lines}} -
    • {{{this}}}
    • +
    • {{{this}}}
    • {{/each}}
    -
    +

    {{i18n "GURPS.importFileVersion" version=this.version}}

    {{i18n "GURPS.importCurrentVersions" GCAVersion=this.GCAVersion GCSVersion=this.GCSVersion}}

    {{i18n "GURPS.importSeeUsersGuide"}}

    - {{i18n "GURPS.gameAidUsersGuide"}} + {{i18n "GURPS.gameAidUsersGuide"}}
    -
    \ No newline at end of file +
    diff --git a/templates/chat-processing.html b/templates/chat-processing.html index be2e6ffce..ba9912161 100644 --- a/templates/chat-processing.html +++ b/templates/chat-processing.html @@ -1,5 +1,5 @@ -
    +
    {{#each lines}} -
    {{{this}}}
    +
    {{{this}}}
    {{/each}} -
    \ No newline at end of file +
    diff --git a/templates/die-roll-chat-message.hbs b/templates/die-roll-chat-message.hbs index 10c4f1fee..0b24a6e5a 100755 --- a/templates/die-roll-chat-message.hbs +++ b/templates/die-roll-chat-message.hbs @@ -62,9 +62,11 @@
    {{/each}} {{#if followon}}{{#if (not failure)}} -
    - {{i18n "GURPS.roll"}} {{i18n "GURPS.damage"}}: {{followon}} - {{/if}}{{/if}} +
    + {{i18n 'GURPS.roll'}} + {{i18n 'GURPS.damage'}}: + {{followon}} + {{/if}}{{/if}}
    \ No newline at end of file diff --git a/templates/equipment-editor-popup.html b/templates/equipment-editor-popup.html index 0aa9fe3d0..0bf953058 100755 --- a/templates/equipment-editor-popup.html +++ b/templates/equipment-editor-popup.html @@ -1,34 +1,31 @@ -
    +
    - +
    -
    +
    Count
    Cost $
    Weight
    -
    lb
    +
    lb
    Uses
    Max Uses
    - +
    Tech Level
    - +
    Notes
    - +
    Page Ref
    - - - {{#if (defined save)}} - + + + {{#if (defined save)}}
    Check this to save this equipment during imports
    - {{else}} - + {{else}}
    Check this to save the current Count/QTY/Uses during imports
    {{/if}} -
    -
    \ No newline at end of file +
    diff --git a/templates/import-gcs-or-gca.hbs b/templates/import-gcs-or-gca.hbs index 31a0ebc10..73af871f7 100644 --- a/templates/import-gcs-or-gca.hbs +++ b/templates/import-gcs-or-gca.hbs @@ -1,7 +1,7 @@ -

    Select a file exported from GCS or GCA.

    -
    - - {{{inputElement}}} +

    Select a file exported from GCS or GCA.

    +
    + + {{{inputElement}}}
    -


    The import will overwrite the data for "{{name}}".

    -
    NOTE: This cannot be un-done. \ No newline at end of file +


    The import will overwrite the data for "{{name}}".

    +
    NOTE: This cannot be un-done. \ No newline at end of file diff --git a/templates/import-gcs-v1-data.html b/templates/import-gcs-v1-data.html index 7016e907d..a086368e0 100644 --- a/templates/import-gcs-v1-data.html +++ b/templates/import-gcs-v1-data.html @@ -1,9 +1,9 @@
    -

    Select a file exported from GCS or GCA.

    -
    - - -
    -


    The import will overwrite the data for {{name}}.

    -
    NOTE: This cannot be un-done. -
    \ No newline at end of file +

    Select a file exported from GCS or GCA.

    +
    + + +
    +


    The import will overwrite the data for {{name}}.

    +
    NOTE: This cannot be un-done. + diff --git a/templates/import-stat-block.html b/templates/import-stat-block.html index 01da00944..4fac5548b 100755 --- a/templates/import-stat-block.html +++ b/templates/import-stat-block.html @@ -1,5 +1,5 @@
    -
    - -
    +
    + +
    diff --git a/templates/inventory-sheet.html b/templates/inventory-sheet.html index 0cb53d99d..931c60ea3 100644 --- a/templates/inventory-sheet.html +++ b/templates/inventory-sheet.html @@ -1,48 +1,50 @@ -
    -
    -
    -
    -
    {{i18n "GURPS.identity"}}
    -
    -
    {{i18n "GURPS.identityName"}}:
    -
    {{{actor.name}}}
    -
    {{i18n "GURPS.identityTitle"}}:
    -
    {{system.traits.title}}
    -
    {{i18n "GURPS.identityPlayer"}}:
    -
    {{system.traits.player}}
    -
    -
    -
    -
    {{i18n "GURPS.miscellaneous"}}
    -
    -
    {{i18n "GURPS.miscellaneousCreated"}}:
    -
    {{system.traits.createdon}}
    -
    {{i18n "GURPS.miscellaneousModified"}}:
    -
    {{system.traits.modifiedon}}
    -
    {{i18n "GURPS.miscellaneousOptions"}}:
    -
    -
    -
    -
    - {{>equipment container='carried' data=this.data}} - {{>equipment container='other' data=this.data}} + +
    +
    +
    +
    {{i18n "GURPS.identity"}}
    +
    +
    {{i18n "GURPS.identityName"}}:
    +
    {{{actor.name}}}
    +
    {{i18n "GURPS.identityTitle"}}:
    +
    {{system.traits.title}}
    +
    {{i18n "GURPS.identityPlayer"}}:
    +
    {{system.traits.player}}
    +
    +
    +
    +
    {{i18n "GURPS.miscellaneous"}}
    +
    +
    {{i18n "GURPS.miscellaneousCreated"}}:
    +
    {{system.traits.createdon}}
    +
    {{i18n "GURPS.miscellaneousModified"}}:
    +
    {{system.traits.modifiedon}}
    +
    {{i18n "GURPS.miscellaneousOptions"}}:
    +
    +
    +
    +
    + {{>equipment container='carried' data=this.data}} {{>equipment container='other' data=this.data}} -
    -
    Notes  
    -
    Ref
    - {{#listeqt system.notes}} -
    - {{#if (notEmpty this.contains)}}{{/if}} - {{#if (notEmpty this.collapsed)}}{{/if}} - {{#if this.notes}}{{{gurpslinkbr this.notes}}}{{else}}this note was intentionally left blank ;-){{/if}} -
    -
    {{{pdflinkext this}}}
    - {{/listeqt}} -
    -
    -
    - +
    +
    Notes  
    +
    Ref
    + {{#listeqt system.notes}} +
    + {{#if (notEmpty this.contains)}}{{/if}} {{#if (notEmpty + this.collapsed)}}{{/if}} {{#if this.notes}}{{{gurpslinkbr + this.notes}}}{{else}}this note was intentionally left blank ;-){{/if}} +
    +
    {{{pdflinkext this}}}
    + {{/listeqt}} +
    +
    +
    +
    diff --git a/templates/item-import.html b/templates/item-import.html index 2e6a76488..9bb2358f4 100644 --- a/templates/item-import.html +++ b/templates/item-import.html @@ -1,7 +1,7 @@
    -

    Select a GCS .eqp file

    -
    - - -
    -
    \ No newline at end of file +

    Select a GCS .eqp file

    +
    + + +
    + diff --git a/templates/item-sheet.html b/templates/item-sheet.html index 4dff7575b..686999a3a 100755 --- a/templates/item-sheet.html +++ b/templates/item-sheet.html @@ -1,63 +1,89 @@ -
    +
    -

    {{i18n "GURPS.itemEditor"}}

    +

    {{i18n "GURPS.itemEditor"}}

    -
    -
    - +
    +
    +
    -
    - +
    +
    -
    +

    {{i18n "GURPS.attributes"}}

    -
    -
    +
    +
    -
    - +
    +
    -
    - +
    + $
    -
    - - +
    + +
    -
    +
    - +
    - +
    - +
    -
    +
    -
    +
    -
    +
    +
    @@ -68,387 +94,388 @@

    {{i18n "GURPS.attributes"}}

    {{i18n "GURPS.itemFeatures"}}

    -
    -
    - \ No newline at end of file + diff --git a/templates/melee-editor-popup.html b/templates/melee-editor-popup.html index 066f856b8..6c7aab669 100755 --- a/templates/melee-editor-popup.html +++ b/templates/melee-editor-popup.html @@ -1,8 +1,8 @@ -
    +
    - +
    -
    +
    Usage
    Skill lvl
    @@ -12,7 +12,10 @@
    Block
    Damage
    -
    Types: burn, cor, cr, cut, fat, imp, pi-, pi, pi+, pi++, tox, dmg
    +
    + Types: burn, cor, cr, cut, fat, imp, pi-, + pi, pi+, pi++, tox, dmg +
    Reach
    ST
    @@ -26,6 +29,6 @@
    Fail
    Notes
    - +
    -
    \ No newline at end of file +
    diff --git a/templates/modifier-bucket/bucket-app.html b/templates/modifier-bucket/bucket-app.html index 88b52f072..f0cfbf9d3 100755 --- a/templates/modifier-bucket/bucket-app.html +++ b/templates/modifier-bucket/bucket-app.html @@ -1,30 +1,31 @@ -
    -
    {{currentActor}}
    - {{#each damageAccumulators}} - {{#if (eq ../accumulatorIndex @index)}} -
    -
    -
    -
    - {{multiplyDice formula count}} {{damagetype}} {{extdamagetype}} -
    -
    -
    - {{/if}} - {{/each}} -
    -
    +
    +
    {{currentActor}}
    + {{#each damageAccumulators}} {{#if (eq ../accumulatorIndex @index)}} +
    +
    +
    +
    + {{multiplyDice formula count}} {{damagetype}} {{extdamagetype}} +
    +
    +
    + {{/if}} {{/each}} +
    +
    {{stack.displaySum}}
    -
    -
    -
    +
    + +
    +
    +
    + +
    -
    - - +
    + +
    -
    \ No newline at end of file +
    diff --git a/templates/modifier-bucket/select-journals.html b/templates/modifier-bucket/select-journals.html index 655283f01..d34ecc6d4 100644 --- a/templates/modifier-bucket/select-journals.html +++ b/templates/modifier-bucket/select-journals.html @@ -1,19 +1,19 @@ -
    +

    {{i18n 'GURPS.modifierSelectJournalsTitle'}}

    -
    -
    +
    +
    {{i18n 'GURPS.showQuestion'}}
    {{i18n 'GURPS.name'}}
    {{i18n 'GURPS.ID'}}
    {{#each allJournals}} -
    -
    -
    {{id}}
    +
    +
    +
    {{id}}
    {{/each}}
    -
    - +
    +
    - \ No newline at end of file + diff --git a/templates/note-editor-popup.html b/templates/note-editor-popup.html index 38159d416..245b04c3d 100755 --- a/templates/note-editor-popup.html +++ b/templates/note-editor-popup.html @@ -1,16 +1,14 @@ -
    -
    -
    -
    +
    +
    +
    Title
    - +
    Note
    - +
    Page Ref
    - {{#if (defined save)}} - -
    Check this to save this note during imports
    + {{#if (defined save)}} +
    Check this to save this note during imports
    {{/if}}
    -
    \ No newline at end of file +
    diff --git a/templates/ranged-editor-popup.html b/templates/ranged-editor-popup.html index cef0170e1..cec4f8777 100755 --- a/templates/ranged-editor-popup.html +++ b/templates/ranged-editor-popup.html @@ -1,8 +1,8 @@ -
    +
    - +
    -
    +
    Usage
    Skill lvl
    @@ -10,7 +10,10 @@
    Acc
    Damage
    -
    Dmg types: burn, cor, cr, cut, fat, imp, pi-, pi, pi+, pi++, tox, dmg
    +
    + Dmg types: burn, cor, cr, cut, fat, imp, + pi-, pi, pi+, pi++, tox, dmg +
    Range
    RoF
    @@ -32,6 +35,6 @@
    Fail
    Notes
    - +
    -
    \ No newline at end of file +
    diff --git a/templates/resource-editor-popup.hbs b/templates/resource-editor-popup.hbs index 31b05bdc1..bc1d04d18 100755 --- a/templates/resource-editor-popup.hbs +++ b/templates/resource-editor-popup.hbs @@ -6,26 +6,11 @@
    - +
    {{i18n 'GURPS.minimum'}}
    - +
    {{i18n 'GURPS.maximum'}}
    - +
    {{i18n 'GURPS.current'}}
    {{i18n 'GURPS.resourceTemplateAlias'}}
    @@ -94,7 +79,7 @@
    {{i18n 'GURPS.condition'}}
    {{i18n 'GURPS.color'}}
    - {{else}} + {{else}}
    diff --git a/templates/simplified.html b/templates/simplified.html index 028407221..30061c324 100755 --- a/templates/simplified.html +++ b/templates/simplified.html @@ -77,14 +77,18 @@
    {{{gurpslink this.notes @root}}}
    - {{this.level}}
    + {{this.level}} +
    {{/each}}
    - + + {{#listeqt system.equipment.carried }} -
    {{{gurpslink this.name}}} +
    + {{{gurpslink this.name}}} +
    +
    {{{gurpslink this.notes}}}
    {{this.count}}
    diff --git a/templates/skill-editor-popup.html b/templates/skill-editor-popup.html index b76b51c31..96c4795ad 100755 --- a/templates/skill-editor-popup.html +++ b/templates/skill-editor-popup.html @@ -1,10 +1,10 @@ -
    +
    - +
    -
    +
    Level
    - +
    RSL
    Pts
    @@ -17,9 +17,9 @@
    Fail
    -
    Notes
    - +
    Notes
    +
    Page Ref
    - +
    -
    \ No newline at end of file +
    diff --git a/templates/slam-calculator.html b/templates/slam-calculator.html index 949b56f8d..494641b69 100644 --- a/templates/slam-calculator.html +++ b/templates/slam-calculator.html @@ -1,39 +1,41 @@ -
    +

    - {{attackerToken.name}} {{i18n "GURPS.slams"}} - + {{attackerToken.name}} + {{i18n "GURPS.slams"}} {{targetToken.name}}

    -
    +

    {{attackerToken.name}}

    -
    +
    -
    +
    -
    - -
    +
    + +
    -
    +
    -
    +

    {{targetToken.name}}

    -
    +
    - + -
    +
    -
    -
    +
    +
    - +
    -
    - +
    +
    - \ No newline at end of file + diff --git a/templates/slam-results.html b/templates/slam-results.html index fd590db2e..5d22e1f3f 100644 --- a/templates/slam-results.html +++ b/templates/slam-results.html @@ -1,34 +1,32 @@ -
    -
    +
    +
    {{attacker}} {{i18n "GURPS.slams"}} {{target}}.
    -
      +
        {{#if isAoAStrong}} -
      • +2 {{i18n "GURPS.slamAOAStrong"}}
      • - {{/if}} - {{#if shieldDB}} -
      • +{{shieldDB}} {{i18n "GURPS.slamForShieldDB"}} [PDF:{{i18n "GURPS.pdfShieldRush"}}]
      • +
      • +2 {{i18n "GURPS.slamAOAStrong"}}
      • + {{/if}} {{#if shieldDB}} +
      • +{{shieldDB}} {{i18n "GURPS.slamForShieldDB"}} [PDF:{{i18n "GURPS.pdfShieldRush"}}]
      • {{/if}}
    -
    - {{result}} -
    -
    - - -
    -
    +
    {{result}}
    +
    + + +
    +
    {{attacker}}
    {{attackerRaw}}
    -
    = {{i18n "GURPS.HP"}} ({{attackerHp}}) × {{i18n "GURPS.slamVelocity"}} - ({{relativeSpeed}}) ÷ 100
    +
    + = {{i18n "GURPS.HP"}} ({{attackerHp}}) × {{i18n "GURPS.slamVelocity"}} ({{relativeSpeed}}) ÷ 100 +
    {{attackerDice.dice}}d{{#if attackerDice.adds}}{{{displayNumber attackerDice.adds}}}{{/if}}
    -
    {{i18n "GURPS.Dice"}}
    +
    {{i18n "GURPS.Dice"}}
    {{attackerResult}}
    @@ -36,8 +34,9 @@
    {{target}}
    {{targetRaw}}
    -
    = {{i18n "GURPS.HP"}} ({{targetHp}}) × {{i18n "GURPS.slamVelocity"}} - ({{relativeSpeed}}) ÷ 100
    +
    + = {{i18n "GURPS.HP"}} ({{targetHp}}) × {{i18n "GURPS.slamVelocity"}} ({{relativeSpeed}}) ÷ 100 +
    {{targetDice.dice}}d{{#if targetDice.adds}}{{{displayNumber targetDice.adds}}}{{/if}}
    @@ -49,4 +48,4 @@
    -
    \ No newline at end of file +
    diff --git a/templates/spell-editor-popup.html b/templates/spell-editor-popup.html index 24a2e2220..89c78a851 100755 --- a/templates/spell-editor-popup.html +++ b/templates/spell-editor-popup.html @@ -1,8 +1,8 @@ -
    +
    - +
    -
    +
    Resist
    Class
    @@ -31,9 +31,9 @@
    Fail
    -
    Notes
    - +
    Notes
    +
    Page Ref
    - +
    -
    \ No newline at end of file +
    diff --git a/templates/transfer-equipment.html b/templates/transfer-equipment.html index 005212bfc..720a96df6 100644 --- a/templates/transfer-equipment.html +++ b/templates/transfer-equipment.html @@ -1,8 +1,8 @@ -
    -

    {{eqt.name}}

    -
    +
    +

    {{eqt.name}}

    +
    - +

    -

    \ No newline at end of file +