diff --git a/internal/handlers/media.go b/internal/handlers/media.go index 42a0f3d..f4c7251 100644 --- a/internal/handlers/media.go +++ b/internal/handlers/media.go @@ -115,20 +115,32 @@ type UpdateMediaNoteRequest struct { // CreateMediaHighlightRequest represents the request for creating a media highlight type CreateMediaHighlightRequest struct { - SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"` - StartPosition string `json:"start_position" validate:"required,max=100"` - EndPosition string `json:"end_position" validate:"required,max=100"` - Color string `json:"color" validate:"omitempty,len=7"` - NoteID string `json:"note_id"` + SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"` + StartPosition string `json:"start_position" validate:"max=1000"` + EndPosition string `json:"end_position" validate:"max=1000"` + EpubcfiStart string `json:"epubcfi_start" validate:"max=2000"` + EpubcfiEnd string `json:"epubcfi_end" validate:"max=2000"` + Color string `json:"color" validate:"omitempty,len=7"` + NoteText string `json:"note_text" validate:"max=10000"` + NoteID string `json:"note_id"` + PercentageStart float64 `json:"percentage_start"` + PercentageEnd float64 `json:"percentage_end"` + ChapterReference int32 `json:"chapter_reference"` } // UpdateMediaHighlightRequest represents the request for updating a media highlight type UpdateMediaHighlightRequest struct { - SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"` - StartPosition string `json:"start_position" validate:"required,max=100"` - EndPosition string `json:"end_position" validate:"required,max=100"` - Color string `json:"color" validate:"omitempty,len=7"` - NoteID string `json:"note_id"` + SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"` + StartPosition string `json:"start_position" validate:"max=1000"` + EndPosition string `json:"end_position" validate:"max=1000"` + EpubcfiStart string `json:"epubcfi_start" validate:"max=2000"` + EpubcfiEnd string `json:"epubcfi_end" validate:"max=2000"` + Color string `json:"color" validate:"omitempty,len=7"` + NoteText string `json:"note_text" validate:"max=10000"` + NoteID string `json:"note_id"` + PercentageStart float64 `json:"percentage_start"` + PercentageEnd float64 `json:"percentage_end"` + ChapterReference int32 `json:"chapter_reference"` } // CreateMediaBookmarkRequest represents the request for creating a media bookmark @@ -1576,14 +1588,20 @@ func (mh *MediaHandler) CreateMediaHighlight(c *echo.Context) error { if mh.annotationSvc != nil { result, err := mh.annotationSvc.SaveHighlight(c.Request().Context(), wsync.SaveHighlightRequest{ - MediaItemID: pgMediaID, - UserID: pgUserID, - SelectionText: req.SelectionText, - StartPosition: req.StartPosition, - EndPosition: req.EndPosition, - Color: color, - Source: "web", - ModifiedAt: time.Now(), + MediaItemID: pgMediaID, + UserID: pgUserID, + SelectionText: req.SelectionText, + StartPosition: req.StartPosition, + EndPosition: req.EndPosition, + EpubcfiStart: req.EpubcfiStart, + EpubcfiEnd: req.EpubcfiEnd, + Color: color, + NoteText: req.NoteText, + PercentageStart: req.PercentageStart, + PercentageEnd: req.PercentageEnd, + ChapterReference: req.ChapterReference, + Source: "web", + ModifiedAt: time.Now(), }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) @@ -1656,6 +1674,42 @@ func (mh *MediaHandler) UpdateMediaHighlight(c *echo.Context) error { color = req.Color } + // Prefer the sync-aware path: the same selection text + CFI resolves to + // the same dedup key, so this performs an LWW update of the existing row + // (including note_text and CFI columns the plain query cannot touch). + if mh.annotationSvc != nil { + userID := c.Get("user_id").(string) + userUUID, err := uuid.Parse(userID) + if err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"}) + } + mediaID := c.Param("id") + mediaUUID, err := uuid.Parse(mediaID) + if err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid media item id"}) + } + result, err := mh.annotationSvc.SaveHighlight(c.Request().Context(), wsync.SaveHighlightRequest{ + MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true}, + UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, + SelectionText: req.SelectionText, + StartPosition: req.StartPosition, + EndPosition: req.EndPosition, + EpubcfiStart: req.EpubcfiStart, + EpubcfiEnd: req.EpubcfiEnd, + Color: color, + NoteText: req.NoteText, + PercentageStart: req.PercentageStart, + PercentageEnd: req.PercentageEnd, + ChapterReference: req.ChapterReference, + Source: "web", + ModifiedAt: time.Now(), + }) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) + } + return c.JSON(http.StatusOK, result.Highlight) + } + highlight, err := mh.db.UpdateMediaHighlight(c.Request().Context(), database.UpdateMediaHighlightParams{ ID: pgtype.UUID{Bytes: highlightUUID, Valid: true}, SelectionText: req.SelectionText, diff --git a/templates/reader.templ b/templates/reader.templ index 8a4b153..748ab0e 100644 --- a/templates/reader.templ +++ b/templates/reader.templ @@ -118,9 +118,89 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm @ReaderSettingsDrawer() - +