@onereach/webform
Version:
Content Builder includes several views for: - Content builder view itself; - Web Form view; - Slack block-kit builder;
1,021 lines (932 loc) • 35.5 kB
JavaScript
/* eslint-disable no-template-curly-in-string */
/* eslint-disable camelcase */
import * as _ from 'lodash';
import espree from 'espree';
import Slimdown from './slimdown.js';
import slackdown from './slackdown.js';
import beautify from 'js-beautify';
export function previewMergeValues (str) {
str = str && str.trim();
if (!str || str === '``') return '';
const mf_re = /^await\s+this\.mergeFields\['(#?[\w\d]*?)'\]\.get\((?:\{path:\s+'([\w\.]+)'\})?\)$/;
const sg_re = /^await\s+this\.get(?:Shared|Global)?\((?:'([^']*?)'|"([^"]*?)"|`([^`]*?)`)\)$/;
const hc_re = /^this\.(helpers|config|error)(\.[a-zA-Z_\$][\w\$]*)?$/;
if (
((!str.startsWith('`') || !str.endsWith('`')) && mf_re.test(str)) ||
sg_re.test(str) ||
hc_re.test(str)
) { str = '`${' + str + '}`'; }
const escChars = {};
const previewString = str
.replace(/\\./g, (match, offset) => {
const key = `__escaped__${offset}__`;
escChars[key] = match;
return key;
})
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(
/("[\s\S]+?")|(`[\s\S]+?`)|('[\s\S]+?')/g,
match => `<#<${match.slice(1, -1)}>#>`
)
.replace(/>#>[\s\S]+?<#</g, '{code}')
.replace(/(.+?<#<)|(>#>.+?)/g, '{code}')
.replace(/<#<|>#>/g, String())
.replace(
/\${this\.((helpers|config|error)(\.[a-zA-Z_\$][\w\$]*)?)}/g,
(match, value) => `{${value}}`
)
.replace(
/\${await\s+this\.get(?:Shared|Global)?\((?:'([^']*?)'|"([^"]*?)"|`([^`]*?)`)\)}/g,
(match, value) => `{${value}}`
)
.replace(
/\${await\s+this\.mergeFields\['(#?[\w\d]*?)'\]\.get\((?:\{path:\s+'([\w\.]+)'\})?\)}/g,
(match, ...rest) => `{${rest[0] + (rest[1] ? `.${rest[1]}` : '')}}`
)
.replace(/{.+?}/g, ' <span style="color: #64b2da">$&</span> ')
.replace(/\n/g, '<br>')
.replace(/^(<br>)+/, '')
.replace(/__escaped__[0-9]+__/g, match => {
return escChars[match][1] === 'n' ? '<br>' : escChars[match][1];
});
return previewString;
}
export function previewMergeValuesInInputs (str) {
str = str && str.trim();
if (!str || str === '``') return '';
const mf_re = /^await\s+this\.mergeFields\['(#?[\w\d]*?)'\]\.get\((?:\{path:\s+'([\w\.]+)'\})?\)$/;
const sg_re = /^await\s+this\.get(?:Shared|Global)?\((?:'([^']*?)'|"([^"]*?)"|`([^`]*?)`)\)$/;
const hc_re = /^this\.(helpers|config|error)(\.[a-zA-Z_\$][\w\$]*)?$/;
if (
((!str.startsWith('`') || !str.endsWith('`')) && mf_re.test(str)) ||
sg_re.test(str) ||
hc_re.test(str)
) { str = '`${' + str + '}`'; }
const escChars = {};
const previewString = str
.replace(/\\./g, (match, offset) => {
const key = `__escaped__${offset}__`;
escChars[key] = match;
return key;
})
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(
/("[\s\S]+?")|(`[\s\S]+?`)|('[\s\S]+?')/g,
match => `<#<${match.slice(1, -1)}>#>`
)
.replace(/>#>[\s\S]+?<#</g, '{code}')
.replace(/(.+?<#<)|(>#>.+?)/g, '{code}')
.replace(/<#<|>#>/g, String())
.replace(
/\${this\.((helpers|config|error)(\.[a-zA-Z_\$][\w\$]*)?)}/g,
(match, value) => `{${value}}`
)
.replace(
/\${await\s+this\.get(?:Shared|Global)?\((?:'([^']*?)'|"([^"]*?)"|`([^`]*?)`)\)}/g,
(match, value) => `{${value}}`
)
.replace(
/\${await\s+this\.mergeFields\['(#?[\w\d]*?)'\]\.get\((?:\{path:\s+'([\w\.]+)'\})?\)}/g,
(match, ...rest) => `{${rest[0] + (rest[1] ? `.${rest[1]}` : '')}}`
)
// .replace(/{.+?}/g, ` <span style="color: #64b2da">$&</span> `)
.replace(/\n/g, '<br>')
.replace(/^(<br>)+/, '')
.replace(/__escaped__[0-9]+__/g, match => {
return escChars[match][1] === 'n' ? '<br>' : escChars[match][1];
});
return previewString;
}
export function sliceTicks (data) {
if (_.isArray(data)) {
return _.map(data, sliceTicks);
}
if (_.isObject(data)) {
return _.mapValues(data, sliceTicks);
}
if (typeof data === 'string' && data.startsWith('`') && data.endsWith('`')) { return data.slice(1, -1); }
return data;
}
export function prepareEmptyValue (label) {
return '';
// return `<span class="empty-value">{{${label}}}</span>`
}
export function prepareRegularValue (value = '') {
value = ' ' + value + ' ';
const newLineRep = '__new_line_temp_replacement';
return _.chain(value)
.replace(/\\`/g, '`')
.replace(/\n/g, newLineRep)
.replace(new RegExp('\\`{3}(.+?)\\`{3}', 'g'), (match, p1) =>
new RegExp(newLineRep, 'g').test(p1)
? `<span class=\"monospace-value block\">${p1}</span>`
: `<span class=\"monospace-value\">${p1}</span>`
)
.replace(new RegExp(newLineRep, 'g'), '\n')
.replace(
new RegExp('\\`{1}(.+?)\\`{1}', 'g'),
'<span class="monospace-value text--red">$1</span>'
)
.replace(
new RegExp("\\${this(.session)?\\.get\\(\\'(.+?)\\'\\)}", 'g'),
'<span class="merge-tag">{$2}</span>'
)
.replace(
/\${await\s+this\.mergeFields\['(#?[\w\d]*?)'\]\.get\((?:\{path:\s+'([\w\.]+)'\})?\)}/g,
(match, ...rest) =>
`<span class=\"merge-tag\">{${rest[0] +
(rest[1] ? `.${rest[1]}` : '')}}</span>`
)
.replace(
new RegExp('((^|\\s|~|\\*){1,})_(.+?)_((\\s|~|\\*|$){1,})', 'g'),
'$1<span class="italized-value">$3</span>$4'
)
.replace(
new RegExp('((^|\\s|_|~){1,})\\*(.+?)\\*((\\s|_|~|$){1,})', 'g'),
'$1<span class="bold-value">$3</span>$4'
)
.replace(
new RegExp('((^|\\s|_|\\*){1,})~(.+?)~((\\s|_|\\*|$){1,})', 'g'),
'$1<span class="strinkethrough-value">$3</span>$4'
)
.replace(/\n/g, '<br />')
.value();
}
export function prepareCompiledValue (previewString) {
if (_.isEmpty(previewString)) return prepareEmptyValue(previewString);
return prepareRegularValue(previewString);
}
export function getFormatedStringForWebForm (value = '') {
if (!value || !value.trim()) return '';
const newLineRep = '__new_line_temp_replacement';
return _.chain(value)
.replace(/\\`/g, '`')
.replace(/\n/g, newLineRep)
.replace(new RegExp('\\`{3}(.+?)\\`{3}', 'g'), (match, p1) =>
new RegExp(newLineRep, 'g').test(p1)
? `<span class=\"monospace-value block\">${p1}</span>`
: `<span class=\"monospace-value\">${p1}</span>`
)
.replace(new RegExp(newLineRep, 'g'), '\n')
.replace(
new RegExp('\\`{1}(.+?)\\`{1}', 'g'),
'<span class="monospace-value text--red">$1</span>'
)
.replace(
new RegExp('((^|\\s|~|\\*){1,})_(.+?)_((\\s|~|\\*|$){1,})', 'g'),
'$1<span class="italized-value">$3</span>$4'
)
.replace(
new RegExp('((^|\\s|_|~){1,})\\*(.+?)\\*((\\s|_|~|$){1,})', 'g'),
'$1<span class="bold-value">$3</span>$4'
)
.replace(
new RegExp('((^|\\s|_|\\*){1,})~(.+?)~((\\s|_|\\*|$){1,})', 'g'),
'$1<span class="strinkethrough-value">$3</span>$4'
)
.replace(/\n/g, '<br />')
.value();
}
// unused. left for the future purpose
export function parseMarkdown (str = '') {
str = str.replace(/\\`/g, '`');
const sd = new Slimdown();
return sd.render(str);
}
// unused. left for the future purpose
export function parseSlack (str = '') {
str = str.replace(/\\`/g, '`');
const sd = slackdown();
return sd.parse(str);
}
export function getSlackFormattedDate (date) {
if (!Date.parse(date)) {
return;
}
date = new Date(date);
const year = date.getFullYear();
const month = (1 + date.getMonth()).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
return month + '/' + day + '/' + year;
}
export const capitalize = (s) => {
if (typeof s !== 'string') return '';
return s.charAt(0).toUpperCase() + s.slice(1);
};
const restrictFocusDisabledClass = 'restrict-focus-disabled';
const SYSTEM_PREFIX = '#';
// eslint-disable-next-line valid-jsdoc
/**
* Present merge-tag value in readable form
* @param {string} [value=''] value to present
* @param {{mergeTagLeftWrapper: string, mergeTagRightWrapper: string}} [options={}]
* @returns {string} readable presentation for merge-tag
*/
export function getReadableMergeTag (value = '', options) {
const mergeTagLeftWrapper = _.get(options, 'mergeTagLeftWrapper', '{');
const mergeTagRightWrapper = _.get(options, 'mergeTagRightWrapper', '}');
return _.chain(value)
.replace(/^[`'"](.*)[`'"]$/g, '$1')
.replace(
/this\.get\([`'"](.*)[`'"]\)/g,
`${mergeTagLeftWrapper}$1${mergeTagRightWrapper}`
)
.replace(
/this\.session\.get\([\`'"](.*)[\`'"]\)/g,
`${mergeTagLeftWrapper}#session.$1${mergeTagRightWrapper}`
)
.replace(
/await this\.getShared\([\`'"](.*)[\`'"]\)/g,
`${mergeTagLeftWrapper}#shared.$1${mergeTagRightWrapper}`
)
.replace(
/await this\.getGlobal\([\`'"](.*)[\`'"]\)/g,
`${mergeTagLeftWrapper}#global.$1${mergeTagRightWrapper}`
)
.replace(/this\.(.+)/, `${mergeTagLeftWrapper}#$1${mergeTagRightWrapper}`)
.value();
}
/**
* Convert all merge-tags in a string to a readable form
* @param {string} [value=''] value to present
* @param {{mergeTagLeftWrapper: string, mergeTagRightWrapper: string}} [options={}]
* @returns {string} readable presentation for merge-tag
*/
export function convertMergeTagsToReadableForm (value = '', options) {
const mergeTagLeftWrapper = _.get(options, 'mergeTagLeftWrapper', '{');
const mergeTagRightWrapper = _.get(options, 'mergeTagRightWrapper', '}');
return _.chain(value)
.replace(/^[`'"](.*)[`'"]$/g, '$1')
.replace(
/\$\{this\.get\([`'"](.*?)[`'"]\)\}/g,
`${mergeTagLeftWrapper}$1${mergeTagRightWrapper}`
)
.replace(
/\$\{this\.session\.get\([\`'"](.*?)[\`'"]\)\}/g,
`${mergeTagLeftWrapper}#session.$1${mergeTagRightWrapper}`
)
.replace(
/\$\{await this\.getShared\([\`'"](.*?)[\`'"]\)\}/g,
`${mergeTagLeftWrapper}#shared.$1${mergeTagRightWrapper}`
)
.replace(
/\$\{await this\.getGlobal\([\`'"](.*?)[\`'"]\)\}/g,
`${mergeTagLeftWrapper}#global.$1${mergeTagRightWrapper}`
)
.replace(
/\$\{this\.(.+?)\}/g,
`${mergeTagLeftWrapper}#$1${mergeTagRightWrapper}`
)
.value();
}
/**
* Present text-expression value in readable form
* @param {string} [value=''] value to present
* @param {{mergeTagLeftWrapper: string, mergeTagRightWrapper: string}} [options={}]
* @returns {string} readable presentation for text-expression
*/
export function getReadableTextExpression (value = '', options) {
const mergeTagValue = _.replace(value, /\$\{(.*)\}/g, '$1');
return getReadableMergeTag(mergeTagValue, options);
}
/**
* converts tags with merge fields to actual code
* @param {string} full path to get value from
* @returns {string} readable presentation for text-expression
*/
function _convertMergeFieldTag (fullPath) {
const keyPaths = _.split(fullPath, '.');
const dataOutName = keyPaths.splice(0, 1);
const path = keyPaths.join('.');
return `await this.mergeFields['${dataOutName}'].get(${
path ? `{path: '${path}'}` : ''
})`;
}
// eslint-disable-next-line valid-jsdoc
/**
* Convert readable form of expression to actual code
* @param {string} [value=''] value to present
* @returns {string} actual expression value
*/
export function convertMergeTagToCode (tag) {
if (_.isEqual(tag, `${SYSTEM_PREFIX}error`)) {
return 'this.error';
} else if (_.startsWith(tag, `${SYSTEM_PREFIX}error.`)) {
return `this.error.${_.replace(
tag,
new RegExp(`^${SYSTEM_PREFIX}error\.`),
''
)}`;
} else if (_.startsWith(tag, `${SYSTEM_PREFIX}session.`)) {
return `this.session.get('${_.replace(
tag,
new RegExp(`^${SYSTEM_PREFIX}session\.`),
''
)}')`;
} else if (_.startsWith(tag, `${SYSTEM_PREFIX}helpers.`)) {
return `this.helpers.${_.replace(
tag,
new RegExp(`^${SYSTEM_PREFIX}helpers\.`),
''
)}`;
} else if (_.startsWith(tag, `${SYSTEM_PREFIX}config.`)) {
return `this.config.${_.replace(
tag,
new RegExp(`^${SYSTEM_PREFIX}config\.`),
''
)}`;
} else if (_.startsWith(tag, `${SYSTEM_PREFIX}request.`)) {
return `this.request.${_.replace(
tag,
new RegExp(`^${SYSTEM_PREFIX}request\.`),
''
)}`;
} else if (_.startsWith(tag, `${SYSTEM_PREFIX}shared.`)) {
const fullPath = _.replace(
tag,
new RegExp(`^${SYSTEM_PREFIX}shared\.`),
''
);
return _convertMergeFieldTag(fullPath);
} else if (_.startsWith(tag, `${SYSTEM_PREFIX}global.`)) {
const fullPath = _.replace(
tag,
new RegExp(`^${SYSTEM_PREFIX}global\.`),
''
);
return _convertMergeFieldTag(fullPath);
} else {
return _convertMergeFieldTag(tag);
}
}
/**
* Get middle part of the string representing parts of template string like
* `string${, `string`, }string`, }string${ and `string`
* @param {Object} token token to parse
* @returns {string} extracted string from text expression
*/
function _extractStringFromTemplateToken (token) {
if (token.type !== 'Template') {
return '';
}
if (token.value.substr(-2, 2) === '${') {
return token.value.substring(1, token.value.length - 2);
}
return token.value.substring(1, token.value.length - 1);
}
/**
* Get string value from '' and "" strings or from string part of template string
* (e.g. `string${, `string`, }string`, }string${ and `string`)
* @param {Object} token token to parse
* @returns {string} properly formatted value
*/
function _extractStringValue (token) {
const value = token.value;
const slashSubstitute = '#sweet_slash_of_mine_823cd925_slash#';
if (_.head(value) === "'") {
return _.chain(value.substring(1, value.length - 1))
.replace(/\\\\/g, slashSubstitute)
.replace(/\\/g, '')
.replace(new RegExp(slashSubstitute, 'g'), '\\')
.value();
}
if (_.head(value) === '"') {
return JSON.parse(value);
}
return _.chain(token)
.thru(_extractStringFromTemplateToken)
.replace(/\\\\/g, slashSubstitute)
.replace(/\\/g, '')
.replace(new RegExp(slashSubstitute, 'g'), '\\')
.value();
}
/**
* Does a given token represent beginning of template string (e.g. `string ${ or `string`)
* @param {Object} token token to parse
* @returns {Boolean} true if token is in a start of template token
*/
function _isStartTemplateToken (token) {
return token.type === 'Template' && _.head(token.value) === '`';
}
/**
* Does a given token represent ending of template string (e.g. }string` or `string`)
* @param {Object} token token to parse
* @returns {boolean} true if token is last token in a template
*/
function _isEndTemplateToken (token) {
return token.type === 'Template' && _.last(token.value) === '`';
}
/**
* Does a given token represent beginning of template string expression (e.g. "something${")
* @param {Object} token token to parse
* @returns {boolean} true if token is first token in a string template expression ${
*/
function _isStartTemplateExprToken (token) {
return token.type === 'Template' && token.value.substr(-2, 2) === '${'; // eslint-disable-line no-magic-numbers
}
/**
* Does a given token represent ending of template string expression (e.g. "}something")
* @param {Object} token token to parse
* @returns {boolean} true if token is last token in a string template expression }
*/
function _isEndTemplateExprToken (token) {
return token.type === 'Template' && _.head(token.value) === '}';
}
/**
* From a given array of token extact all tokens part of a template string ${} expression
* @param {Array} tokens - tokens array to look in
* @param {Number} startWith - a position in array to start search from
* @returns {Array} - token belonging to template string ${} expression
*/
function _getAllTemplateExprTokens (tokens, startWith) {
const exprTokens = [tokens[startWith]];
let endReached = false;
/* eslint-disable */
for (let i = startWith + 1; i < tokens.length && !endReached; i++) {
exprTokens.push(tokens[i]);
if (_isEndTemplateExprToken(tokens[i])) {
endReached = true;
}
}
/* eslint-enable */
return exprTokens;
}
/**
* Figure out if a given tokens array represent ${this.get('stuff')} js expression
* @param {Array} tokens list of token to parse
* @returns {boolean} returns true if tokens represent get expression
*/
function _isGetExpressionTokens (tokens) {
return (
tokens.length === 8 &&
_isStartTemplateExprToken(_.head(tokens)) &&
tokens[1].value === 'this' &&
tokens[2].value === '.' &&
tokens[3].value === 'get' &&
tokens[4].value === '(' &&
tokens[5].type === 'String' &&
tokens[6].value === ')' &&
_isEndTemplateExprToken(_.last(tokens))
);
}
/**
* Figure out if a given tokens array represent ${this.session.get('stuff')} js expression
* @param {Array} tokens list of token to parse
* @returns {boolean} returns true if tokens represent session get expression
*/
function _isSessionGetExpressionTokens (tokens) {
return (
tokens.length === 10 &&
_isStartTemplateExprToken(_.head(tokens)) &&
tokens[1].value === 'this' &&
tokens[2].value === '.' &&
tokens[3].value === 'session' &&
tokens[4].value === '.' &&
tokens[5].value === 'get' &&
tokens[6].value === '(' &&
tokens[7].type === 'String' &&
tokens[8].value === ')' &&
_isEndTemplateExprToken(_.last(tokens))
);
}
/**
* Figure out if a given tokens array represent ${this.get('stuff', 'default')} js expression
* @param {Array} tokens tokens to parse
* @returns {boolean} returns true if token represent get expression with default value
*/
function _isGetDefaultExpressionTokens (tokens) {
return (
tokens.length === 10 &&
_isStartTemplateExprToken(_.head(tokens)) &&
tokens[1].value === 'this' &&
tokens[2].value === '.' &&
tokens[3].value === 'get' &&
tokens[4].value === '(' &&
tokens[5].type === 'String' &&
tokens[6].value === ',' &&
tokens[7].type === 'String' &&
tokens[8].value === ')' &&
_isEndTemplateExprToken(_.last(tokens))
);
}
/**
* Figure out if a given tokens array represent ${this.session.get('stuff', 'default')} js expression
* @param {Array} tokens tokens to parse
* @returns {boolean} returns true if token represent session get expression with default value
*/
function _isSessionGetDefaultExpressionTokens (tokens) {
return (
tokens.length === 12 &&
_isStartTemplateExprToken(_.head(tokens)) &&
tokens[1].value === 'this' &&
tokens[2].value === '.' &&
tokens[3].value === 'session' &&
tokens[4].value === '.' &&
tokens[5].value === 'get' &&
tokens[6].value === '(' &&
tokens[7].type === 'String' &&
tokens[8].value === ',' &&
tokens[9].type === 'String' &&
tokens[10].value === ')' &&
_isEndTemplateExprToken(_.last(tokens))
);
}
/**
* Figure out if a given tokens array represent ${await this.[functionName]('stuff')} js expression
* @param {Array} tokens list of token to parse
* @param {String} functionName that should be parsed as getter
* @returns {boolean} returns true if tokens represent session get expression
*/
function _isAsyncFunctionExpressionTokens (tokens, functionName) {
return (
tokens.length === 9 &&
_isStartTemplateExprToken(_.head(tokens)) &&
tokens[1].value === 'await' &&
tokens[2].value === 'this' &&
tokens[3].value === '.' &&
tokens[4].value === functionName &&
tokens[5].value === '(' &&
tokens[6].type === 'String' &&
tokens[7].value === ')' &&
_isEndTemplateExprToken(_.last(tokens))
);
}
/**
* Figure out if a given tokens array represent ${await this.[functionName]('stuff', 'default')} js expression
* @param {Array} tokens tokens to parse
* @param {String} functionName that should be parsed as getter
* @returns {boolean} returns true if token represent session get expression with default value
*/
function _isAsyncFunctionDefaultExpressionTokens (tokens, functionName) {
return (
tokens.length === 11 &&
_isStartTemplateExprToken(_.head(tokens)) &&
tokens[1].value === 'await' &&
tokens[2].value === 'this' &&
tokens[3].value === '.' &&
tokens[4].value === functionName &&
tokens[5].value === '(' &&
tokens[6].type === 'String' &&
tokens[7].value === ',' &&
tokens[8].type === 'String' &&
tokens[9].value === ')' &&
_isEndTemplateExprToken(_.last(tokens))
);
}
/**
* Figure out if a given tokens array represent ${await this.mergeFields[mergeFieldName].get(...)} js expression
* @param {Array} tokens tokens to parse
* @param {String} functionName that should be parsed as getter
* @returns {boolean} returns true if token represent session get expression with default value
*/
function _isAsyncFunctionMergeFieldExpressionTokens (tokens) {
return (
tokens.length >= 13 &&
_isStartTemplateExprToken(_.head(tokens)) &&
tokens[1].value === 'await' &&
tokens[2].value === 'this' &&
tokens[3].value === '.' &&
tokens[4].value === 'mergeFields' &&
tokens[5].value === '[' &&
tokens[6].type === 'String' &&
tokens[7].value === ']' &&
tokens[8].value === '.' &&
tokens[9].value === 'get' &&
tokens[10].value === '(' &&
_isEndTemplateExprToken(_.last(tokens))
);
}
/**
* Figure out if a given tokens array represent ${this.helpers.stuff} (or other allowed services) js expression
* @param {Array} tokens tokens to parse
* @returns {Boolean} returns true if token represent allowed expression
*/
function _isAllowedExpressionTokens (tokens) {
const allowedConditionStart =
_isStartTemplateExprToken(_.head(tokens)) &&
tokens[1].value === 'this' &&
tokens[2].value === '.' &&
_.includes(['helpers', 'request', 'config', 'error'], tokens[3].value);
return (
(tokens.length === 5 && allowedConditionStart) ||
(tokens.length > 5 &&
allowedConditionStart &&
_isEndTemplateExprToken(_.last(tokens)))
);
}
/**
* Extracts path part from ${this.get('path')} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|string} returns '' if there're no expression tokens or object with path instead
*/
function _extractGetExpressionFromTokens (tokens) {
if (!_isGetExpressionTokens(tokens)) {
return '';
}
return { path: _extractStringValue(tokens[5]) };
}
/**
* Extracts 'path' and 'default' parts from ${this.get('path', default)} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|String} returns '' if there're no expression tokens or object with path instead
*/
function _extractGetDefaultExpressionFromTokens (tokens) {
if (!_isGetDefaultExpressionTokens(tokens)) {
return '';
}
return {
path: _extractStringValue(tokens[5]),
defaultValue: _extractStringValue(tokens[7])
};
}
/**
* Extracts path part from ${this.get('path')} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|string} returns '' if there're no expression tokens or object with path instead
*/
function _extractSessionGetExpressionFromTokens (tokens) {
if (!_isSessionGetExpressionTokens(tokens)) {
return '';
}
return { path: `${SYSTEM_PREFIX}session.${_extractStringValue(tokens[7])}` };
}
/**
* Extracts 'path' and 'default' parts from ${this.get('path', default)} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|String} returns '' if there're no expression tokens or object with path instead
*/
function _extractSessionGetDefaultExpressionFromTokens (tokens) {
if (!_isSessionGetDefaultExpressionTokens(tokens)) {
return '';
}
return {
path: `${SYSTEM_PREFIX}session.${_extractStringValue(tokens[7])}`,
defaultValue: _extractStringValue(tokens[9])
};
}
/**
* Extracts path part from ${await this.getShared('path')} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|string} returns '' if there're no expression tokens or object with path instead
*/
function _extractSharedGetExpressionFromTokens (tokens) {
if (!_isAsyncFunctionExpressionTokens(tokens, 'getShared')) {
return '';
}
return { path: `${SYSTEM_PREFIX}shared.${_extractStringValue(tokens[6])}` };
}
/**
* Extracts 'path' and 'default' parts from ${await this.getShared('path', default)} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|String} returns '' if there're no expression tokens or object with path instead
*/
function _extractSharedGetDefaultExpressionFromTokens (tokens) {
if (!_isAsyncFunctionDefaultExpressionTokens(tokens, 'getShared')) {
return '';
}
return {
path: `${SYSTEM_PREFIX}shared.${_extractStringValue(tokens[6])}`,
defaultValue: _extractStringValue(tokens[8])
};
}
/**
* Extracts path part from ${await this.getGlobal('path')} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|string} returns '' if there're no expression tokens or object with path instead
*/
function _extractGlobalGetExpressionFromTokens (tokens) {
if (!_isAsyncFunctionExpressionTokens(tokens, 'getGlobal')) {
return '';
}
return { path: `${SYSTEM_PREFIX}global.${_extractStringValue(tokens[6])}` };
}
/**
* Extracts 'path' and 'default' parts from ${await this.getGlobal('path', default)} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|String} returns '' if there're no expression tokens or object with path instead
*/
function _extractGlobalGetDefaultExpressionFromTokens (tokens) {
if (!_isAsyncFunctionDefaultExpressionTokens(tokens, 'getGlobal')) {
return '';
}
return {
path: `${SYSTEM_PREFIX}global.${_extractStringValue(tokens[6])}`,
defaultValue: _extractStringValue(tokens[8])
};
}
/**
* Extracts 'path' and 'default' parts from ${await this.getGlobal('path', default)} expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|String} returns '' if there're no expression tokens or object with path instead
*/
function _extractMergeFieldExpressionFromTokens (tokens) {
const path = _.get(
_.map(tokens, 'value')
.join('')
.match(/\bpath: *'(.*?)'/),
'[1]'
);
const dafalutValue = _.get(
_.map(tokens, 'value')
.join('')
.match(/\bdefaultValue: *'(.*?)'/),
'[1]'
);
/* console.log('path', { path, dafalutValue }); */
return {
path: `${_extractStringValue(tokens[6])}${path ? `.${path}` : ''}`,
defaultValue: dafalutValue
};
}
/**
* Extracts 'path' part from ${this.helpers.path} (or another allowed service) expression
* and returns object containing this info
* @param {Array} tokens list of token
* @returns {Object|String} returns '' if there're no expression tokens or object with path instead
*/
function _extractAllowedExpressionFromTokens (tokens) {
if (!_isAllowedExpressionTokens(tokens)) {
return '';
}
const path = _.reduce(
tokens,
(memo, token, index) => {
if (index > 4 && index < tokens.length - 1) {
memo += token.value;
}
return memo;
},
''
);
return {
path: `${SYSTEM_PREFIX}${tokens[3].value}${path ? `.${path}` : ''}`
};
}
/**
* Figure out if a given expression is 'string', "string", `string ${this.get('stuff', 'default')}`,
* `string ${this.session.get('stuff', 'default')}`, `string ${this.helpers.stuff}`
* @param {string} expression expression to extract from
* @returns {Array|undefined} list of expressions
*/
export function stringifyExpression (expression) { // eslint-disable-line complexity
let tokens;
try {
const espreeOptions = {
ecmaVersion: 9,
ecmaFeatures: {
globalReturn: true,
impliedStrict: true
}
};
tokens = espree.tokenize(expression, espreeOptions);
} catch (e) {
console.error('catched error -> ', e);
return;
}
const firstToken = _.head(tokens);
// expression is a simple single- or double-quoted string
if (tokens.length === 1 && firstToken.type === 'String') {
return [_extractStringValue(firstToken)];
}
// expression is a "plain" string expression/template (e.g. `plain string`)
if (tokens.length === 1 && _isStartTemplateToken(firstToken) && _isEndTemplateToken(firstToken)) {
return [_extractStringValue(firstToken)];
}
// expression is a string expression/template (e.g. `string ${template}`) if it starts and ends with `
if (tokens.length > 1 && _isStartTemplateToken(firstToken) && _isEndTemplateToken(_.last(tokens))) {
// fill in first "string" part of the text even if it's empty
const result = [_extractStringValue(firstToken)];
let i = 0;
// check ${} parts of string template
while (i < tokens.length - 1) {
const templateExprTokens = _getAllTemplateExprTokens(tokens, i);
const templateExprTokensLength = templateExprTokens.length;
// if next tokens represent ${this.get('stuff')} expression extract them and look further
if (_isGetExpressionTokens(templateExprTokens)) {
result.push(_extractGetExpressionFromTokens(templateExprTokens));
// if next tokens represent ${this.get('stuff', 'default')} expression extract them and look further
} else if (_isGetDefaultExpressionTokens(templateExprTokens)) {
result.push(_extractGetDefaultExpressionFromTokens(templateExprTokens));
// if next tokens represent ${this.session.get('stuff')} expression extract them and look further
} else if (_isSessionGetExpressionTokens(templateExprTokens)) {
result.push(_extractSessionGetExpressionFromTokens(templateExprTokens));
// if next tokens represent ${this.session.get('stuff', 'default')} expression extract them and look further
} else if (_isSessionGetDefaultExpressionTokens(templateExprTokens)) {
result.push(_extractSessionGetDefaultExpressionFromTokens(templateExprTokens));
// if next tokens represent ${this.shared.get('stuff')} (or other "supported" service call) expression extract them and look further
} else if (_isAsyncFunctionExpressionTokens(templateExprTokens, 'getShared')) {
result.push(_extractSharedGetExpressionFromTokens(templateExprTokens));
// if next tokens represent ${this.shared.get('stuff', 'default')} expression extract them and look further
} else if (_isAsyncFunctionDefaultExpressionTokens(templateExprTokens, 'getShared')) {
result.push(_extractSharedGetDefaultExpressionFromTokens(templateExprTokens));
// if next tokens represent ${this.helpers.stuff} (or other "supported" service call) expression extract them and look further
} else if (_isAsyncFunctionExpressionTokens(templateExprTokens, 'getGlobal')) {
result.push(_extractGlobalGetExpressionFromTokens(templateExprTokens));
// if next tokens represent ${this.shared.get('stuff', 'default')} expression extract them and look further
} else if (_isAsyncFunctionDefaultExpressionTokens(templateExprTokens, 'getGlobal')) {
result.push(_extractGlobalGetDefaultExpressionFromTokens(templateExprTokens));
// if next tokens represent ${this.helpers.stuff} (or other "supported" service call) expression extract them and look further
} else if (_isAsyncFunctionMergeFieldExpressionTokens(templateExprTokens)) {
result.push(_extractMergeFieldExpressionFromTokens(templateExprTokens));
} else if (_isAllowedExpressionTokens(templateExprTokens)) {
result.push(_extractAllowedExpressionFromTokens(templateExprTokens));
// otherwise it's not convertible
} else {
return;
}
// extract string value that goes after get expression
result.push(_extractStringValue(tokens[i + templateExprTokensLength - 1]));
i += templateExprTokensLength - 1;
}
return _.compact(result);
}
}
export const hasMergeField = (value) => {
const mergeFieldRegex = /await\s+this\.mergeFields\['(#?[\w\d]*?)'\]\.get\(.*?\)|\$\{[^\}]*\}|this(\.session)?\.get(Shared|Global)?\(.*?\)|this\.(helpers|config|error)(\.[\w\$]+)?/g;
return mergeFieldRegex.test(value);
};
export const wait = (fn, time = 1000) => {
return new Promise(resolve => {
setTimeout(() => {
fn();
resolve();
}, time);
});
};
export function beautifyCode (source, options) {
const opts = options || { indent_size: 2 };
return beautify(source, opts);
}
export function stringifyValue (value) {
const strValue = JSON.stringify(value, null, 2);
// RegExp: all kinds of merge fields
const mergeFieldRegex = /"(await\s+this\.mergeFields\['(#?[\w\d]*?)'\]\.get\(.*?\)|this(\.session)?\.get(Shared|Global)?\(.*?\)|this\.(helpers|config|error)(\.[\w\$]+)?)"/g;
// RegExp: "`...`"
const templateRegex = /"(`[^\$`]*(\$\{[^\}]*\})?[^`]*?`)"/g;
// RegExp: "[`..`]: ..."
const keyTemplateRegexp = /"(\[`[^\$`]*(\$\{[^\}]*\})?[^`]*?`\])":/g;
// RegExp: "await (async () => ...)()"
const codeRegExp = /"(await\s*\(async\s+\([^)]*?\)\s*=>\s*([\s\S])*?\)\([^)]*?\))"/g;
const replaced = strValue
.replace(codeRegExp, (match, p1) => {
const bodyReg = /await\s*\(async\s+\([^)]*?\)\s*=>\s*([\s\S]*?)\)\([^)]*?\)/;
const bodyMatch = p1.match(bodyReg);
if (bodyMatch) {
const body = bodyMatch[1];
return p1.replace(body, JSON.parse(`"${body}"`));
}
})
.replace(keyTemplateRegexp, '$1:')
.replace(templateRegex, '$1')
.replace(mergeFieldRegex, '$1');
return beautifyCode(replaced);
}
// remove beautify identation
function removeIdentation (str) {
return str.replace(/\n[\s]*/g, '');
}
export function parseCodeInput (value) {
const wrapInAsyncFn = /await\s*\(async\s+\([^)]*?\)\s*=>\s*([\s\S]*?)\)\([^)]*?\)/;
const keyTemplateRegexp = /(\[`[^\$`]*(\$\{[^\}]*\})?[^`]*?`\]):/;
const codeRegExp = /(\[`[^\$`]*(\$\{[^\}]*\})?[^`]*?`\]):|await\s*\(async\s+\([^)]*?\)\s*=>\s*([\s\S]*?)\)\([^)]*?\)|`[^`\$]*(\$\{[^\}]*\})?[^`]*?`|await\s+this\.mergeFields\['(#?[\w\d]*?)'\]\.get\([\s\S]*?\)|this(\.session)?\.get(Shared|Global)?\(.*?\)|this\.(helpers|config|error)(\.[\w\$]+)?/g;
const replaced = value
.replace(codeRegExp, (match, p1) => {
if (keyTemplateRegexp.test(match)) return `"${p1}":`;
if (wrapInAsyncFn.test(match)) {
const asyncWrapMatch = match.match(wrapInAsyncFn);
const body = asyncWrapMatch[1];
return `"${match.replace(body, JSON.stringify(removeIdentation(body)).slice(1, -1))}"`;
}
const expr = removeIdentation(match);
return `"${expr}"`;
});
let parsed;
try {
parsed = JSON.parse(replaced);
} finally {
return parsed;
}
}
export const isCodeValue = (value) => {
const codeRegExp = /await\s*\(async\s+\([^)]*?\)\s*=>\s*([\s\S]*?)\)\([^)]*?\)/g;
return codeRegExp.test(value);
};
export const clearLSByVuexPersistedKey = () => {
window.localStorage.removeItem('_secure__ls__metadata');
_.forIn(window.localStorage, (value, objKey) => {
if (_.startsWith(objKey, 'vuex_') === true) {
window.localStorage.removeItem(objKey);
}
});
};