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: `