@silexlabs/grapesjs-data-source
Version:
Grapesjs Data Source
196 lines • 7.96 kB
JavaScript
;
/*
* Silex website builder, free/libre no-code tool for makers.
* Copyright (c) 2023 lexoyo and Silex Labs foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.handlePreviewIndex = handlePreviewIndex;
exports.evaluatePropertyToken = evaluatePropertyToken;
exports.evaluateExpressionTokens = evaluateExpressionTokens;
exports.evaluateStateToken = evaluateStateToken;
exports.resolveStateExpression = resolveStateExpression;
exports.evaluateFilterToken = evaluateFilterToken;
const types_1 = require("../types");
const state_1 = require("./state");
const token_1 = require("./token");
const utils_1 = require("../utils");
/**
* Handle preview index for array data
*/
function handlePreviewIndex(value, token) {
if (typeof token.previewIndex === 'undefined') {
return value;
}
if (Array.isArray(value)) {
return value[token.previewIndex];
}
return value;
}
/**
* Evaluate a property token
*/
function evaluatePropertyToken(token, remaining, context, prevValues) {
var _a;
// Handle "fixed" property (hard coded string set by the user)
if (token.fieldId === types_1.FIXED_TOKEN_ID) {
return evaluateExpressionTokens(remaining, context, (_a = token.options) === null || _a === void 0 ? void 0 : _a.value);
}
// Get data object
let prevObj;
if (typeof prevValues === 'undefined' || prevValues === null) {
if (!token.dataSourceId) {
throw new Error(`Data source ID is missing for token: ${JSON.stringify(token)}`);
}
prevObj = context.previewData[token.dataSourceId];
}
else {
prevObj = prevValues;
}
// Get the next value
let value = prevObj ? prevObj[token.fieldId] : null;
// Handle preview index if resolvePreviewIndex is true
// Don't resolve if next token is a filter - filters need to operate on full arrays
if (context.resolvePreviewIndex) {
const nextToken = remaining[0];
if (!nextToken || nextToken.type !== 'filter') {
value = handlePreviewIndex(value, token);
}
}
// For non-final tokens, handle preview index only if next token is not a filter
// Filters need to operate on full arrays, not individual items
if (remaining.length > 0 && !context.resolvePreviewIndex) {
const nextToken = remaining[0];
if (nextToken.type !== 'filter') {
value = handlePreviewIndex(value, token);
}
}
// Special handling for items state
// @ts-expect-error - Runtime property check for items handling
if (token.isItems && typeof token.previewIndex !== 'undefined') {
if (remaining.length > 0) {
value = [value];
}
}
return evaluateExpressionTokens(remaining, context, value);
}
/**
* Evaluate expression tokens recursively
*/
function evaluateExpressionTokens(expression, context, prevValues = null) {
if (expression.length === 0) {
return prevValues;
}
// Always create defensive copies of tokens to prevent mutations from affecting original data
const cleanExpression = expression.map(token => (Object.assign({}, token)));
const [token, ...rest] = cleanExpression;
switch (token.type) {
case 'state': {
return evaluateStateToken(token, rest, context, prevValues);
}
case 'property': {
return evaluatePropertyToken(token, rest, context, prevValues);
}
case 'filter': {
return evaluateFilterToken(token, rest, context, prevValues);
}
default:
throw new Error(`Unsupported token type: ${JSON.stringify(token)}`);
}
}
/**
* Evaluate a state token
*/
function evaluateStateToken(state, remaining, context, prevValues) {
var _a;
const resolvedExpression = resolveStateExpression(state, context.component, context);
if (!resolvedExpression) {
throw new Error(`Unable to resolve state: ${JSON.stringify(state)}`);
}
// Special handling for items state - always wrap result in array when resolvePreviewIndex is true
const previewIndex = (_a = resolvedExpression[resolvedExpression.length - 1]) === null || _a === void 0 ? void 0 : _a.previewIndex;
if (state.storedStateId === 'items' && typeof previewIndex !== 'undefined') {
// @ts-expect-error - Adding runtime property for items state handling
resolvedExpression[0].isItems = true;
}
return evaluateExpressionTokens([...resolvedExpression, ...remaining], context, prevValues);
}
/**
* Resolve a state token to its expression
*/
function resolveStateExpression(state, component, context) {
const parent = (0, state_1.getParentByPersistentId)(state.componentId, component);
if (!parent) {
console.error('Component not found for state', state, component.get('id-plugin-data-source'));
return null;
}
// Get the expression of the state
const storedState = (0, state_1.getState)(parent, state.storedStateId, state.exposed);
if (!(storedState === null || storedState === void 0 ? void 0 : storedState.expression)) {
console.warn('State is not defined on component', parent.getId(), state, storedState);
return null;
}
// Create a minimal DataTree-like object for fromStored compatibility
return storedState.expression
.flatMap((token) => {
var _a;
switch (token.type) {
case 'state': {
return (_a = resolveStateExpression((0, token_1.fromStored)(token, component.getId()), parent, context)) !== null && _a !== void 0 ? _a : [];
}
default:
return token;
}
});
}
/**
* Evaluate a filter token (Liquid filter)
*/
function evaluateFilterToken(token, remaining, context, prevValues) {
var _a;
const options = Object.entries(token.options).reduce((acc, [key, value]) => {
// If value is a primitive (number, string, boolean), use it directly
// Only evaluate as expression if it's an actual expression object/array
const expression = (0, utils_1.toExpression)(value);
acc[key] = expression ? evaluateExpressionTokens(expression, context, null) : value;
return acc;
}, {});
const filter = context.filters.find(f => f.id === token.id);
if (!filter) {
throw new Error(`Filter not found: ${token.id}`);
}
let value;
try {
value = filter.apply(prevValues, options);
}
catch (e) {
console.warn(`Filter "${filter.id}" error:`, e, {
filter: filter.id,
prevValues,
options,
valueType: typeof prevValues,
isArray: Array.isArray(prevValues),
isNull: prevValues === null,
});
// Mimic behavior of liquid - return null on error
return null;
}
// Don't resolve before filters - they need arrays
const nextIsFilter = remaining.length > 0 && ((_a = remaining[0]) === null || _a === void 0 ? void 0 : _a.type) === 'filter';
if (!nextIsFilter && (context.resolvePreviewIndex || remaining.length > 0)) {
value = handlePreviewIndex(value, token);
}
return evaluateExpressionTokens(remaining, context, value);
}
//# sourceMappingURL=expressionEvaluator.js.map