feat(editor): implement US-EDITOR-UX with global block selection, redesigned slash menu (favorites & preview), contextual placeholders, smart paste extended, turn into and undo/redo toasts

This commit is contained in:
Antigravity
2026-05-27 21:47:50 +00:00
parent e3cb1307d3
commit ad8b8b815e
9 changed files with 1112 additions and 157 deletions

View File

@@ -0,0 +1,60 @@
import { Extension } from '@tiptap/core'
import { Plugin, PluginKey, NodeSelection } from '@tiptap/pm/state'
import { Decoration, DecorationSet } from '@tiptap/pm/view'
/**
* Extension ProseMirror/TipTap qui ajoute la classe CSS 'block-selected'
* aux blocs de premier niveau inclus ou traversés par la sélection active.
*/
export const BlockSelectionExtension = Extension.create({
name: 'blockSelection',
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('blockSelection'),
props: {
decorations(state) {
const { from, to, empty } = state.selection
if (empty) return DecorationSet.empty
const decorations: Decoration[] = []
// Parcourir uniquement les nœuds de premier niveau (enfants directs de doc)
state.doc.forEach((node, offset) => {
const start = offset
const end = offset + node.nodeSize
// Vérifier si la plage de sélection [from, to] intersecte le bloc [start, end]
const intersects = Math.max(start, from) < Math.min(end, to)
if (intersects) {
// Il faut s'assurer que c'est un nœud de bloc pour appliquer Decoration.node
if (node.isBlock) {
try {
decorations.push(
Decoration.node(start, end, {
class: 'block-selected',
})
)
} catch (e) {
// Fallback en cas d'erreur sur des structures de nœuds particulières
}
}
}
})
const isNodeSel = state.selection instanceof NodeSelection
// Si la sélection s'étend sur plusieurs blocs ou si c'est une NodeSelection explicite,
// on renvoie l'ensemble des décorations pour coloration visuelle.
if (decorations.length > 1 || (decorations.length === 1 && isNodeSel)) {
return DecorationSet.create(state.doc, decorations)
}
return DecorationSet.empty
},
},
}),
]
},
})