diff --git a/cmd/enhance.go b/cmd/enhance.go index d448567..f0ff5b9 100644 --- a/cmd/enhance.go +++ b/cmd/enhance.go @@ -10,6 +10,7 @@ import ( "github.com/insertr/insertr/internal/content" "github.com/insertr/insertr/internal/db" + "github.com/insertr/insertr/internal/engine" ) var enhanceCmd = &cobra.Command{ @@ -49,7 +50,7 @@ func runEnhance(cmd *cobra.Command, args []string) { outputDir := viper.GetString("cli.output") // Create content client - var client content.ContentClient + var client engine.ContentClient if apiURL != "" { fmt.Printf("🌐 Using content API: %s\n", apiURL) client = content.NewHTTPClient(apiURL, apiKey) diff --git a/internal/content/client.go b/internal/content/client.go index 673d21e..a44ab59 100644 --- a/internal/content/client.go +++ b/internal/content/client.go @@ -8,6 +8,8 @@ import ( "net/url" "strings" "time" + + "github.com/insertr/insertr/internal/engine" ) // HTTPClient implements ContentClient for HTTP API access @@ -29,7 +31,7 @@ func NewHTTPClient(baseURL, apiKey string) *HTTPClient { } // GetContent fetches a single content item by ID -func (c *HTTPClient) GetContent(siteID, contentID string) (*ContentItem, error) { +func (c *HTTPClient) GetContent(siteID, contentID string) (*engine.ContentItem, error) { url := fmt.Sprintf("%s/api/content/%s?site_id=%s", c.BaseURL, contentID, siteID) req, err := http.NewRequest("GET", url, nil) @@ -60,7 +62,7 @@ func (c *HTTPClient) GetContent(siteID, contentID string) (*ContentItem, error) return nil, fmt.Errorf("reading response: %w", err) } - var item ContentItem + var item engine.ContentItem if err := json.Unmarshal(body, &item); err != nil { return nil, fmt.Errorf("parsing response: %w", err) } @@ -69,9 +71,9 @@ func (c *HTTPClient) GetContent(siteID, contentID string) (*ContentItem, error) } // GetBulkContent fetches multiple content items by IDs -func (c *HTTPClient) GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error) { +func (c *HTTPClient) GetBulkContent(siteID string, contentIDs []string) (map[string]engine.ContentItem, error) { if len(contentIDs) == 0 { - return make(map[string]ContentItem), nil + return make(map[string]engine.ContentItem), nil } // Build query parameters @@ -107,13 +109,13 @@ func (c *HTTPClient) GetBulkContent(siteID string, contentIDs []string) (map[str return nil, fmt.Errorf("reading response: %w", err) } - var response ContentResponse + var response engine.ContentResponse if err := json.Unmarshal(body, &response); err != nil { return nil, fmt.Errorf("parsing response: %w", err) } // Convert slice to map for easy lookup - result := make(map[string]ContentItem) + result := make(map[string]engine.ContentItem) for _, item := range response.Content { result[item.ID] = item } @@ -122,7 +124,7 @@ func (c *HTTPClient) GetBulkContent(siteID string, contentIDs []string) (map[str } // GetAllContent fetches all content for a site -func (c *HTTPClient) GetAllContent(siteID string) (map[string]ContentItem, error) { +func (c *HTTPClient) GetAllContent(siteID string) (map[string]engine.ContentItem, error) { url := fmt.Sprintf("%s/api/content?site_id=%s", c.BaseURL, siteID) req, err := http.NewRequest("GET", url, nil) @@ -149,13 +151,13 @@ func (c *HTTPClient) GetAllContent(siteID string) (map[string]ContentItem, error return nil, fmt.Errorf("reading response: %w", err) } - var response ContentResponse + var response engine.ContentResponse if err := json.Unmarshal(body, &response); err != nil { return nil, fmt.Errorf("parsing response: %w", err) } // Convert slice to map for easy lookup - result := make(map[string]ContentItem) + result := make(map[string]engine.ContentItem) for _, item := range response.Content { result[item.ID] = item } diff --git a/internal/content/database.go b/internal/content/database.go index 4f2003d..09b8c51 100644 --- a/internal/content/database.go +++ b/internal/content/database.go @@ -9,6 +9,7 @@ import ( "github.com/insertr/insertr/internal/db" "github.com/insertr/insertr/internal/db/postgresql" "github.com/insertr/insertr/internal/db/sqlite" + "github.com/insertr/insertr/internal/engine" ) // DatabaseClient implements ContentClient for direct database access @@ -24,7 +25,7 @@ func NewDatabaseClient(database *db.Database) *DatabaseClient { } // GetContent fetches a single content item by ID -func (d *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, error) { +func (d *DatabaseClient) GetContent(siteID, contentID string) (*engine.ContentItem, error) { ctx := context.Background() var content interface{} var err error @@ -56,9 +57,9 @@ func (d *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, err } // GetBulkContent fetches multiple content items by IDs -func (d *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error) { +func (d *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map[string]engine.ContentItem, error) { if len(contentIDs) == 0 { - return make(map[string]ContentItem), nil + return make(map[string]engine.ContentItem), nil } ctx := context.Background() @@ -87,7 +88,7 @@ func (d *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map items := d.convertToContentItemList(dbContent) // Convert slice to map for easy lookup - result := make(map[string]ContentItem) + result := make(map[string]engine.ContentItem) for _, item := range items { result[item.ID] = item } @@ -96,7 +97,7 @@ func (d *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map } // GetAllContent fetches all content for a site -func (d *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, error) { +func (d *DatabaseClient) GetAllContent(siteID string) (map[string]engine.ContentItem, error) { ctx := context.Background() var dbContent interface{} var err error @@ -117,7 +118,7 @@ func (d *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, e items := d.convertToContentItemList(dbContent) // Convert slice to map for easy lookup - result := make(map[string]ContentItem) + result := make(map[string]engine.ContentItem) for _, item := range items { result[item.ID] = item } @@ -125,12 +126,12 @@ func (d *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, e return result, nil } -// convertToContentItem converts database models to content.ContentItem -func (d *DatabaseClient) convertToContentItem(content interface{}) ContentItem { +// convertToContentItem converts database models to engine.ContentItem +func (d *DatabaseClient) convertToContentItem(content interface{}) engine.ContentItem { switch d.db.GetDBType() { case "sqlite3": c := content.(sqlite.Content) - return ContentItem{ + return engine.ContentItem{ ID: c.ID, SiteID: c.SiteID, Value: c.Value, @@ -139,7 +140,7 @@ func (d *DatabaseClient) convertToContentItem(content interface{}) ContentItem { } case "postgresql": c := content.(postgresql.Content) - return ContentItem{ + return engine.ContentItem{ ID: c.ID, SiteID: c.SiteID, Value: c.Value, @@ -147,26 +148,26 @@ func (d *DatabaseClient) convertToContentItem(content interface{}) ContentItem { UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339), } } - return ContentItem{} // Should never happen + return engine.ContentItem{} // Should never happen } -// convertToContentItemList converts database model lists to content.ContentItem slice -func (d *DatabaseClient) convertToContentItemList(contentList interface{}) []ContentItem { +// convertToContentItemList converts database model lists to engine.ContentItem slice +func (d *DatabaseClient) convertToContentItemList(contentList interface{}) []engine.ContentItem { switch d.db.GetDBType() { case "sqlite3": list := contentList.([]sqlite.Content) - items := make([]ContentItem, len(list)) + items := make([]engine.ContentItem, len(list)) for i, content := range list { items[i] = d.convertToContentItem(content) } return items case "postgresql": list := contentList.([]postgresql.Content) - items := make([]ContentItem, len(list)) + items := make([]engine.ContentItem, len(list)) for i, content := range list { items[i] = d.convertToContentItem(content) } return items } - return []ContentItem{} // Should never happen + return []engine.ContentItem{} // Should never happen } diff --git a/internal/content/enhancer.go b/internal/content/enhancer.go index 373bbaf..041045c 100644 --- a/internal/content/enhancer.go +++ b/internal/content/enhancer.go @@ -15,7 +15,7 @@ type Enhancer struct { } // NewEnhancer creates a new HTML enhancer using unified engine -func NewEnhancer(client ContentClient, siteID string) *Enhancer { +func NewEnhancer(client engine.ContentClient, siteID string) *Enhancer { // Create database client for engine var engineClient engine.ContentClient if dbClient, ok := client.(*DatabaseClient); ok { diff --git a/internal/content/markdown.go b/internal/content/markdown.go deleted file mode 100644 index 4b49db6..0000000 --- a/internal/content/markdown.go +++ /dev/null @@ -1,76 +0,0 @@ -package content - -import ( - "bytes" - "log" - "strings" - - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer/html" - "github.com/yuin/goldmark/util" -) - -// MarkdownProcessor handles minimal markdown processing -// Supports only: **bold**, *italic*, and [link](url) -type MarkdownProcessor struct { - parser goldmark.Markdown -} - -// NewMarkdownProcessor creates a new markdown processor with minimal configuration -func NewMarkdownProcessor() *MarkdownProcessor { - // Configure goldmark to only support basic inline formatting - md := goldmark.New( - goldmark.WithParserOptions( - parser.WithInlineParsers( - // Bold (**text**) and italic (*text*) - same parser handles both - util.Prioritized(parser.NewEmphasisParser(), 500), - - // Links [text](url) - util.Prioritized(parser.NewLinkParser(), 600), - ), - // Disable all block parsers except paragraph (no headings, lists, etc.) - parser.WithBlockParsers( - util.Prioritized(parser.NewParagraphParser(), 200), - ), - ), - goldmark.WithRendererOptions( - html.WithXHTML(), //
instead of
- html.WithHardWraps(), // Line breaks become
- html.WithUnsafe(), // Allow existing HTML to pass through - ), - ) - - return &MarkdownProcessor{parser: md} -} - -// ToHTML converts markdown string to HTML -func (mp *MarkdownProcessor) ToHTML(markdown string) (string, error) { - if markdown == "" { - return "", nil - } - - var buf bytes.Buffer - if err := mp.parser.Convert([]byte(markdown), &buf); err != nil { - log.Printf("Markdown conversion failed: %v", err) - return "", err - } - - html := buf.String() - - // Clean up goldmark's paragraph wrapping for inline content - // If content is wrapped in a single

tag, extract just the inner content - html = strings.TrimSpace(html) - - if strings.HasPrefix(html, "

") && strings.HasSuffix(html, "

") { - // Check if this is a single paragraph (no other

tags inside) - inner := html[3 : len(html)-4] // Remove

and

- if !strings.Contains(inner, "

") { - // Single paragraph - return just the inner content for inline injection - return inner, nil - } - } - - // Multiple paragraphs or other block content - return as-is - return html, nil -} diff --git a/internal/content/mock.go b/internal/content/mock.go index 9d33d70..2d45360 100644 --- a/internal/content/mock.go +++ b/internal/content/mock.go @@ -2,17 +2,19 @@ package content import ( "time" + + "github.com/insertr/insertr/internal/engine" ) // MockClient implements ContentClient with mock data for development type MockClient struct { - data map[string]ContentItem + data map[string]engine.ContentItem } // NewMockClient creates a new mock content client with sample data func NewMockClient() *MockClient { // Generate realistic mock content based on actual generated IDs - data := map[string]ContentItem{ + data := map[string]engine.ContentItem{ // Navigation (index.html has collision suffix) "navbar-logo-2b10ad": { ID: "navbar-logo-2b10ad", @@ -98,7 +100,7 @@ func NewMockClient() *MockClient { } // GetContent fetches a single content item by ID -func (m *MockClient) GetContent(siteID, contentID string) (*ContentItem, error) { +func (m *MockClient) GetContent(siteID, contentID string) (*engine.ContentItem, error) { if item, exists := m.data[contentID]; exists && item.SiteID == siteID { return &item, nil } @@ -108,8 +110,8 @@ func (m *MockClient) GetContent(siteID, contentID string) (*ContentItem, error) } // GetBulkContent fetches multiple content items by IDs -func (m *MockClient) GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error) { - result := make(map[string]ContentItem) +func (m *MockClient) GetBulkContent(siteID string, contentIDs []string) (map[string]engine.ContentItem, error) { + result := make(map[string]engine.ContentItem) for _, id := range contentIDs { item, err := m.GetContent(siteID, id) @@ -125,8 +127,8 @@ func (m *MockClient) GetBulkContent(siteID string, contentIDs []string) (map[str } // GetAllContent fetches all content for a site -func (m *MockClient) GetAllContent(siteID string) (map[string]ContentItem, error) { - result := make(map[string]ContentItem) +func (m *MockClient) GetAllContent(siteID string) (map[string]engine.ContentItem, error) { + result := make(map[string]engine.ContentItem) for _, item := range m.data { if item.SiteID == siteID { diff --git a/internal/content/site_manager.go b/internal/content/site_manager.go index 4ca9dc4..0d16714 100644 --- a/internal/content/site_manager.go +++ b/internal/content/site_manager.go @@ -7,6 +7,8 @@ import ( "path/filepath" "sync" "time" + + "github.com/insertr/insertr/internal/engine" ) // SiteConfig represents configuration for a registered site @@ -28,7 +30,7 @@ type SiteManager struct { } // NewSiteManager creates a new site manager -func NewSiteManager(contentClient ContentClient, backupDir string, devMode bool) *SiteManager { +func NewSiteManager(contentClient engine.ContentClient, backupDir string, devMode bool) *SiteManager { if backupDir == "" { backupDir = "./insertr-backups" } diff --git a/internal/content/types.go b/internal/content/types.go deleted file mode 100644 index b28270f..0000000 --- a/internal/content/types.go +++ /dev/null @@ -1,28 +0,0 @@ -package content - -// ContentItem represents a piece of content from the database -type ContentItem struct { - ID string `json:"id"` - SiteID string `json:"site_id"` - Value string `json:"value"` - Type string `json:"type"` - UpdatedAt string `json:"updated_at"` -} - -// ContentResponse represents the API response structure -type ContentResponse struct { - Content []ContentItem `json:"content"` - Error string `json:"error,omitempty"` -} - -// ContentClient interface for content retrieval -type ContentClient interface { - // GetContent fetches content by ID - GetContent(siteID, contentID string) (*ContentItem, error) - - // GetBulkContent fetches multiple content items by IDs - GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error) - - // GetAllContent fetches all content for a site - GetAllContent(siteID string) (map[string]ContentItem, error) -} diff --git a/internal/engine/database_client.go b/internal/engine/database_client.go index 4e99257..632d8ef 100644 --- a/internal/engine/database_client.go +++ b/internal/engine/database_client.go @@ -110,3 +110,47 @@ func (c *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType()) } } + +// GetAllContent retrieves all content items for a site +func (c *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, error) { + switch c.database.GetDBType() { + case "sqlite3": + contents, err := c.database.GetSQLiteQueries().GetAllContent(context.Background(), siteID) + if err != nil { + return nil, err + } + + items := make(map[string]ContentItem) + for _, content := range contents { + items[content.ID] = ContentItem{ + ID: content.ID, + SiteID: content.SiteID, + Value: content.Value, + Type: content.Type, + LastEditedBy: content.LastEditedBy, + } + } + return items, nil + + case "postgresql": + contents, err := c.database.GetPostgreSQLQueries().GetAllContent(context.Background(), siteID) + if err != nil { + return nil, err + } + + items := make(map[string]ContentItem) + for _, content := range contents { + items[content.ID] = ContentItem{ + ID: content.ID, + SiteID: content.SiteID, + Value: content.Value, + Type: content.Type, + LastEditedBy: content.LastEditedBy, + } + } + return items, nil + + default: + return nil, fmt.Errorf("unsupported database type: %s", c.database.GetDBType()) + } +} diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 26787f8..4a79ed2 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -37,20 +37,33 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro processedElements := make([]ProcessedElement, len(elements)) for i, elem := range elements { - // Generate ID using the same algorithm as the parser - id := e.idGenerator.Generate(elem.Node, input.FilePath) + // Check if element already has a data-content-id + existingID := e.getAttribute(elem.Node, "data-content-id") + var id string + var wasGenerated bool + + if existingID != "" { + // Use existing ID from enhanced element + id = existingID + wasGenerated = false + } else { + // Generate new ID for unprocessed element + id = e.idGenerator.Generate(elem.Node, input.FilePath) + wasGenerated = true + } + generatedIDs[fmt.Sprintf("element_%d", i)] = id processedElements[i] = ProcessedElement{ Node: elem.Node, ID: id, Type: elem.Type, - Generated: true, + Generated: wasGenerated, Tag: elem.Node.Data, Classes: GetClasses(elem.Node), } - // Add content attributes to the node + // Add/update content attributes to the node e.addContentAttributes(elem.Node, id, elem.Type) } @@ -133,6 +146,16 @@ func (e *ContentEngine) addContentAttributes(node *html.Node, contentID, content e.setAttribute(node, "data-content-type", contentType) } +// getAttribute gets an attribute value from an HTML node +func (e *ContentEngine) getAttribute(node *html.Node, key string) string { + for _, attr := range node.Attr { + if attr.Key == key { + return attr.Val + } + } + return "" +} + // setAttribute sets an attribute on an HTML node func (e *ContentEngine) setAttribute(node *html.Node, key, value string) { // Remove existing attribute if it exists diff --git a/internal/engine/types.go b/internal/engine/types.go index 0518663..6530c4d 100644 --- a/internal/engine/types.go +++ b/internal/engine/types.go @@ -43,17 +43,25 @@ type ProcessedElement struct { } // ContentClient interface for accessing content data -// This will be implemented by database clients +// This will be implemented by database clients, HTTP clients, and mock clients type ContentClient interface { GetContent(siteID, contentID string) (*ContentItem, error) GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error) + GetAllContent(siteID string) (map[string]ContentItem, error) } // ContentItem represents a piece of content from the database type ContentItem struct { - ID string - SiteID string - Value string - Type string - LastEditedBy string + ID string `json:"id"` + SiteID string `json:"site_id"` + Value string `json:"value"` + Type string `json:"type"` + UpdatedAt string `json:"updated_at"` + LastEditedBy string `json:"last_edited_by,omitempty"` +} + +// ContentResponse represents the API response structure +type ContentResponse struct { + Content []ContentItem `json:"content"` + Error string `json:"error,omitempty"` } diff --git a/lib/src/core/api-client.js b/lib/src/core/api-client.js index b9e405c..8b08a1c 100644 --- a/lib/src/core/api-client.js +++ b/lib/src/core/api-client.js @@ -29,24 +29,15 @@ export class ApiClient { } - async createContent(contentId, content, type, htmlMarkup = null) { + async createContent(content, type, htmlMarkup) { try { const payload = { + html_markup: htmlMarkup, // Always send HTML markup - server extracts ID or generates new one value: content, type: type, file_path: this.getCurrentFilePath() // Always include file path for consistent ID generation }; - if (contentId) { - // Enhanced site - provide existing ID - payload.id = contentId; - } else if (htmlMarkup) { - // Non-enhanced site - provide HTML markup for unified engine ID generation - payload.html_markup = htmlMarkup; - } else { - throw new Error('Either contentId or htmlMarkup must be provided'); - } - const response = await fetch(`${this.baseUrl}?site_id=${this.siteId}`, { method: 'POST', headers: { @@ -61,7 +52,7 @@ export class ApiClient { console.log(`✅ Content created: ${result.id} (${result.type})`); return result; } else { - console.warn(`⚠️ Create failed (${response.status}): ${contentId || 'backend-generated'}`); + console.warn(`⚠️ Create failed (${response.status}): server will generate ID`); return null; } } catch (error) { @@ -69,7 +60,7 @@ export class ApiClient { console.warn(`🔌 API Server not reachable at ${this.baseUrl}`); console.warn('💡 Start full-stack development: just dev'); } else { - console.error('Failed to create content:', contentId, error); + console.error('Failed to create content:', error); } return false; } diff --git a/lib/src/core/editor.js b/lib/src/core/editor.js index 2ccc460..7161c73 100644 --- a/lib/src/core/editor.js +++ b/lib/src/core/editor.js @@ -102,13 +102,12 @@ export class InsertrEditor { contentValue = formData.text || formData; } - // Universal upsert - works for both new and existing content + // Universal upsert - server handles ID extraction/generation from markup const contentType = this.determineContentType(meta.element); const result = await this.apiClient.createContent( - meta.contentId, // Use existing ID if available, null if new contentValue, contentType, - meta.htmlMarkup + meta.htmlMarkup // Always send HTML markup - server is smart about ID handling ); if (result) { diff --git a/lib/src/core/insertr.js b/lib/src/core/insertr.js index f0a2d89..369adfa 100644 --- a/lib/src/core/insertr.js +++ b/lib/src/core/insertr.js @@ -106,6 +106,11 @@ export class InsertrCore { getElementMetadata(element) { const existingId = element.getAttribute('data-content-id'); + // Ensure element has insertr class for server processing + if (!element.classList.contains('insertr')) { + element.classList.add('insertr'); + } + // Send HTML markup to server for unified ID generation return { contentId: existingId, // null if new content, existing ID if updating diff --git a/test-sites/demo-site/insertr.js b/test-sites/demo-site/insertr.js index d118a0a..7599c8f 100644 --- a/test-sites/demo-site/insertr.js +++ b/test-sites/demo-site/insertr.js @@ -109,6 +109,11 @@ var Insertr = (function () { getElementMetadata(element) { const existingId = element.getAttribute('data-content-id'); + // Ensure element has insertr class for server processing + if (!element.classList.contains('insertr')) { + element.classList.add('insertr'); + } + // Send HTML markup to server for unified ID generation return { contentId: existingId, // null if new content, existing ID if updating @@ -2820,13 +2825,12 @@ Please report this to https://github.com/markedjs/marked.`,e){let r="

An error contentValue = formData.text || formData; } - // Universal upsert - works for both new and existing content + // Universal upsert - server handles ID extraction/generation from markup const contentType = this.determineContentType(meta.element); const result = await this.apiClient.createContent( - meta.contentId, // Use existing ID if available, null if new contentValue, contentType, - meta.htmlMarkup + meta.htmlMarkup // Always send HTML markup - server is smart about ID handling ); if (result) { @@ -3761,24 +3765,15 @@ Please report this to https://github.com/markedjs/marked.`,e){let r="

An error } - async createContent(contentId, content, type, htmlMarkup = null) { + async createContent(content, type, htmlMarkup) { try { const payload = { + html_markup: htmlMarkup, // Always send HTML markup - server extracts ID or generates new one value: content, type: type, file_path: this.getCurrentFilePath() // Always include file path for consistent ID generation }; - if (contentId) { - // Enhanced site - provide existing ID - payload.id = contentId; - } else if (htmlMarkup) { - // Non-enhanced site - provide HTML markup for unified engine ID generation - payload.html_markup = htmlMarkup; - } else { - throw new Error('Either contentId or htmlMarkup must be provided'); - } - const response = await fetch(`${this.baseUrl}?site_id=${this.siteId}`, { method: 'POST', headers: { @@ -3793,7 +3788,7 @@ Please report this to https://github.com/markedjs/marked.`,e){let r="

An error console.log(`✅ Content created: ${result.id} (${result.type})`); return result; } else { - console.warn(`⚠️ Create failed (${response.status}): ${contentId || 'backend-generated'}`); + console.warn(`⚠️ Create failed (${response.status}): server will generate ID`); return null; } } catch (error) { @@ -3801,7 +3796,7 @@ Please report this to https://github.com/markedjs/marked.`,e){let r="

An error console.warn(`🔌 API Server not reachable at ${this.baseUrl}`); console.warn('💡 Start full-stack development: just dev'); } else { - console.error('Failed to create content:', contentId, error); + console.error('Failed to create content:', error); } return false; }