Implement collection item reordering with bulk operations and persistent HTML attributes
- Add bulk reorder API endpoint (PUT /api/collections/{id}/reorder) with atomic transactions
- Replace individual position updates with efficient bulk operations in frontend
- Implement unified ID generation and proper data-item-id injection during enhancement
- Fix collection item position persistence through content edit cycles
- Add optimistic UI with rollback capability for better user experience
- Update sqlc queries to include last_edited_by fields in position updates
- Remove obsolete data-content-type attributes and unify naming conventions
This commit is contained in:
@@ -3,7 +3,6 @@ package engine
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
@@ -103,8 +102,8 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
|
||||
// Generate structural ID for the collection container
|
||||
collectionID := e.idGenerator.Generate(collectionElem.Node, input.FilePath)
|
||||
|
||||
// Add data-content-id attribute to the collection container
|
||||
e.setAttribute(collectionElem.Node, "data-content-id", collectionID)
|
||||
// Add data-collection-id attribute to the collection container
|
||||
e.setAttribute(collectionElem.Node, "data-collection-id", collectionID)
|
||||
|
||||
// Process collection during enhancement or content injection
|
||||
if input.Mode == Enhancement || input.Mode == ContentInjection {
|
||||
@@ -243,7 +242,6 @@ func (e *ContentEngine) addContentAttributes(node *html.Node, contentID string)
|
||||
e.setAttribute(node, "data-content-id", contentID)
|
||||
}
|
||||
|
||||
|
||||
// setAttribute sets an attribute on an HTML node
|
||||
func (e *ContentEngine) setAttribute(node *html.Node, key, value string) {
|
||||
// Remove existing attribute if it exists
|
||||
@@ -377,8 +375,6 @@ func (e *ContentEngine) extractHTMLContent(node *html.Node) string {
|
||||
return strings.TrimSpace(content.String())
|
||||
}
|
||||
|
||||
|
||||
|
||||
// extractOriginalTemplate extracts the outer HTML of the element (including the element itself)
|
||||
func (e *ContentEngine) extractOriginalTemplate(node *html.Node) string {
|
||||
var buf strings.Builder
|
||||
@@ -549,6 +545,12 @@ func (e *ContentEngine) extractAndStoreTemplatesAndItems(collectionNode *html.No
|
||||
return fmt.Errorf("failed to store initial collection items: %w", err)
|
||||
}
|
||||
|
||||
// Reconstruct items from database to ensure proper data-item-id injection
|
||||
err = e.reconstructCollectionItems(collectionNode, collectionID, siteID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to reconstruct initial collection items: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -619,6 +621,12 @@ func (e *ContentEngine) reconstructCollectionItems(collectionNode *html.Node, co
|
||||
for structuralChild := structuralBody.FirstChild; structuralChild != nil; {
|
||||
next := structuralChild.NextSibling
|
||||
structuralBody.RemoveChild(structuralChild)
|
||||
|
||||
// Inject data-item-id attribute for collection item identification
|
||||
if structuralChild.Type == html.ElementNode {
|
||||
e.setAttribute(structuralChild, "data-item-id", item.ItemID)
|
||||
}
|
||||
|
||||
collectionNode.AppendChild(structuralChild)
|
||||
structuralChild = next
|
||||
}
|
||||
@@ -730,15 +738,18 @@ func (e *ContentEngine) CreateCollectionItemFromTemplate(
|
||||
templateHTML string,
|
||||
lastEditedBy string,
|
||||
) (*CollectionItemWithTemplate, error) {
|
||||
// Generate unique item ID
|
||||
itemID := fmt.Sprintf("%s-item-%d", collectionID, time.Now().Unix())
|
||||
|
||||
// Create virtual element from template (like enhancement path)
|
||||
virtualElement, err := e.createVirtualElementFromTemplate(templateHTML)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create virtual element: %w", err)
|
||||
}
|
||||
|
||||
// Generate unique item ID using unified generator with collection context
|
||||
itemID := e.idGenerator.Generate(virtualElement, "collection-item")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create virtual element: %w", err)
|
||||
}
|
||||
|
||||
// Process .insertr elements and create content entries (unified approach)
|
||||
contentEntries, err := e.processChildElementsAsContent(virtualElement, siteID, itemID)
|
||||
if err != nil {
|
||||
@@ -808,8 +819,8 @@ func (e *ContentEngine) storeChildrenAsCollectionItems(collectionNode *html.Node
|
||||
|
||||
// Store each child using unified .insertr approach (content table + structural template)
|
||||
for i, childElement := range childElements {
|
||||
// Generate item ID (like content ID generation)
|
||||
itemID := fmt.Sprintf("%s-initial-%d", collectionID, i+1)
|
||||
// Generate item ID using unified generator with collection context
|
||||
itemID := e.idGenerator.Generate(childElement, "collection-item")
|
||||
|
||||
// Process .insertr elements within this child (unified approach)
|
||||
contentEntries, err := e.processChildElementsAsContent(childElement, siteID, itemID)
|
||||
|
||||
@@ -162,7 +162,6 @@ func (i *Injector) findElementByTag(node *html.Node, tag string) *html.Node {
|
||||
// AddContentAttributes adds necessary data attributes and insertr class for editor functionality
|
||||
func (i *Injector) AddContentAttributes(node *html.Node, contentID string, contentType string) {
|
||||
i.setAttribute(node, "data-content-id", contentID)
|
||||
i.setAttribute(node, "data-content-type", contentType)
|
||||
i.addClass(node, "insertr")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user