@cocreate/plugins 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CoCreate.config.js +1 -1
- package/package.json +5 -4
- package/src/index.js +225 -188
- package/src/operators.js +0 -293
package/CoCreate.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/plugins",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "CoCreate plugins",
|
|
5
5
|
"author": "CoCreate LLC",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -24,16 +24,17 @@
|
|
|
24
24
|
"type": "GitHub Sponsors ❤",
|
|
25
25
|
"url": "https://github.com/sponsors/CoCreate-app"
|
|
26
26
|
},
|
|
27
|
+
"type": "module",
|
|
27
28
|
"main": "./src/index.js",
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@cocreate/observer": "^1.20.
|
|
30
|
+
"@cocreate/observer": "^1.20.2"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@cocreate/webpack": "^1.6.
|
|
33
|
+
"@cocreate/webpack": "^1.6.2"
|
|
33
34
|
},
|
|
34
35
|
"allowScripts": {
|
|
35
36
|
"@cocreate/observer@1.19.0": true,
|
|
36
37
|
"@cocreate/utils@1.42.2": true,
|
|
37
38
|
"@cocreate/webpack@1.4.3": true
|
|
38
39
|
}
|
|
39
|
-
}
|
|
40
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,115 +1,198 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* This program is free software: you can redistribute it and/or modify
|
|
5
|
-
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
-
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
* (at your option) any later version.
|
|
8
|
-
*
|
|
9
|
-
* This program is distributed in the hope that it will be useful,
|
|
10
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
* GNU Affero General Public License for more details.
|
|
13
|
-
*
|
|
14
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
-
*
|
|
17
|
-
********************************************************************************/
|
|
18
|
-
|
|
19
|
-
import Observer from "@cocreate/observer";
|
|
20
|
-
import { processOperators } from "./operators.js";
|
|
21
|
-
|
|
22
|
-
const plugins = {};
|
|
1
|
+
import Observer from '@cocreate/observer';
|
|
2
|
+
import { astAsync } from '@cocreate/utils';
|
|
3
|
+
|
|
23
4
|
const scriptCache = new Map();
|
|
24
|
-
const cssMarker = typeof document !== 'undefined'
|
|
5
|
+
const cssMarker = typeof document !== 'undefined'
|
|
6
|
+
? document.head.querySelector('meta[name="viewport"]') || document.head.lastChild
|
|
7
|
+
: null;
|
|
25
8
|
|
|
26
9
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @
|
|
10
|
+
* Retrieves the global plugin configuration object from window.CoCreateConfig.
|
|
11
|
+
* @returns {Object}
|
|
29
12
|
*/
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
13
|
+
function getPluginsConfig() {
|
|
14
|
+
if (typeof window !== 'undefined' && window.CoCreateConfig && window.CoCreateConfig.plugins) {
|
|
15
|
+
return window.CoCreateConfig.plugins;
|
|
16
|
+
}
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Dynamically registers or updates a plugin definition in global config.
|
|
22
|
+
* @param {string} name - Plugin identifier
|
|
23
|
+
* @param {Object} config - Plugin configuration object (js, css, etc.)
|
|
24
|
+
*/
|
|
25
|
+
export function registerPlugin(name, config) {
|
|
26
|
+
if (typeof window !== 'undefined') {
|
|
27
|
+
window.CoCreateConfig = window.CoCreateConfig || {};
|
|
28
|
+
window.CoCreateConfig.plugins = window.CoCreateConfig.plugins || {};
|
|
29
|
+
window.CoCreateConfig.plugins[name] = config;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Dynamic script loader to ensure plugin scripts are loaded once.
|
|
35
|
+
* Sets crossOrigin="anonymous" by default for simple string URLs.
|
|
36
|
+
* @param {string|Object} item - Script URL or object with configuration
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
function loadScript(item) {
|
|
40
|
+
const src = typeof item === 'string' ? item : item?.src;
|
|
41
|
+
if (!src) return Promise.resolve();
|
|
42
|
+
|
|
43
|
+
if (scriptCache.has(src)) return scriptCache.get(src);
|
|
44
|
+
|
|
45
|
+
const promise = new Promise((resolve, reject) => {
|
|
46
|
+
const script = document.createElement('script');
|
|
47
|
+
script.src = src;
|
|
48
|
+
script.async = true;
|
|
49
|
+
|
|
50
|
+
// Default crossOrigin to "anonymous" unless explicitly set otherwise in an object
|
|
51
|
+
const crossOrigin = typeof item === 'object' && 'crossOrigin' in item ? item.crossOrigin : 'anonymous';
|
|
52
|
+
if (crossOrigin) {
|
|
53
|
+
script.crossOrigin = crossOrigin;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof item === 'object' && item.integrity) {
|
|
57
|
+
script.integrity = item.integrity;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
script.onload = () => resolve();
|
|
61
|
+
script.onerror = () => {
|
|
62
|
+
scriptCache.delete(src);
|
|
63
|
+
reject(new Error(`Failed to load script: ${src}`));
|
|
64
|
+
};
|
|
65
|
+
document.head.appendChild(script);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
scriptCache.set(src, promise);
|
|
69
|
+
return promise;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Dynamic CSS stylesheet loader for plugins.
|
|
74
|
+
* Sets crossOrigin="anonymous" by default for simple string URLs.
|
|
75
|
+
* @param {string|Object} item - Stylesheet URL or object with configuration
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
function loadCSS(item) {
|
|
79
|
+
const href = typeof item === 'string' ? item : item?.href;
|
|
80
|
+
if (!href) return Promise.resolve();
|
|
81
|
+
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
if (document.querySelector(`link[href="${href}"]`)) {
|
|
84
|
+
return resolve();
|
|
85
|
+
}
|
|
86
|
+
const link = document.createElement('link');
|
|
87
|
+
link.rel = 'stylesheet';
|
|
88
|
+
link.href = href;
|
|
89
|
+
|
|
90
|
+
// Default crossOrigin to "anonymous" unless explicitly set otherwise in an object
|
|
91
|
+
const crossOrigin = typeof item === 'object' && 'crossOrigin' in item ? item.crossOrigin : 'anonymous';
|
|
92
|
+
if (crossOrigin) {
|
|
93
|
+
link.crossOrigin = crossOrigin;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof item === 'object' && item.integrity) {
|
|
97
|
+
link.integrity = item.integrity;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
link.onload = () => resolve();
|
|
101
|
+
link.onerror = () => reject(new Error(`Failed to load stylesheet: ${href}`));
|
|
102
|
+
|
|
103
|
+
if (cssMarker && cssMarker.parentNode) {
|
|
104
|
+
cssMarker.parentNode.insertBefore(link, cssMarker);
|
|
105
|
+
} else {
|
|
106
|
+
document.head.appendChild(link);
|
|
39
107
|
}
|
|
40
108
|
});
|
|
41
109
|
}
|
|
42
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Preloads JS scripts and CSS stylesheets declared in global plugins config.
|
|
113
|
+
* Runs in parallel using Promise.allSettled.
|
|
114
|
+
* @param {Array<string>} pluginNames - Names of target plugins to preload
|
|
115
|
+
*/
|
|
43
116
|
async function preloadResources(pluginNames) {
|
|
117
|
+
const plugins = getPluginsConfig();
|
|
118
|
+
const promises = [];
|
|
119
|
+
|
|
44
120
|
for (const name of pluginNames) {
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const link = document.createElement("link");
|
|
51
|
-
link.rel = "stylesheet";
|
|
52
|
-
link.href = href;
|
|
53
|
-
if (cssMarker) cssMarker.parentNode.insertBefore(link, cssMarker);
|
|
54
|
-
else document.head.appendChild(link);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
121
|
+
const config = plugins[name];
|
|
122
|
+
if (config) {
|
|
123
|
+
if (config.css) {
|
|
124
|
+
const cssFiles = Array.isArray(config.css) ? config.css : [config.css];
|
|
125
|
+
cssFiles.forEach(href => promises.push(loadCSS(href)));
|
|
57
126
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const src = typeof item === 'string' ? item : item.src;
|
|
62
|
-
if (!src) continue;
|
|
63
|
-
if (!scriptCache.has(src)) {
|
|
64
|
-
const existingScript = document.querySelector(`script[src*="${src}"]`);
|
|
65
|
-
if (existingScript && (existingScript.dataset.loaded === "true" || window[name])) {
|
|
66
|
-
scriptCache.set(src, Promise.resolve());
|
|
67
|
-
} else {
|
|
68
|
-
const s = document.createElement("script");
|
|
69
|
-
s.src = src;
|
|
70
|
-
scriptCache.set(src, new Promise((resolve) => {
|
|
71
|
-
s.onload = () => { s.dataset.loaded = "true"; resolve(); };
|
|
72
|
-
s.onerror = () => resolve();
|
|
73
|
-
document.head.appendChild(s);
|
|
74
|
-
}));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
await scriptCache.get(src);
|
|
78
|
-
}
|
|
127
|
+
if (config.js) {
|
|
128
|
+
const jsFiles = Array.isArray(config.js) ? config.js : [config.js];
|
|
129
|
+
jsFiles.forEach(src => promises.push(loadScript(src)));
|
|
79
130
|
}
|
|
80
131
|
}
|
|
81
132
|
}
|
|
133
|
+
|
|
134
|
+
const results = await Promise.allSettled(promises);
|
|
135
|
+
results.forEach(res => {
|
|
136
|
+
if (res.status === 'rejected') {
|
|
137
|
+
console.warn('[Plugin System]', res.reason.message || res.reason);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Helper to poll window for plugin library constructor if not immediately present.
|
|
144
|
+
* @param {string} pluginName - Target plugin identifier
|
|
145
|
+
* @param {number} retries - Number of retries
|
|
146
|
+
* @param {number} delay - Retry interval in ms
|
|
147
|
+
* @returns {Promise<Function|Object|null>}
|
|
148
|
+
*/
|
|
149
|
+
async function waitForPluginOnWindow(pluginName, retries = 5, delay = 50) {
|
|
150
|
+
for (let i = 0; i < retries; i++) {
|
|
151
|
+
const lib = getPluginLibrary(pluginName);
|
|
152
|
+
if (lib) return lib;
|
|
153
|
+
await new Promise(res => setTimeout(res, delay));
|
|
154
|
+
}
|
|
155
|
+
return getPluginLibrary(pluginName);
|
|
82
156
|
}
|
|
83
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Helper to resolve and normalize plugin library constructor from window object.
|
|
160
|
+
* @param {string} pluginName - The target plugin identifier
|
|
161
|
+
* @returns {Function|Object|null}
|
|
162
|
+
*/
|
|
163
|
+
function getPluginLibrary(pluginName) {
|
|
164
|
+
if (typeof window === 'undefined') return null;
|
|
165
|
+
if (window[pluginName]) return window[pluginName];
|
|
166
|
+
|
|
167
|
+
// Try capitalized variant (e.g. 'simpleBar' -> 'SimpleBar')
|
|
168
|
+
const capitalized = pluginName.charAt(0).toUpperCase() + pluginName.slice(1);
|
|
169
|
+
if (window[capitalized]) return window[capitalized];
|
|
170
|
+
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Parses element 'plugin' attribute, executes AST configuration via astAsync,
|
|
176
|
+
* sets core library on window, and attaches instance onto element property.
|
|
177
|
+
* @param {Element} el - DOM element with plugin attribute
|
|
178
|
+
*/
|
|
84
179
|
async function processPlugin(el) {
|
|
85
|
-
if (!el.attributes) return;
|
|
180
|
+
if (!el || !el.attributes) return;
|
|
86
181
|
|
|
87
|
-
// Direct attributes collection map lookup to avoid binding/context prototype errors
|
|
88
182
|
const pluginAttrNode = el.attributes['plugin'] || el.attributes.getNamedItem?.('plugin');
|
|
89
183
|
const rawPluginAttr = pluginAttrNode?.value?.trim();
|
|
90
184
|
if (!rawPluginAttr) return;
|
|
91
185
|
|
|
92
|
-
const context = {
|
|
93
|
-
element: el,
|
|
94
|
-
this: el,
|
|
95
|
-
plugin: null, // Tracks active sibling plugin instance
|
|
96
|
-
data: { ...el.dataset },
|
|
97
|
-
event: {},
|
|
98
|
-
crud: window.CoCreate?.crud || null
|
|
99
|
-
};
|
|
100
|
-
|
|
101
186
|
try {
|
|
102
187
|
let configs = [];
|
|
103
|
-
|
|
104
|
-
// Unified Direct Parser: Pure JSON parsing with simple zero-argument fallback
|
|
188
|
+
|
|
105
189
|
if (rawPluginAttr.startsWith('{') || rawPluginAttr.startsWith('[')) {
|
|
106
190
|
const parsedConfig = JSON.parse(rawPluginAttr);
|
|
107
191
|
configs = Array.isArray(parsedConfig) ? parsedConfig : [parsedConfig];
|
|
108
192
|
} else {
|
|
109
|
-
// Symmetrical Fallback: Plain strings mapped straight to constructor blocks
|
|
110
193
|
configs = [{ [rawPluginAttr]: [] }];
|
|
111
194
|
}
|
|
112
|
-
|
|
195
|
+
|
|
113
196
|
const targetPlugins = new Set();
|
|
114
197
|
configs.forEach(conf => {
|
|
115
198
|
Object.keys(conf).forEach(k => {
|
|
@@ -118,119 +201,77 @@ async function processPlugin(el) {
|
|
|
118
201
|
}
|
|
119
202
|
});
|
|
120
203
|
});
|
|
121
|
-
|
|
204
|
+
|
|
122
205
|
await preloadResources(Array.from(targetPlugins));
|
|
123
206
|
|
|
124
|
-
// Execute each configuration block sequentially
|
|
125
207
|
for (const configBlock of configs) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
console.error("[Plugin System] Failed to parse and execute plugin configuration:", err, rawPluginAttr);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
208
|
+
for (const [pluginName, astValue] of Object.entries(configBlock)) {
|
|
209
|
+
// Poll window to ensure script execution has completed
|
|
210
|
+
const pluginLib = await waitForPluginOnWindow(pluginName);
|
|
132
211
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const isEs6Class = /^\s*class\s+/.test(ConstructorFunc.toString());
|
|
138
|
-
|
|
139
|
-
if (isEs6Class) {
|
|
140
|
-
return new ConstructorFunc(...argsArray);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
// Fallback Step A: Attempt standard function invocation (e.g. particlesJS)
|
|
145
|
-
return ConstructorFunc(...argsArray);
|
|
146
|
-
} catch (err) {
|
|
147
|
-
// Fallback Step B: If invocation throws constructor error (e.g. Babel transpiled ES5 classes)
|
|
148
|
-
if (err instanceof TypeError) {
|
|
149
|
-
return new ConstructorFunc(...argsArray);
|
|
150
|
-
}
|
|
151
|
-
throw err;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
212
|
+
// Ensure plugin library definition is attached to window once loaded
|
|
213
|
+
if (pluginLib && typeof window !== 'undefined' && !window[pluginName]) {
|
|
214
|
+
window[pluginName] = pluginLib;
|
|
215
|
+
}
|
|
154
216
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// 1. If key is an operator, process it natively via AST engine
|
|
158
|
-
if (key.startsWith("$")) {
|
|
159
|
-
await processOperators(context, { [key]: value });
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
217
|
+
// Format operator key with $ for AST engine recognition (e.g. SimpleBar -> $SimpleBar)
|
|
218
|
+
const operatorName = pluginName.startsWith('$') ? pluginName : `$${pluginName}`;
|
|
162
219
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
220
|
+
// Create constructor wrapper to safely instantiate class/function
|
|
221
|
+
const opWrapper = function(params) {
|
|
222
|
+
const args = Array.isArray(params) ? params : [params];
|
|
223
|
+
if (typeof pluginLib === 'function') {
|
|
224
|
+
try {
|
|
225
|
+
return new pluginLib(...args);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
return pluginLib(...args);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return pluginLib;
|
|
169
231
|
};
|
|
170
|
-
return await processOperators(localContext, value);
|
|
171
|
-
};
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const parts = key.split('.');
|
|
176
|
-
const rootPluginName = parts[0];
|
|
177
232
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
ConstructorFunc = ConstructorFunc[part];
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 4. If it's a function, treat it as a constructor to initialize the plugin
|
|
187
|
-
if (typeof ConstructorFunc === 'function' && !context.element[rootPluginName]) {
|
|
188
|
-
let constructorArgs = [];
|
|
189
|
-
if (value !== undefined) {
|
|
190
|
-
const evaluatedArgs = await processOperators(context, value);
|
|
191
|
-
constructorArgs = Array.isArray(evaluatedArgs) ? evaluatedArgs : [evaluatedArgs];
|
|
192
|
-
}
|
|
233
|
+
// Provide operator wrapper in context for AST registry lookup
|
|
234
|
+
const context = {
|
|
235
|
+
[operatorName]: opWrapper
|
|
236
|
+
};
|
|
193
237
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
238
|
+
// Execute AST with the $ operator key so astAsync invokes operatorKey logic
|
|
239
|
+
const result = await astAsync({
|
|
240
|
+
node: el,
|
|
241
|
+
sandbox: context,
|
|
242
|
+
executeAST: { [operatorName]: astValue }
|
|
243
|
+
});
|
|
200
244
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
let method = methodTarget;
|
|
205
|
-
let parent = methodTarget;
|
|
206
|
-
for (let i = 1; i < parts.length; i++) {
|
|
207
|
-
parent = method;
|
|
208
|
-
if (method) method = method[parts[i]];
|
|
209
|
-
}
|
|
210
|
-
if (typeof method === 'function') {
|
|
211
|
-
let methodArgs = [];
|
|
212
|
-
if (value !== undefined) {
|
|
213
|
-
const evaluatedArgs = await processOperators(context, value);
|
|
214
|
-
methodArgs = Array.isArray(evaluatedArgs) ? evaluatedArgs : [evaluatedArgs];
|
|
245
|
+
if (result !== undefined && !pluginName.startsWith('$')) {
|
|
246
|
+
// Attach returned plugin instance directly onto element property
|
|
247
|
+
el[pluginName] = result;
|
|
215
248
|
}
|
|
216
|
-
await method.apply(parent, methodArgs);
|
|
217
|
-
continue;
|
|
218
249
|
}
|
|
219
250
|
}
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.error('[Plugin System] Failed to parse and execute plugin configuration:', err, rawPluginAttr);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
220
255
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
await context.plugin[key].apply(context.plugin, methodArgs);
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
256
|
+
/**
|
|
257
|
+
* Initializes plugins on single elements, collections, or selector query matches.
|
|
258
|
+
* @param {Element|NodeList|Array|string} elements - Target element(s) or DOM query selector
|
|
259
|
+
*/
|
|
260
|
+
export function init(elements) {
|
|
261
|
+
if (!elements) return;
|
|
231
262
|
|
|
232
|
-
|
|
233
|
-
|
|
263
|
+
if (typeof elements === 'string') {
|
|
264
|
+
elements = document.querySelectorAll(elements);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (elements instanceof Element) {
|
|
268
|
+
processPlugin(elements);
|
|
269
|
+
} else if (elements.length !== undefined || Array.isArray(elements)) {
|
|
270
|
+
Array.from(elements).forEach(el => {
|
|
271
|
+
if (el instanceof Element) {
|
|
272
|
+
processPlugin(el);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
234
275
|
}
|
|
235
276
|
}
|
|
236
277
|
|
|
@@ -244,11 +285,7 @@ if (typeof document !== 'undefined') {
|
|
|
244
285
|
callback: (mutation) => init(mutation.target)
|
|
245
286
|
});
|
|
246
287
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
})
|
|
252
|
-
.catch(() => {})
|
|
253
|
-
.finally(() => init(document.querySelectorAll(selector)));
|
|
254
|
-
}
|
|
288
|
+
init(document.querySelectorAll(selector));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export default { init, registerPlugin };
|
package/src/operators.js
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/********************************************************************************
|
|
2
|
-
* Copyright (C) 2026 CoCreate and Contributors.
|
|
3
|
-
*
|
|
4
|
-
* This program is free software: you can redistribute it and/or modify
|
|
5
|
-
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
-
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
* (at your option) any later version.
|
|
8
|
-
*
|
|
9
|
-
* This program is distributed in the hope that it will be useful,
|
|
10
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
* GNU Affero General Public License for more details.
|
|
13
|
-
*
|
|
14
|
-
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
-
*
|
|
17
|
-
********************************************************************************/
|
|
18
|
-
|
|
19
|
-
import { getValueFromObject, queryElements } from "@cocreate/utils";
|
|
20
|
-
|
|
21
|
-
const IsomorphicGlobals = {
|
|
22
|
-
Math,
|
|
23
|
-
Number,
|
|
24
|
-
String,
|
|
25
|
-
Boolean,
|
|
26
|
-
Date,
|
|
27
|
-
JSON,
|
|
28
|
-
Array,
|
|
29
|
-
Object,
|
|
30
|
-
parseInt,
|
|
31
|
-
parseFloat,
|
|
32
|
-
encodeURIComponent,
|
|
33
|
-
decodeURIComponent,
|
|
34
|
-
btoa: (str) => {
|
|
35
|
-
if (typeof btoa === 'function') return btoa(String(str));
|
|
36
|
-
return Buffer.from(String(str)).toString("base64");
|
|
37
|
-
},
|
|
38
|
-
atob: (b64) => {
|
|
39
|
-
if (typeof atob === 'function') return atob(b64);
|
|
40
|
-
return Buffer.from(String(b64), "base64").toString("utf-8");
|
|
41
|
-
},
|
|
42
|
-
concat: (...args) => args.flat().map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(''),
|
|
43
|
-
|
|
44
|
-
// Core Asynchronous Flow Control Operators
|
|
45
|
-
await: {
|
|
46
|
-
all: async (promises) => Promise.all(promises),
|
|
47
|
-
any: async (promises) => Promise.any(promises),
|
|
48
|
-
race: async (promises) => Promise.race(promises),
|
|
49
|
-
allSettled: async (promises) => Promise.allSettled(promises),
|
|
50
|
-
delay: (ms) => new Promise(resolve => setTimeout(resolve, Number(ms) || 0))
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const BrowserGlobals = {
|
|
55
|
-
get $organization_id() { return typeof localStorage !== 'undefined' ? localStorage.getItem("organization_id") : ""; },
|
|
56
|
-
get $user_id() { return typeof localStorage !== 'undefined' ? localStorage.getItem("user_id") : ""; },
|
|
57
|
-
get $client_id() { return typeof localStorage !== 'undefined' ? localStorage.getItem("clientId") : ""; },
|
|
58
|
-
get $session_id() { return typeof localStorage !== 'undefined' ? localStorage.getItem("session_id") : ""; },
|
|
59
|
-
get $innerWidth() { return typeof window !== 'undefined' ? window.innerWidth : 0; },
|
|
60
|
-
get $innerHeight() { return typeof window !== 'undefined' ? window.innerHeight : 0; },
|
|
61
|
-
get $href() { return typeof window !== 'undefined' ? window.location.href.replace(/\/$/, "") : ""; },
|
|
62
|
-
get $origin() { return typeof window !== 'undefined' ? window.location.origin : ""; },
|
|
63
|
-
get $protocol() { return typeof window !== 'undefined' ? window.location.protocol : ""; },
|
|
64
|
-
get $hostname() { return typeof window !== 'undefined' ? window.location.hostname : ""; },
|
|
65
|
-
get $host() { return typeof window !== 'undefined' ? window.location.host : ""; },
|
|
66
|
-
get $port() { return typeof window !== 'undefined' ? window.location.port : ""; },
|
|
67
|
-
get $pathname() { return typeof window !== 'undefined' ? window.location.pathname.replace(/\/$/, "") : ""; },
|
|
68
|
-
get $hash() { return typeof window !== 'undefined' ? window.location.hash : ""; },
|
|
69
|
-
get $subdomain() {
|
|
70
|
-
if (typeof window === 'undefined') return "";
|
|
71
|
-
const parts = window.location.hostname.split('.');
|
|
72
|
-
return parts.length > 2 ? parts[0] : "";
|
|
73
|
-
},
|
|
74
|
-
get $relativePath() {
|
|
75
|
-
if (typeof window === 'undefined') return "./";
|
|
76
|
-
const currentPath = window.location.pathname.replace(/\/[^\/]*$/, "");
|
|
77
|
-
const depth = currentPath.split("/").filter(Boolean).length;
|
|
78
|
-
return depth > 0 ? "../".repeat(depth) : "./";
|
|
79
|
-
},
|
|
80
|
-
get $path() {
|
|
81
|
-
if (typeof window === 'undefined') return "";
|
|
82
|
-
let path = window.location.pathname;
|
|
83
|
-
if (path.split("/").pop().includes(".")) path = path.replace(/\/[^\/]+$/, "/");
|
|
84
|
-
return path === "/" ? "" : path;
|
|
85
|
-
},
|
|
86
|
-
$true: () => true,
|
|
87
|
-
$false: () => false,
|
|
88
|
-
$parse: (value = "") => {
|
|
89
|
-
try { return JSON.parse(value); } catch (e) { return value; }
|
|
90
|
-
},
|
|
91
|
-
$query: function(selector) {
|
|
92
|
-
if (typeof document === 'undefined') return undefined;
|
|
93
|
-
let element = this?.element || null;
|
|
94
|
-
if (typeof selector === 'string') {
|
|
95
|
-
selector = selector.trim().replace(/^['"]|['"]$/g, '');
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const results = queryElements({ element, selector });
|
|
99
|
-
return results && results.length > 0 ? results[0] : undefined;
|
|
100
|
-
} catch (error) {
|
|
101
|
-
console.warn(`[AST Engine] Invalid $query selector => "${selector}"`, error);
|
|
102
|
-
return undefined;
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
$queryAll: function(selector) {
|
|
106
|
-
if (typeof document === 'undefined') return [];
|
|
107
|
-
let element = this?.element || null;
|
|
108
|
-
if (typeof selector === 'string') {
|
|
109
|
-
selector = selector.trim().replace(/^['"]|['"]$/g, '');
|
|
110
|
-
if (selector.includes('$document')) return [];
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
const results = queryElements({ element, selector });
|
|
114
|
-
return results ? Array.from(results) : [];
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.warn(`[AST Engine] Invalid $queryAll selector => "${selector}"`, error);
|
|
117
|
-
return [];
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const getSandboxScope = (context) => {
|
|
123
|
-
const isBrowser = typeof window !== 'undefined';
|
|
124
|
-
return {
|
|
125
|
-
...IsomorphicGlobals,
|
|
126
|
-
...(isBrowser ? BrowserGlobals : {}),
|
|
127
|
-
...context
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Main recursive traversal loop of the AST Compiler.
|
|
133
|
-
* Analyzes object structures from the inside-out to resolve nested expression boundaries.
|
|
134
|
-
*/
|
|
135
|
-
export async function processOperators(context, execute) {
|
|
136
|
-
if (execute instanceof Promise) {
|
|
137
|
-
execute = await execute;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (execute === null || execute === undefined) {
|
|
141
|
-
return execute;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (Array.isArray(execute)) {
|
|
145
|
-
const evaluatedArray = [];
|
|
146
|
-
for (let i = 0; i < execute.length; i++) {
|
|
147
|
-
evaluatedArray[i] = await processOperators(context, execute[i]);
|
|
148
|
-
}
|
|
149
|
-
return evaluatedArray;
|
|
150
|
-
} else if (typeof execute === "object") {
|
|
151
|
-
const keys = Object.keys(execute);
|
|
152
|
-
|
|
153
|
-
// Match operators starting with "$" except raw database storage parameters
|
|
154
|
-
const operatorKey = keys.find(k => k.startsWith("$") && !["$array", "$filter", "$storage", "$database"].includes(k));
|
|
155
|
-
|
|
156
|
-
if (operatorKey && keys.length === 1) {
|
|
157
|
-
return await executeOperator(context, operatorKey, execute[operatorKey]);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const evaluatedObject = {};
|
|
161
|
-
for (const key of keys) {
|
|
162
|
-
let finalKey = key;
|
|
163
|
-
|
|
164
|
-
if (key.startsWith("$") && !["$array", "$filter", "$storage", "$database"].includes(key)) {
|
|
165
|
-
const resolvedKey = await executeOperator(context, key);
|
|
166
|
-
if (typeof resolvedKey === "string" || typeof resolvedKey === "number") {
|
|
167
|
-
finalKey = String(resolvedKey);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
let finalValue = execute[key];
|
|
172
|
-
if (typeof finalValue === "string" && finalValue.startsWith("$") && !["$array", "$filter", "$storage", "$database"].includes(finalValue)) {
|
|
173
|
-
finalValue = await executeOperator(context, finalValue);
|
|
174
|
-
} else if (Array.isArray(finalValue) || (typeof finalValue === "object" && finalValue !== null)) {
|
|
175
|
-
finalValue = await processOperators(context, finalValue);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
evaluatedObject[finalKey] = finalValue;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return evaluatedObject;
|
|
182
|
-
} else if (typeof execute === "string" && execute.startsWith("$") && !["$array", "$filter", "$storage", "$database"].includes(execute)) {
|
|
183
|
-
return await executeOperator(context, execute);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return execute;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function executeOperator(context, operator, args) {
|
|
190
|
-
const localContext = {
|
|
191
|
-
state: {},
|
|
192
|
-
...context
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// Evaluate core context property mappings
|
|
196
|
-
if (args === undefined) {
|
|
197
|
-
if (operator.startsWith("$state.")) {
|
|
198
|
-
return getValueFromObject(localContext.state, operator.substring(7));
|
|
199
|
-
} else if (operator === "$state") {
|
|
200
|
-
return localContext.state;
|
|
201
|
-
} else if (operator.startsWith("$data.")) {
|
|
202
|
-
return getValueFromObject(localContext.data, operator.substring(6));
|
|
203
|
-
} else if (operator === "$data") {
|
|
204
|
-
return localContext.data;
|
|
205
|
-
} else if (operator.startsWith("$event.")) {
|
|
206
|
-
return getValueFromObject(localContext.event, operator.substring(7));
|
|
207
|
-
} else if (operator === "$event") {
|
|
208
|
-
return localContext.event;
|
|
209
|
-
} else if (operator.startsWith("$request.")) {
|
|
210
|
-
return getValueFromObject(localContext.request, operator.substring(9));
|
|
211
|
-
} else if (operator === "$request") {
|
|
212
|
-
return localContext.request;
|
|
213
|
-
} else if (operator.startsWith("$header.")) {
|
|
214
|
-
const headerKey = operator.substring(8).toLowerCase();
|
|
215
|
-
return localContext.request?.headers?.[headerKey];
|
|
216
|
-
} else if (operator === "$rawBody") {
|
|
217
|
-
return localContext.rawBody;
|
|
218
|
-
} else if (operator === "$this" || operator === "$element") {
|
|
219
|
-
return localContext.element;
|
|
220
|
-
} else if (operator === "$plugin" || operator === "$instance") {
|
|
221
|
-
return localContext.plugin;
|
|
222
|
-
} else if (operator === "$value") {
|
|
223
|
-
const el = localContext.element;
|
|
224
|
-
return el && typeof el.getValue === 'function' ? el.getValue() : "";
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
let isExplicitNew = false;
|
|
229
|
-
let cleanPath = operator.startsWith("$") ? operator.substring(1) : operator;
|
|
230
|
-
|
|
231
|
-
if (cleanPath.startsWith("new.")) {
|
|
232
|
-
isExplicitNew = true;
|
|
233
|
-
cleanPath = cleanPath.substring(4);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const parts = cleanPath.split('.');
|
|
237
|
-
const SandboxScope = getSandboxScope(localContext);
|
|
238
|
-
|
|
239
|
-
let target = SandboxScope[parts[0]];
|
|
240
|
-
let parentContext = SandboxScope;
|
|
241
|
-
|
|
242
|
-
if (target === undefined && localContext.element) {
|
|
243
|
-
target = localContext.element[parts[0]];
|
|
244
|
-
parentContext = localContext.element;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (target !== undefined) {
|
|
248
|
-
for (let i = 1; i < parts.length; i++) {
|
|
249
|
-
parentContext = target;
|
|
250
|
-
target = target[parts[i]];
|
|
251
|
-
if (target === undefined) break;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (typeof target === 'function') {
|
|
255
|
-
let argsArray = [];
|
|
256
|
-
if (args !== undefined) {
|
|
257
|
-
if (typeof args === 'object' && args !== null && (args.$args !== undefined || args.$arguments !== undefined)) {
|
|
258
|
-
const explicitArgs = args.$args !== undefined ? args.$args : args.$arguments;
|
|
259
|
-
|
|
260
|
-
if (Array.isArray(explicitArgs)) {
|
|
261
|
-
argsArray = [];
|
|
262
|
-
for (let i = 0; i < explicitArgs.length; i++) {
|
|
263
|
-
argsArray.push(await processOperators(localContext, explicitArgs[i]));
|
|
264
|
-
}
|
|
265
|
-
} else {
|
|
266
|
-
const evaluatedSingle = await processOperators(localContext, explicitArgs);
|
|
267
|
-
argsArray = [evaluatedSingle];
|
|
268
|
-
}
|
|
269
|
-
} else {
|
|
270
|
-
const evaluatedSingle = await processOperators(localContext, args);
|
|
271
|
-
argsArray = [evaluatedSingle];
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const isEs6Class = /^\s*class\s+/.test(target.toString());
|
|
276
|
-
|
|
277
|
-
if (isExplicitNew || isEs6Class) {
|
|
278
|
-
return new target(...argsArray);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return await target.apply(parentContext, argsArray);
|
|
282
|
-
} else if (target !== undefined) {
|
|
283
|
-
return target;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return operator;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export { processOperators as processOperatorsAsync };
|
|
291
|
-
export const customOperators = {};
|
|
292
|
-
|
|
293
|
-
export default processOperators;
|