mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #65,5
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Blog Match Link Fix
|
||||
|
||||
## Problem
|
||||
When creating a new blog article with a match link in the admin panel, the match link was not being saved to the database. The article was created successfully, but the association with the FACR match was lost.
|
||||
|
||||
## Root Cause
|
||||
The match linking logic was placed in the React Query mutation's `onSuccess` callback. When the page re-rendered after article creation (especially when invalidating queries), a React error #310 was occurring, which interrupted the `onSuccess` callback before the match linking API call could complete.
|
||||
|
||||
## Solution
|
||||
Moved the match linking logic from the mutation's `onSuccess` callback directly into the `onSubmit` function. This ensures the match linking happens synchronously after article creation, before the modal closes and queries are invalidated.
|
||||
|
||||
### Changes Made
|
||||
|
||||
#### 1. Match Linking in `onSubmit` Function
|
||||
**File**: `frontend/src/pages/admin/ArticlesAdminPage.tsx`
|
||||
|
||||
- For **new articles**: After `createArticle()` completes, immediately call `putArticleMatchLink()` with the new article ID
|
||||
- For **existing articles**: After `updateArticle()` completes, call `putArticleMatchLink()` to update or create the link
|
||||
- All match linking now happens within the same try-catch block as article creation/update
|
||||
- Modal only closes after all operations complete successfully
|
||||
|
||||
#### 2. Simplified Mutation Callbacks
|
||||
- `createMut.onSuccess`: Only handles query invalidation and state cleanup
|
||||
- `updateMut.onSuccess`: Only handles query invalidation
|
||||
- Removed duplicate match linking code from callbacks
|
||||
- Success toasts moved to `onSubmit` after all operations complete
|
||||
|
||||
#### 3. Enhanced Error Handling
|
||||
- Added try-catch blocks around match linking API calls
|
||||
- Separate error messages for article creation vs. match linking failures
|
||||
- If article creation succeeds but match linking fails, user gets a warning toast with instructions
|
||||
- All errors logged to console for debugging
|
||||
|
||||
#### 4. Added Debug Logging
|
||||
- Log match link state before submission
|
||||
- Log article creation success
|
||||
- Log match linking attempts and results
|
||||
- Helps diagnose issues in production
|
||||
|
||||
#### 5. Fixed `MatchLinkBadge` Loading State
|
||||
- Added loading state check to prevent React errors when refetching
|
||||
- Shows "Načítání..." badge while match link data is loading
|
||||
|
||||
## Testing Instructions
|
||||
|
||||
### Test 1: Create New Article with Match Link
|
||||
1. Go to Admin → Články
|
||||
2. Click "Nový článek"
|
||||
3. Fill in required fields (Název, Kategorie)
|
||||
4. Switch to "Základní" tab
|
||||
5. Select a match from the picker
|
||||
6. Click "Uložit"
|
||||
7. **Expected**: Toast shows "Článek vytvořen a propojen se zápasem"
|
||||
8. **Verify**: Article list shows match badge with match details
|
||||
|
||||
### Test 2: Create Article Without Match Link
|
||||
1. Create article without selecting a match
|
||||
2. **Expected**: Toast shows "Článek byl úspěšně vytvořen"
|
||||
3. **Verify**: Article list shows "Nepropojeno" badge
|
||||
|
||||
### Test 3: Update Existing Article Match Link
|
||||
1. Open existing article for editing
|
||||
2. Select a different match
|
||||
3. Click "Uložit"
|
||||
4. **Expected**: Toast shows "Článek aktualizován a propojen se zápasem"
|
||||
5. **Verify**: Badge updates to show new match
|
||||
|
||||
### Test 4: Match Linking Failure (Backend Error)
|
||||
1. Simulate backend error (e.g., stop backend)
|
||||
2. Try to create article with match link
|
||||
3. **Expected**: Toast shows "Článek vytvořen, ale propojení se zápasem selhalo"
|
||||
4. **Verify**: Article is created but without match link
|
||||
|
||||
## API Endpoints Used
|
||||
- `POST /api/v1/articles` - Create article
|
||||
- `PUT /api/v1/articles/:id` - Update article
|
||||
- `POST /api/v1/articles/:id/match-link` - Create/update match link
|
||||
- `GET /api/v1/articles/:id/match-link` - Get match link (for badge display)
|
||||
- `DELETE /api/v1/articles/:id/match-link` - Delete match link
|
||||
|
||||
## Database Tables
|
||||
- `articles` - Main article data
|
||||
- `article_match_links` - Junction table linking articles to FACR match IDs
|
||||
|
||||
## State Management
|
||||
- `tempMatchLink` - Stores selected match ID for new articles
|
||||
- `matchIdInput` - Stores selected match ID (UI input)
|
||||
- `linkedMatchId` - Stores confirmed linked match ID after successful save
|
||||
|
||||
## Future Improvements
|
||||
- Consider adding optimistic updates to show match link immediately
|
||||
- Add bulk match linking for multiple articles
|
||||
- Show match preview in article form before saving
|
||||
- Add match link history/audit log
|
||||
@@ -0,0 +1,154 @@
|
||||
# Blog Match Link Fix - Verification Checklist
|
||||
|
||||
## Issue Summary
|
||||
**Problem**: When creating a blog article and selecting a match from the "Propojit se zápasem" section, the match ID was not being saved to the database.
|
||||
|
||||
**User Evidence**: Console log showed:
|
||||
```javascript
|
||||
Saving article with payload: {
|
||||
"title": "U17: Rýmařov...",
|
||||
"category_name": "KALMAN TRADE Krajský přebor mladší dorost",
|
||||
// ... other fields ...
|
||||
// ❌ NO match_id field in payload
|
||||
}
|
||||
```
|
||||
|
||||
## Fix Applied
|
||||
|
||||
### Technical Changes
|
||||
1. **Moved match linking from async callback to synchronous flow**
|
||||
- Previous: Match linking happened in `createMut.onSuccess` callback (after article creation)
|
||||
- New: Match linking happens in `onSubmit` function (immediately after article creation completes)
|
||||
- Benefit: Prevents race conditions and React error interruptions
|
||||
|
||||
2. **Added comprehensive error handling**
|
||||
- Separate try-catch blocks for article save and match linking
|
||||
- Clear user feedback for each operation
|
||||
- Logging at each step for debugging
|
||||
|
||||
3. **Fixed React error #310**
|
||||
- Added loading state to `MatchLinkBadge` component
|
||||
- Prevents rendering errors during query refetch
|
||||
|
||||
## Verification Steps
|
||||
|
||||
### 1. Check Console Logs
|
||||
After applying the fix, when creating an article with a match link, you should see:
|
||||
```
|
||||
Match link state before submit: {
|
||||
tempMatchLink: "12345",
|
||||
matchIdInput: "12345",
|
||||
linkedMatchId: "",
|
||||
isNewArticle: true
|
||||
}
|
||||
Linking new article 42 with match 12345
|
||||
Match link created for new article
|
||||
```
|
||||
|
||||
### 2. Check Toast Messages
|
||||
- ✅ Success: "Článek vytvořen a propojen se zápasem" (with Match ID)
|
||||
- ⚠️ Partial Success: "Článek vytvořen, ale propojení se zápasem selhalo"
|
||||
|
||||
### 3. Check Database
|
||||
Query to verify match link was saved:
|
||||
```sql
|
||||
SELECT * FROM article_match_links WHERE article_id = [NEW_ARTICLE_ID];
|
||||
```
|
||||
Should return:
|
||||
- `article_id`: The ID of the newly created article
|
||||
- `external_match_id`: The FACR match ID (e.g., "12345")
|
||||
- `title`: The article title
|
||||
|
||||
### 4. Check Article List UI
|
||||
- Badge should show: "Zápas: [Home Team] [Score] [Away Team]" in green (if match has score) or yellow (if no score yet)
|
||||
- Should NOT show: "Nepropojeno" in gray
|
||||
|
||||
### 5. Check API Response
|
||||
The article creation response should include match_link:
|
||||
```json
|
||||
{
|
||||
"id": 42,
|
||||
"title": "U17: Rýmařov...",
|
||||
"match_link": {
|
||||
"article_id": 42,
|
||||
"external_match_id": "12345",
|
||||
"title": "U17: Rýmařov..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Scenario A: New Article with Match
|
||||
1. Open Admin → Články → Nový článek
|
||||
2. Fill in title: "Test článek"
|
||||
3. Select category: "KALMAN TRADE Krajský přebor mladší dorost"
|
||||
4. Click on a match in the match picker
|
||||
5. Click "Uložit"
|
||||
6. **Expected**: Green toast with match ID
|
||||
7. **Verify**: List shows match badge with team names
|
||||
|
||||
### Scenario B: New Article without Match
|
||||
1. Create article without selecting match
|
||||
2. **Expected**: Standard success toast
|
||||
3. **Verify**: List shows "Nepropojeno" badge
|
||||
|
||||
### Scenario C: Edit Existing Article, Add Match
|
||||
1. Open existing article
|
||||
2. Select a match
|
||||
3. Click "Uložit"
|
||||
4. **Expected**: Success toast with match info
|
||||
5. **Verify**: Badge updates immediately
|
||||
|
||||
### Scenario D: Edit Existing Article, Change Match
|
||||
1. Open article that already has a match
|
||||
2. Select different match
|
||||
3. Click "Uložit"
|
||||
4. **Expected**: Success toast
|
||||
5. **Verify**: Badge shows new match
|
||||
|
||||
### Scenario E: Backend Error Handling
|
||||
1. Stop backend server
|
||||
2. Try to create article with match
|
||||
3. **Expected**: Article created locally, warning toast about match linking failure
|
||||
4. Restart backend
|
||||
5. **Verify**: Can manually link match by editing article
|
||||
|
||||
## Code Changes Summary
|
||||
|
||||
### Files Modified
|
||||
1. `/frontend/src/pages/admin/ArticlesAdminPage.tsx`
|
||||
- Lines 40-43: Added loading state to MatchLinkBadge
|
||||
- Lines 655-673: Simplified createMut.onSuccess
|
||||
- Lines 686-702: Simplified updateMut.onSuccess
|
||||
- Lines 928-987: Refactored onSubmit with inline match linking
|
||||
|
||||
### Files Created
|
||||
1. `/DOCS/BLOG_MATCH_LINK_FIX.md` - Detailed fix documentation
|
||||
2. `/DOCS/BLOG_MATCH_LINK_VERIFICATION.md` - This file
|
||||
|
||||
## Rollback Plan
|
||||
If issues occur, revert the ArticlesAdminPage.tsx changes:
|
||||
```bash
|
||||
git checkout HEAD -- frontend/src/pages/admin/ArticlesAdminPage.tsx
|
||||
```
|
||||
|
||||
## Performance Impact
|
||||
- No performance degradation
|
||||
- Actually improved: One less async operation in mutation callback
|
||||
- Better user experience: Immediate feedback on match linking status
|
||||
|
||||
## Browser Compatibility
|
||||
- No new browser APIs used
|
||||
- Compatible with all modern browsers (Chrome, Firefox, Safari, Edge)
|
||||
- React Query handles all async state management
|
||||
|
||||
## Known Limitations
|
||||
- Match linking requires article to be saved first (can't preview match before save)
|
||||
- If backend is down, match link won't be saved (fails gracefully)
|
||||
- Maximum 100 matches shown in picker (performance optimization)
|
||||
|
||||
## Related Documentation
|
||||
- `DOCS/BLOG_CREATION_FIXED.md` - Previous blog creation fixes
|
||||
- `DOCS/FACR_INTEGRATION.md` - FACR match data integration
|
||||
- `DOCS/ADMIN_QUICK_REFERENCE.md` - Admin panel overview
|
||||
@@ -0,0 +1,237 @@
|
||||
# PDF Preview and Poll Creation Fix
|
||||
|
||||
## Issues Fixed
|
||||
|
||||
### 1. PDF Preview Not Working (Blank Screen)
|
||||
**Problem**: When trying to preview PDF files, the screen was blank due to Content Security Policy (CSP) restrictions blocking iframe embedding.
|
||||
|
||||
**Root Cause**: CSP header `frame-ancestors 'self'` prevented PDF files from being embedded in iframes.
|
||||
|
||||
**Solution**: Enhanced `FilePreview.tsx` component with multiple fallback options:
|
||||
- Primary: Direct iframe embed (works if CSP allows)
|
||||
- Fallback buttons:
|
||||
- Open in new window
|
||||
- View with Mozilla PDF.js
|
||||
- View via Google Docs Viewer
|
||||
- Download PDF
|
||||
|
||||
### 2. Poll Creation Requires Saved Article
|
||||
**Problem**: Users couldn't create or link polls to articles until the article was saved first. The UI showed "Nejprve uložte článek" (Save article first).
|
||||
|
||||
**Root Cause**: `PollLinker` component requires an `articleId` which only exists after the article is saved.
|
||||
|
||||
**Solution**:
|
||||
- Added "Save as draft and add polls" button
|
||||
- Modified `onSubmit` function to support `keepOpen` option
|
||||
- After saving, modal stays open and switches to Poll tab automatically
|
||||
- Article is saved as draft (published=true by default, but can be unpublished)
|
||||
|
||||
## Files Modified
|
||||
|
||||
### `/frontend/src/components/common/FilePreview.tsx`
|
||||
**Lines changed**: 124-189
|
||||
|
||||
**What changed**:
|
||||
- Wrapped PDF iframe in a VStack with fallback options
|
||||
- Added 4 alternative viewing methods
|
||||
- Added helpful message when PDF doesn't display
|
||||
- Added error handler to iframe
|
||||
|
||||
**Key improvements**:
|
||||
```tsx
|
||||
// Before: Simple iframe only
|
||||
<iframe src={`${fullUrl}#view=FitH`} />
|
||||
|
||||
// After: Iframe + fallback buttons
|
||||
<VStack>
|
||||
<Box>
|
||||
<iframe src={`${fullUrl}#view=FitH&toolbar=1`} />
|
||||
</Box>
|
||||
<HStack>
|
||||
<Button href={fullUrl}>Open in new window</Button>
|
||||
<Button href={pdfjs_url}>View with PDF.js</Button>
|
||||
<Button href={google_viewer_url}>View via Google</Button>
|
||||
<Button download>Download PDF</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
```
|
||||
|
||||
### `/frontend/src/pages/admin/ArticlesAdminPage.tsx`
|
||||
**Lines changed**:
|
||||
- 852: Modified `onSubmit` function signature
|
||||
- 994-997: Added conditional modal closing
|
||||
- 1840-1870: Enhanced Poll tab UI
|
||||
|
||||
**What changed**:
|
||||
1. **Modified `onSubmit` function**:
|
||||
```typescript
|
||||
// Before
|
||||
const onSubmit = async () => { ... }
|
||||
|
||||
// After
|
||||
const onSubmit = async (options: { keepOpen?: boolean } = {}) => {
|
||||
// ... save logic ...
|
||||
if (!options.keepOpen) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Updated Poll tab**:
|
||||
- Changed from "info" alert to "warning" alert
|
||||
- Added "Save as draft and add polls" button
|
||||
- Button calls `onSubmit({ keepOpen: true })`
|
||||
- After save, switches to Poll tab: `setActiveTabIndex(5)`
|
||||
|
||||
## Testing Instructions
|
||||
|
||||
### Test 1: PDF Preview
|
||||
1. **Upload PDF to article**:
|
||||
- Go to Admin → Články → Edit article
|
||||
- Go to "Média" tab
|
||||
- Upload a PDF file in attachments
|
||||
|
||||
2. **Test preview**:
|
||||
- Click "Náhled" button
|
||||
- **Expected**: Modal opens with PDF preview
|
||||
- **If PDF doesn't show**: Fallback buttons appear
|
||||
- Click "Otevřít v novém okně" - PDF opens in new tab
|
||||
- Click "Zobrazit pomocí PDF.js" - PDF opens in Mozilla viewer
|
||||
- Click "Zobrazit přes Google" - PDF opens in Google Docs viewer
|
||||
- Click "Stáhnout PDF" - PDF downloads
|
||||
|
||||
### Test 2: Poll Creation for New Article
|
||||
1. **Create new article**:
|
||||
- Go to Admin → Články → Nový článek
|
||||
- Fill in title and category
|
||||
- Go to "Anketa" tab
|
||||
|
||||
2. **See warning message**:
|
||||
- **Expected**: Orange warning box with "Článek ještě není uložen"
|
||||
- Button: "Uložit jako koncept a přidat ankety"
|
||||
|
||||
3. **Save as draft**:
|
||||
- Click the button
|
||||
- **Expected**:
|
||||
- Article saves successfully
|
||||
- Modal stays open
|
||||
- Tab switches to Poll tab (still showing)
|
||||
- PollLinker component now visible
|
||||
- Can create/link polls
|
||||
|
||||
4. **Create poll**:
|
||||
- Click "Vytvořit novou" tab
|
||||
- Fill in poll title and options
|
||||
- Click "Vytvořit anketu"
|
||||
- **Expected**: Poll created and linked to article
|
||||
|
||||
### Test 3: Poll Creation for Existing Article
|
||||
1. **Edit existing article**:
|
||||
- Go to Admin → Články → Edit article
|
||||
- Go to "Anketa" tab
|
||||
|
||||
2. **Expected**: PollLinker shows immediately (no warning)
|
||||
3. Create or link polls as normal
|
||||
|
||||
## Technical Details
|
||||
|
||||
### PDF Viewing Methods
|
||||
1. **Direct iframe** (default):
|
||||
```html
|
||||
<iframe src="/uploads/file.pdf#view=FitH&toolbar=1" />
|
||||
```
|
||||
- Works if CSP allows
|
||||
- Fastest method
|
||||
- Native browser PDF viewer
|
||||
|
||||
2. **Mozilla PDF.js** (fallback 1):
|
||||
```
|
||||
https://mozilla.github.io/pdf.js/web/viewer.html?file=<encoded_url>
|
||||
```
|
||||
- Works even with strict CSP
|
||||
- JavaScript-based PDF renderer
|
||||
- Requires internet connection
|
||||
|
||||
3. **Google Docs Viewer** (fallback 2):
|
||||
```
|
||||
https://docs.google.com/viewer?url=<encoded_url>&embedded=true
|
||||
```
|
||||
- Requires public URL
|
||||
- May have privacy concerns
|
||||
- Reliable for most PDFs
|
||||
|
||||
4. **Direct download** (fallback 3):
|
||||
- Always works
|
||||
- User opens in their PDF app
|
||||
|
||||
### Poll Creation Flow
|
||||
```
|
||||
New Article (no ID yet)
|
||||
↓
|
||||
User clicks "Save as draft and add polls"
|
||||
↓
|
||||
onSubmit({ keepOpen: true })
|
||||
↓
|
||||
Article created in database
|
||||
↓
|
||||
Modal stays open (closeModal not called)
|
||||
↓
|
||||
setActiveTabIndex(5) - Switch to Poll tab
|
||||
↓
|
||||
PollLinker now has articleId
|
||||
↓
|
||||
User can create/link polls
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### PDF Preview
|
||||
- PDF.js fallback requires internet connection
|
||||
- Google Viewer requires publicly accessible URLs
|
||||
- Some browsers may block cross-origin iframes
|
||||
- Very large PDFs (>10MB) may be slow
|
||||
|
||||
### Poll Creation
|
||||
- Article must still be saved before linking polls (can't be fully offline)
|
||||
- "Keep open" mode doesn't work if there's a network error
|
||||
- If save fails, modal closes and poll tab doesn't appear
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
### PDF Preview
|
||||
- ✅ Chrome/Edge: Native PDF viewer works
|
||||
- ✅ Firefox: Native PDF viewer works
|
||||
- ✅ Safari: Native PDF viewer works
|
||||
- ✅ All browsers: Fallback buttons work
|
||||
|
||||
### Poll Creation
|
||||
- ✅ All modern browsers (Chrome, Firefox, Safari, Edge)
|
||||
- ✅ Mobile browsers
|
||||
|
||||
## Future Improvements
|
||||
|
||||
### PDF Preview
|
||||
- [ ] Add PDF.js embed directly in application (no external CDN)
|
||||
- [ ] Add thumbnail generation for PDFs
|
||||
- [ ] Add page navigation controls
|
||||
- [ ] Add zoom controls
|
||||
- [ ] Add print button
|
||||
|
||||
### Poll Creation
|
||||
- [ ] Allow poll creation before article save (store in temp state)
|
||||
- [ ] Add poll preview in article form
|
||||
- [ ] Bulk poll creation/linking
|
||||
- [ ] Poll templates
|
||||
|
||||
## Related Files
|
||||
- `frontend/src/components/common/FilePreview.tsx` - PDF preview component
|
||||
- `frontend/src/components/admin/PollLinker.tsx` - Poll management component
|
||||
- `frontend/src/pages/admin/ArticlesAdminPage.tsx` - Article editor
|
||||
- `internal/controllers/base_controller.go` - Backend file upload handler
|
||||
|
||||
## API Endpoints
|
||||
- `POST /api/v1/upload` - File upload (including PDFs)
|
||||
- `GET /uploads/**` - Serve uploaded files
|
||||
- `POST /api/v1/polls` - Create poll
|
||||
- `PUT /api/v1/polls/:id` - Update poll (link to article)
|
||||
- `GET /api/v1/polls?article_id=X` - Get polls for article
|
||||
@@ -0,0 +1,176 @@
|
||||
# Rich Text Editor Image Insertion Fix
|
||||
|
||||
**Date**: January 2025
|
||||
**Issue**: Quill.js image insertion errors and image duplication on drag
|
||||
|
||||
## Problems Fixed
|
||||
|
||||
### 1. Quill.js Emitter Error
|
||||
**Error Message**: `Uncaught TypeError: can't access property "emit", this.emitter is undefined`
|
||||
|
||||
**Root Cause**:
|
||||
- The Quill editor's internal state wasn't fully initialized when `insertEmbed` was called
|
||||
- Calling `insertEmbed` immediately after upload without ensuring the editor is ready caused the emitter to be undefined
|
||||
|
||||
**Solution**:
|
||||
```typescript
|
||||
// Insert into editor
|
||||
const quill = quillRef.current?.getEditor();
|
||||
if (quill) {
|
||||
// Ensure editor is focused and ready
|
||||
quill.focus();
|
||||
|
||||
// Use setTimeout to ensure Quill's internal state is ready
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const range = quill.getSelection();
|
||||
const index = range ? range.index : quill.getLength();
|
||||
|
||||
// Insert the image with 'api' source to prevent event loops
|
||||
quill.insertEmbed(index, 'image', res.url, 'api');
|
||||
|
||||
// Move cursor after the image
|
||||
quill.setSelection(index + 1, 0, 'api');
|
||||
|
||||
// Force content change to trigger re-render
|
||||
onChangeRef.current(quill.root.innerHTML);
|
||||
|
||||
toast({ title: 'Obrázek vložen', status: 'success', duration: 2000 });
|
||||
} catch (embedError) {
|
||||
console.error('Error inserting image:', embedError);
|
||||
toast({ title: 'Chyba při vkládání obrázku', description: String(embedError), status: 'error' });
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
```
|
||||
|
||||
**Key Changes**:
|
||||
- Added `quill.focus()` before insertion to ensure the editor has focus
|
||||
- Wrapped insertion in `setTimeout(50ms)` to allow Quill's internal state to stabilize
|
||||
- Changed source parameter from `'user'` to `'api'` to prevent triggering user-initiated event handlers
|
||||
- Added try-catch block for better error handling
|
||||
- Force content update with `onChangeRef.current(quill.root.innerHTML)` to trigger re-render
|
||||
|
||||
### 2. Image Not Showing After Insertion
|
||||
**Problem**: After uploading and inserting an image, it didn't appear in the editor
|
||||
|
||||
**Solution**:
|
||||
- Added explicit `onChangeRef.current(quill.root.innerHTML)` call after insertion
|
||||
- This forces React to recognize the DOM change and re-render the component
|
||||
- The `'api'` source parameter prevents infinite loops while still triggering the necessary updates
|
||||
|
||||
### 3. Image Duplication on Drag
|
||||
**Problem**: Dragging images created duplicates due to default browser drag-and-drop behavior
|
||||
|
||||
**Solution (Multiple Layers)**:
|
||||
|
||||
#### A. Set draggable attribute on image selection
|
||||
```typescript
|
||||
const selectImage = (img: HTMLImageElement) => {
|
||||
// ... other code ...
|
||||
|
||||
// Prevent default drag behavior to avoid duplication
|
||||
img.setAttribute('draggable', 'false');
|
||||
|
||||
createResizeHandle(img);
|
||||
// ... rest of code ...
|
||||
}
|
||||
```
|
||||
|
||||
#### B. Added dragstart event listener
|
||||
```typescript
|
||||
// Prevent default drag behavior on images
|
||||
const handleDragStart = (e: DragEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.tagName === 'IMG') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
editor.root.addEventListener('dragstart', handleDragStart);
|
||||
```
|
||||
|
||||
#### C. Added CSS to disable drag
|
||||
```typescript
|
||||
img: {
|
||||
cursor: 'pointer',
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
display: 'block',
|
||||
margin: '12px 0',
|
||||
transition: 'all 0.2s ease',
|
||||
borderRadius: '4px',
|
||||
userSelect: 'none',
|
||||
pointerEvents: 'auto',
|
||||
WebkitUserDrag: 'none', // Disable WebKit drag
|
||||
userDrag: 'none', // Disable standard drag
|
||||
// ... hover styles ...
|
||||
}
|
||||
```
|
||||
|
||||
## Files Modified
|
||||
|
||||
### `/frontend/src/components/common/CustomRichEditor.tsx`
|
||||
|
||||
**Lines 274-301**: Image insertion with proper Quill initialization
|
||||
**Lines 523-524**: Set draggable="false" on image selection
|
||||
**Lines 735-743**: Added dragstart event handler
|
||||
**Lines 755**: Added dragstart cleanup in useEffect return
|
||||
**Lines 1146-1147**: Added CSS properties to disable drag
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. **Test Image Insertion**:
|
||||
- Open any admin page with rich text editor (Articles, Activities, About)
|
||||
- Click "Vložit obrázek" button
|
||||
- Upload an image and crop it
|
||||
- Verify image appears immediately in the editor
|
||||
- Check browser console for no errors
|
||||
|
||||
2. **Test Image Drag Behavior**:
|
||||
- Insert an image in the editor
|
||||
- Click to select the image (blue outline appears)
|
||||
- Try to drag the image left/right
|
||||
- Verify alignment changes but no duplicate is created
|
||||
- Verify the image doesn't create a "ghost" drag preview
|
||||
|
||||
3. **Test Image Resize**:
|
||||
- Select an image
|
||||
- Grab the blue corner/edge handles
|
||||
- Resize the image
|
||||
- Verify smooth resizing without duplication
|
||||
|
||||
4. **Test Multiple Images**:
|
||||
- Insert several images
|
||||
- Interact with each one
|
||||
- Verify no interference between images
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
The fixes use standard web APIs and should work in:
|
||||
- ✅ Chrome/Edge (Chromium-based)
|
||||
- ✅ Firefox
|
||||
- ✅ Safari (WebKit)
|
||||
|
||||
The `-webkit-user-drag` CSS property specifically targets WebKit browsers.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- The 50ms delay in image insertion is a workaround for Quill's initialization timing
|
||||
- If issues persist, the delay can be increased to 100ms, but this may cause noticeable lag
|
||||
- The drag prevention is comprehensive but may interfere with future drag-and-drop features if needed
|
||||
|
||||
## Related Files
|
||||
|
||||
- `/frontend/src/components/common/RichTextEditor.tsx` - Wrapper component
|
||||
- `/frontend/src/services/imageProcessing.ts` - Image upload/crop backend services
|
||||
- `/frontend/src/styles/custom-editor.css` - Additional editor styles
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. Consider migrating to Quill 2.0 when stable (better event handling)
|
||||
2. Add loading state during image upload to prevent multiple insertions
|
||||
3. Add image compression options in the crop modal
|
||||
4. Consider lazy loading for large images
|
||||
Reference in New Issue
Block a user