Files
roll20-character-sheets/backbone/source/scaffold/scripts/utility.js
T

237 lines
10 KiB
JavaScript

/*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;