@cocreate/plugins 1.2.0 → 1.2.1
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/index.js +176 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.2.1](https://github.com/CoCreate-app/CoCreate-plugins/compare/v1.2.0...v1.2.1) (2026-03-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* Handle case-insensitive plugin global mapping and add support for function reference assignments in plugin properties. ([904a947](https://github.com/CoCreate-app/CoCreate-plugins/commit/904a947abc868937b6e650b62e080d00341272a1))
|
|
7
|
+
|
|
1
8
|
# [1.2.0](https://github.com/CoCreate-app/CoCreate-plugins/compare/v1.1.1...v1.2.0) (2026-02-15)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {dotNotationToObject} from "@cocreate/utils";
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {Object} PluginDefinition
|
|
6
|
-
* @property {Array<string|Object>} [js] - List of JS files to load. Can be strings (URLs) or objects with src, integrity, etc
|
|
6
|
+
* @property {Array<string|Object>} [js] - List of JS files to load. Can be strings (URLs) or objects with src, integrity, etc
|
|
7
7
|
* @property {Array<string>} [css] - List of CSS files to load.
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -107,6 +107,8 @@ async function processPlugin(el) {
|
|
|
107
107
|
|
|
108
108
|
// Load JS with Promise Cache
|
|
109
109
|
if (pluginDef.js) {
|
|
110
|
+
const preWindowKeys = (typeof window !== 'undefined') ? new Set(Object.keys(window)) : new Set();
|
|
111
|
+
|
|
110
112
|
for (const item of pluginDef.js) {
|
|
111
113
|
const src = typeof item === 'string' ? item : item.src;
|
|
112
114
|
const integrity = typeof item === 'object' ? item.integrity : null;
|
|
@@ -152,6 +154,36 @@ async function processPlugin(el) {
|
|
|
152
154
|
console.error(`Failed to load script: ${src}`, e);
|
|
153
155
|
}
|
|
154
156
|
}
|
|
157
|
+
|
|
158
|
+
// After loading JS files, map newly-added globals to the expected plugin name.
|
|
159
|
+
// Exact (case-insensitive) matching only.
|
|
160
|
+
try {
|
|
161
|
+
if (typeof window !== 'undefined') {
|
|
162
|
+
const expectedName = pluginName;
|
|
163
|
+
const lower = expectedName.toLowerCase();
|
|
164
|
+
|
|
165
|
+
const allKeys = Object.keys(window);
|
|
166
|
+
const newKeys = allKeys.filter(k => !preWindowKeys.has(k));
|
|
167
|
+
let mappedKey = null;
|
|
168
|
+
|
|
169
|
+
for (const k of newKeys) {
|
|
170
|
+
if (k.toLowerCase() === lower) { mappedKey = k; break; }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!mappedKey) {
|
|
174
|
+
for (const k of allKeys) {
|
|
175
|
+
if (k.toLowerCase() === lower) { mappedKey = k; break; }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (mappedKey && !window[expectedName]) {
|
|
180
|
+
window[expectedName] = window[mappedKey];
|
|
181
|
+
console.debug(`Mapped plugin global: window.${expectedName} <- window.${mappedKey}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} catch (err) {
|
|
185
|
+
// Non-fatal
|
|
186
|
+
}
|
|
155
187
|
}
|
|
156
188
|
}
|
|
157
189
|
|
|
@@ -234,7 +266,7 @@ function executeGenericPlugin(el, name) {
|
|
|
234
266
|
|
|
235
267
|
// Pass context: Window as parent, Plugin Name as property (for potential context binding)
|
|
236
268
|
// el and name used to store the result on the element.
|
|
237
|
-
update(Target, val, window, name, el, name);
|
|
269
|
+
update(Target, val, window, name, el, name, el);
|
|
238
270
|
|
|
239
271
|
console.log(`Processed ${name}`);
|
|
240
272
|
} catch (e) {
|
|
@@ -244,7 +276,126 @@ function executeGenericPlugin(el, name) {
|
|
|
244
276
|
}
|
|
245
277
|
}
|
|
246
278
|
|
|
247
|
-
function
|
|
279
|
+
function resolvePathWithParent(root, path) {
|
|
280
|
+
if (!root || !path || typeof path !== "string") return { parent: null, value: undefined };
|
|
281
|
+
const parts = path.split(".").filter(Boolean);
|
|
282
|
+
if (!parts.length) return { parent: null, value: undefined };
|
|
283
|
+
|
|
284
|
+
let parent = null;
|
|
285
|
+
let current = root;
|
|
286
|
+
for (let i = 0; i < parts.length; i++) {
|
|
287
|
+
const part = parts[i];
|
|
288
|
+
if (current == null) return { parent: null, value: undefined };
|
|
289
|
+
parent = current;
|
|
290
|
+
current = current[part];
|
|
291
|
+
}
|
|
292
|
+
return { parent, value: current };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function normalizeCrudPayload(value) {
|
|
296
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
297
|
+
|
|
298
|
+
if (value.type && Array.isArray(value[value.type])) return value[value.type];
|
|
299
|
+
if (value.method && typeof value.method === "string") {
|
|
300
|
+
const type = value.method.split(".")[0];
|
|
301
|
+
if (type && Array.isArray(value[type])) return value[type];
|
|
302
|
+
}
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function getPluginInstancesFromElement(el) {
|
|
307
|
+
if (!el || !el.__cocreatePluginInstances) return [];
|
|
308
|
+
return Object.values(el.__cocreatePluginInstances).filter(Boolean);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isReferenceAssignment(val) {
|
|
312
|
+
return typeof val === "string" && val.trim().startsWith("=");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function normalizeReferencePath(refPath) {
|
|
316
|
+
if (typeof refPath !== "string") return "";
|
|
317
|
+
return refPath.trim().replace(/^=\s*/, "");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function resolveCallableReference(refPath, parent, hostElement) {
|
|
321
|
+
const normalized = normalizeReferencePath(refPath);
|
|
322
|
+
if (!normalized) return { fn: undefined, context: undefined, methodName: undefined };
|
|
323
|
+
|
|
324
|
+
const methodName = normalized.split(".").pop();
|
|
325
|
+
const startsWithThis = normalized === "$this" || normalized.startsWith("$this.");
|
|
326
|
+
const startsWithWindow = normalized === "$window" || normalized.startsWith("$window.");
|
|
327
|
+
const startsWithToken = normalized.startsWith("$");
|
|
328
|
+
|
|
329
|
+
const candidates = [];
|
|
330
|
+
if (startsWithThis) {
|
|
331
|
+
const path = normalized.replace(/^\$this\.?/, "");
|
|
332
|
+
candidates.push({ root: hostElement || parent, path });
|
|
333
|
+
} else if (startsWithWindow) {
|
|
334
|
+
const path = normalized.replace(/^\$window\.?/, "");
|
|
335
|
+
candidates.push({ root: window, path });
|
|
336
|
+
} else if (startsWithToken) {
|
|
337
|
+
const path = normalized.replace(/^\$/, "");
|
|
338
|
+
candidates.push({ root: hostElement, path });
|
|
339
|
+
candidates.push({ root: parent, path });
|
|
340
|
+
candidates.push({ root: window, path });
|
|
341
|
+
} else {
|
|
342
|
+
candidates.push({ root: hostElement, path: normalized });
|
|
343
|
+
candidates.push({ root: parent, path: normalized });
|
|
344
|
+
candidates.push({ root: window, path: normalized });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const candidate of candidates) {
|
|
348
|
+
if (!candidate.root) continue;
|
|
349
|
+
const { parent: resolvedParent, value } = resolvePathWithParent(candidate.root, candidate.path);
|
|
350
|
+
if (typeof value === "function") {
|
|
351
|
+
return { fn: value, context: resolvedParent, methodName };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (methodName) {
|
|
356
|
+
const instances = getPluginInstancesFromElement(hostElement || parent);
|
|
357
|
+
for (const instance of instances) {
|
|
358
|
+
if (instance && typeof instance[methodName] === "function") {
|
|
359
|
+
return { fn: instance[methodName], context: instance, methodName };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return { fn: undefined, context: undefined, methodName };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function createFunctionAdapter(refPath, parent, property, hostElement) {
|
|
368
|
+
const normalizedRefPath = normalizeReferencePath(refPath);
|
|
369
|
+
const methodName = normalizedRefPath.split(".").pop();
|
|
370
|
+
|
|
371
|
+
return function (...args) {
|
|
372
|
+
const resolved = resolveCallableReference(normalizedRefPath, parent, hostElement);
|
|
373
|
+
const fn = resolved.fn;
|
|
374
|
+
const context = resolved.context;
|
|
375
|
+
|
|
376
|
+
if (typeof fn !== "function") {
|
|
377
|
+
console.error(`Plugin adapter failed: "${normalizedRefPath}" did not resolve to a function for ${property}.`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (property === "setValue") {
|
|
382
|
+
const payload = normalizeCrudPayload(args[0]);
|
|
383
|
+
|
|
384
|
+
if (methodName === "addEventSource" && context && typeof context.getEventSources === "function") {
|
|
385
|
+
const sources = context.getEventSources();
|
|
386
|
+
if (Array.isArray(sources)) {
|
|
387
|
+
sources.forEach(source => source && typeof source.remove === "function" && source.remove());
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return fn.call(context || this, payload);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return fn.apply(context || this, args);
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function update(Target, val, parent, property, elParent, elProperty, hostElement) {
|
|
248
399
|
// RESOLUTION: Handle case-insensitivity before processing targets.
|
|
249
400
|
// If Target is missing, check parent for a property matching 'property' (case-insensitive).
|
|
250
401
|
if (!Target && parent && property) {
|
|
@@ -261,6 +412,13 @@ function update(Target, val, parent, property, elParent, elProperty) {
|
|
|
261
412
|
|
|
262
413
|
let instance;
|
|
263
414
|
if (typeof Target === 'function') {
|
|
415
|
+
if (isReferenceAssignment(val) && parent && property) {
|
|
416
|
+
instance = createFunctionAdapter(val, parent, property, hostElement);
|
|
417
|
+
parent[property] = instance;
|
|
418
|
+
if (elParent && elProperty) elParent[elProperty] = instance;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
264
422
|
if (!isConstructor(Target, property)) {
|
|
265
423
|
// Call as a function (method or standalone)
|
|
266
424
|
// Use 'parent' as context (this) if available to maintain class references
|
|
@@ -291,6 +449,12 @@ function update(Target, val, parent, property, elParent, elProperty) {
|
|
|
291
449
|
elParent[elProperty] = instance;
|
|
292
450
|
}
|
|
293
451
|
|
|
452
|
+
if (instance && instance.el && typeof instance.el === "object") {
|
|
453
|
+
if (!instance.el.__cocreatePluginInstances) instance.el.__cocreatePluginInstances = {};
|
|
454
|
+
const key = property || (Target && Target.name) || "instance";
|
|
455
|
+
instance.el.__cocreatePluginInstances[key] = instance;
|
|
456
|
+
}
|
|
457
|
+
|
|
294
458
|
} else if (typeof Target === 'object' && Target !== null && typeof val === 'object' && val !== null && !Array.isArray(val)) {
|
|
295
459
|
// Prepare the next level of the element structure
|
|
296
460
|
if (elParent && elProperty) {
|
|
@@ -300,10 +464,17 @@ function update(Target, val, parent, property, elParent, elProperty) {
|
|
|
300
464
|
const nextElParent = elParent[elProperty];
|
|
301
465
|
|
|
302
466
|
for (let key in val) {
|
|
303
|
-
update(Target[key], val[key], Target, key, nextElParent, key);
|
|
467
|
+
update(Target[key], val[key], Target, key, nextElParent, key, hostElement);
|
|
304
468
|
}
|
|
305
469
|
}
|
|
306
470
|
} else if (parent && property) {
|
|
471
|
+
if (isReferenceAssignment(val)) {
|
|
472
|
+
const adapter = createFunctionAdapter(val, parent, property, hostElement);
|
|
473
|
+
parent[property] = adapter;
|
|
474
|
+
if (elParent && elProperty) elParent[elProperty] = adapter;
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
307
478
|
// If it's not a function, we are setting a value on the plugin object
|
|
308
479
|
parent[property] = val;
|
|
309
480
|
|
|
@@ -418,4 +589,4 @@ if (typeof document !== 'undefined') {
|
|
|
418
589
|
});
|
|
419
590
|
}
|
|
420
591
|
|
|
421
|
-
export default { init, plugins }
|
|
592
|
+
export default { init, plugins }
|