diff options
Diffstat (limited to 'assets/admin/lib/component-tool.js')
| -rw-r--r-- | assets/admin/lib/component-tool.js | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/assets/admin/lib/component-tool.js b/assets/admin/lib/component-tool.js new file mode 100644 index 0000000..3217667 --- /dev/null +++ b/assets/admin/lib/component-tool.js @@ -0,0 +1,133 @@ +/** + * EditorJS block tool for embedding web components with props. + * + * Saved data format: + * { + * "name": "site-greeting", + * "props": { "name": "visitor", "theme": "dark" } + * } + * + * Renders in HTML as: <site-greeting name="visitor" theme="dark"></site-greeting> + */ +export default class ComponentTool { + static get toolbox() { + return { + title: 'Component', + icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" stroke-linecap="round" d="M7 8l-4 4 4 4M17 8l4 4-4 4M14 4l-4 16"/></svg>', + }; + } + + constructor({ data }) { + this.data = { + name: data.name || '', + props: data.props || {}, + }; + this.wrapper = null; + } + + render() { + this.wrapper = document.createElement('div'); + this.wrapper.classList.add('component-tool'); + this._renderUI(); + return this.wrapper; + } + + _renderUI() { + this.wrapper.innerHTML = ''; + + // Component name input + const nameLabel = document.createElement('label'); + nameLabel.textContent = 'Component tag name'; + nameLabel.style.cssText = 'display:block;font-size:12px;color:#888;margin-bottom:2px;'; + const nameInput = document.createElement('input'); + nameInput.type = 'text'; + nameInput.placeholder = 'e.g. site-greeting'; + nameInput.value = this.data.name; + nameInput.style.cssText = 'width:100%;padding:6px 8px;border:1px solid #ccc;border-radius:4px;font-family:monospace;margin-bottom:8px;'; + nameInput.addEventListener('input', () => { this.data.name = nameInput.value; }); + + // Props section + const propsLabel = document.createElement('label'); + propsLabel.textContent = 'Props'; + propsLabel.style.cssText = 'display:block;font-size:12px;color:#888;margin-bottom:2px;'; + + const propsContainer = document.createElement('div'); + propsContainer.classList.add('props-container'); + + const addBtn = document.createElement('button'); + addBtn.type = 'button'; + addBtn.textContent = '+ Add prop'; + addBtn.style.cssText = 'padding:4px 10px;font-size:12px;border:1px solid #ccc;border-radius:4px;background:#f5f5f5;cursor:pointer;margin-top:4px;'; + addBtn.addEventListener('click', () => { + this.data.props[''] = ''; + this._renderProps(propsContainer); + }); + + this.wrapper.append(nameLabel, nameInput, propsLabel, propsContainer, addBtn); + this._renderProps(propsContainer); + } + + _renderProps(container) { + container.innerHTML = ''; + const entries = Object.entries(this.data.props); + entries.forEach(([key, value], i) => { + const row = document.createElement('div'); + row.style.cssText = 'display:flex;gap:4px;margin-bottom:4px;'; + + const kInput = document.createElement('input'); + kInput.type = 'text'; + kInput.placeholder = 'key'; + kInput.value = key; + kInput.style.cssText = 'flex:1;padding:4px 6px;border:1px solid #ccc;border-radius:4px;font-family:monospace;'; + + const vInput = document.createElement('input'); + vInput.type = 'text'; + vInput.placeholder = 'value'; + vInput.value = value; + vInput.style.cssText = 'flex:2;padding:4px 6px;border:1px solid #ccc;border-radius:4px;'; + + const removeBtn = document.createElement('button'); + removeBtn.type = 'button'; + removeBtn.textContent = '×'; + removeBtn.style.cssText = 'padding:4px 8px;border:1px solid #ccc;border-radius:4px;background:#f5f5f5;cursor:pointer;'; + + // Rebuild props object on change + const updateProps = () => { + const newProps = {}; + container.querySelectorAll('div').forEach((r) => { + const inputs = r.querySelectorAll('input'); + if (inputs[0] && inputs[0].value) { + newProps[inputs[0].value] = inputs[1] ? inputs[1].value : ''; + } + }); + this.data.props = newProps; + }; + + kInput.addEventListener('input', updateProps); + vInput.addEventListener('input', updateProps); + removeBtn.addEventListener('click', () => { + row.remove(); + updateProps(); + }); + + row.append(kInput, vInput, removeBtn); + container.append(row); + }); + } + + save() { + // Clean out empty-key props + const cleaned = {}; + for (const [k, v] of Object.entries(this.data.props)) { + if (k.trim()) cleaned[k.trim()] = v; + } + return { + name: this.data.name.trim(), + props: cleaned, + }; + } + + validate(savedData) { + return savedData.name.trim() !== ''; + } +} |
