/*jshint esversion: 11, laxcomma:true, eqeqeq:true*/ /*jshint -W014,-W084,-W030,-W033*/ /** * Replaces problem characters to use a string as a regex * @param {string} text - The text to replace characters in * @returns {string} * @example * const textForRegex = k.sanitizeForRegex('.some thing[with characters]'); * console.log(textForRegex);// => "\.some thing\[with characters\]" */ const sanitizeForRegex = function(text){ return text.replace(/\.|\||\(|\)|\[|\]|\-|\+|\?|\/|\{|\}|\^|\$|\*/g,'\\$&'); }; kFuncs.sanitizeForRegex = sanitizeForRegex; /** * Converts a value to a number, it\'s default value, or `0` if no default value passed. * @param {string|number} val - Value to convert to a number * @param {number} def - The default value, uses 0 if not passed * @returns {number|undefined} * @example * const num = k.value('100'); * console.log(num);// => 100 */ const value = function(val,def){ return (+val||def||0); }; kFuncs.value = value; /** * Extracts the section (e.g. `repeating_equipment`), rowID (e.g `-;lkj098J:LKj`), and field name (e.g. `bulk`) from a repeating attribute name. * @param {string} string - The string to parse * @returns {array} - Array of matches. Index 0: the section name, e.g. repeating_equipment | Index 1:the row ID | index 2: The name of the attribute * @returns {string[]} * @example * //Extract info from a full repeating name * const [section,rowID,attrName] = k.parseRepeatName('repeating_equipment_-8908asdflkjZlkj23_name'); * console.log(section);// => "repeating_equipment" * console.log(rowID);// => "-8908asdflkjZlkj23" * console.log(attrName);// => "name" * * //Extract info from just a row name * const [section,rowID,attrName] = k.parseRepeatName('repeating_equipment_-8908asdflkjZlkj23'); * console.log(section);// => "repeating_equipment" * console.log(rowID);// => "-8908asdflkjZlkj23" * console.log(attrName);// => undefined */ const parseRepeatName = function(string){ let match = string.match(/(repeating_[^_]+)_([^_]+)(?:_(.+))?/); match.shift(); return match; }; kFuncs.parseRepeatName = parseRepeatName; /** * Parses out the components of a trigger name similar to [parseRepeatName](#parserepeatname). Aliases: parseClickTrigger. * * Aliases: `k.parseClickTrigger` * @param {string} string The triggerName property of the * @returns {array} - For a repeating button named `repeating_equipment_-LKJhpoi98;lj_roll`, the array will be `['repeating_equipment','-LKJhpoi98;lj','roll']`. For a non repeating button named `roll`, the array will be `[undefined,undefined,'roll']` * @returns {string[]} * @example * //Parse a non repeating trigger * const [section,rowID,attrName] = k.parseTriggerName('clicked:some-button'); * console.log(section);// => undefined * console.log(rowID);// => undefined * console.log(attrName);// => "some-button" * * //Parse a repeating trigger * const [section,rowID,attrName] = k.parseTriggerName('clicked:repeating_attack_-234lkjpd8fu8usadf_some-button'); * console.log(section);// => "repeating_attack" * console.log(rowID);// => "-234lkjpd8fu8usadf" * console.log(attrName);// => "some-button" * * //Parse a repeating name * const [section,rowID,attrName] = k.parseTriggerName('repeating_attack_-234lkjpd8fu8usadf_some-button'); * console.log(section);// => "repeating_attack" * console.log(rowID);// => "-234lkjpd8fu8usadf" * console.log(attrName);// => "some-button" */ const parseTriggerName = function(string){ let match = string.replace(/^clicked:/,'').match(/(?:(repeating_[^_]+)_([^_]+)_)?(.+)/); match.shift(); return match; }; kFuncs.parseTriggerName = parseTriggerName; const parseClickTrigger = parseTriggerName; kFuncs.parseClickTrigger = parseClickTrigger; /** * Parses out the attribute name from the htmlattribute name. * @param {string} string - The triggerName property of the [event](https://wiki.roll20.net/Sheet_Worker_Scripts#eventInfo_Object). * @returns {string} * @example * //Parse a name * const attrName = k.parseHtmlName('attr_attribute_1'); * console.log(attrName);// => "attribute_1" */ const parseHTMLName = function(string){ let match = string.match(/(?:attr|act|roll)_(.+)/); match.shift(); return match[0]; }; kFuncs.parseHTMLName = parseHTMLName; /** * Capitalize each word in a string * @param {string} string - The string to capitalize * @returns {string} * @example * const capitalized = k.capitalize('a word'); * console.log(capitalized);// => "A Word" */ const capitalize = function(string){ return string.replace(/(?:^|\s+|\/)[a-z]/ig,(letter)=>letter.toUpperCase()); }; kFuncs.capitalize = capitalize; /** * Extracts a roll query result for use in later functions. Must be awaited as per [startRoll documentation](https://wiki.roll20.net/Sheet_Worker_Scripts#Roll_Parsing.28NEW.29). Stolen from [Oosh\'s Adventures with Startroll thread](https://app.roll20.net/forum/post/10346883/adventures-with-startroll). * @param {string} query - The query should be just the text as the `?{` and `}` at the start/end of the query are added by the function. * @returns {Promise} - Resolves to the selected value from the roll query * @example * const rollFunction = async function(){ * //Get the result of a choose from list query * const queryResult = await extractQueryResult('Prompt Text Here|Option 1|Option 2'); * console.log(queryResult);//=> "Option 1" or "Option 2" depending on what the user selects * * //Get free from input from the user * const freeResult = await extractQueryResult('Prompt Text Here'); * consoel.log(freeResult);// => Whatever the user entered * } */ const extractQueryResult = async function(query){ debug('entering extractQueryResult'); let queryRoll = await startRoll(`!{{query=[[0[response=?{${query}}]]]}}`); finishRoll(queryRoll.rollId); return queryRoll.results.query.expression.replace(/^.+?response=|\]$/g,''); }; kFuncs.extractQueryResult = extractQueryResult; /** * Simulates a query for ensuring that async/await works correctly in the sheetworker environment when doing conditional startRolls. E.g. if you have an if/else and only one of the conditions results in `startRoll` being called (and thus an `await`), the sheetworker environment would normally crash. Awaiting this in the condition that does not actually need to call `startRoll` will keep the environment in sync. * @param {string|number} [value] - The value to return. Optional. * @returns {Promise} - Resolves to the value passed to the function * @example * const rollFunction = async function(){ * //Get the result of a choose from list query * const queryResult = await pseudoQuery('a value'); * console.log(queryResult);//=> "a value" * } */ const pseudoQuery = async function(value){ debug('entering pseudoQuery'); let queryRoll = await startRoll(`!{{query=[[0[response=${value}]]]}}`); finishRoll(queryRoll.rollId); return queryRoll.results.query.expression.replace(/^.+?response=|\]$/g,''); }; kFuncs.pseudoQuery = pseudoQuery; /** * An alias for console.log. * @param {any} msg - The message can be a straight string, an object, or an array. If it is an object or array, the object will be broken down so that each key is used as a label to output followed by the value of that key. If the value of the key is an object or array, it will be output via `console.table`. */ const log = function(msg){ if(typeof msg === 'string'){ console.log(`%c${kFuncs.sheetName} log| ${msg}`,"background-color:#159ccf"); }else if(typeof msg === 'object'){ Object.keys(msg).forEach((m)=>{ if(typeof msg[m] === 'string'){ console.log(`%c${kFuncs.sheetName} log| ${m}: ${msg[m]}`,"background-color:#159ccf"); }else{ console.log(`%c${kFuncs.sheetName} log| ${typeof msg[m]} ${m}`,"background-color:#159ccf"); console.table(msg[m]); } }); } }; kFuncs.log = log; /** * Alias for console.log that only triggers when debug mode is enabled or when the sheet\'s version is `0`. Useful for entering test logs that will not pollute the console on the live sheet. * @param {any} msg - 'See {@link k.log} * @param {boolean} force - Pass as a truthy value to force the debug output to be output to the console regardless of debug mode. * @returns {void} */ const debug = function(msg,force){ if(!kFuncs.debugMode && !force && kFuncs.version > 0) return; if(typeof msg === 'string'){ console.log(`%c${kFuncs.sheetName} DEBUG| ${msg}`,"background-color:tan;color:red;"); }else if(typeof msg === 'object'){ Object.keys(msg).forEach((m)=>{ if(typeof msg[m] === 'string'){ console.log(`%c${kFuncs.sheetName} DEBUG| ${m}: ${msg[m]}`,"background-color:tan;color:red;"); }else{ console.log(`%c${kFuncs.sheetName} DEBUG| ${typeof msg[m]} ${m}`,"background-color:tan;color:red;font-weight:bold;"); console.table(msg[m]); } }); } }; kFuncs.debug = debug; /** * Orders the section id arrays for all sections in the `sections` object to match the repOrder attribute. * @param {attributesProxy} attributes - The attributes object that must have a value for the reporder for each section. * @param {[object]} sections - Object containing the IDs for the repeating sections, indexed by repeating section name. */ const orderSections = function(attributes,sections){ Object.keys(sections).forEach((section)=>{ attributes.attributes[`_reporder_${section}`] = commaArray(attributes[`_reporder_${section}`]); orderSection(attributes.attributes[`_reporder_${section}`],sections[section]); }); }; kFuncs.orderSections = orderSections; /** * Orders a single ID array. * @param {[string]} repOrder - Array of IDs in the order they are in on the sheet. * @param {[string]} IDs - Array of IDs to be ordered. */ const orderSection = function(repOrder,IDs=[]){ IDs.sort((a,b)=>{ return repOrder.indexOf(a.toLowerCase()) - repOrder.indexOf(b.toLowerCase()); }); }; kFuncs.orderSection = orderSection; /** * Splits a comma delimited string into an array * @param {string} string - The string to split. * @returns {array} - The string segments of the comma delimited list. */ const commaArray = function(string=''){ return string.toLowerCase().split(/\s*,\s*/); }; kFuncs.commaArray = commaArray;