feat: Complete HTML-first architecture implementation with API integration

- Replace value field with html_content for direct HTML storage
- Add original_template field for style detection preservation
- Remove all markdown processing from injector (delete markdown.go)
- Fix critical content extraction/injection bugs in engine
- Add missing UpdateContent PUT handler for content persistence
- Fix API client field names and add updateContent() method
- Resolve content type validation (only allow text/link types)
- Add UUID-based ID generation to prevent collisions
- Complete first-pass processing workflow for unprocessed elements
- Verify end-to-end: Enhancement → Database → API → Editor → Persistence

All 37 files updated for HTML-first content management system.
Phase 3a implementation complete and production ready.
This commit is contained in:
2025-09-20 16:42:00 +02:00
parent bb5ea6f873
commit 2177055c76
37 changed files with 1189 additions and 737 deletions

31
lib/package-lock.json generated
View File

@@ -8,10 +8,6 @@
"name": "@insertr/lib",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"marked": "^16.2.1",
"turndown": "^7.2.1"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.0",
"@rollup/plugin-terser": "^0.4.0",
@@ -68,12 +64,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@mixmark-io/domino": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
"integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==",
"license": "BSD-2-Clause"
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
@@ -264,18 +254,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/marked": {
"version": "16.2.1",
"resolved": "https://registry.npmjs.org/marked/-/marked-16.2.1.tgz",
"integrity": "sha512-r3UrXED9lMlHF97jJByry90cwrZBBvZmjG1L68oYfuPMW+uDTnuMbyJDymCWwbTE+f+3LhpNDKfpR3a3saFyjA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -434,15 +412,6 @@
"engines": {
"node": ">=10"
}
},
"node_modules/turndown": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.1.tgz",
"integrity": "sha512-7YiPJw6rLClQL3oUKN3KgMaXeJJ2lAyZItclgKDurqnH61so4k4IH/qwmMva0zpuJc/FhRExBBnk7EbeFANlgQ==",
"license": "MIT",
"dependencies": {
"@mixmark-io/domino": "^2.2.0"
}
}
}
}

View File

@@ -33,7 +33,7 @@ export class ApiClient {
try {
const payload = {
html_markup: htmlMarkup, // Always send HTML markup - server extracts ID or generates new one
value: content,
html_content: content,
type: type,
file_path: this.getCurrentFilePath() // Always include file path for consistent ID generation
};
@@ -66,6 +66,40 @@ export class ApiClient {
}
}
async updateContent(contentId, content) {
try {
const payload = {
html_content: content
};
const response = await fetch(`${this.baseUrl}/${contentId}?site_id=${this.siteId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.getAuthToken()}`
},
body: JSON.stringify(payload)
});
if (response.ok) {
const result = await response.json();
console.log(`✅ Content updated: ${contentId}`);
return result;
} else {
console.warn(`⚠️ Update failed (${response.status}): ${contentId}`);
return false;
}
} catch (error) {
if (error.name === 'TypeError' && error.message.includes('fetch')) {
console.warn(`🔌 API Server not reachable at ${this.baseUrl}`);
console.warn('💡 Start full-stack development: just dev');
} else {
console.error('Failed to update content:', contentId, error);
}
return false;
}
}
async getContentVersions(contentId) {
try {
const response = await fetch(`${this.baseUrl}/${contentId}/versions?site_id=${this.siteId}`);

View File

@@ -134,24 +134,13 @@ export class InsertrCore {
detectContentType(element) {
const tag = element.tagName.toLowerCase();
if (element.classList.contains('insertr-group')) {
return 'group';
// Only return database-valid types: 'text' or 'link'
if (tag === 'a' || tag === 'button') {
return 'link';
}
switch (tag) {
case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
return 'text';
case 'p':
return 'textarea';
case 'a': case 'button':
return 'link';
case 'div': case 'section':
return 'text';
case 'span':
return 'text';
default:
return 'text';
}
// All other elements are text content
return 'text';
}
// Get all elements with their metadata, including group elements