@momentumcms/server-analog 0.5.0 → 0.5.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/index.cjs +62 -20
- package/index.js +62 -20
- package/package.json +3 -2
- package/src/lib/server-analog.d.ts +2 -2
package/index.cjs
CHANGED
|
@@ -317,7 +317,7 @@ function detectMimeType(buffer) {
|
|
|
317
317
|
if (match) {
|
|
318
318
|
if (sig.bytes[0] === 82 && sig.bytes[1] === 73) {
|
|
319
319
|
if (buffer.length >= 12) {
|
|
320
|
-
const formatId = buffer.
|
|
320
|
+
const formatId = String.fromCharCode(...buffer.subarray(8, 12));
|
|
321
321
|
if (formatId === "WEBP") {
|
|
322
322
|
return "image/webp";
|
|
323
323
|
}
|
|
@@ -330,7 +330,7 @@ function detectMimeType(buffer) {
|
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
332
|
if (sig.mimeType === "video/mp4" && buffer.length >= 8) {
|
|
333
|
-
const boxType = buffer.
|
|
333
|
+
const boxType = String.fromCharCode(...buffer.subarray(4, 8));
|
|
334
334
|
if (boxType === "ftyp") {
|
|
335
335
|
return "video/mp4";
|
|
336
336
|
}
|
|
@@ -339,7 +339,7 @@ function detectMimeType(buffer) {
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
if (isTextContent(buffer)) {
|
|
342
|
-
const text2 = buffer.
|
|
342
|
+
const text2 = new TextDecoder().decode(buffer.subarray(0, Math.min(buffer.length, 1e3)));
|
|
343
343
|
if (text2.trim().startsWith("{") || text2.trim().startsWith("[")) {
|
|
344
344
|
return "application/json";
|
|
345
345
|
}
|
|
@@ -4335,11 +4335,11 @@ SwaggerUIBundle({
|
|
|
4335
4335
|
|
|
4336
4336
|
// libs/server-core/src/lib/preview-renderer.ts
|
|
4337
4337
|
function renderPreviewHTML(options) {
|
|
4338
|
-
const { doc, collection } = options;
|
|
4338
|
+
const { doc, collection, customFieldRenderers } = options;
|
|
4339
4339
|
const titleField = collection.admin?.useAsTitle ?? "id";
|
|
4340
4340
|
const title = escapeHtml(String(doc[titleField] ?? doc["id"] ?? "Untitled"));
|
|
4341
4341
|
const fields = collection.fields ?? [];
|
|
4342
|
-
const fieldHtml = fields.filter((f) => !isHiddenField(f) && !isLayoutField(f) && f.name !== titleField).map((f) => renderField(f, doc)).filter(Boolean).join("\n");
|
|
4342
|
+
const fieldHtml = fields.filter((f) => !isHiddenField(f) && !isLayoutField(f) && f.name !== titleField).map((f) => renderField(f, doc, customFieldRenderers)).filter(Boolean).join("\n");
|
|
4343
4343
|
const richTextFields = fields.filter((f) => f.type === "richText").map((f) => f.name);
|
|
4344
4344
|
return `<!DOCTYPE html>
|
|
4345
4345
|
<html lang="en">
|
|
@@ -4373,6 +4373,7 @@ ${fieldHtml}
|
|
|
4373
4373
|
(function(){
|
|
4374
4374
|
var richTextFields=${JSON.stringify(richTextFields)};
|
|
4375
4375
|
window.addEventListener('message',function(e){
|
|
4376
|
+
if(e.origin!==window.location.origin)return;
|
|
4376
4377
|
if(!e.data||e.data.type!=='momentum-preview-update')return;
|
|
4377
4378
|
var d=e.data.data;if(!d)return;
|
|
4378
4379
|
document.querySelectorAll('[data-field]').forEach(function(el){
|
|
@@ -4393,16 +4394,20 @@ if(titleEl){var tf=titleEl.getAttribute('data-field');if(d[tf]!==undefined)title
|
|
|
4393
4394
|
</body>
|
|
4394
4395
|
</html>`;
|
|
4395
4396
|
}
|
|
4396
|
-
function renderField(field, doc) {
|
|
4397
|
+
function renderField(field, doc, customRenderers) {
|
|
4397
4398
|
const value = doc[field.name];
|
|
4398
4399
|
if (value === void 0 || value === null) {
|
|
4399
4400
|
return renderFieldWrapper(field, "");
|
|
4400
4401
|
}
|
|
4402
|
+
const editorKey = field.admin?.editor;
|
|
4403
|
+
if (editorKey && customRenderers?.[editorKey]) {
|
|
4404
|
+
return renderFieldWrapper(field, customRenderers[editorKey](value, field));
|
|
4405
|
+
}
|
|
4401
4406
|
switch (field.type) {
|
|
4402
4407
|
case "richText":
|
|
4403
4408
|
return renderFieldWrapper(
|
|
4404
4409
|
field,
|
|
4405
|
-
`<div class="field-value rich-text" data-field="${escapeHtml(field.name)}">${String(value)}</div>`
|
|
4410
|
+
`<div class="field-value rich-text" data-field="${escapeHtml(field.name)}">${escapeHtml(String(value))}</div>`
|
|
4406
4411
|
);
|
|
4407
4412
|
case "checkbox":
|
|
4408
4413
|
return renderFieldWrapper(
|
|
@@ -4733,6 +4738,21 @@ function coerceCsvValue(value, fieldType) {
|
|
|
4733
4738
|
}
|
|
4734
4739
|
|
|
4735
4740
|
// libs/server-analog/src/lib/server-analog.ts
|
|
4741
|
+
function getEmailBuilderFieldName(collection) {
|
|
4742
|
+
const field = collection.fields.find(
|
|
4743
|
+
(f) => f.type === "json" && f.admin?.editor === "email-builder"
|
|
4744
|
+
);
|
|
4745
|
+
return field?.name;
|
|
4746
|
+
}
|
|
4747
|
+
async function renderEmailPreviewHTML(doc, blocksFieldName) {
|
|
4748
|
+
const emailPkg = "@momentumcms/email";
|
|
4749
|
+
const { renderEmailFromBlocks } = await import(emailPkg);
|
|
4750
|
+
const blocks2 = doc[blocksFieldName];
|
|
4751
|
+
if (!Array.isArray(blocks2) || blocks2.length === 0) {
|
|
4752
|
+
return '<html><body style="display:flex;align-items:center;justify-content:center;min-height:100vh;color:#666;font-family:sans-serif"><p>No email blocks yet.</p></body></html>';
|
|
4753
|
+
}
|
|
4754
|
+
return renderEmailFromBlocks({ blocks: blocks2 });
|
|
4755
|
+
}
|
|
4736
4756
|
function nestBracketParams(flat) {
|
|
4737
4757
|
const result = {};
|
|
4738
4758
|
for (const [key, value] of Object.entries(flat)) {
|
|
@@ -5417,26 +5437,47 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5417
5437
|
};
|
|
5418
5438
|
}
|
|
5419
5439
|
}
|
|
5420
|
-
if (seg2 === "preview" && seg1 && method === "GET") {
|
|
5440
|
+
if (seg2 === "preview" && seg1 && (method === "GET" || method === "POST")) {
|
|
5441
|
+
if (!user) {
|
|
5442
|
+
utils.setResponseStatus(event, 401);
|
|
5443
|
+
return { error: "Authentication required to access preview" };
|
|
5444
|
+
}
|
|
5421
5445
|
try {
|
|
5422
5446
|
const collectionSlug2 = seg0;
|
|
5423
5447
|
const docId = seg1;
|
|
5424
|
-
const contextApi = getContextualAPI(user);
|
|
5425
|
-
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5426
|
-
if (!doc) {
|
|
5427
|
-
utils.setResponseStatus(event, 404);
|
|
5428
|
-
return { error: "Document not found" };
|
|
5429
|
-
}
|
|
5430
5448
|
const collectionConfig = config.collections.find((c) => c.slug === collectionSlug2);
|
|
5431
5449
|
if (!collectionConfig) {
|
|
5432
5450
|
utils.setResponseStatus(event, 404);
|
|
5433
5451
|
return { error: "Collection not found" };
|
|
5434
5452
|
}
|
|
5435
|
-
const
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5453
|
+
const accessFn = collectionConfig.access?.read;
|
|
5454
|
+
if (accessFn) {
|
|
5455
|
+
const allowed = await Promise.resolve(accessFn({ req: { user } }));
|
|
5456
|
+
if (!allowed) {
|
|
5457
|
+
utils.setResponseStatus(event, 403);
|
|
5458
|
+
return { error: "Access denied" };
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
let docRecord;
|
|
5462
|
+
if (method === "POST") {
|
|
5463
|
+
const body2 = await safeReadBody(event, utils, method);
|
|
5464
|
+
if (body2["data"] && typeof body2["data"] === "object") {
|
|
5465
|
+
docRecord = body2["data"];
|
|
5466
|
+
} else {
|
|
5467
|
+
utils.setResponseStatus(event, 400);
|
|
5468
|
+
return { error: "POST preview requires { data: ... } body" };
|
|
5469
|
+
}
|
|
5470
|
+
} else {
|
|
5471
|
+
const contextApi = getContextualAPI(user);
|
|
5472
|
+
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5473
|
+
if (!doc) {
|
|
5474
|
+
utils.setResponseStatus(event, 404);
|
|
5475
|
+
return { error: "Document not found" };
|
|
5476
|
+
}
|
|
5477
|
+
docRecord = doc;
|
|
5478
|
+
}
|
|
5479
|
+
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
5480
|
+
const html = emailField ? await renderEmailPreviewHTML(docRecord, emailField) : renderPreviewHTML({ doc: docRecord, collection: collectionConfig });
|
|
5440
5481
|
utils.setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
5441
5482
|
return utils.send(event, html);
|
|
5442
5483
|
} catch (error) {
|
|
@@ -5705,12 +5746,13 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5705
5746
|
fields[field.name] = field.data.toString("utf-8");
|
|
5706
5747
|
}
|
|
5707
5748
|
}
|
|
5749
|
+
const collectionUpload = postUploadCol.upload ?? {};
|
|
5708
5750
|
const uploadRequest = {
|
|
5709
5751
|
file,
|
|
5710
5752
|
user,
|
|
5711
5753
|
fields,
|
|
5712
5754
|
collectionSlug: seg0,
|
|
5713
|
-
collectionUpload
|
|
5755
|
+
collectionUpload
|
|
5714
5756
|
};
|
|
5715
5757
|
const response2 = await handleCollectionUpload(uploadConfig, uploadRequest);
|
|
5716
5758
|
utils.setResponseStatus(event, response2.status);
|
package/index.js
CHANGED
|
@@ -294,7 +294,7 @@ function detectMimeType(buffer) {
|
|
|
294
294
|
if (match) {
|
|
295
295
|
if (sig.bytes[0] === 82 && sig.bytes[1] === 73) {
|
|
296
296
|
if (buffer.length >= 12) {
|
|
297
|
-
const formatId = buffer.
|
|
297
|
+
const formatId = String.fromCharCode(...buffer.subarray(8, 12));
|
|
298
298
|
if (formatId === "WEBP") {
|
|
299
299
|
return "image/webp";
|
|
300
300
|
}
|
|
@@ -307,7 +307,7 @@ function detectMimeType(buffer) {
|
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
if (sig.mimeType === "video/mp4" && buffer.length >= 8) {
|
|
310
|
-
const boxType = buffer.
|
|
310
|
+
const boxType = String.fromCharCode(...buffer.subarray(4, 8));
|
|
311
311
|
if (boxType === "ftyp") {
|
|
312
312
|
return "video/mp4";
|
|
313
313
|
}
|
|
@@ -316,7 +316,7 @@ function detectMimeType(buffer) {
|
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
if (isTextContent(buffer)) {
|
|
319
|
-
const text2 = buffer.
|
|
319
|
+
const text2 = new TextDecoder().decode(buffer.subarray(0, Math.min(buffer.length, 1e3)));
|
|
320
320
|
if (text2.trim().startsWith("{") || text2.trim().startsWith("[")) {
|
|
321
321
|
return "application/json";
|
|
322
322
|
}
|
|
@@ -4316,11 +4316,11 @@ SwaggerUIBundle({
|
|
|
4316
4316
|
|
|
4317
4317
|
// libs/server-core/src/lib/preview-renderer.ts
|
|
4318
4318
|
function renderPreviewHTML(options) {
|
|
4319
|
-
const { doc, collection } = options;
|
|
4319
|
+
const { doc, collection, customFieldRenderers } = options;
|
|
4320
4320
|
const titleField = collection.admin?.useAsTitle ?? "id";
|
|
4321
4321
|
const title = escapeHtml(String(doc[titleField] ?? doc["id"] ?? "Untitled"));
|
|
4322
4322
|
const fields = collection.fields ?? [];
|
|
4323
|
-
const fieldHtml = fields.filter((f) => !isHiddenField(f) && !isLayoutField(f) && f.name !== titleField).map((f) => renderField(f, doc)).filter(Boolean).join("\n");
|
|
4323
|
+
const fieldHtml = fields.filter((f) => !isHiddenField(f) && !isLayoutField(f) && f.name !== titleField).map((f) => renderField(f, doc, customFieldRenderers)).filter(Boolean).join("\n");
|
|
4324
4324
|
const richTextFields = fields.filter((f) => f.type === "richText").map((f) => f.name);
|
|
4325
4325
|
return `<!DOCTYPE html>
|
|
4326
4326
|
<html lang="en">
|
|
@@ -4354,6 +4354,7 @@ ${fieldHtml}
|
|
|
4354
4354
|
(function(){
|
|
4355
4355
|
var richTextFields=${JSON.stringify(richTextFields)};
|
|
4356
4356
|
window.addEventListener('message',function(e){
|
|
4357
|
+
if(e.origin!==window.location.origin)return;
|
|
4357
4358
|
if(!e.data||e.data.type!=='momentum-preview-update')return;
|
|
4358
4359
|
var d=e.data.data;if(!d)return;
|
|
4359
4360
|
document.querySelectorAll('[data-field]').forEach(function(el){
|
|
@@ -4374,16 +4375,20 @@ if(titleEl){var tf=titleEl.getAttribute('data-field');if(d[tf]!==undefined)title
|
|
|
4374
4375
|
</body>
|
|
4375
4376
|
</html>`;
|
|
4376
4377
|
}
|
|
4377
|
-
function renderField(field, doc) {
|
|
4378
|
+
function renderField(field, doc, customRenderers) {
|
|
4378
4379
|
const value = doc[field.name];
|
|
4379
4380
|
if (value === void 0 || value === null) {
|
|
4380
4381
|
return renderFieldWrapper(field, "");
|
|
4381
4382
|
}
|
|
4383
|
+
const editorKey = field.admin?.editor;
|
|
4384
|
+
if (editorKey && customRenderers?.[editorKey]) {
|
|
4385
|
+
return renderFieldWrapper(field, customRenderers[editorKey](value, field));
|
|
4386
|
+
}
|
|
4382
4387
|
switch (field.type) {
|
|
4383
4388
|
case "richText":
|
|
4384
4389
|
return renderFieldWrapper(
|
|
4385
4390
|
field,
|
|
4386
|
-
`<div class="field-value rich-text" data-field="${escapeHtml(field.name)}">${String(value)}</div>`
|
|
4391
|
+
`<div class="field-value rich-text" data-field="${escapeHtml(field.name)}">${escapeHtml(String(value))}</div>`
|
|
4387
4392
|
);
|
|
4388
4393
|
case "checkbox":
|
|
4389
4394
|
return renderFieldWrapper(
|
|
@@ -4714,6 +4719,21 @@ function coerceCsvValue(value, fieldType) {
|
|
|
4714
4719
|
}
|
|
4715
4720
|
|
|
4716
4721
|
// libs/server-analog/src/lib/server-analog.ts
|
|
4722
|
+
function getEmailBuilderFieldName(collection) {
|
|
4723
|
+
const field = collection.fields.find(
|
|
4724
|
+
(f) => f.type === "json" && f.admin?.editor === "email-builder"
|
|
4725
|
+
);
|
|
4726
|
+
return field?.name;
|
|
4727
|
+
}
|
|
4728
|
+
async function renderEmailPreviewHTML(doc, blocksFieldName) {
|
|
4729
|
+
const emailPkg = "@momentumcms/email";
|
|
4730
|
+
const { renderEmailFromBlocks } = await import(emailPkg);
|
|
4731
|
+
const blocks2 = doc[blocksFieldName];
|
|
4732
|
+
if (!Array.isArray(blocks2) || blocks2.length === 0) {
|
|
4733
|
+
return '<html><body style="display:flex;align-items:center;justify-content:center;min-height:100vh;color:#666;font-family:sans-serif"><p>No email blocks yet.</p></body></html>';
|
|
4734
|
+
}
|
|
4735
|
+
return renderEmailFromBlocks({ blocks: blocks2 });
|
|
4736
|
+
}
|
|
4717
4737
|
function nestBracketParams(flat) {
|
|
4718
4738
|
const result = {};
|
|
4719
4739
|
for (const [key, value] of Object.entries(flat)) {
|
|
@@ -5398,26 +5418,47 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5398
5418
|
};
|
|
5399
5419
|
}
|
|
5400
5420
|
}
|
|
5401
|
-
if (seg2 === "preview" && seg1 && method === "GET") {
|
|
5421
|
+
if (seg2 === "preview" && seg1 && (method === "GET" || method === "POST")) {
|
|
5422
|
+
if (!user) {
|
|
5423
|
+
utils.setResponseStatus(event, 401);
|
|
5424
|
+
return { error: "Authentication required to access preview" };
|
|
5425
|
+
}
|
|
5402
5426
|
try {
|
|
5403
5427
|
const collectionSlug2 = seg0;
|
|
5404
5428
|
const docId = seg1;
|
|
5405
|
-
const contextApi = getContextualAPI(user);
|
|
5406
|
-
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5407
|
-
if (!doc) {
|
|
5408
|
-
utils.setResponseStatus(event, 404);
|
|
5409
|
-
return { error: "Document not found" };
|
|
5410
|
-
}
|
|
5411
5429
|
const collectionConfig = config.collections.find((c) => c.slug === collectionSlug2);
|
|
5412
5430
|
if (!collectionConfig) {
|
|
5413
5431
|
utils.setResponseStatus(event, 404);
|
|
5414
5432
|
return { error: "Collection not found" };
|
|
5415
5433
|
}
|
|
5416
|
-
const
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5434
|
+
const accessFn = collectionConfig.access?.read;
|
|
5435
|
+
if (accessFn) {
|
|
5436
|
+
const allowed = await Promise.resolve(accessFn({ req: { user } }));
|
|
5437
|
+
if (!allowed) {
|
|
5438
|
+
utils.setResponseStatus(event, 403);
|
|
5439
|
+
return { error: "Access denied" };
|
|
5440
|
+
}
|
|
5441
|
+
}
|
|
5442
|
+
let docRecord;
|
|
5443
|
+
if (method === "POST") {
|
|
5444
|
+
const body2 = await safeReadBody(event, utils, method);
|
|
5445
|
+
if (body2["data"] && typeof body2["data"] === "object") {
|
|
5446
|
+
docRecord = body2["data"];
|
|
5447
|
+
} else {
|
|
5448
|
+
utils.setResponseStatus(event, 400);
|
|
5449
|
+
return { error: "POST preview requires { data: ... } body" };
|
|
5450
|
+
}
|
|
5451
|
+
} else {
|
|
5452
|
+
const contextApi = getContextualAPI(user);
|
|
5453
|
+
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5454
|
+
if (!doc) {
|
|
5455
|
+
utils.setResponseStatus(event, 404);
|
|
5456
|
+
return { error: "Document not found" };
|
|
5457
|
+
}
|
|
5458
|
+
docRecord = doc;
|
|
5459
|
+
}
|
|
5460
|
+
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
5461
|
+
const html = emailField ? await renderEmailPreviewHTML(docRecord, emailField) : renderPreviewHTML({ doc: docRecord, collection: collectionConfig });
|
|
5421
5462
|
utils.setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
5422
5463
|
return utils.send(event, html);
|
|
5423
5464
|
} catch (error) {
|
|
@@ -5686,12 +5727,13 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5686
5727
|
fields[field.name] = field.data.toString("utf-8");
|
|
5687
5728
|
}
|
|
5688
5729
|
}
|
|
5730
|
+
const collectionUpload = postUploadCol.upload ?? {};
|
|
5689
5731
|
const uploadRequest = {
|
|
5690
5732
|
file,
|
|
5691
5733
|
user,
|
|
5692
5734
|
fields,
|
|
5693
5735
|
collectionSlug: seg0,
|
|
5694
|
-
collectionUpload
|
|
5736
|
+
collectionUpload
|
|
5695
5737
|
};
|
|
5696
5738
|
const response2 = await handleCollectionUpload(uploadConfig, uploadRequest);
|
|
5697
5739
|
utils.setResponseStatus(event, response2.status);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momentumcms/server-analog",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Nitro/h3 adapter for Momentum CMS with Analog.js support",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Momentum CMS Contributors",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"types": "./src/index.d.ts",
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@momentumcms/core": ">=0.0.1",
|
|
31
|
-
"@momentumcms/server-core": ">=0.0.1"
|
|
31
|
+
"@momentumcms/server-core": ">=0.0.1",
|
|
32
|
+
"@momentumcms/storage": ">=0.0.1"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"@aws-sdk/client-s3": "^3.983.0",
|
|
@@ -43,7 +43,7 @@ export type ReadMultipartFormDataFn = (event: H3Event) => Promise<Array<{
|
|
|
43
43
|
/**
|
|
44
44
|
* Type for send function from h3.
|
|
45
45
|
*/
|
|
46
|
-
export type SendFn = (event: H3Event, data: Buffer | string, type?: string) => unknown;
|
|
46
|
+
export type SendFn = (event: H3Event, data: Buffer | Uint8Array | string, type?: string) => unknown;
|
|
47
47
|
/**
|
|
48
48
|
* Extended h3 utilities for comprehensive API handling.
|
|
49
49
|
*/
|
|
@@ -59,7 +59,7 @@ export interface MomentumH3Utils {
|
|
|
59
59
|
type?: string;
|
|
60
60
|
data: Buffer;
|
|
61
61
|
}> | undefined>;
|
|
62
|
-
send(event: H3Event, data: Buffer | string, type?: string): unknown;
|
|
62
|
+
send(event: H3Event, data: Buffer | Uint8Array | string, type?: string): unknown;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* Creates an h3 event handler for Momentum CMS API.
|