diff --git a/.gitignore b/.gitignore index a3312a1..5ed3c31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /node_modules -/definitions +/trash +/index.html /uploads /data.json .env diff --git a/TODO.md b/TODO.md index a485d3b..857d54f 100644 --- a/TODO.md +++ b/TODO.md @@ -75,7 +75,7 @@ - [ ] Enable/Disable function feature - [ ] UnInstall function - [ ]
// action example -- [ ] module: { id: string, definitionId: string, load: 'cars.load', props: {}, links: {}} // load example +- [ ] module: { id: string, blockId: string, load: 'cars.load', props: {}, links: {}} // load example ## File Management diff --git a/functions/test.js b/functions/test.js index 2852d52..79a91c9 100644 --- a/functions/test.js +++ b/functions/test.js @@ -3,13 +3,6 @@ import { db } from "#services" export default { name: 'Test function', slug: 'test_function', - // props: [ - // { - // slug: 'collection', - // type: 'collection', - // name: 'Collection' - // } - // ], action(body, context) { console.log(body, context) diff --git a/public/css/components/html.css b/public/css/components/html.css index beab7a2..3fd167b 100644 --- a/public/css/components/html.css +++ b/public/css/components/html.css @@ -1,8 +1,31 @@ +:root { + --heading-color-1: #1a202c; + --heading-color-2: #2d3748; + --heading-color-3: #4a5568; + --color-code: #353535; + --color-link: #3182ce; + --color-bg-code: #edf2f7; + --color-bg-pre: #f7fafc; + --color-bg-blockquote: #cbd5e0; + --color-link-hover: #2b6cb0; +} + +[data-section-text-light] [data-html], [data-theme="dark"] [data-html] { + --heading-color-1: #edf2f7; /* Light color for headings */ + --heading-color-2: #e2e8f0; /* Slightly darker light color for subheadings */ + --heading-color-3: #cbd5e0; /* Even darker for minor headings */ + --color-code: #e2e8f0; /* Light color for inline code */ + --color-link: #63b3ed; /* Light blue for links */ + --color-bg-code: #2d3748; /* Dark background for code blocks */ + --color-bg-pre: #1a202c; /* Very dark background for preformatted text */ + --color-bg-blockquote: #2d3748; /* Darker background for blockquotes */ + --color-link-hover: #90cdf4; /* Lighter blue for link hover state */ +} [data-html] h1 { font-size: 2.25rem; /* 36px */ line-height: 1.25; /* Adjust for your design */ font-weight: 700; /* Bold */ - color: #1a202c; /* Dark color, change to your palette */ + color: var(--heading-color-1); /* Dark color, change to your palette */ margin-bottom: 1rem; /* Spacing below the heading */ } @@ -10,7 +33,7 @@ font-size: 1.5rem; /* 30px */ line-height: 1.3; font-weight: 600; /* Semi-bold */ - color: #2d3748; + color: var(--heading-color-2); margin-bottom: 0.875rem; } @@ -18,7 +41,7 @@ font-size: 1.25rem; /* 24px */ line-height: 1.4; font-weight: 600; - color: #4a5568; + color: var(--heading-color-3); margin-bottom: 0.75rem; } @@ -26,27 +49,27 @@ font-size: 1rem; /* 20px */ line-height: 1.5; font-weight: 600; - color: #4a5568; + color: var(--heading-color-3); margin-bottom: 0.5rem; } [data-html] p { font-size: 1rem; /* 16px */ line-height: 1.75; /* Taller line height for readability */ - color: #2d3748; + color: var(--heading-color-2); margin-bottom: 0.5rem; } [data-html] a { font-size: 1rem; line-height: 1.75; - color: #3182ce; /* Link color */ + color: var(--color-link); /* Link color */ text-decoration: underline; transition: all 0.2s ease; } [data-html] a:hover { - color: #2b6cb0; /* Darker link color on hover */ + color: var(--color-link-hover); /* Darker link color on hover */ } [data-html] ul { @@ -64,7 +87,7 @@ [data-html] li { font-size: 1rem; line-height: 1.75; - color: #2d3748; + color: var(--heading-color-2); margin-bottom: 0.5rem; } @@ -72,24 +95,24 @@ font-size: 1.125rem; /* 18px */ line-height: 1.6; font-weight: 500; - color: #4a5568; - border-left: 4px solid #cbd5e0; /* Light gray border */ + color: var(--heading-color-3); + border-left: 4px solid var(--color-bg-blockquote); /* Light gray border */ padding-left: 1rem; margin-bottom: 1rem; } [data-html] code { font-family: 'Courier New', Courier, monospace; - background-color: #edf2f7; - color: #e53e3e; /* Slightly different color for contrast */ + background-color: var(--color-bg-code); + color: var(--color-code); /* Slightly different color for contrast */ padding: 0.25rem 0.5rem; border-radius: 0.25rem; } [data-html] pre { font-family: 'Courier New', Courier, monospace; - background-color: #f7fafc; - color: #2d3748; + background-color: var(--color-bg-pre); + color: var(--heading-color-2); padding: 1rem; border-radius: 0.5rem; overflow-x: auto; /* Enable horizontal scroll for long code blocks */ diff --git a/public/css/section.preview.css b/public/css/section.preview.css index 07a4486..9396e8d 100644 --- a/public/css/section.preview.css +++ b/public/css/section.preview.css @@ -119,8 +119,8 @@ } -[data-body]:has([data-definition-module]) [data-slot]:has([data-definition-module]), -[data-body]:has([data-definition-module]) [data-columns]:has([data-definition-module]) { +[data-body]:has([data-block-module]) [data-slot]:has([data-block-module]), +[data-body]:has([data-block-module]) [data-columns]:has([data-block-module]) { box-shadow: inset 0 0 32px -8px var(--color-primary); border: 1px solid var(--color-primary); } diff --git a/public/css/sitebuilder.edit.css b/public/css/sitebuilder.edit.css index fcff836..78ca3f5 100644 --- a/public/css/sitebuilder.edit.css +++ b/public/css/sitebuilder.edit.css @@ -295,12 +295,12 @@ iframe { /* background-color: var(--sidebar-bg); */ } -[data-definitions] { +[data-blocks] { display: flex; flex-direction: column; gap: 8px; } -[data-definition-module] { +[data-block-module] { padding: 0.5rem 1rem; width: 100%; transition: all 0.2s ease; @@ -445,10 +445,10 @@ iframe { pointer-events: all; } -.sortable-chosen [data-definition-icon]:not(:hover) { +.sortable-chosen [data-block-icon]:not(:hover) { display: none; } -.sortable-ghost [data-definition-icon]:not(:hover) { +.sortable-ghost [data-block-icon]:not(:hover) { display: none; } diff --git a/public/css/sitebuilder.preview.css b/public/css/sitebuilder.preview.css index 06df1ec..425e6fd 100644 --- a/public/css/sitebuilder.preview.css +++ b/public/css/sitebuilder.preview.css @@ -21,14 +21,14 @@ body { background-color: white; } -[data-body] [data-definition-id] { +[data-body] [data-block-id] { background-color: #0026ff40; overflow: hidden; color: transparent; height: 32px; width: 100%; } -[data-body] [data-definition-id] [data-definition-icon] { +[data-body] [data-block-id] [data-block-icon] { /* background-color: red; */ display: none; } diff --git a/public/js/actions.js b/public/js/actions.js index 240c6f0..e31e16d 100644 --- a/public/js/actions.js +++ b/public/js/actions.js @@ -113,7 +113,7 @@ const actions = { let body = { slug: window.location.pathname, - definitionId: res.id, + blockId: res.id, order: 1, moduleId: el.parentNode.dataset.slot } @@ -182,9 +182,9 @@ const actions = { const modal = document.querySelector(`[data-modal="field"]`) - const formHtml = await request('definition.getFieldForm', { + const formHtml = await request('block.getFieldForm', { type: value, - handler: 'definition.addField', + handler: 'block.addField', mode: 'add', id: modal.dataset.id }) @@ -202,7 +202,7 @@ const actions = { modal.dataset.id = modal.querySelector('[name="id"]').value modal.querySelector('[data-modal-body]').innerHTML = '' - const html = await request('definition.getFieldTypeSelector', {}) + const html = await request('block.getFieldTypeSelector', {}) modal.querySelector('[data-modal-title]').textContent = 'Choose a type' modal.querySelector('[data-modal-body]').innerHTML = html @@ -439,9 +439,9 @@ const actions = { const modal = document.querySelector(`[data-modal="field"]`) modal.dataset.id = modal.querySelector('[name="id"]').value - const formHtml = await request('definition.getFieldForm', { + const formHtml = await request('block.getFieldForm', { type: field.type, - handler: 'definition.setField', + handler: 'block.setField', mode: 'edit', id: modal.dataset.id }) @@ -469,7 +469,7 @@ const actions = { openConfirm({ title: 'Are you sure?', description: 'Are you sure to remove this field?', - action: 'definition.removeField', + action: 'block.removeField', id: el.dataset.id, slug: el.dataset.slug }) @@ -518,17 +518,15 @@ const actions = { id: mod.dataset.moduleId, slug: decodeURIComponent(location.pathname), fullWidth: !isFullWidth, - paddingTop: +section.style.paddingTop.replace('px', ''), - paddingBottom: +section.style.paddingBottom.replace('px', ''), }) }, 'open-edit-module-ai'(el) { document.querySelector('[data-modal="update-ai"]').dataset.modalOpen = true - const definitionId = el.dataset.id + const blockId = el.dataset.id const form = document.querySelector('[data-modal="update-ai"] [data-form]') - setFormValue(form, { id: definitionId}) + setFormValue(form, { id: blockId}) async function onTextareaKeyDown(e) { @@ -560,7 +558,7 @@ const actions = { form.dataset.load = '' await request('ai.updateModule', { - id: definitionId, + id: blockId, template: form.querySelector('[name="template"]').value ?? '' }).then(res => { delete document.querySelector('[data-modal="update-ai"]').dataset.modalOpen @@ -782,8 +780,8 @@ const actions = { ev.stopPropagation() openConfirm({ title: 'Are you sure?', - description: 'Are you sure to remove this module definition?', - action: 'definition.delete', + description: 'Are you sure to remove this module block?', + action: 'block.delete', id: el.dataset.id }) }, diff --git a/public/js/fileuploader.js b/public/js/fileuploader.js index 0e2f93a..a0bc3bc 100644 --- a/public/js/fileuploader.js +++ b/public/js/fileuploader.js @@ -105,8 +105,24 @@ export function FileUploader(el) { const res = await fetch('/api/file/upload', {method: 'POST', body: form}).then(res => res.json()) setValue(res.id) } - - - }) + el.addEventListener('paste', async (event) => { + const items = (event.clipboardData || event.originalEvent.clipboardData).items; + for (let i = 0; i < items.length; i++) { + if (items[i].type.startsWith('image/')) { + const file = items[i].getAsFile(); + const form = new FormData(); + form.set('file', file); + const res = await fetch('/api/file/upload', { method: 'POST', body: form }).then(res => res.json()); + const id = res.id; + + if (multiple) { + let currentValue = JSON.parse(element.value) || []; + setValue([...currentValue, id]); + } else { + setValue(id); + } + } + } + }); } \ No newline at end of file diff --git a/public/js/section-resizer.js b/public/js/section-resizer.js index bb047c0..97a1ed4 100644 --- a/public/js/section-resizer.js +++ b/public/js/section-resizer.js @@ -33,12 +33,9 @@ export function SectionResizer(element) { dragging = false; if(changed) { setTimeout(async () => { - const fullWidth = section.hasAttribute('data-section-full-width') - await request('module.saveSettings', { id: element.parentElement.parentElement.dataset.moduleId, slug: window.location.pathname, - fullWidth, paddingTop: +section.style.paddingTop.replace('px', ''), paddingBottom: +section.style.paddingBottom.replace('px', ''), }) diff --git a/public/js/setup.js b/public/js/setup.js index 25fb053..4bc2405 100644 --- a/public/js/setup.js +++ b/public/js/setup.js @@ -1,13 +1,12 @@ import { setFormValue } from "/js/form.js"; -import { FileUploader } from './fileuploader.js' +// import { FileUploader } from './fileuploader.js' -FileUploader(document.querySelector('[data-file-label]')) +// FileUploader(document.querySelector('[data-file-label]')) document.querySelector('[data-form]').action = '/api/setup' document.querySelector('[data-form]').method = 'POST' setFormValue(document.querySelector('[data-form]'), { password: 'Passw0rd!', - template: 'test' }) \ No newline at end of file diff --git a/public/js/sortable.js b/public/js/sortable.js index d6b55c8..4a9308b 100644 --- a/public/js/sortable.js +++ b/public/js/sortable.js @@ -80,23 +80,23 @@ export function initSortableIframe() { } export function initSortable() { - const definitionsElement = document.querySelector('[data-definitions]') - Sortable.get(definitionsElement)?.destroy() + const blocksElement = document.querySelector('[data-blocks]') + Sortable.get(blocksElement)?.destroy() - new Sortable(definitionsElement, { + new Sortable(blocksElement, { group: { name: 'modules', pull: 'clone', put: false, }, sort: false, - draggable: '[data-definition-id]', + draggable: '[data-block-id]', animation: 150, async onEnd(event) { - if(event.to == definitionsElement) return; + if(event.to == blocksElement) return; const body = { slug: window.location.pathname, - definitionId: event.item.dataset.definitionId, + blockId: event.item.dataset.blockId, order: event.newIndex + 1, moduleId: event.to.dataset.columns } diff --git a/services/export.js b/services/export.js index d56df3a..baddc2c 100644 --- a/services/export.js +++ b/services/export.js @@ -5,15 +5,15 @@ const files = {} const functionFiles = [] async function exportModule(module) { - const definition = await db('definitions').query().filter('id', '=', module.definitionId).first() + const block = await db('blocks').query().filter('id', '=', module.blockId).first() - if(definition.name === 'Section') { + if(block.name === 'Section') { const columns = await db('modules').query().filter('moduleId', '=', module.id).first() module.props['content'] = await exportModules({moduleId: columns.id}) } else { - for(let prop of definition.props ?? []) { + for(let prop of block.props ?? []) { if(prop.type == 'file') { if(prop.multiple) { for(let item of module.props[prop.slug] ?? []) { @@ -31,8 +31,8 @@ async function exportModule(module) { } } - module.definition = definition.name - delete module.definitionId + module.block = block.name + delete module.blockId delete module.id delete module.pageId @@ -106,7 +106,7 @@ async function exportFunctionFiles() { } async function exportBlocks() { - let blocks = await db('definitions').query().all() + let blocks = await db('blocks').query().all() blocks = blocks.filter(x => x.name !== 'Rich Text' && x.name !== 'Columns' && x.name !== 'Section') for(let block of blocks) { diff --git a/services/setup.js b/services/setup.js index 6d48fef..65cfb40 100644 --- a/services/setup.js +++ b/services/setup.js @@ -1,37 +1,59 @@ -import {cpSync, existsSync, mkdirSync, readFileSync, rmSync} from 'node:fs' +import { existsSync, mkdirSync } from 'node:fs' import {db} from "./db.js" -import JSZip from 'jszip' -import { copyFile, mkdir, writeFile } from 'node:fs/promises' -import { slugify } from '../src/handlers/content.js' -import { join, basename } from 'node:path' import { roleFields } from '../src/handlers/role.js' const defaultModules = { Section: { - template: `
{{{content}}}
`, + template: ` +
+ {{{content}}} +
+ `, props: [ { type: 'checkbox', slug: 'fullWidth', label: 'Full Width', + hidden: true, defaultValue: true }, + { + type: 'color', + slug: 'backgroundColor', + label: 'Background Color', + }, + { + type: 'file', + slug: 'backgroundImage', + label: 'Background Image', + file_type: 'image', + multiple: false + }, + { + type: 'checkbox', + slug: 'lightColor', + label: 'Light text colors', + defaultValue: false + }, { type: 'input', slug: 'paddingTop', label: 'Padding Top', + hidden: true, defaultValue: 0 }, { type: 'input', slug: 'paddingBottom', label: 'Padding Bottom', + hidden: true, defaultValue: 0 }, { type: 'slot', slug: 'content', - label: 'Content' + label: 'Content', + hidden: true, }, ] }, @@ -58,465 +80,482 @@ const defaultModules = { } export async function setupCms(req, res) { - const {template, password, file} = req.body + const { password } = req.body let adminRoleId; - if(!existsSync('./uploads')) { - mkdirSync('./uploads') + await initializeFiles(); + await initializeRoles(); + await initializeUsers(); + await initializeBlocks(); + await initializeSettings(); + + async function initializeFiles() { + if(!existsSync('./uploads')) { + mkdirSync('./uploads') + } + } + + async function initializeSettings() { + await db('settings').insert({}) + } + + async function initializeRoles() { + const adminRole = await db('roles').insert({ + name: 'Admin', + permissions: (roleFields.find(x => x.slug === 'permissions')?.items ?? []).map( x => x.value) + }); + adminRoleId = adminRole.id } - async function importUsers(users) { - const adminUser = users.find(user => user.username === 'admin'); + + async function initializeUsers() { const admin = await db('users').insert({ - name: adminUser ? adminUser.name : 'Admin', + name: 'Admin', username: 'admin', password: `_%${password}%_`, - slug: adminUser?.slug ?? 'admin', - profile: adminUser?.profile ?? '', + slug: 'admin', + profile: '', role: adminRoleId, - email: adminUser?.email ?? 'admin@example.com' + email: 'admin@example.com' }); res.cookie('userId', admin.id, { httpOnly: true }) - - for (let user of users) { - if (user.username !== 'admin') { - await db('users').insert({ - name: user.name, - username: user.username, - password: `_%${password}%_`, - slug: user.slug, - profile: user.profile, - email: user.email - }); - } - } - - } - async function importRoles(roles) { - const adminRole = await db('roles').insert({ - name: 'Admin', - permissions: (roleFields.find(x => x.slug === 'permissions')?.items ?? []).map( x => x.value) - }); - - adminRoleId = adminRole.id - - for (let role of roles) { - if (role.name !== 'admin') { - await db('roles').insert({ - name: role.name, - permissions: user.permissions, - }); - } + async function initializeBlocks() { + for(let key in defaultModules) { + const res = await db('blocks').insert({ + name: key, + props: defaultModules[key].props, + template: defaultModules[key].template, + }) } } + res.redirect('/admin') +} - let _definitions = {} - let _collections = {} - const idMap = {} - - async function importPages(pages) { - for(let page of pages) { - const request = { - name: page.name, - slug: page.slug, - head: page.head, - dynamic: page.dynamic, - script: page.script, - style: page.style, - seo: page.seo, - include_site_head: page.include_site_head - } - - if(page.dynamic) { - request.collectionId = _collections[page.collection].id - } +// _definitions[res.name] = res + // for (let user of users) { + // if (user.username !== 'admin') { + // await db('users').insert({ + // name: user.name, + // username: user.username, + // password: `_%${password}%_`, + // slug: user.slug, + // profile: user.profile, + // email: user.email + // }); + // } + // } + + + // } + + // async function importRoles(roles) { + + // for (let role of roles) { + // if (role.name !== 'admin') { + // await db('roles').insert({ + // name: role.name, + // permissions: user.permissions, + // }); + // } + // } + // } + + // let _definitions = {} + // let _collections = {} + // const idMap = {} + + // async function importPages(pages) { + // for(let page of pages) { + // const request = { + // name: page.name, + // slug: page.slug, + // head: page.head, + // dynamic: page.dynamic, + // script: page.script, + // style: page.style, + // seo: page.seo, + // include_site_head: page.include_site_head + // } + + // if(page.dynamic) { + // request.collectionId = _collections[page.collection].id + // } - const res = await db('pages').insert(request) - - async function addModules(modules, moduleId = null) { - let result = [] - let index = 0 - let afterInsertActions = [] - for(let module of modules) { - for(let prop of _definitions[module.definition]?.props ?? []) { - if(!module.props) continue; - const value = module.props[prop.slug] - - if(prop.type === 'relation') { - const value = module.props[prop.slug] - if(value && !value.filters) { - if(prop.multiple) { - if(value) { - module.props[prop.slug] = value.map(x => idMap[x]) - } - } else { - if(value) { - module.props[prop.slug] = idMap[value] - } - } - } - } - - if(prop.type === 'file') { - if(prop.multiple) { - let result = [] - for(let item of value ?? []) { - - const {id} = await db('files').insert({ - name: item - }) + // const res = await db('pages').insert(request) + + // async function addModules(modules, moduleId = null) { + // let result = [] + // let index = 0 + // let afterInsertActions = [] + // for(let module of modules) { + // for(let prop of _definitions[module.definition]?.props ?? []) { + // if(!module.props) continue; + // const value = module.props[prop.slug] + + // if(prop.type === 'relation') { + // const value = module.props[prop.slug] + // if(value && !value.filters) { + // if(prop.multiple) { + // if(value) { + // module.props[prop.slug] = value.map(x => idMap[x]) + // } + // } else { + // if(value) { + // module.props[prop.slug] = idMap[value] + // } + // } + // } + // } + + // if(prop.type === 'file') { + // if(prop.multiple) { + // let result = [] + // for(let item of value ?? []) { + + // const {id} = await db('files').insert({ + // name: item + // }) - afterInsertActions.push({ - type: 'move-file', - value: { - name: item, - path: file ? `./temp/site/files/${item}` : `./templates/${template}/files/${item}`, - id - } - }) - result.push(id) - } + // afterInsertActions.push({ + // type: 'move-file', + // value: { + // name: item, + // path: file ? `./temp/site/files/${item}` : `./templates/${template}/files/${item}`, + // id + // } + // }) + // result.push(id) + // } - module.props[prop.slug] = result - } else { - if(value) { - const {id} = await db('files').insert({ - name: value - }) + // module.props[prop.slug] = result + // } else { + // if(value) { + // const {id} = await db('files').insert({ + // name: value + // }) - afterInsertActions.push({ - type: 'move-file', - value: { - path: file ? `./temp/site/files/${value}` : `./templates/${template}/files/${value}`, - id - } - }) + // afterInsertActions.push({ + // type: 'move-file', + // value: { + // path: file ? `./temp/site/files/${value}` : `./templates/${template}/files/${value}`, + // id + // } + // }) - module.props[prop.slug] = id - } - } - } - - if(prop.type === 'slot') { - afterInsertActions.push({ - type: 'insert-module', - value: module.props[prop.slug] - }) - delete module.props[prop.slug] - } - } + // module.props[prop.slug] = id + // } + // } + // } + + // if(prop.type === 'slot') { + // afterInsertActions.push({ + // type: 'insert-module', + // value: module.props[prop.slug] + // }) + // delete module.props[prop.slug] + // } + // } - let res2 - if(!moduleId) { - - let res1 = await db('modules').insert({ - definitionId: _definitions[module.definition].id, - moduleId, - pageId: res.id, - props: module.props ?? {}, - links: module.links ?? {}, - order: index++ - }) - - res2 = await db('modules').insert({ - definitionId: _definitions['Columns'].id, - moduleId: res1.id, - props: {}, - links: {}, - order: 0, - }) - } else { - res2 = await db('modules').insert({ - definitionId: _definitions[module.definition].id, - moduleId, - props: module.props ?? {}, - links: module.links ?? {}, - cols: module.cols ?? 12, - order: index++ - }) - } - - for(let action of afterInsertActions) { - if(action.type === 'insert-module') { - const res = await addModules(action.value ?? [], res2.id) - } - if(action.type === 'move-file') { - cpSync(action.value.path, `./uploads/${action.value.id}`) + // let res2 + // if(!moduleId) { + + // let res1 = await db('modules').insert({ + // definitionId: _definitions[module.definition].id, + // moduleId, + // pageId: res.id, + // props: module.props ?? {}, + // links: module.links ?? {}, + // order: index++ + // }) + + // res2 = await db('modules').insert({ + // definitionId: _definitions['Columns'].id, + // moduleId: res1.id, + // props: {}, + // links: {}, + // order: 0, + // }) + // } else { + // res2 = await db('modules').insert({ + // definitionId: _definitions[module.definition].id, + // moduleId, + // props: module.props ?? {}, + // links: module.links ?? {}, + // cols: module.cols ?? 12, + // order: index++ + // }) + // } + + // for(let action of afterInsertActions) { + // if(action.type === 'insert-module') { + // const res = await addModules(action.value ?? [], res2.id) + // } + // if(action.type === 'move-file') { + // cpSync(action.value.path, `./uploads/${action.value.id}`) - } - } - afterInsertActions = [] - - result.push(res2.id) - } - return result; - } + // } + // } + // afterInsertActions = [] + + // result.push(res2.id) + // } + // return result; + // } - await addModules(page.modules) - } - } - - async function importCollections(collections) { - for(let collection of collections) { - let {name, fields, contents} = collection - - const slugField = fields.find(x => x.slug === 'slug') - if(slugField) { - slugField.hidden = true - } else { - fields = [ - {slug: 'slug', label: 'Slug', hidden: true, type: 'input'}, - ...fields - ] - } - const nameField = fields.find(x => x.slug === 'name') - if(nameField) { - nameField.default = true - } else { - fields = [ - {slug: 'name', label: 'Name', default: true, type: 'input'}, - ...fields - ] - } + // await addModules(page.modules) + // } + // } + + // async function importCollections(collections) { + // for(let collection of collections) { + // let {name, fields, contents} = collection + + // const slugField = fields.find(x => x.slug === 'slug') + // if(slugField) { + // slugField.hidden = true + // } else { + // fields = [ + // {slug: 'slug', label: 'Slug', hidden: true, type: 'input'}, + // ...fields + // ] + // } + // const nameField = fields.find(x => x.slug === 'name') + // if(nameField) { + // nameField.default = true + // } else { + // fields = [ + // {slug: 'name', label: 'Name', default: true, type: 'input'}, + // ...fields + // ] + // } - const res = await db('collections').insert({name, fields}) - _collections[res.name] = res - - for(let item of contents) { - if(!item.slug) { - item.slug = slugify(item.name) - } - for(let key in item) { - const field = fields.find(x => x.slug === key) - const prop = item[key] - - if(key === 'id') { - continue - // handle relations. - } - - if(!field) console.log('Field not found: ', key, fields) - if(field.type === 'file') { - if(field.multiple) { - let result; - for(let name of prop) { - const {id} = await db('files').insert({ - name - }) + // const res = await db('collections').insert({name, fields}) + // _collections[res.name] = res + + // for(let item of contents) { + // if(!item.slug) { + // item.slug = slugify(item.name) + // } + // for(let key in item) { + // const field = fields.find(x => x.slug === key) + // const prop = item[key] + + // if(key === 'id') { + // continue + // // handle relations. + // } + + // if(!field) console.log('Field not found: ', key, fields) + // if(field.type === 'file') { + // if(field.multiple) { + // let result; + // for(let name of prop) { + // const {id} = await db('files').insert({ + // name + // }) - if(file) { - cpSync(`./temp/site/files/${name}`, `./uploads/${id}`) - } else { - cpSync(`./templates/${template}/files/${name}`, `./uploads/${id}`) - } - result.push(id) - } - item[key] = result - } else { - const {id} = await db('files').insert({ - name: prop - }) + // if(file) { + // cpSync(`./temp/site/files/${name}`, `./uploads/${id}`) + // } else { + // cpSync(`./templates/${template}/files/${name}`, `./uploads/${id}`) + // } + // result.push(id) + // } + // item[key] = result + // } else { + // const {id} = await db('files').insert({ + // name: prop + // }) - if(file) { - cpSync(`./temp/site/files/${prop}`, `./uploads/${id}`) - } else { - cpSync(`./templates/${template}/files/${prop}`, `./uploads/${id}`) - } - item[key] = id - } - } + // if(file) { + // cpSync(`./temp/site/files/${prop}`, `./uploads/${id}`) + // } else { + // cpSync(`./templates/${template}/files/${prop}`, `./uploads/${id}`) + // } + // item[key] = id + // } + // } - } - item._type = res.id + // } + // item._type = res.id - const itemId = item.id - delete item.id - const res2 = await db('contents').insert(item) + // const itemId = item.id + // delete item.id + // const res2 = await db('contents').insert(item) - idMap[itemId] = res2.id - } - } - - for(let collection of await db('collections').query().all()) { - for(let field of collection.fields) { - if(field.type === 'relation') { - field.collectionId = _collections[field.collection].id - delete field.collection - } - } - await db('collections').update(collection) - - const contents = await db('contents').query().filter('_type', '=', collection.id).all() - - for(let item of contents) { - for(let field of collection.fields) { - if(field.type === 'relation') { - if(field.multiple) { - item[field.slug] = item[field.slug].map(x => idMap[x]) - } else { - item[field.slug] = idMap[item[field.slug]] - } - } - } - await db('contents').update(item) - } - } - } - - if(file) { - const zipFile = './uploads/' + file; - - if(!existsSync('./temp')) { - await mkdir('./temp') - } else { - rmSync('./temp', {force: true, recursive: true}) - await mkdir('./temp') - } - - const zip = new JSZip() - - const content = await zip.loadAsync(readFileSync(zipFile) , {createFolders: true}) - - const promises = [] - - for(let key in defaultModules) { - const res = await db('definitions').insert({ - name: key, - props: defaultModules[key].props, - template: defaultModules[key].template, - }) - _definitions[res.name] = res - } - - content.forEach((path, file) => { - promises.push( - new Promise(async resolve => { - if (!file.dir) { - // Extract the file content as a buffer - const fileContent = await file.async('nodebuffer'); - - // Write the file content to the specified path - await writeFile('./temp/' + path, fileContent); - } else { - await mkdir('./temp/' + path, {recursive: true}) - } - resolve() // After all contents finished.. - }) - ) - }) - - await Promise.all(promises) - - const pagesFile = './temp/site/pages.json' - const collectionsFile = './temp/site/collections.json' - const definitionsFile = './temp/site/definitions.json' - const usersFile = './temp/site/users.json' - const rolesFile = './temp/site/roles.json' - - const pages = JSON.parse(readFileSync(pagesFile)) - const collections = JSON.parse(readFileSync(collectionsFile)) - const definitions = JSON.parse(readFileSync(definitionsFile)) - const users = JSON.parse(readFileSync(usersFile)) - const roles = JSON.parse(readFileSync(rolesFile)) - - await importCollections(collections) - - await importRoles(roles) - await importUsers(users) - - for (let definition of definitions) { - for(let prop of definition.props) { - if(prop.type === 'relation') { - prop.collectionId = _collections[prop.collection]?.id - delete prop.collection - } - } - - if(definition.file) { - const srcFile = './temp/site/definitions/' + basename(definition.file) - const destFile = './definitions/' + basename(definition.file) - definition.file = destFile - - if(!existsSync('./definitions')) { - mkdirSync('./definitions') - } - cpSync(srcFile, destFile) - } + // idMap[itemId] = res2.id + // } + // } + + // for(let collection of await db('collections').query().all()) { + // for(let field of collection.fields) { + // if(field.type === 'relation') { + // field.collectionId = _collections[field.collection].id + // delete field.collection + // } + // } + // await db('collections').update(collection) + + // const contents = await db('contents').query().filter('_type', '=', collection.id).all() + + // for(let item of contents) { + // for(let field of collection.fields) { + // if(field.type === 'relation') { + // if(field.multiple) { + // item[field.slug] = item[field.slug].map(x => idMap[x]) + // } else { + // item[field.slug] = idMap[item[field.slug]] + // } + // } + // } + // await db('contents').update(item) + // } + // } + // } + + // if(file) { + // const zipFile = './uploads/' + file; + + // if(!existsSync('./temp')) { + // await mkdir('./temp') + // } else { + // rmSync('./temp', {force: true, recursive: true}) + // await mkdir('./temp') + // } + + // const zip = new JSZip() + + // const content = await zip.loadAsync(readFileSync(zipFile) , {createFolders: true}) + + // const promises = [] + + // for(let key in defaultModules) { + // const res = await db('definitions').insert({ + // name: key, + // props: defaultModules[key].props, + // template: defaultModules[key].template, + // }) + // _definitions[res.name] = res + // } + + // content.forEach((path, file) => { + // promises.push( + // new Promise(async resolve => { + // if (!file.dir) { + // // Extract the file content as a buffer + // const fileContent = await file.async('nodebuffer'); + + // // Write the file content to the specified path + // await writeFile('./temp/' + path, fileContent); + // } else { + // await mkdir('./temp/' + path, {recursive: true}) + // } + // resolve() // After all contents finished.. + // }) + // ) + // }) + + // await Promise.all(promises) + + // const pagesFile = './temp/site/pages.json' + // const collectionsFile = './temp/site/collections.json' + // const definitionsFile = './temp/site/definitions.json' + // const usersFile = './temp/site/users.json' + // const rolesFile = './temp/site/roles.json' + + // const pages = JSON.parse(readFileSync(pagesFile)) + // const collections = JSON.parse(readFileSync(collectionsFile)) + // const definitions = JSON.parse(readFileSync(definitionsFile)) + // const users = JSON.parse(readFileSync(usersFile)) + // const roles = JSON.parse(readFileSync(rolesFile)) + + // await importCollections(collections) + + // await importRoles(roles) + // await importUsers(users) + + // for (let definition of definitions) { + // for(let prop of definition.props) { + // if(prop.type === 'relation') { + // prop.collectionId = _collections[prop.collection]?.id + // delete prop.collection + // } + // } + + // if(definition.file) { + // const srcFile = './temp/site/definitions/' + basename(definition.file) + // const destFile = './definitions/' + basename(definition.file) + // definition.file = destFile + + // if(!existsSync('./definitions')) { + // mkdirSync('./definitions') + // } + // cpSync(srcFile, destFile) + // } - const res = await db('definitions').insert(definition) + // const res = await db('definitions').insert(definition) - _definitions[res.name] = res - } + // _definitions[res.name] = res + // } - await importPages(pages) + // await importPages(pages) + + // } else { + // // inset default modules - } else { - // inset default modules - for(let key in defaultModules) { - const res = await db('definitions').insert({ - name: key, - props: defaultModules[key].props, - template: defaultModules[key].template, - }) - _definitions[res.name] = res - - } - const mod = await import(`../templates/${template}/index.js`) + // const mod = await import(`../templates/${template}/index.js`) - if(mod.default) { - for (let definition of mod.default.definitions) { - let def = definition - if(definition.file) { - const filePath = `./templates/${template}/definitions/${definition.file}` - const newFilePath = './definitions/' + definition.file - - if(!existsSync('./definitions')) { - mkdirSync('./definitions') - } - await copyFile(filePath, newFilePath) - - def = { - ...def, - file: newFilePath, - ...(await import(join('..', newFilePath))).default - } - - // delete def.load - } - const res = await db('definitions').insert({...def}) - _definitions[res.name] = res - } - - await importRoles(mod.default.roles ?? []); - await importUsers(mod.default.users ?? []); + // if(mod.default) { + // for (let definition of mod.default.definitions) { + // let def = definition + // if(definition.file) { + // const filePath = `./templates/${template}/definitions/${definition.file}` + // const newFilePath = './definitions/' + definition.file + + // if(!existsSync('./definitions')) { + // mkdirSync('./definitions') + // } + // await copyFile(filePath, newFilePath) + + // def = { + // ...def, + // file: newFilePath, + // ...(await import(join('..', newFilePath))).default + // } + + // // delete def.load + // } + // const res = await db('definitions').insert({...def}) + // _definitions[res.name] = res + // } + + // await importRoles(mod.default.roles ?? []); + // await importUsers(mod.default.users ?? []); - await importCollections(mod.default.collections) + // await importCollections(mod.default.collections) - for(let key in _definitions) { - const definition = _definitions[key] - for(let prop of definition.props ?? []) { + // for(let key in _definitions) { + // const definition = _definitions[key] + // for(let prop of definition.props ?? []) { - if(prop.type === 'relation') { - prop.collectionId = _collections[prop.collection]?.id - delete prop.collection - } - } - await db('definitions').update(definition) - } + // if(prop.type === 'relation') { + // prop.collectionId = _collections[prop.collection]?.id + // delete prop.collection + // } + // } + // await db('definitions').update(definition) + // } - await importPages(mod.default.pages) - - } - } - - res.redirect('/admin') -} + // await importPages(mod.default.pages) + // } + // } +// } diff --git a/src/components/Button.js b/src/components/Button.js index fa01d72..74d87eb 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -1,6 +1,18 @@ import { attributes } from "#helpers" -export function Button({href, ghost = false, icon = false, text, color = 'default', block, action, outline = false, size="medium", dataset = {} , type="button"}) { +export function Button({ + href, + ghost = false, + icon = false, + text, + color = 'default', + block, + action, + outline = false, + size = "medium", + dataset = {}, + type = "button" +}) { let tag = href ? 'a' : 'button' let attrs = attributes({ href, diff --git a/src/handlers.js b/src/handlers.js index c2ef0e4..701f335 100644 --- a/src/handlers.js +++ b/src/handlers.js @@ -1,12 +1,11 @@ import ai from "./handlers/ai.js" import collection from "./handlers/collection.js" import content from "./handlers/content.js" -import definition from "./handlers/definition.js" +import block from "./handlers/block.js" import module from './handlers/module.js' import page from "./handlers/page.js" import role from "./handlers/role.js" import settings from "./handlers/settings.js" -import setup from "./handlers/setup.js" import auth from "./handlers/auth.js" import table from "./handlers/table.js" import user from "./handlers/user.js" @@ -14,10 +13,9 @@ import user from "./handlers/user.js" export default { module, page, - definition, + block, collection, content, - setup, ai, user, role, diff --git a/src/handlers/ai/createModule.js b/src/handlers/ai/createModule.js index 338fe26..17b544e 100644 --- a/src/handlers/ai/createModule.js +++ b/src/handlers/ai/createModule.js @@ -79,7 +79,7 @@ export async function createModule(body) { payload.prompt = { template: [template] } - res = await db('definitions').insert(payload) + res = await db('blocks').insert(payload) } else { console.log('something went wrong') diff --git a/src/handlers/ai/updateModule.js b/src/handlers/ai/updateModule.js index 5736159..dda29fb 100644 --- a/src/handlers/ai/updateModule.js +++ b/src/handlers/ai/updateModule.js @@ -1,20 +1,20 @@ import { db } from "#services"; import { generateResponse } from "#helpers"; -function generateUpdateModuleSystemPrompt({ collections, definition }) { +function generateUpdateModuleSystemPrompt({ collections, block }) { return ` You are an expert tasked with updating a Tailwind CSS/Handlebars module. Please follow the specifications provided to ensure compatibility and design continuity. - **Module Details**: - - **Name**: ${definition.name} + - **Name**: ${block.name} - **Previous Version** (for reference, do not execute): - ${JSON.stringify(definition.prompt?.template ?? [], null, 2)} + ${JSON.stringify(block.prompt?.template ?? [], null, 2)} - **Current Template**: - \`${definition.template}\` + \`${block.template}\` - **Properties**: - **Available Props**: - ${JSON.stringify(definition.props, null, 2)} + ${JSON.stringify(block.props, null, 2)} - **Prop Definitions**: - InputProp: { "type": "input", "slug": "string", "label": "string" } - TextareaProp: { "type": "textarea", "slug": "string", "label": "string" } @@ -49,7 +49,7 @@ function generateUpdateModuleSystemPrompt({ collections, definition }) { - **Response Format**: Provide the updated module configuration as follows: { - "name": "${definition.name}", + "name": "${block.name}", "template": "Updated Handlebars/Tailwind template string goes here", "props": 'List any updated props here' | [] // empty array if there is no need to add props } @@ -76,35 +76,35 @@ export async function updateModule(body) { const collections = await db('collections').query().all() - const definition = await db('definitions').query().filter('id', '=', id).first() - const systemPrompt = generateUpdateModuleSystemPrompt({ collections, definition }) + const block = await db('blocks').query().filter('id', '=', id).first() + const systemPrompt = generateUpdateModuleSystemPrompt({ collections, block }) const prompt = generateUpdateModulePrompt({ template }) const payload = await generateResponse(systemPrompt, prompt, image) if (payload.template) { - definition.template = payload.template + block.template = payload.template if (payload.props.length != 0) { - definition.props = [...definition.props, ...payload.props] + block.props = [...block.props, ...payload.props] } - if (definition.prompt) { - if (typeof definition.prompt.template === 'string') { - definition.prompt.template = [definition.prompt.template] + if (block.prompt) { + if (typeof block.prompt.template === 'string') { + block.prompt.template = [block.prompt.template] } - definition.prompt = { - template: [...definition.prompt.template, template] + block.prompt = { + template: [...block.prompt.template, template] } } else { - definition.prompt = { + block.prompt = { template: [prompt] } } - await db('definitions').update(definition) + await db('blocks').update(block) } else { console.log('something went wrong') console.log(payload) diff --git a/src/handlers/definition.js b/src/handlers/block.js similarity index 67% rename from src/handlers/definition.js rename to src/handlers/block.js index bdfcb56..4afd7c2 100644 --- a/src/handlers/definition.js +++ b/src/handlers/block.js @@ -4,7 +4,7 @@ import { FieldForm, FieldTypeSelector } from "../pages/fields.js" export default { async create(body) { body.props ??= [] - await db('definitions').insert(body) + await db('blocks').insert(body) return { redirect: '/admin' @@ -14,21 +14,21 @@ export default { body.settings ??= [] body.contentType ??= [] body.multiple ??= false - await db('definitions').update(body) + await db('blocks').update(body) return { pageReload: true } }, async load(body) { - const res = await db('definitions').query().filter('id', '=', body.id).first() + const res = await db('blocks').query().filter('id', '=', body.id).first() return res }, async getByModuleId(body) { const module = await db('modules').query().filter('id', '=', body.moduleId).first(); - const definition = await db('definitions').query().filter('id', '=', module.definitionId).first() - return definition + const block = await db('blocks').query().filter('id', '=', module.blockId).first() + return block }, async addField(body) { const {id, ...field} = body @@ -36,11 +36,11 @@ export default { if(field.type === 'select') field.items = field.items.split('\n').map(x => x.trim()) - const definition = await db('definitions').query().filter('id', '=', id).first() + const block = await db('blocks').query().filter('id', '=', id).first() - definition.props.push(field) + block.props.push(field) - await db('definitions').update(definition) + await db('blocks').update(block) return { pageReload: true @@ -48,29 +48,29 @@ export default { }, async setField(body) { const {id, ...field} = body - const definition = await db('definitions').query().filter('id', '=', id).first() + const block = await db('blocks').query().filter('id', '=', id).first() if(field.type === 'select') field.items = field.items.split('\n').map(x => x.trim()) - definition.props = definition.props.map(x => { + block.props = block.props.map(x => { if(x.slug === field.slug) { return field } return x }) - const res = await db('definitions').update(definition) + const res = await db('blocks').update(block) return { pageReload: true } }, async removeField(body) { - const original = await db('definitions').query().filter('id', '=', body.id).first() + const original = await db('blocks').query().filter('id', '=', body.id).first() original.props = original.props.filter(x => x.slug !== body.slug) - await db('definitions').update(original) + await db('blocks').update(original) }, async getFieldTypeSelector(body) { @@ -87,8 +87,8 @@ export default { return FieldForm({mode, collections, handler, type, id}) }, async delete(body) { - await db('definitions').remove(body.id) - const modules = await db('modules').query().filter('definitionId', '=', body.id).all() + await db('blocks').remove(body.id) + const modules = await db('modules').query().filter('blockId', '=', body.id).all() for(let module of modules) { await db('modules').remove(module.id) diff --git a/src/handlers/module.js b/src/handlers/module.js index be4a2c9..f339e92 100644 --- a/src/handlers/module.js +++ b/src/handlers/module.js @@ -2,7 +2,7 @@ import { db } from "#services" import { join } from 'path' import { html } from "svelite-html" import { Form } from "#components" -import { FieldInput } from "../pages/collections.js" +import { FieldInput } from "../pages/field-input.js" function DynamicFieldInput(field, fields, linked, module, collections, functions = {}) { function getLinkedText(key) { @@ -88,7 +88,7 @@ function DynamicFieldInput(field, fields, linked, module, collections, functions return FieldInput(options, collections, functions) } -function sidebarModuleSettings(definition, module, collection, collections, functions) { +function sidebarModuleSettings(block, module, collection, collections, functions) { const fields = [] fields.push({slug: 'settings.logo', label: 'Site\'s Logo', type: 'file', multiple: false, file_type: 'image'}) fields.push({slug: 'settings.favicon', label: 'Site\'s Favicon', type: 'file', multiple: false, file_type: 'image'}) @@ -112,7 +112,7 @@ function sidebarModuleSettings(definition, module, collection, collections, func return html`
- ${definition.props?.length ? Form({ + ${block.props?.length ? Form({ name: 'module-settings', cancelAction: 'close-module-settings', handler: 'module.saveSettings', @@ -120,7 +120,7 @@ function sidebarModuleSettings(definition, module, collection, collections, func fields: [ ``, ``, - (definition.props ?? []).map(prop => DynamicFieldInput(prop, fields, module.links?.[prop.slug], module, collections, functions)).join('') + (block.props ?? []).filter(x => !x.hidden).map(prop => DynamicFieldInput(prop, fields, module.links?.[prop.slug], module, collections, functions)).join('') ], cancelAction: 'close-module-settings' }): ` @@ -146,7 +146,7 @@ export default { async create(body) { await db('modules').insert({ moduleId: body.moduleId, - definitionId: body.definitionId, + blockId: body.blockId, order: body.order, cols: 12, props: {}, @@ -160,11 +160,11 @@ export default { await db('modules').remove(body.id) }, async createSection(body) { - const definitions = await db('definitions').query().all(); + const blocks = await db('blocks').query().all(); const mod = await db('modules').insert({ pageId: body.pageId, - definitionId: definitions.find(x => x.name === 'Section').id, + blockId: blocks.find(x => x.name === 'Section').id, order: body.order, props: { fullWidth: false, @@ -174,7 +174,7 @@ export default { const mod2 = await db('modules').insert({ moduleId: mod.id, - definitionId: definitions.find(x => x.name === 'Columns').id, + blockId: blocks.find(x => x.name === 'Columns').id, order: 0, props: { cols: [], @@ -187,7 +187,7 @@ export default { const moduleId = body.id const module = await db('modules').query().filter('id', '=', moduleId).first(); const page = await getPageFromModule(module); - const definition = await db('definitions').query().filter('id', '=', module.definitionId).first(); + const block = await db('blocks').query().filter('id', '=', module.blockId).first(); const collections = await db('collections').query().all() if(page?.collectionId) { @@ -200,7 +200,7 @@ export default { } - const res = sidebarModuleSettings(definition, module, collection, collections, functions) + const res = sidebarModuleSettings(block, module, collection, collections, functions) return res; } @@ -208,7 +208,7 @@ export default { } - const res = sidebarModuleSettings(definition, module, null, collections, functions) + const res = sidebarModuleSettings(block, module, null, collections, functions) return res }, async loadSettings(body) { @@ -351,7 +351,7 @@ export default { await db('settings').update(settings) } - await db('modules').update({...original, props}) + await db('modules').update({...original, props: {...original.props, ...props}}) }, async updateOrders(body) { diff --git a/src/handlers/setup.js b/src/handlers/setup.js deleted file mode 100644 index ec6990e..0000000 --- a/src/handlers/setup.js +++ /dev/null @@ -1,7 +0,0 @@ -import { db } from "#services" - -export default { - async setup(body) { - - } -} \ No newline at end of file diff --git a/src/middlewares/context.js b/src/middlewares/context.js index 27d1685..af5c575 100644 --- a/src/middlewares/context.js +++ b/src/middlewares/context.js @@ -5,7 +5,7 @@ import path from 'node:path' import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); - + export function contextMiddleware({functions}) { return async (req, res, next) => { console.log(`[${req.method}] ${req.url}`) @@ -14,7 +14,7 @@ export function contextMiddleware({functions}) { if(!user) { res.cookie('userId', '', {httpOnly: true}) - if(req.query.mode == 'edit' || req.query.mode == 'preview') { + if(req.url.startsWith('/admin')) { return res.redirect('/login') } } @@ -49,7 +49,7 @@ export function contextMiddleware({functions}) { req.url = req.url.slice(0, -1) } - const blocks = await db('definitions').query().all() + const blocks = await db('blocks').query().all() // Check if initialized if(blocks.length == 0) { diff --git a/src/page.js b/src/page.js index a866c11..4d41058 100644 --- a/src/page.js +++ b/src/page.js @@ -18,44 +18,6 @@ import { RoleCreatePage, RoleListPage, RoleUpdatePage } from './pages/roles.js'; import { constants } from 'http2'; import { FunctionListPage } from './pages/functions.js'; -let definitions = {} - -async function loadModuleDefinitions() { - const defs = await db('definitions').query().all() - definitions = {} - for (let definition of defs) { - if (definition.file) { - try { - const module = await import(join('..', definition.file)).then(res => { - return res.default - }) - - definitions[definition.id].load = module.load - definitions[definition.id].actions = module.actions - } catch (err) { - definitions[definition.id] = definition - } - } else { - definitions[definition.id] = definition - } - - if (typeof definitions[definition.id].template === 'string') { - definitions[definition.id].template = hbs.compile(definitions[definition.id].template) - } - } -} - -export async function handleModuleAction({ module, method, body }) { - const definition = definitions[module.definitionId] - let res; - - if (definition.actions && definition.actions[method]) { - res = await definition.actions[method](body) - } - - return res; -} - function DashboardPage() { return Page({ title: 'Dashboard', @@ -141,10 +103,6 @@ export async function renderBody(body, { props, mode, url, view, context, params }, ].filter(Boolean) - await loadModuleDefinitions() - - // let {page} = await getPage(url.split('?')[0]) - // if (!page && view === 'iframe') view = 'pages.create' if(mode === 'edit' && !view) view = 'dashboard' let pageContent; @@ -231,9 +189,15 @@ export async function renderBody(body, { props, mode, url, view, context, params params } + const renderedModules = (await Promise.all(body.map(x => renderModule(x, { props, mode, permissions, request })))).join('') + + if(mode === 'view') { + return renderedModules + } + return `
- ${(await Promise.all(body.map(x => renderModule(x, { props, mode, definitions, permissions, request })))).join('')} + ${renderedModules} ${previewContent}
` diff --git a/src/pages/blocks.js b/src/pages/blocks.js index 61437df..b01599b 100644 --- a/src/pages/blocks.js +++ b/src/pages/blocks.js @@ -12,7 +12,7 @@ export async function BlockCreatePage({permissions}) { title: `Create New Block`, body: [ Form({ - handler: 'definition.create', + handler: 'block.create', fields: [ Input({label: 'Name', placeholder: 'Enter Block name', name: 'name'}), Textarea({label: 'Template', rows: 15, placeholder: 'Enter Block template (Handlebars)', name: 'template'}), @@ -26,7 +26,7 @@ export async function BlockCreatePage({permissions}) { } export async function BlockUpdatePage({query, permissions}) { - const block = await db('definitions').query().filter('id', '=', query.id).first() + const block = await db('blocks').query().filter('id', '=', query.id).first() return [ Page({ @@ -39,9 +39,9 @@ export async function BlockUpdatePage({query, permissions}) { title: `Update Block (${block.name})`, body: Stack({vertical: true, gap: 'lg'}, [ Form({ - load: 'definition.load', + load: 'block.load', id: block.id, - handler: 'definition.update', + handler: 'block.update', fields: [ ``, Input({label: 'Name', placeholder: 'Enter Module name', name: 'name'}), diff --git a/src/pages/collections.js b/src/pages/collections.js index 977cc6f..2c8bec8 100644 --- a/src/pages/collections.js +++ b/src/pages/collections.js @@ -3,6 +3,7 @@ import { Button, Card, CardBody, Checkbox, EmptyTable, File, Form, Input, Label, import { CollectionDataTable, DataTable } from "./dataTable.js"; import { FieldModal, FieldsList } from "./fields.js"; import { getText, getValue, isSelected } from "#helpers"; +import { FieldInput } from "./field-input.js"; export function CollectionForm({id, fields, handler, cancelAction, onSubmit}) { let load; @@ -35,111 +36,6 @@ export function CollectionForm({id, fields, handler, cancelAction, onSubmit}) { ]) } -function Relation(field) { - return Label({ - symbolic: true, - text: field.label, - inline: false, - body: Stack({ vertical: true, align: 'start'}, [ - Button({ - text: 'Choose', - color: 'primary', - action: 'open-relation-modal', - dataset: { - 'collection-id': field.collectionId, - 'modal-name': 'relation-field-modal', - 'field-name': field.name, - 'field-multiple': field.multiple ? true : false - } - }), - ``, - '
', - ]), - }) -} - -function RichText(field) { - return Label({ - symbolic: true, - text: field.label, - body: `
` - }) -} - -export function FieldInput(field, collections = [], functions = {}) { - let options = { - name: field.slug, - label: field.label, - placeholder: field.placeholder ?? 'Enter ' + field.label - } - if(field.type === 'select') { - options.items = field.items ?? [] - options.placeholder = field.placeholder ?? 'Choose ' + field.label - options.multiple = field.multiple - } - - if(field.type === 'relation') { - options.collectionId = field.collectionId - options.multiple = field.multiple - } - - if(field.type === 'file') { - options.type = field.file_type - options.multiple = field.multiple - options.size = field.size - } - - if(field.type === 'input') { - options.type = field.input_type - } - - function CollectionField({name, label, placeholder}) { - return Select({ - name, - placeholder: 'Choose Collection', - label, - items: collections.map(x => ({text: x.name, value: x.id})) - }) - } - - function SelectFieldType({name, label, placeholder, items, multiple}) { - if(multiple) { - return Label({ - text: label, - body: items.map(item => Checkbox({multiple: true, name, checked: isSelected(item), multiple: true, label: getText(item), value: getValue(item)})) - }) - } - - return Select({name, placeholder, label, items}) - } - - function FunctionField({name, label, placeholder}) { - return Select({ - name, - placeholder: 'Choose a Function', - label, - items: Object.keys(functions).map(x => ({value: x, text: functions[x].name})) - }) - } - - const inputs = { - input: Input, - select: SelectFieldType, - textarea: Textarea, - checkbox: Checkbox, - file: File, - relation: Relation, - 'rich-text': RichText, - collection: CollectionField, - function: FunctionField, - hidden: (options) => Label({text: options.label}) - } - - if(inputs[field.type]) { - return inputs[field.type](options) - } -} - export function RelationFieldModal() { return Modal({ name: 'relation-field-modal', diff --git a/src/pages/field-input.js b/src/pages/field-input.js new file mode 100644 index 0000000..6ef7f3c --- /dev/null +++ b/src/pages/field-input.js @@ -0,0 +1,113 @@ +import { Button, Checkbox, File, Input, Label, Select, Stack, Textarea } from "#components" +import { getText, getValue, isSelected } from "#helpers" + +function Relation(field) { + return Label({ + symbolic: true, + text: field.label, + inline: false, + body: Stack({ vertical: true, align: 'start'}, [ + Button({ + text: 'Choose', + color: 'primary', + action: 'open-relation-modal', + dataset: { + 'collection-id': field.collectionId, + 'modal-name': 'relation-field-modal', + 'field-name': field.name, + 'field-multiple': field.multiple ? true : false + } + }), + ``, + '
', + ]), + }) +} + +function RichText(field) { + return Label({ + symbolic: true, + text: field.label, + body: `
` + }) +} + +export function FieldInput(field, collections = [], functions = {}) { + let options = { + name: field.slug, + label: field.label, + placeholder: field.placeholder ?? 'Enter ' + field.label + } + if(field.type === 'select') { + options.items = field.items ?? [] + options.placeholder = field.placeholder ?? 'Choose ' + field.label + options.multiple = field.multiple + } + + if(field.type === 'relation') { + options.collectionId = field.collectionId + options.multiple = field.multiple + } + + if(field.type === 'file') { + options.type = field.file_type + options.multiple = field.multiple + options.size = field.size + } + + if(field.type === 'input') { + options.type = field.input_type + } + + function CollectionField({name, label, placeholder}) { + return Select({ + name, + placeholder: 'Choose Collection', + label, + items: collections.map(x => ({text: x.name, value: x.id})) + }) + } + + function SelectFieldType({name, label, placeholder, items, multiple}) { + if(multiple) { + return Label({ + text: label, + body: items.map(item => Checkbox({multiple: true, name, checked: isSelected(item), multiple: true, label: getText(item), value: getValue(item)})) + }) + } + + return Select({name, placeholder, label, items}) + } + + function FunctionField({name, label, placeholder}) { + return Select({ + name, + placeholder: 'Choose a Function', + label, + items: Object.keys(functions).map(x => ({value: x, text: functions[x].name})) + }) + } + + function DateInput(options) { + return Input({...options, type: 'datetime'}) + } + + const inputs = { + input: Input, + select: SelectFieldType, + textarea: Textarea, + checkbox: Checkbox, + file: File, + relation: Relation, + 'rich-text': RichText, + collection: CollectionField, + function: FunctionField, + color: Input, + date: DateInput, + hidden: (options) => Label({text: options.label}) + } + + if(inputs[field.type]) { + return inputs[field.type](options) + } +} \ No newline at end of file diff --git a/src/pages/fields.js b/src/pages/fields.js index 3f07dbb..31ba84f 100644 --- a/src/pages/fields.js +++ b/src/pages/fields.js @@ -11,6 +11,8 @@ const fieldTypes = [ { text: 'Relation', value: 'relation' }, { text: 'Collection', value: 'collection', hidden: true }, { text: 'Function', value: 'function' }, + { text: 'Color', value: 'color' }, + { text: 'Date Time', value: 'date' }, ] function fieldTypeText(key) { diff --git a/src/pages/roles.js b/src/pages/roles.js index d4a561f..1e55af3 100644 --- a/src/pages/roles.js +++ b/src/pages/roles.js @@ -1,7 +1,7 @@ import { Button, Form, Page } from "#components"; import { roleFields, RolesDataTable } from "../handlers/role.js"; import { getUrl } from "#helpers"; -import { FieldInput } from "./collections.js"; +import { FieldInput } from "./field-input.js"; export async function RoleListPage() { return Page({ diff --git a/src/pages/settings.js b/src/pages/settings.js index 9dad949..312f784 100644 --- a/src/pages/settings.js +++ b/src/pages/settings.js @@ -1,6 +1,6 @@ import { Button, Card, CardBody, File, Form, Input, Label, Page, Textarea } from "#components"; import { db } from "#services"; -import { FieldInput } from "./collections.js"; +import { FieldInput } from "./field-input.js"; export async function SettingsGeneralPage() { return Page({ diff --git a/src/pages/setup.js b/src/pages/setup.js index 58033fd..0b31636 100644 --- a/src/pages/setup.js +++ b/src/pages/setup.js @@ -12,21 +12,22 @@ export function SetupPage({templates}) { ${Form({ handler: 'setup.setup', fields: [ + `

Setup CMS

`, Input({ name: 'password', placeholder: 'Enter Admin Password', label: 'Admin Password' }), - Select({ - items: templates, - name: 'template', - placeholder: 'Choose a template', - label: 'Template' - }), - File({ - name: 'file', - label: 'Import zip' - }) + // Select({ + // items: templates, + // name: 'template', + // placeholder: 'Choose a template', + // label: 'Template' + // }), + // File({ + // name: 'file', + // label: 'Import zip' + // }) ] })}
` diff --git a/src/pages/sidebar.js b/src/pages/sidebar.js index 3a9bf76..07f07d2 100644 --- a/src/pages/sidebar.js +++ b/src/pages/sidebar.js @@ -4,8 +4,7 @@ import { html } from "svelite-html" import { getPage, getPageSlug, getUrl } from "#helpers" async function sidebarBlocks({ query, permissions }) { - // const modules = Object.keys(definitions).map(key => definitions[key]).filter(x => !['Section', 'Columns', 'RichText'].includes(x.name)) - const blocks = (await db('definitions').query().all()).filter(x => !['Section', 'Columns', 'RichText'].includes(x.name)) + const blocks = (await db('blocks').query().all()).filter(x => !['Section', 'Columns', 'RichText'].includes(x.name)) return `
${blocks.map(x => ` @@ -146,6 +145,8 @@ export async function Sidebar({query, url, view, permissions, config, context}) async function sidebarFooter() { const profile = context.user + if(!profile) return '' + return ` ${profile.profile ? `Avatar` : `${profile.name[0].toUpperCase()}`} @@ -221,6 +222,8 @@ export async function Sidebar({query, url, view, permissions, config, context}) return '' } + if(!context.user) return ''; + return `
${sidebarHeader()} @@ -248,16 +251,16 @@ export async function Sidebar({query, url, view, permissions, config, context}) } async function BlockList() { - const blocks = (await db('definitions').query().all()).filter(x => !['Section', 'Columns'].includes(x.name)) + const blocks = (await db('blocks').query().all()).filter(x => !['Section', 'Columns'].includes(x.name)) return ` -
+
Drag and drop blocks to page! - ${blocks.map(x => `
+ ${blocks.map(x => `
${x.name}
`).join('')}
diff --git a/src/pages/users.js b/src/pages/users.js index df9b4b6..5204866 100644 --- a/src/pages/users.js +++ b/src/pages/users.js @@ -2,7 +2,7 @@ import { Button, Form, Page } from "#components"; import { db } from "#services"; import { userFields, UsersDataTable } from "../handlers/user.js"; import { getUrl } from "#helpers"; -import { FieldInput } from "./collections.js"; +import { FieldInput } from "./field-input.js"; export async function UserListPage() { return Page({ diff --git a/src/renderModule.js b/src/renderModule.js index 992de36..9cfd144 100644 --- a/src/renderModule.js +++ b/src/renderModule.js @@ -1,4 +1,5 @@ import { html } from 'svelite-html'; +import hbs from 'handlebars' import { db } from '#services'; import { getDataTableItems } from './pages/dataTable.js'; @@ -58,35 +59,35 @@ export async function loadRelationFieldType(value, field, depth = 1) { return value } -export async function renderModule(module, {props, mode, definitions, permissions, request}) { +export async function renderModule(module, {props, mode, permissions, request}) { module.props ??= {} const settings = await db('settings').query().first() ?? {} - let definition = definitions[module.definitionId] + let block = await db('blocks').query().filter('id', '=', module.blockId).first() - async function loadModuleProps(definition, module) { - for(let item of definition.props ?? []) { + async function loadModuleProps(block, module) { + for(let item of block.props ?? []) { if(item.type === 'slot') { let result = '' const modules = await db('modules').query().filter('moduleId', '=', module.id).all().then(res => res.sort((a, b) => a.order > b.order ? 1 : -1)) let index = 0 for(let mod of modules) { - if(definition.name === 'Columns') { + if(block.name === 'Columns') { const cols = mod.cols ?? 12 result += `
- ${await renderModule(mod, { props, request, mode, definitions, permissions})} + ${await renderModule(mod, { props, request, mode, permissions})}
` } else { - result += await renderModule(mod, { props, request, mode, definitions, permissions}) + result += await renderModule(mod, { props, request, mode, permissions}) } index = index + 1; } if(result) { - if(definition.name === 'Columns' || definition.name === 'Section') { + if(block.name === 'Columns' || block.name === 'Section') { module.props[item.slug] = result } else { @@ -117,14 +118,6 @@ export async function renderModule(module, {props, mode, definitions, permission } } } - - if(definition.load) { - module.props = { - ...module.props, - ...(await definition.load({request, db, definition, module})) - - } - } if (module.links) { for (let key in module.links) { @@ -141,15 +134,15 @@ export async function renderModule(module, {props, mode, definitions, permission } } - await loadModuleProps(definition, module) + await loadModuleProps(block, module) let rendered; try { - if(definition.name == 'Columns') { + if(block.name == 'Columns') { rendered = `
${module.props.content}
`; } else { - rendered = definition.template({settings, ...module.props}); + rendered = hbs.compile(block.template)({settings, ...module.props}); } } catch(err) { rendered = 'Something went wrong: ' + err.message @@ -170,17 +163,19 @@ export async function renderModule(module, {props, mode, definitions, permission const iconDelete = '' const iconResizeSection = '' const iconAi = '' + const iconSettings = `` - if(definition.name === 'Section') { + if(block.name === 'Section') { const icon = module.props.fullWidth ? '' : '' startActions = [ - // ModuleAction({icon: settings, action: 'open-module-settings'}), ModuleAction({icon: iconAdd, action: 'add-section'}), + ModuleAction({icon: iconSettings, action: 'open-module-settings'}), ModuleAction({icon, action: 'toggle-full-width'}), + // ModuleAction({icon: 'color', action: 'toggle-section-background-menu'}), ] endActions = [ ModuleAction({icon: iconDelete, action: 'open-delete-module-confirm'}), @@ -191,10 +186,10 @@ export async function renderModule(module, {props, mode, definitions, permission ModuleAction({ icon: [ '', - definition.name === 'Section' ? '' : '
' + definition.name + '
' + block.name === 'Section' ? '' : '
' + block.name + '
' ].join('') }), - !['Section', 'Columns', 'RichText'].includes(definition.name) ? ModuleAction({icon: iconAi, action: 'open-edit-module-ai', id: definition.id}) : '', + !['Section', 'Columns', 'RichText'].includes(block.name) ? ModuleAction({icon: iconAi, action: 'open-edit-module-ai', id: block.id}) : '', ] endActions = [ @@ -215,6 +210,10 @@ export async function renderModule(module, {props, mode, definitions, permission
` + + if(mode === 'view') { + return rendered + } if(mode === 'preview' && permissions) { @@ -223,20 +222,20 @@ export async function renderModule(module, {props, mode, definitions, permission ` } - if(definition.name === 'Columns') + if(block.name === 'Columns') { return `${rendered}` } let sectionResizer = '' - if(definition.name === 'Section') { + if(block.name === 'Section') { sectionResizer += `
` sectionResizer += `
` } return ` -
+
${sectionResizer} ${rendered}