From f7debe69e13660bf9ddf8c3ad8c9206a4f510409 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 15:47:35 +0100 Subject: [PATCH 01/28] include stuff from simply-modules dir --- pages/imports/actions/listSimplyModules.js | 6 +++ pages/imports/dataApi/listSimplyModules.js | 57 ++++++++++++++++++++++ pages/publish/actions/getAppData.js | 38 +++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 pages/imports/actions/listSimplyModules.js create mode 100644 pages/imports/dataApi/listSimplyModules.js diff --git a/pages/imports/actions/listSimplyModules.js b/pages/imports/actions/listSimplyModules.js new file mode 100644 index 0000000..e0ce0a6 --- /dev/null +++ b/pages/imports/actions/listSimplyModules.js @@ -0,0 +1,6 @@ +function() { + return simplyDataApi.listSimplyModules() + .catch(function(error) { + return []; + }); +} \ No newline at end of file diff --git a/pages/imports/dataApi/listSimplyModules.js b/pages/imports/dataApi/listSimplyModules.js new file mode 100644 index 0000000..e07b96e --- /dev/null +++ b/pages/imports/dataApi/listSimplyModules.js @@ -0,0 +1,57 @@ +function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + simplyModules.forEach(function(moduleGroup) { + console.log(moduleGroup.id); // test-import + moduleGroup.contents.forEach(function(module) { + console.log(module.id); // test + module.contents.forEach(function(moduleComponentCategory) { + console.log(moduleComponentCategory.id); // base-components + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'component'; + }); + break; + case "base-components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'baseComponent'; + }); + break; + case "pages": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'page'; + }); + break; + case "builders": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'builder'; + }); + case "imports": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'import'; + }); + break; + } + }); + }); + }); + return simplyModules; + }); +} \ No newline at end of file diff --git a/pages/publish/actions/getAppData.js b/pages/publish/actions/getAppData.js index 3d385f1..612054e 100644 --- a/pages/publish/actions/getAppData.js +++ b/pages/publish/actions/getAppData.js @@ -12,6 +12,44 @@ function() { }); appData.pageFrame = frame; }) + .then(function() { + return simplyApp.actions.listSimplyModules() + .then(function(modules) { + modules.forEach(function(module) { + // test-import + console.log(module); + module.contents.forEach(function(moduleGroup) { + // test + moduleGroup.contents.forEach(function(moduleComponentCategory) { + switch (moduleComponentCategory.id) { + case "base-components": + case "components": + case "pages": + case "builders": + case "imports": + moduleComponentCategory.contents.forEach(function(component) { + component.contents.forEach(function(part) { + if (part.id == "meta") { + return; + }; + + if (typeof appData[part.id] === "undefined") { + appData[part.id] = []; + } + parts = JSON.parse(part.contents); + parts.forEach(function(entry) { + entry.base = component.baseType + "/" + component.id; + appData[part.id].push(entry); + }); + }); + }); + break; + } + }); + }); + }); + }); + }) .then(function() { return simplyApp.actions.listBuilders() .then(function(builders) { From 6d576a36732bf89d436313281b276ef491d5c518 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 15:47:47 +0100 Subject: [PATCH 02/28] update generated --- generated.html | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/generated.html b/generated.html index 47d18a5..1b9ee57 100644 --- a/generated.html +++ b/generated.html @@ -2514,6 +2514,64 @@ return components; }); }, + // page/imports + "listSimplyModules" : function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + simplyModules.forEach(function(moduleGroup) { + console.log(moduleGroup.id); // test-import + moduleGroup.contents.forEach(function(module) { + console.log(module.id); // test + module.contents.forEach(function(moduleComponentCategory) { + console.log(moduleComponentCategory.id); // base-components + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'component'; + }); + break; + case "base-components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'baseComponent'; + }); + break; + case "pages": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'page'; + }); + break; + case "builders": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'builder'; + }); + case "imports": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'import'; + }); + break; + } + }); + }); + }); + return simplyModules; + }); + }, // page/page "deletePage" : function(component) { return simplyRawApi.delete("pages/" + component) @@ -2995,6 +3053,13 @@ return []; }); }, + // page/imports + "listSimplyModules" : function() { + return simplyDataApi.listSimplyModules() + .catch(function(error) { + return []; + }); + }, // page/page "deletePage" : function(component) { return simplyDataApi.deletePage(component); @@ -3074,6 +3139,44 @@ }); appData.pageFrame = frame; }) + .then(function() { + return simplyApp.actions.listSimplyModules() + .then(function(modules) { + modules.forEach(function(module) { + // test-import + console.log(module); + module.contents.forEach(function(moduleGroup) { + // test + moduleGroup.contents.forEach(function(moduleComponentCategory) { + switch (moduleComponentCategory.id) { + case "base-components": + case "components": + case "pages": + case "builders": + case "imports": + moduleComponentCategory.contents.forEach(function(component) { + component.contents.forEach(function(part) { + if (part.id == "meta") { + return; + }; + + if (typeof appData[part.id] === "undefined") { + appData[part.id] = []; + } + parts = JSON.parse(part.contents); + parts.forEach(function(entry) { + entry.base = component.baseType + "/" + component.id; + appData[part.id].push(entry); + }); + }); + }); + break; + } + }); + }); + }); + }); + }) .then(function() { return simplyApp.actions.listBuilders() .then(function(builders) { From e8c3f3653a35eb33133183459fa1334bc0641f54 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 15:48:36 +0100 Subject: [PATCH 03/28] remove console logs --- pages/imports/dataApi/listSimplyModules.js | 3 --- pages/publish/actions/getAppData.js | 1 - 2 files changed, 4 deletions(-) diff --git a/pages/imports/dataApi/listSimplyModules.js b/pages/imports/dataApi/listSimplyModules.js index e07b96e..babda99 100644 --- a/pages/imports/dataApi/listSimplyModules.js +++ b/pages/imports/dataApi/listSimplyModules.js @@ -8,11 +8,8 @@ function() { }) .then(function(simplyModules) { simplyModules.forEach(function(moduleGroup) { - console.log(moduleGroup.id); // test-import moduleGroup.contents.forEach(function(module) { - console.log(module.id); // test module.contents.forEach(function(moduleComponentCategory) { - console.log(moduleComponentCategory.id); // base-components switch(moduleComponentCategory.id) { case "components": moduleComponentCategory.contents.forEach(function(component) { diff --git a/pages/publish/actions/getAppData.js b/pages/publish/actions/getAppData.js index 612054e..65b7ae0 100644 --- a/pages/publish/actions/getAppData.js +++ b/pages/publish/actions/getAppData.js @@ -17,7 +17,6 @@ function() { .then(function(modules) { modules.forEach(function(module) { // test-import - console.log(module); module.contents.forEach(function(moduleGroup) { // test moduleGroup.contents.forEach(function(moduleComponentCategory) { From aaadc4c1be4c848e8a0491f5e71b5a0d7e9d067d Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Fri, 23 Jan 2026 21:10:06 +0100 Subject: [PATCH 04/28] Add JSZip library as base component. --- base-components/jszip/headHtml/js-zip.html | 1 + base-components/jszip/meta.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 base-components/jszip/headHtml/js-zip.html create mode 100644 base-components/jszip/meta.json diff --git a/base-components/jszip/headHtml/js-zip.html b/base-components/jszip/headHtml/js-zip.html new file mode 100644 index 0000000..b822289 --- /dev/null +++ b/base-components/jszip/headHtml/js-zip.html @@ -0,0 +1 @@ + diff --git a/base-components/jszip/meta.json b/base-components/jszip/meta.json new file mode 100644 index 0000000..1618e6b --- /dev/null +++ b/base-components/jszip/meta.json @@ -0,0 +1 @@ +{"id":"jszip","description":"Create, read and edit .zip files with Javascript"} \ No newline at end of file From 841f9810f9fbcef839455c77df98b2eed9891b01 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Fri, 23 Jan 2026 21:18:44 +0100 Subject: [PATCH 05/28] Add action to fetch ZIP files and extract their contents for an import. --- pages/import/actions/fetchImportUrls.js | 75 +++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 pages/import/actions/fetchImportUrls.js diff --git a/pages/import/actions/fetchImportUrls.js b/pages/import/actions/fetchImportUrls.js new file mode 100644 index 0000000..716a823 --- /dev/null +++ b/pages/import/actions/fetchImportUrls.js @@ -0,0 +1,75 @@ +async function (component) { + // @TODO: Check if the URL is actually a ZIP file and not another type + + const zipProxyUrl = 'zithub.pother.ca' + + const imports = await simplyApp.actions.getImport(component) + + const importUrls = imports + .filter(importItem => importItem.id === 'importUrls') + .map(importItem => JSON.parse(importItem.contents)) + .flat(2) + + const fetches = importUrls.map(importDetails => { + const url = new URL(importDetails.url) + + // GitHub does not send CORS headers, so a custom build proxy is used + if (url.hostname.endsWith('github.com')) { + url.pathname = `${url.href}` + url.hostname = zipProxyUrl + } + + return fetch(url) + }) + + const responses = await Promise.all(fetches) + + const arrayBufferResponses = responses.map(async response => { + let blob = await response.blob() + + return await blob.arrayBuffer() + }) + + const arrayBuffers = await Promise.all(arrayBufferResponses) + + const zipArchives = arrayBuffers.map(async arrayBuffer => { + const files = {} + + const zip = await JSZip.loadAsync(arrayBuffer) + const zipFiles = Object.values(zip.files) + + for (const file of zipFiles) { + if (file.dir === false) { + files[file.name] = await file.async('blob') + } + } + + return files + }) + + const archiveFiles = await Promise.all(zipArchives) + + // @CHECKME: Instead of a naked object, should FileList and File be used? + + const fileCollections = archiveFiles.map(async fileCollection => { + for (const [ fileName, blob ] of Object.entries(fileCollection)) { + const bytes = await blob.arrayBuffer() + + let contents, type + + try { + type = 'text/plain;charset=utf-8' + contents = new TextDecoder('utf-8', { fatal: true }).decode(bytes) + } catch (e) { + type = 'application/octet-stream' + contents = blob + } + + fileCollection[fileName] = { contents, type } + } + + return fileCollection + }) + + return await Promise.all(fileCollections) +} From 265cf78f290d742838bd441e1ace72c03e2b3a92 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Fri, 23 Jan 2026 21:21:16 +0100 Subject: [PATCH 06/28] Update generated.html to reflect the latest changes. --- generated.html | 81 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/generated.html b/generated.html index 47d18a5..7c855f0 100644 --- a/generated.html +++ b/generated.html @@ -1250,6 +1250,9 @@ src: local('Cocon'), url('/assets/fonts/CoconRegularFont.woff') format('woff'); } + + + - \ No newline at end of file + From 26af2a57a279546941991e1dd3b64ea311e803f7 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:19:35 +0100 Subject: [PATCH 07/28] add styling for import hint --- components/1-styling/componentCss/style.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/1-styling/componentCss/style.css b/components/1-styling/componentCss/style.css index 1d6d1b8..5d7330e 100644 --- a/components/1-styling/componentCss/style.css +++ b/components/1-styling/componentCss/style.css @@ -236,4 +236,9 @@ header .simplycode-controls { } .simplycode-editor-code textarea { margin-top: -1px; -} \ No newline at end of file +} +strong.simplycode-module { + color: var(--simplycode-highlight-dark); + float: right; + padding-right: 5px; +} From 813e7458b1b2cafb25241a148b7de793e5109c8a Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:19:58 +0100 Subject: [PATCH 08/28] add UI hint --- pages/base-components/pageTemplates/Base components.html | 7 ++++++- pages/component-list/pageTemplates/List components.html | 7 ++++++- pages/pages/pageTemplates/Pages.html | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pages/base-components/pageTemplates/Base components.html b/pages/base-components/pageTemplates/Base components.html index 49c89ef..10fe7c3 100644 --- a/pages/base-components/pageTemplates/Base components.html +++ b/pages/base-components/pageTemplates/Base components.html @@ -3,7 +3,12 @@

Base components

diff --git a/pages/component-list/pageTemplates/List components.html b/pages/component-list/pageTemplates/List components.html index 5b90b64..8610993 100644 --- a/pages/component-list/pageTemplates/List components.html +++ b/pages/component-list/pageTemplates/List components.html @@ -3,7 +3,12 @@

Components

diff --git a/pages/pages/pageTemplates/Pages.html b/pages/pages/pageTemplates/Pages.html index cd5b2dc..110623c 100644 --- a/pages/pages/pageTemplates/Pages.html +++ b/pages/pages/pageTemplates/Pages.html @@ -3,7 +3,12 @@

Pages

From d772a7c4848e034fb42a5932e85331374e8c4ac3 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:20:43 +0100 Subject: [PATCH 09/28] add data api for merged versions --- .../dataApi/listMergedBaseComponents.js | 73 +++++++++++++++++++ .../dataApi/listMergedComponents.js | 73 +++++++++++++++++++ pages/pages/dataApi/listMergedPages.js | 73 +++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 pages/base-components/dataApi/listMergedBaseComponents.js create mode 100644 pages/component-list/dataApi/listMergedComponents.js create mode 100644 pages/pages/dataApi/listMergedPages.js diff --git a/pages/base-components/dataApi/listMergedBaseComponents.js b/pages/base-components/dataApi/listMergedBaseComponents.js new file mode 100644 index 0000000..72d2bd4 --- /dev/null +++ b/pages/base-components/dataApi/listMergedBaseComponents.js @@ -0,0 +1,73 @@ +async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModuleBaseComponents(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listBaseComponents(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; +} \ No newline at end of file diff --git a/pages/component-list/dataApi/listMergedComponents.js b/pages/component-list/dataApi/listMergedComponents.js new file mode 100644 index 0000000..ceb7eef --- /dev/null +++ b/pages/component-list/dataApi/listMergedComponents.js @@ -0,0 +1,73 @@ +async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModuleComponents(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listComponents(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; +} \ No newline at end of file diff --git a/pages/pages/dataApi/listMergedPages.js b/pages/pages/dataApi/listMergedPages.js new file mode 100644 index 0000000..4d11e84 --- /dev/null +++ b/pages/pages/dataApi/listMergedPages.js @@ -0,0 +1,73 @@ +async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModulePages(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listPages(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; +} \ No newline at end of file From 2d68914c4754443d6fcafa0ba7b82c4db0205dc6 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:22:34 +0100 Subject: [PATCH 10/28] add functions to get module base-components, components and pages --- .../dataApi/listSimplyModuleBaseComponents.js | 29 +++++++++++++++++++ .../dataApi/listSimplyModuleComponents.js | 29 +++++++++++++++++++ .../imports/dataApi/listSimplyModulePages.js | 29 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 pages/imports/dataApi/listSimplyModuleBaseComponents.js create mode 100644 pages/imports/dataApi/listSimplyModuleComponents.js create mode 100644 pages/imports/dataApi/listSimplyModulePages.js diff --git a/pages/imports/dataApi/listSimplyModuleBaseComponents.js b/pages/imports/dataApi/listSimplyModuleBaseComponents.js new file mode 100644 index 0000000..a984773 --- /dev/null +++ b/pages/imports/dataApi/listSimplyModuleBaseComponents.js @@ -0,0 +1,29 @@ +function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "base-components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'baseComponent'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); +} \ No newline at end of file diff --git a/pages/imports/dataApi/listSimplyModuleComponents.js b/pages/imports/dataApi/listSimplyModuleComponents.js new file mode 100644 index 0000000..43b2b78 --- /dev/null +++ b/pages/imports/dataApi/listSimplyModuleComponents.js @@ -0,0 +1,29 @@ +function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'component'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); +} \ No newline at end of file diff --git a/pages/imports/dataApi/listSimplyModulePages.js b/pages/imports/dataApi/listSimplyModulePages.js new file mode 100644 index 0000000..44a102d --- /dev/null +++ b/pages/imports/dataApi/listSimplyModulePages.js @@ -0,0 +1,29 @@ +function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'page'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); +} \ No newline at end of file From 02ae2c513b58ea8dd100f1783295a44bdb00b129 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:23:11 +0100 Subject: [PATCH 11/28] remove modules, already handled in list calls --- pages/publish/actions/getAppData.js | 37 ----------------------------- 1 file changed, 37 deletions(-) diff --git a/pages/publish/actions/getAppData.js b/pages/publish/actions/getAppData.js index 65b7ae0..3d385f1 100644 --- a/pages/publish/actions/getAppData.js +++ b/pages/publish/actions/getAppData.js @@ -12,43 +12,6 @@ function() { }); appData.pageFrame = frame; }) - .then(function() { - return simplyApp.actions.listSimplyModules() - .then(function(modules) { - modules.forEach(function(module) { - // test-import - module.contents.forEach(function(moduleGroup) { - // test - moduleGroup.contents.forEach(function(moduleComponentCategory) { - switch (moduleComponentCategory.id) { - case "base-components": - case "components": - case "pages": - case "builders": - case "imports": - moduleComponentCategory.contents.forEach(function(component) { - component.contents.forEach(function(part) { - if (part.id == "meta") { - return; - }; - - if (typeof appData[part.id] === "undefined") { - appData[part.id] = []; - } - parts = JSON.parse(part.contents); - parts.forEach(function(entry) { - entry.base = component.baseType + "/" + component.id; - appData[part.id].push(entry); - }); - }); - }); - break; - } - }); - }); - }); - }); - }) .then(function() { return simplyApp.actions.listBuilders() .then(function(builders) { From 85fd0578e891a395bb89012f4d5b4c3dc1747c19 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:23:46 +0100 Subject: [PATCH 12/28] use merged versions --- pages/base-components/actions/listBaseComponents.js | 2 +- pages/component-list/actions/listComponents.js | 2 +- pages/pages/actions/listPages.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/base-components/actions/listBaseComponents.js b/pages/base-components/actions/listBaseComponents.js index 3299b0c..085e70a 100644 --- a/pages/base-components/actions/listBaseComponents.js +++ b/pages/base-components/actions/listBaseComponents.js @@ -1,5 +1,5 @@ function() { - return simplyDataApi.listBaseComponents() + return simplyDataApi.listMergedBaseComponents() .catch(function(error) { return []; }); diff --git a/pages/component-list/actions/listComponents.js b/pages/component-list/actions/listComponents.js index d6725ce..2037e7b 100644 --- a/pages/component-list/actions/listComponents.js +++ b/pages/component-list/actions/listComponents.js @@ -1,5 +1,5 @@ function() { - return simplyDataApi.listComponents() + return simplyDataApi.listMergedComponents() .catch(function(error) { return []; }); diff --git a/pages/pages/actions/listPages.js b/pages/pages/actions/listPages.js index 753c913..95b5d09 100644 --- a/pages/pages/actions/listPages.js +++ b/pages/pages/actions/listPages.js @@ -1,5 +1,5 @@ function() { - return simplyDataApi.listPages() + return simplyDataApi.listMergedPages() .catch(function(error) { return []; }); From 6e3cb0fccc0847dd7802e85d205e34426eead680 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:24:05 +0100 Subject: [PATCH 13/28] update generated --- generated.html | 386 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 339 insertions(+), 47 deletions(-) diff --git a/generated.html b/generated.html index 1b9ee57..4058ec8 100644 --- a/generated.html +++ b/generated.html @@ -949,6 +949,12 @@ .simplycode-editor-code textarea { margin-top: -1px; } + strong.simplycode-module { + color: var(--simplycode-highlight-dark); + float: right; + padding-right: 5px; + } + /* simplycode-import-url (component/component-import-url) */ .simplycode-editor-url input { width: 100%; @@ -2310,6 +2316,80 @@ return components; }); }, + // page/base-components + "listMergedBaseComponents" : async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModuleBaseComponents(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listBaseComponents(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; + }, // page/builder "deleteBuilder" : function(component) { return simplyRawApi.delete("builders/" + component) @@ -2446,6 +2526,80 @@ return components; }); }, + // page/component-list + "listMergedComponents" : async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModuleComponents(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listComponents(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; + }, // page/import "deleteImport" : function(component) { return simplyRawApi.delete("imports/" + component) @@ -2515,6 +2669,96 @@ }); }, // page/imports + "listSimplyModuleBaseComponents" : function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "base-components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'baseComponent'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); + }, + // page/imports + "listSimplyModuleComponents" : function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'component'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); + }, + // page/imports + "listSimplyModulePages" : function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'page'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); + }, + // page/imports "listSimplyModules" : function() { return simplyRawApi.get("simply-modules") .then(function(response) { @@ -2525,11 +2769,8 @@ }) .then(function(simplyModules) { simplyModules.forEach(function(moduleGroup) { - console.log(moduleGroup.id); // test-import moduleGroup.contents.forEach(function(module) { - console.log(module.id); // test module.contents.forEach(function(moduleComponentCategory) { - console.log(moduleComponentCategory.id); // base-components switch(moduleComponentCategory.id) { case "components": moduleComponentCategory.contents.forEach(function(component) { @@ -2687,6 +2928,80 @@ }); }, // page/pages + "listMergedPages" : async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModulePages(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listPages(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; + }, + // page/pages "listPages" : function() { return simplyRawApi.get("pages") .then(function(response) { @@ -2931,7 +3246,7 @@ }, // page/base-components "listBaseComponents" : function() { - return simplyDataApi.listBaseComponents() + return simplyDataApi.listMergedBaseComponents() .catch(function(error) { return []; }); @@ -3009,7 +3324,7 @@ }, // page/component-list "listComponents" : function() { - return simplyDataApi.listComponents() + return simplyDataApi.listMergedComponents() .catch(function(error) { return []; }); @@ -3119,7 +3434,7 @@ }, // page/pages "listPages" : function() { - return simplyDataApi.listPages() + return simplyDataApi.listMergedPages() .catch(function(error) { return []; }); @@ -3139,44 +3454,6 @@ }); appData.pageFrame = frame; }) - .then(function() { - return simplyApp.actions.listSimplyModules() - .then(function(modules) { - modules.forEach(function(module) { - // test-import - console.log(module); - module.contents.forEach(function(moduleGroup) { - // test - moduleGroup.contents.forEach(function(moduleComponentCategory) { - switch (moduleComponentCategory.id) { - case "base-components": - case "components": - case "pages": - case "builders": - case "imports": - moduleComponentCategory.contents.forEach(function(component) { - component.contents.forEach(function(part) { - if (part.id == "meta") { - return; - }; - - if (typeof appData[part.id] === "undefined") { - appData[part.id] = []; - } - parts = JSON.parse(part.contents); - parts.forEach(function(entry) { - entry.base = component.baseType + "/" + component.id; - appData[part.id].push(entry); - }); - }); - }); - break; - } - }); - }); - }); - }); - }) .then(function() { return simplyApp.actions.listBuilders() .then(function(builders) { @@ -5705,7 +5982,12 @@

Base components

@@ -5803,7 +6085,12 @@

Components

@@ -5913,7 +6200,12 @@

Pages

From 9c5d716af0b4052dbd9810d566e5c16b9cd8437a Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 15:47:35 +0100 Subject: [PATCH 14/28] include stuff from simply-modules dir --- pages/imports/actions/listSimplyModules.js | 6 +++ pages/imports/dataApi/listSimplyModules.js | 57 ++++++++++++++++++++++ pages/publish/actions/getAppData.js | 38 +++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 pages/imports/actions/listSimplyModules.js create mode 100644 pages/imports/dataApi/listSimplyModules.js diff --git a/pages/imports/actions/listSimplyModules.js b/pages/imports/actions/listSimplyModules.js new file mode 100644 index 0000000..e0ce0a6 --- /dev/null +++ b/pages/imports/actions/listSimplyModules.js @@ -0,0 +1,6 @@ +function() { + return simplyDataApi.listSimplyModules() + .catch(function(error) { + return []; + }); +} \ No newline at end of file diff --git a/pages/imports/dataApi/listSimplyModules.js b/pages/imports/dataApi/listSimplyModules.js new file mode 100644 index 0000000..e07b96e --- /dev/null +++ b/pages/imports/dataApi/listSimplyModules.js @@ -0,0 +1,57 @@ +function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + simplyModules.forEach(function(moduleGroup) { + console.log(moduleGroup.id); // test-import + moduleGroup.contents.forEach(function(module) { + console.log(module.id); // test + module.contents.forEach(function(moduleComponentCategory) { + console.log(moduleComponentCategory.id); // base-components + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'component'; + }); + break; + case "base-components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'baseComponent'; + }); + break; + case "pages": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'page'; + }); + break; + case "builders": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'builder'; + }); + case "imports": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'import'; + }); + break; + } + }); + }); + }); + return simplyModules; + }); +} \ No newline at end of file diff --git a/pages/publish/actions/getAppData.js b/pages/publish/actions/getAppData.js index 3d385f1..612054e 100644 --- a/pages/publish/actions/getAppData.js +++ b/pages/publish/actions/getAppData.js @@ -12,6 +12,44 @@ function() { }); appData.pageFrame = frame; }) + .then(function() { + return simplyApp.actions.listSimplyModules() + .then(function(modules) { + modules.forEach(function(module) { + // test-import + console.log(module); + module.contents.forEach(function(moduleGroup) { + // test + moduleGroup.contents.forEach(function(moduleComponentCategory) { + switch (moduleComponentCategory.id) { + case "base-components": + case "components": + case "pages": + case "builders": + case "imports": + moduleComponentCategory.contents.forEach(function(component) { + component.contents.forEach(function(part) { + if (part.id == "meta") { + return; + }; + + if (typeof appData[part.id] === "undefined") { + appData[part.id] = []; + } + parts = JSON.parse(part.contents); + parts.forEach(function(entry) { + entry.base = component.baseType + "/" + component.id; + appData[part.id].push(entry); + }); + }); + }); + break; + } + }); + }); + }); + }); + }) .then(function() { return simplyApp.actions.listBuilders() .then(function(builders) { From 1ab4f67c4ed71b1901c19b68f72c15439b1fa1ee Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 15:47:47 +0100 Subject: [PATCH 15/28] update generated --- generated.html | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/generated.html b/generated.html index 7c855f0..05838c6 100644 --- a/generated.html +++ b/generated.html @@ -2517,6 +2517,64 @@ return components; }); }, + // page/imports + "listSimplyModules" : function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + simplyModules.forEach(function(moduleGroup) { + console.log(moduleGroup.id); // test-import + moduleGroup.contents.forEach(function(module) { + console.log(module.id); // test + module.contents.forEach(function(moduleComponentCategory) { + console.log(moduleComponentCategory.id); // base-components + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'component'; + }); + break; + case "base-components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'baseComponent'; + }); + break; + case "pages": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'page'; + }); + break; + case "builders": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'builder'; + }); + case "imports": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'import'; + }); + break; + } + }); + }); + }); + return simplyModules; + }); + }, // page/page "deletePage" : function(component) { return simplyRawApi.delete("pages/" + component) @@ -3074,6 +3132,13 @@ return []; }); }, + // page/imports + "listSimplyModules" : function() { + return simplyDataApi.listSimplyModules() + .catch(function(error) { + return []; + }); + }, // page/page "deletePage" : function(component) { return simplyDataApi.deletePage(component); @@ -3153,6 +3218,44 @@ }); appData.pageFrame = frame; }) + .then(function() { + return simplyApp.actions.listSimplyModules() + .then(function(modules) { + modules.forEach(function(module) { + // test-import + console.log(module); + module.contents.forEach(function(moduleGroup) { + // test + moduleGroup.contents.forEach(function(moduleComponentCategory) { + switch (moduleComponentCategory.id) { + case "base-components": + case "components": + case "pages": + case "builders": + case "imports": + moduleComponentCategory.contents.forEach(function(component) { + component.contents.forEach(function(part) { + if (part.id == "meta") { + return; + }; + + if (typeof appData[part.id] === "undefined") { + appData[part.id] = []; + } + parts = JSON.parse(part.contents); + parts.forEach(function(entry) { + entry.base = component.baseType + "/" + component.id; + appData[part.id].push(entry); + }); + }); + }); + break; + } + }); + }); + }); + }); + }) .then(function() { return simplyApp.actions.listBuilders() .then(function(builders) { From a80e78fa6d9df9ada7442ae2a52ce27d5f95e407 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 15:48:36 +0100 Subject: [PATCH 16/28] remove console logs --- pages/imports/dataApi/listSimplyModules.js | 3 --- pages/publish/actions/getAppData.js | 1 - 2 files changed, 4 deletions(-) diff --git a/pages/imports/dataApi/listSimplyModules.js b/pages/imports/dataApi/listSimplyModules.js index e07b96e..babda99 100644 --- a/pages/imports/dataApi/listSimplyModules.js +++ b/pages/imports/dataApi/listSimplyModules.js @@ -8,11 +8,8 @@ function() { }) .then(function(simplyModules) { simplyModules.forEach(function(moduleGroup) { - console.log(moduleGroup.id); // test-import moduleGroup.contents.forEach(function(module) { - console.log(module.id); // test module.contents.forEach(function(moduleComponentCategory) { - console.log(moduleComponentCategory.id); // base-components switch(moduleComponentCategory.id) { case "components": moduleComponentCategory.contents.forEach(function(component) { diff --git a/pages/publish/actions/getAppData.js b/pages/publish/actions/getAppData.js index 612054e..65b7ae0 100644 --- a/pages/publish/actions/getAppData.js +++ b/pages/publish/actions/getAppData.js @@ -17,7 +17,6 @@ function() { .then(function(modules) { modules.forEach(function(module) { // test-import - console.log(module); module.contents.forEach(function(moduleGroup) { // test moduleGroup.contents.forEach(function(moduleComponentCategory) { From 0541eb2b4ee50e14b0d5d2349420cabad11bf56b Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:19:35 +0100 Subject: [PATCH 17/28] add styling for import hint --- components/1-styling/componentCss/style.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/1-styling/componentCss/style.css b/components/1-styling/componentCss/style.css index 1d6d1b8..5d7330e 100644 --- a/components/1-styling/componentCss/style.css +++ b/components/1-styling/componentCss/style.css @@ -236,4 +236,9 @@ header .simplycode-controls { } .simplycode-editor-code textarea { margin-top: -1px; -} \ No newline at end of file +} +strong.simplycode-module { + color: var(--simplycode-highlight-dark); + float: right; + padding-right: 5px; +} From 88e3068dbb6a645c3e34063848e2042ccc1fa8ed Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:19:58 +0100 Subject: [PATCH 18/28] add UI hint --- pages/base-components/pageTemplates/Base components.html | 7 ++++++- pages/component-list/pageTemplates/List components.html | 7 ++++++- pages/pages/pageTemplates/Pages.html | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pages/base-components/pageTemplates/Base components.html b/pages/base-components/pageTemplates/Base components.html index 49c89ef..10fe7c3 100644 --- a/pages/base-components/pageTemplates/Base components.html +++ b/pages/base-components/pageTemplates/Base components.html @@ -3,7 +3,12 @@

Base components

diff --git a/pages/component-list/pageTemplates/List components.html b/pages/component-list/pageTemplates/List components.html index 5b90b64..8610993 100644 --- a/pages/component-list/pageTemplates/List components.html +++ b/pages/component-list/pageTemplates/List components.html @@ -3,7 +3,12 @@

Components

diff --git a/pages/pages/pageTemplates/Pages.html b/pages/pages/pageTemplates/Pages.html index cd5b2dc..110623c 100644 --- a/pages/pages/pageTemplates/Pages.html +++ b/pages/pages/pageTemplates/Pages.html @@ -3,7 +3,12 @@

Pages

From afe1f6c869f16233eb2f7f0669cc36cd35d3e021 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:20:43 +0100 Subject: [PATCH 19/28] add data api for merged versions --- .../dataApi/listMergedBaseComponents.js | 73 +++++++++++++++++++ .../dataApi/listMergedComponents.js | 73 +++++++++++++++++++ pages/pages/dataApi/listMergedPages.js | 73 +++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 pages/base-components/dataApi/listMergedBaseComponents.js create mode 100644 pages/component-list/dataApi/listMergedComponents.js create mode 100644 pages/pages/dataApi/listMergedPages.js diff --git a/pages/base-components/dataApi/listMergedBaseComponents.js b/pages/base-components/dataApi/listMergedBaseComponents.js new file mode 100644 index 0000000..72d2bd4 --- /dev/null +++ b/pages/base-components/dataApi/listMergedBaseComponents.js @@ -0,0 +1,73 @@ +async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModuleBaseComponents(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listBaseComponents(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; +} \ No newline at end of file diff --git a/pages/component-list/dataApi/listMergedComponents.js b/pages/component-list/dataApi/listMergedComponents.js new file mode 100644 index 0000000..ceb7eef --- /dev/null +++ b/pages/component-list/dataApi/listMergedComponents.js @@ -0,0 +1,73 @@ +async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModuleComponents(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listComponents(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; +} \ No newline at end of file diff --git a/pages/pages/dataApi/listMergedPages.js b/pages/pages/dataApi/listMergedPages.js new file mode 100644 index 0000000..4d11e84 --- /dev/null +++ b/pages/pages/dataApi/listMergedPages.js @@ -0,0 +1,73 @@ +async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModulePages(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listPages(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; +} \ No newline at end of file From 8636ad4fa3f8fe120de6569257db57f90fefb67b Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:22:34 +0100 Subject: [PATCH 20/28] add functions to get module base-components, components and pages --- .../dataApi/listSimplyModuleBaseComponents.js | 29 +++++++++++++++++++ .../dataApi/listSimplyModuleComponents.js | 29 +++++++++++++++++++ .../imports/dataApi/listSimplyModulePages.js | 29 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 pages/imports/dataApi/listSimplyModuleBaseComponents.js create mode 100644 pages/imports/dataApi/listSimplyModuleComponents.js create mode 100644 pages/imports/dataApi/listSimplyModulePages.js diff --git a/pages/imports/dataApi/listSimplyModuleBaseComponents.js b/pages/imports/dataApi/listSimplyModuleBaseComponents.js new file mode 100644 index 0000000..a984773 --- /dev/null +++ b/pages/imports/dataApi/listSimplyModuleBaseComponents.js @@ -0,0 +1,29 @@ +function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "base-components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'baseComponent'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); +} \ No newline at end of file diff --git a/pages/imports/dataApi/listSimplyModuleComponents.js b/pages/imports/dataApi/listSimplyModuleComponents.js new file mode 100644 index 0000000..43b2b78 --- /dev/null +++ b/pages/imports/dataApi/listSimplyModuleComponents.js @@ -0,0 +1,29 @@ +function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'component'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); +} \ No newline at end of file diff --git a/pages/imports/dataApi/listSimplyModulePages.js b/pages/imports/dataApi/listSimplyModulePages.js new file mode 100644 index 0000000..44a102d --- /dev/null +++ b/pages/imports/dataApi/listSimplyModulePages.js @@ -0,0 +1,29 @@ +function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'page'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); +} \ No newline at end of file From 521abb488775b84ed9ebd69ddd0decad6408f572 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:23:11 +0100 Subject: [PATCH 21/28] remove modules, already handled in list calls --- pages/publish/actions/getAppData.js | 37 ----------------------------- 1 file changed, 37 deletions(-) diff --git a/pages/publish/actions/getAppData.js b/pages/publish/actions/getAppData.js index 65b7ae0..3d385f1 100644 --- a/pages/publish/actions/getAppData.js +++ b/pages/publish/actions/getAppData.js @@ -12,43 +12,6 @@ function() { }); appData.pageFrame = frame; }) - .then(function() { - return simplyApp.actions.listSimplyModules() - .then(function(modules) { - modules.forEach(function(module) { - // test-import - module.contents.forEach(function(moduleGroup) { - // test - moduleGroup.contents.forEach(function(moduleComponentCategory) { - switch (moduleComponentCategory.id) { - case "base-components": - case "components": - case "pages": - case "builders": - case "imports": - moduleComponentCategory.contents.forEach(function(component) { - component.contents.forEach(function(part) { - if (part.id == "meta") { - return; - }; - - if (typeof appData[part.id] === "undefined") { - appData[part.id] = []; - } - parts = JSON.parse(part.contents); - parts.forEach(function(entry) { - entry.base = component.baseType + "/" + component.id; - appData[part.id].push(entry); - }); - }); - }); - break; - } - }); - }); - }); - }); - }) .then(function() { return simplyApp.actions.listBuilders() .then(function(builders) { From 99ffeb7d85ea2b34d863d81de593ca93cfaa1250 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:23:46 +0100 Subject: [PATCH 22/28] use merged versions --- pages/base-components/actions/listBaseComponents.js | 2 +- pages/component-list/actions/listComponents.js | 2 +- pages/pages/actions/listPages.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/base-components/actions/listBaseComponents.js b/pages/base-components/actions/listBaseComponents.js index 3299b0c..085e70a 100644 --- a/pages/base-components/actions/listBaseComponents.js +++ b/pages/base-components/actions/listBaseComponents.js @@ -1,5 +1,5 @@ function() { - return simplyDataApi.listBaseComponents() + return simplyDataApi.listMergedBaseComponents() .catch(function(error) { return []; }); diff --git a/pages/component-list/actions/listComponents.js b/pages/component-list/actions/listComponents.js index d6725ce..2037e7b 100644 --- a/pages/component-list/actions/listComponents.js +++ b/pages/component-list/actions/listComponents.js @@ -1,5 +1,5 @@ function() { - return simplyDataApi.listComponents() + return simplyDataApi.listMergedComponents() .catch(function(error) { return []; }); diff --git a/pages/pages/actions/listPages.js b/pages/pages/actions/listPages.js index 753c913..95b5d09 100644 --- a/pages/pages/actions/listPages.js +++ b/pages/pages/actions/listPages.js @@ -1,5 +1,5 @@ function() { - return simplyDataApi.listPages() + return simplyDataApi.listMergedPages() .catch(function(error) { return []; }); From b70d869441c6f73a649313bcba638e31570889e4 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Fri, 23 Jan 2026 22:24:05 +0100 Subject: [PATCH 23/28] update generated --- generated.html | 386 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 339 insertions(+), 47 deletions(-) diff --git a/generated.html b/generated.html index 05838c6..f766995 100644 --- a/generated.html +++ b/generated.html @@ -949,6 +949,12 @@ .simplycode-editor-code textarea { margin-top: -1px; } + strong.simplycode-module { + color: var(--simplycode-highlight-dark); + float: right; + padding-right: 5px; + } + /* simplycode-import-url (component/component-import-url) */ .simplycode-editor-url input { width: 100%; @@ -2313,6 +2319,80 @@ return components; }); }, + // page/base-components + "listMergedBaseComponents" : async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModuleBaseComponents(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listBaseComponents(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; + }, // page/builder "deleteBuilder" : function(component) { return simplyRawApi.delete("builders/" + component) @@ -2449,6 +2529,80 @@ return components; }); }, + // page/component-list + "listMergedComponents" : async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModuleComponents(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listComponents(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; + }, // page/import "deleteImport" : function(component) { return simplyRawApi.delete("imports/" + component) @@ -2518,6 +2672,96 @@ }); }, // page/imports + "listSimplyModuleBaseComponents" : function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "base-components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'baseComponent'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); + }, + // page/imports + "listSimplyModuleComponents" : function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'component'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); + }, + // page/imports + "listSimplyModulePages" : function() { + return simplyRawApi.get("simply-modules") + .then(function(response) { + if (response.status === 200) { + return response.json(); + } + throw new Error("listSimplyModules failed", response.status); + }) + .then(function(simplyModules) { + let components = []; + simplyModules.forEach(function(moduleGroup) { + moduleGroup.contents.forEach(function(module) { + module.contents.forEach(function(moduleComponentCategory) { + switch(moduleComponentCategory.id) { + case "components": + moduleComponentCategory.contents.forEach(function(component) { + simplyDataApi.mergeComponent(component.contents); + component.description = component.contents.description; + component.baseType = 'page'; + components.push(component); + }); + break; + } + }); + }); + }); + return components; + }); + }, + // page/imports "listSimplyModules" : function() { return simplyRawApi.get("simply-modules") .then(function(response) { @@ -2528,11 +2772,8 @@ }) .then(function(simplyModules) { simplyModules.forEach(function(moduleGroup) { - console.log(moduleGroup.id); // test-import moduleGroup.contents.forEach(function(module) { - console.log(module.id); // test module.contents.forEach(function(moduleComponentCategory) { - console.log(moduleComponentCategory.id); // base-components switch(moduleComponentCategory.id) { case "components": moduleComponentCategory.contents.forEach(function(component) { @@ -2690,6 +2931,80 @@ }); }, // page/pages + "listMergedPages" : async function() { + let moduleComponents; + let localComponents; + + try { + moduleComponents = await simplyDataApi.listSimplyModulePages(); + } catch(e) { + moduleComponents = []; + } + + try { + localComponents = await simplyDataApi.listPages(); + } catch(e) { + localComponents = []; + } + + let componentIndex = {}; + let result = []; + + moduleComponents.forEach(function(component) { + componentIndex[component.id] = component; + component.imported = true; + result.push(component); + }); + + localComponents.forEach(function(localComponent) { + if (typeof componentIndex[localComponent.id] !== "undefined") { + // FIXME: Merge the module + local component; + let mergedComponent = {}; + + componentIndex[localComponent.id].contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + localComponent.contents.forEach(function(componentPart) { + if (typeof mergedComponent[componentPart.id] === "undefined") { + mergedComponent[componentPart.id] = {}; + } + let parts = JSON.parse(componentPart.contents); + let nameField = simplyApp.actions.getComponentNameField(componentPart.id); + if (Array.isArray(parts)) { + parts.forEach(function(part) { + mergedComponent[componentPart.id][part[nameField]] = part; + }); + } + }); + + componentIndex[localComponent.id].contents = []; + Object.keys(mergedComponent).forEach(function(componentPart) { + let content = []; + Object.values(mergedComponent[componentPart]).forEach(function(part) { + content.push(part); + }); + let mergedPart = { + "id" : componentPart, + "contents" : JSON.stringify(content) + }; + componentIndex[localComponent.id].contents.push(mergedPart); + }); + } else { + result.push(localComponent); + } + }); + return result; + }, + // page/pages "listPages" : function() { return simplyRawApi.get("pages") .then(function(response) { @@ -2934,7 +3249,7 @@ }, // page/base-components "listBaseComponents" : function() { - return simplyDataApi.listBaseComponents() + return simplyDataApi.listMergedBaseComponents() .catch(function(error) { return []; }); @@ -3012,7 +3327,7 @@ }, // page/component-list "listComponents" : function() { - return simplyDataApi.listComponents() + return simplyDataApi.listMergedComponents() .catch(function(error) { return []; }); @@ -3198,7 +3513,7 @@ }, // page/pages "listPages" : function() { - return simplyDataApi.listPages() + return simplyDataApi.listMergedPages() .catch(function(error) { return []; }); @@ -3218,44 +3533,6 @@ }); appData.pageFrame = frame; }) - .then(function() { - return simplyApp.actions.listSimplyModules() - .then(function(modules) { - modules.forEach(function(module) { - // test-import - console.log(module); - module.contents.forEach(function(moduleGroup) { - // test - moduleGroup.contents.forEach(function(moduleComponentCategory) { - switch (moduleComponentCategory.id) { - case "base-components": - case "components": - case "pages": - case "builders": - case "imports": - moduleComponentCategory.contents.forEach(function(component) { - component.contents.forEach(function(part) { - if (part.id == "meta") { - return; - }; - - if (typeof appData[part.id] === "undefined") { - appData[part.id] = []; - } - parts = JSON.parse(part.contents); - parts.forEach(function(entry) { - entry.base = component.baseType + "/" + component.id; - appData[part.id].push(entry); - }); - }); - }); - break; - } - }); - }); - }); - }); - }) .then(function() { return simplyApp.actions.listBuilders() .then(function(builders) { @@ -5784,7 +6061,12 @@

Base components

@@ -5882,7 +6164,12 @@

Components

@@ -5992,7 +6279,12 @@

Pages

From 19e85cac0df1e90e897502afc82b8e19e179c57d Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Sat, 24 Jan 2026 01:57:12 +0100 Subject: [PATCH 24/28] fetch imports on save of the import and save them to simply-modules --- base-components/jszip/headHtml/js-zip.html | 2 +- generated.html | 60 ++++++++++++++++++++-- pages/import/actions/fetchImport.js | 47 +++++++++++++++++ pages/import/actions/saveImport.js | 5 ++ 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 pages/import/actions/fetchImport.js diff --git a/base-components/jszip/headHtml/js-zip.html b/base-components/jszip/headHtml/js-zip.html index b822289..fa78a4f 100644 --- a/base-components/jszip/headHtml/js-zip.html +++ b/base-components/jszip/headHtml/js-zip.html @@ -1 +1 @@ - + diff --git a/generated.html b/generated.html index f766995..0f44705 100644 --- a/generated.html +++ b/generated.html @@ -1257,7 +1257,7 @@ } - + - + \ No newline at end of file diff --git a/pages/import/actions/fetchImport.js b/pages/import/actions/fetchImport.js new file mode 100644 index 0000000..445dde0 --- /dev/null +++ b/pages/import/actions/fetchImport.js @@ -0,0 +1,47 @@ +async function (importUrl, targetPath) { + const zipProxyUrl = 'zithub.pother.ca' + + // GitHub does not send CORS headers, so a custom build proxy is used + const url = new URL(importUrl); + if (url.hostname.endsWith('github.com')) { + url.pathname = `${url.href}`; + url.hostname = zipProxyUrl; + } + + const response = await fetch(url); + let blob = await response.blob(); + let arrayBuffer = blob.arrayBuffer(); + + const files = {} + + const zip = await JSZip.loadAsync(arrayBuffer); + const zipFiles = Object.values(zip.files); + + for (const file of zipFiles) { + if (file.dir === false) { + files[file.name] = await file.async('blob'); + } + } + + for (const [ fileName, blob ] of Object.entries(files)) { + const bytes = await blob.arrayBuffer(); + let contents, type; + + try { + type = 'text/plain;charset=utf-8'; + contents = new TextDecoder('utf-8', { fatal: true }).decode(bytes); + } catch (e) { + type = 'application/octet-stream'; + contents = blob; + } + + let subpathFilename = fileName.split("/"); + let zipProvider = new URL(importUrl); + if (zipProvider.hostname.endsWith('github.com')) { + subpathFilename.shift(); // github adds the repo name as the directory in the zip, remove that; + } + subpathFilename = subpathFilename.join("/"); + + await simplyRawApi.putRaw(targetPath + subpathFilename, {}, contents); + } +} \ No newline at end of file diff --git a/pages/import/actions/saveImport.js b/pages/import/actions/saveImport.js index 087636d..c6a1420 100644 --- a/pages/import/actions/saveImport.js +++ b/pages/import/actions/saveImport.js @@ -11,6 +11,11 @@ function(component) { }); } }; + + component.parts.importUrls.forEach(function(importUrl) { + let modulePath = "simply-modules/" + component.id + "/" + importUrl.import + "/"; + promises.push(simplyApp.actions.fetchImport(importUrl.url, modulePath)); + }); var meta = clone(component); delete meta.parts; From abd007a4675150dad27fbc2bda9fcdfad26c9281 Mon Sep 17 00:00:00 2001 From: Yvo Brevoort Date: Sat, 24 Jan 2026 02:19:21 +0100 Subject: [PATCH 25/28] add 'install all' button on imports overview --- .../simply-toolbar-imports.html | 34 ++++ .../componentTemplates/simply-toolbar.html | 1 + generated.html | 150 +++++++++--------- pages/import/actions/fetchImportUrls.js | 75 --------- pages/imports/commands/fetchImports.js | 32 ++++ .../imports/dataApi/listSimplyModulePages.js | 2 +- 6 files changed, 140 insertions(+), 154 deletions(-) create mode 100644 components/simply-toolbar/componentTemplates/simply-toolbar-imports.html delete mode 100644 pages/import/actions/fetchImportUrls.js create mode 100644 pages/imports/commands/fetchImports.js diff --git a/components/simply-toolbar/componentTemplates/simply-toolbar-imports.html b/components/simply-toolbar/componentTemplates/simply-toolbar-imports.html new file mode 100644 index 0000000..528eab6 --- /dev/null +++ b/components/simply-toolbar/componentTemplates/simply-toolbar-imports.html @@ -0,0 +1,34 @@ +
+ +
+ + + + + + \ No newline at end of file diff --git a/components/simply-toolbar/componentTemplates/simply-toolbar.html b/components/simply-toolbar/componentTemplates/simply-toolbar.html index a504805..adde7a8 100644 --- a/components/simply-toolbar/componentTemplates/simply-toolbar.html +++ b/components/simply-toolbar/componentTemplates/simply-toolbar.html @@ -2,6 +2,7 @@ + diff --git a/generated.html b/generated.html index 0f44705..7457042 100644 --- a/generated.html +++ b/generated.html @@ -2746,7 +2746,7 @@ moduleGroup.contents.forEach(function(module) { module.contents.forEach(function(moduleComponentCategory) { switch(moduleComponentCategory.id) { - case "components": + case "pages": moduleComponentCategory.contents.forEach(function(component) { simplyDataApi.mergeComponent(component.contents); component.description = component.contents.description; @@ -3385,83 +3385,6 @@ } }, // page/import - "fetchImportUrls" : async function (component) { - // @TODO: Check if the URL is actually a ZIP file and not another type - - const zipProxyUrl = 'zithub.pother.ca' - - const imports = await simplyApp.actions.getImport(component) - - const importUrls = imports - .filter(importItem => importItem.id === 'importUrls') - .map(importItem => JSON.parse(importItem.contents)) - .flat(2) - - const fetches = importUrls.map(importDetails => { - const url = new URL(importDetails.url) - - // GitHub does not send CORS headers, so a custom build proxy is used - if (url.hostname.endsWith('github.com')) { - url.pathname = `${url.href}` - url.hostname = zipProxyUrl - } - - return fetch(url) - }) - - const responses = await Promise.all(fetches) - - const arrayBufferResponses = responses.map(async response => { - let blob = await response.blob() - - return await blob.arrayBuffer() - }) - - const arrayBuffers = await Promise.all(arrayBufferResponses) - - const zipArchives = arrayBuffers.map(async arrayBuffer => { - const files = {} - - const zip = await JSZip.loadAsync(arrayBuffer) - const zipFiles = Object.values(zip.files) - - for (const file of zipFiles) { - if (file.dir === false) { - files[file.name] = await file.async('blob') - } - } - - return files - }) - - const archiveFiles = await Promise.all(zipArchives) - - // @CHECKME: Instead of a naked object, should FileList and File be used? - - const fileCollections = archiveFiles.map(async fileCollection => { - for (const [ fileName, blob ] of Object.entries(fileCollection)) { - const bytes = await blob.arrayBuffer() - - let contents, type - - try { - type = 'text/plain;charset=utf-8' - contents = new TextDecoder('utf-8', { fatal: true }).decode(bytes) - } catch (e) { - type = 'application/octet-stream' - contents = blob - } - - fileCollection[fileName] = { contents, type } - } - - return fileCollection - }) - - return await Promise.all(fileCollections) - } - , - // page/import "getImport" : function(component) { return simplyDataApi.getImport(component); }, @@ -4191,6 +4114,39 @@ document.location.hash="imports/" + newComponent.id; }); }, + // page/imports + "fetchImports" : function() { + editor.pageData.alerts.unshift({ + "data-simply-template" : "info", + "message" : "Installing imports...", + "state" : "new" + }); + var promises = []; + simplyApp.actions.listImports() + .then(function(modules) { + modules.forEach(function(module) { + module.contents.forEach(function(componentPart) { + if (componentPart.id === "importUrls") { + let importUrls = JSON.parse(componentPart.contents); + importUrls.forEach(function(importUrl) { + let targetPath = "simply-modules/" + module.id + "/" + importUrl.import + "/"; + promises.push(simplyApp.actions.fetchImport(importUrl.url, targetPath)); + }); + } + }); + }); + }) + .then(function() { + return Promise.all(promises); + }) + .then(function() { + editor.pageData.alerts.unshift({ + "data-simply-template" : "info", + "message" : "Installation done!", + "state" : "new" + }); + }); + }, // page/page "deletePage" : function() { if (!confirm("Delete page?")) { @@ -5975,6 +5931,43 @@ + + + + + + + +