Skip to content

Fix editor article#36

Merged
raynaldlao merged 20 commits into
masterfrom
fix-editor-article
Jul 7, 2026
Merged

Fix editor article#36
raynaldlao merged 20 commits into
masterfrom
fix-editor-article

Conversation

@raynaldlao

@raynaldlao raynaldlao commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Description

Cette PR couvre l'ensemble des corrections apportées à l'éditeur BlockNote, l'intégration vidéo YouTube, la navigation dans les blocs void, le fallback Firefox Reader Mode, le nettoyage des fichiers orphelins, la compatibilité BlockNote v0.51+ dans le convertisseur ProseMirror, et divers correctifs CSS/session.


Modifications

🎬 Blocs vidéo YouTube

  • video-override-spec.js : création de createYouTubeVideoSpec qui force les URLs YouTube uniquement. Les URLs valides s'affichent en iframe 16:9 avec bascule pointer-events, les URLs invalides déclenchent un toast et sont supprimées. Le viewer (ArticleViewer.jsx) les masque silencieusement
  • ArticleForm.jsx : surcharge du label du bouton d'ajout vidéo via applyVideoDictOverrides avec injection file_blocks.add_button_text.video
  • article.css : masquage de l'onglet YouTube URL dans la popover de remplacement d'image (ciblé via .bn-panel-popover)
  • CustomFilePanel : affichage du bon panneau selon le type de bloc — Upload pour les images, Embed pour les vidéos. Correction du crash UploadTab en passant setLoading, désactivation du filePanel natif BlockNoteView au profit d'un FilePanelController enfant
  • Bouton upload : rendu permanent (plus de toggle dans handleSelectionChange), ajout de .bn-panel à la liste d'exclusion mousedown

🧭 Navigation dans les blocs void

  • Suppression du gap-cursor ProseMirror : désactivation de gapCursor dans useCreateBlockNote, élimination complète de la navigation par curseur clignotant
  • Navigation fléchée : implémentation DOM-based pour les blocs void via elementFromPoint, détection de direction avec arrowDirRef, saut vers le bloc void adjacent, masquage CSS des wrappers fantômes .bn-visual-media-wrapper
  • Stabilité du scroll : scrollBefore setTextCursorPosition, RAF fallback pour les transitions void→texte, scrollThreshold/scrollMargin pour réduire l'auto-scroll PM, scroll-margin-top pour un offset nav-bar cohérent
  • Correction Backspace : interception en capture phase, collapse du range DOM accidentel sur paragraphe gap-click vide pour stopper la cascade deleteSelection
  • Support gap-click vidéo : ajout de [data-content-type="video"] dans BLOCK_SELECTOR pour que les blocs vidéo vides déclenchent aussi l'insertion de paragraphe
  • Bordure de sélection : nettoyage de .ProseMirror-selectednode uniquement quand le curseur est sur un bloc image, préservation de la bordure sur les blocs vidéo

📖 Fallback Firefox Reader Mode

  • article_detail.html et article_edit.html : rendu du contenu de l'article en HTML statique, masqué par défaut via CSS (position: absolute; left: -99999px), révélé dans Firefox Reader Mode qui ignore ces styles. Balise <meta name="author"> pour l'affichage natif de l'auteur
  • article.css : styles pour .article-static-content (titres, paragraphes, blockquote, code, images, tableaux). .meta-author en italique
  • ArticleViewer.jsx : suppression du fallback statique au montage React via useEffect
  • Espacement Reader Mode : <br> après </table> dans le convertisseur pour éviter le chevauchement entre éléments adjacents (blockquote, table) quand Reader Mode supprime tous les CSS

🗑️ Nettoyage des fichiers orphelins

  • file_management.py / file_storage_repository.py : ajout de delete(file_id) abstrait dans les ports
  • file_service.py : implémentation de delete_file(file_id) qui délègue au storage
  • article_service.py : _extract_image_uuids() (fonction module-level) qui parcourt l'arbre BlockNote et extrait les UUIDs depuis props/attrs. Appelée dans delete_article() et update_article() pour supprimer les fichiers non référencés
  • sqlalchemy_file_storage_adapter.py : delete() idempotent

🔄 Compatibilité BlockNote v0.51+

utils/prosemirror_to_html.py :

  • Tableau BlockNote : support du format content en tant qu'objet avec rows/cells (BlockNote v0.51+) tout en conservant la rétrocompatibilité ProseMirror (liste de tableRow)
  • Styles de texte BlockNote : support du format styles: {"bold": true} (objet booléen) en plus du format legacy marks: [{"type": "bold"}]
  • Quote BlockNote : ajout de "type": "quote" comme alias de "type": "blockquote"
  • Fallback props/attrs : lecture de props en priorité, fallback attrs (compatibilité ascendante/descendante)

🎨 CSS

frontend/static/css/article.css :

  • Label langage du code block : correction de contraste pour lisibilité sur fond sombre. color: var(--on-primary) (blanc) en light mode, color: var(--on-surface) (blanc cassé) en dark mode via [data-theme="dark"]. Rendu visible à 65% d'opacité en mode readonly sans hover
  • Tableau statique : border-collapse, padding: 0.5rem 0.75rem, border: 1px solid var(--outline), largeur 100% avec variables de thème
  • Nettoyage de classes CSS mortes (.detail-body, .comment-reply-input, .article-form-actions)

⏱️ Session

  • blog_comment_application.py : durée de session augmentée de 30 min → 12h

Fichiers modifiés

blog_comment_application.py
docker-compose.yml
frontend/core/tests/useCodeBlockGapClick.test.js
frontend/core/tests/video-dict.test.js
frontend/core/tests/video-override-spec.test.js
frontend/core/components/ArticleForm.jsx
frontend/core/components/ArticleViewer.jsx
frontend/core/hooks/useCodeBlockGapClick.js
frontend/core/utils/video-override-spec.js
frontend/static/css/article.css
frontend/templates/article_detail.html
frontend/templates/article_edit.html
src/application/input_ports/file_management.py
src/application/output_ports/file_storage_repository.py
src/application/services/article_service.py
src/application/services/file_service.py
src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_file_storage_adapter.py
src/infrastructure/output_adapters/sqlalchemy/sqlalchemy_setup_database.py
tests/test_prosemirror_to_html.py
tests/tests_infrastructure/tests_input_adapters/tests_flask/flask_test_utils.py
tests/tests_integration/test_security_integration.py
tests/tests_services/test_article_service.py
tests/tests_services/test_file_service.py
utils/prosemirror_to_html.py

Notes techniques

  • --on-primary vaut #3f008e (violet foncé) en dark mode → invisible sur fond sombre du code block. Override [data-theme="dark"] corrige
  • Firefox Reader Mode supprime tous les CSS (y compris inline style=""). Solution : <br> structural après </table>, contenu HTML statique en position: absolute; left: -99999px
  • Le tableau BlockNote utilise content comme objet (pas une liste). Géré via isinstance(content, dict)
  • props est le format BlockNote v0.51+, attrs est le format legacy ProseMirror. Les deux sont supportés
  • Blocs void (image, vidéo) : le gap-cursor ProseMirror est désactivé, navigation par elementFromPoint DOM avec détection de direction
  • Rétrocompatibilité assurée : tous les tests legacy passent sans modification

raynaldlao added 20 commits July 2, 2026 02:58
…bn-panel to the mousedown skip list removing the uploadFile toggle from handleSelectionChange so the image upload tab stays visible and preventing the file panel from closing when clicking the upload button
…load for image blocks and only Embed for video blocks fixes UploadTab crash by passing setLoading and disables BlockNoteView’s filePanel while delegating to a child FilePanelController for correct tab filtering
…tentional clicks by cleaning up .ProseMirror-selectednode only when the cursor is on an image block fixing the border leak after inserting an image via slash command and keeping the border visible on video blocks for proper user feedback thanks to an early return when blockType is not image
…ugin by disabling gapCursor in useCreateBlockNote eliminates all gap‑cursor navigation and blinking underscores removes the old
…s using DOM‑based targeting replaces unreliable BlockNote detection with a keydown fallback uses elementFromPoint to avoid jumps between distant blocks hides the selected bn‑visual‑media-wrapper ghost via CSS and keeps console diagnostics for gap‑cursor debugging
…d‑block arrow navigation tracks arrow direction with arrowDirRef detects empty bn‑visual‑media-wrapper ghosts in RAF and jumps to the adjacent void block instead and removes leftover debug logs
…ast void blocks by scrolling before setTextCursorPosition adding a RAF fallback for void→text transitions reducing PM auto‑scroll with scrollThreshold/scrollMargin disabling gapCursor hiding bn‑visual‑media-wrapper ghosts via CSS and applying scroll‑margin‑top for consistent nav‑bar offset
…ast void blocks by scrolling before setTextCursorPosition so ProseMirror doesn’t override manual positioning and adds a fallback for void→text transitions where the ghost wrapper disappears before RAF runs updating only ArticleForm.jsx for scroll correction and fallback
…Replace popover by targeting the tab inside .bn-panel-popover so the tab disappears for image replacement while remaining visible for video insertion
… through createYouTubeVideoSpec so valid URLs render as 16/9 iframes with pointer‑events toggle while invalid URLs trigger a toast and are removed and the viewer hides them silently updating ArticleForm.jsx and ArticleViewer.jsx to use the factory and adding a dedicated test suite
…[data-content-type="video"] in BLOCK_SELECTOR so empty native video blocks without a bn‑visual‑media-wrapper also trigger gap‑click paragraph insertion updating useCodeBlockGapClick.js accordingly
… by injecting file_blocks.add_button_text.video in applyVideoDictOverrides and fixing initial render with a DOM post‑process then adds a focused test in video-dict.test.js for the new dictionary key
…ting Backspace in capture phase, collapsing any accidental non‑collapsed DOM range on an empty gap‑click paragraph to stop ProseMirror’s deleteSelection cascade, removing all debug logs and adding two Backspace‑interception tests plus a proper afterEach unmount to avoid stale listener leakage
…ideo blocks by keeping BlockNote’s native no‑URL render in view‑only mode while overriding the placeholder button text, with changes limited to video-override-spec.js
…to restore all block attributes, drop unused YouTube regex/helpers, add <meta name="author"> for Firefox Reader Mode, revert the reader‑fallback markup experiment and clean up CSS (italic .meta-author, remove dead reader blocks). Tests updated for props/attrs compatibility and E501 fixes
…/update by reading image UUIDs from BlockNote `props`, comparing them to stored files, and deleting any unreferenced ones. Add delete() to storage ports, implement delete_file() in file_service, call it from article_service, provide idempotent SQLAlchemy delete, inject file_service in app setup, and update tests for extraction, orphan cleanup and delegation
…edit page by rendering the article content as static HTML, hidden by default and revealed in Reader Mode. Include <meta name="author">, add the inline CSS + <noscript> override and reuse the same fallback pattern as article_detail.html
…edit page by rendering the article content as static HTML, hidden by default and revealed in Reader Mode. Include <meta name="author">, add the inline CSS + <noscript> override and reuse the same fallback pattern as article_detail.html
…, updating blog_comment_application.py and the security integration test accordingly
…mes, keep white (--on-primary) by default and override to --on-surface in dark mode so the trigger stays readable on dark Shiki backgrounds
@raynaldlao raynaldlao self-assigned this Jul 7, 2026
@raynaldlao
raynaldlao merged commit 37abe1b into master Jul 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant