diff --git a/memento-note/app/globals.css b/memento-note/app/globals.css
index cfe4540..981d8ff 100644
--- a/memento-note/app/globals.css
+++ b/memento-note/app/globals.css
@@ -3000,4 +3000,37 @@ html.font-system * {
border-radius: 2px;
box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.3);
color: inherit;
-}
\ No newline at end of file
+}
+
+/* Multi-column layout — seamless like Notion, no boxes */
+.tiptap-columns {
+ display: flex !important;
+ gap: 24px !important;
+ margin: 8px 0 !important;
+ padding: 0 !important;
+ border: none !important;
+ background: none !important;
+ border-radius: 0 !important;
+}
+
+.tiptap-column {
+ flex: 1 1 0% !important;
+ min-width: 0 !important;
+ padding: 0 !important;
+ border: none !important;
+ border-radius: 0 !important;
+ background: none !important;
+ border-left: 1px solid color-mix(in oklab, var(--foreground) 8%, transparent) !important;
+ padding-left: 24px !important;
+ overflow-wrap: break-word !important;
+ word-break: break-word !important;
+}
+
+.tiptap-column:first-child {
+ border-left: none !important;
+ padding-left: 0 !important;
+}
+
+.tiptap-column > *:first-child { margin-top: 0; }
+.tiptap-column > *:last-child { margin-bottom: 0; }
+.tiptap-column p { margin: 4px 0; }
\ No newline at end of file
diff --git a/memento-note/components/block-action-menu.tsx b/memento-note/components/block-action-menu.tsx
index ab61524..54ad401 100644
--- a/memento-note/components/block-action-menu.tsx
+++ b/memento-note/components/block-action-menu.tsx
@@ -14,12 +14,14 @@ import {
Heading1, Heading2, Heading3, List, ListOrdered,
CheckSquare, Quote, CodeXml, Database,
ArrowUp, ArrowDown, AlignLeft, ClipboardCopy, Sparkles,
- ChevronsRightLeft, MessageSquareWarning, ListTree,
+ ChevronsRightLeft, MessageSquareWarning, ListTree, Columns3,
+ Plus, Minus,
} from 'lucide-react'
import { replaceBlockWithStructuredView } from '@/components/tiptap-structured-view-block-extension'
import { insertToggleBlock, turnIntoToggleBlock } from '@/components/tiptap-toggle-extension'
import { turnIntoCalloutBlock } from '@/components/tiptap-callout-extension'
import { insertOutlineBlock } from '@/components/tiptap-outline-extension'
+import { insertColumnsBlock } from '@/components/tiptap-columns-extension'
interface BlockActionMenuProps {
editor: Editor
@@ -391,6 +393,12 @@ export function BlockActionMenu({
{t('richTextEditor.slashOutline')}
+ {/* Colonnes */}
+
+
{/* Création de diagramme */}
diff --git a/memento-note/components/rich-text-editor.tsx b/memento-note/components/rich-text-editor.tsx
index 34fb9fa..4a1d344 100644
--- a/memento-note/components/rich-text-editor.tsx
+++ b/memento-note/components/rich-text-editor.tsx
@@ -32,6 +32,7 @@ import { OutlineExtension, insertOutlineBlock } from './tiptap-outline-extension
import { FindReplaceBar, FindReplaceExtension } from './editor-find-replace-bar'
import { LinkPreviewExtension, insertLinkPreview, isUrl } from './tiptap-link-preview-extension'
import { MathEquationExtension, InlineMathExtension, insertMathEquation } from './tiptap-math-extension'
+import { ColumnsExtension, ColumnNode, insertColumnsBlock } from './tiptap-columns-extension'
import { RtlPreserveExtension } from './tiptap-rtl-preserve-extension'
import { ClipArticleExtension } from './tiptap-clip-article-extension'
import { BlockPicker, type BlockSuggestion } from './block-picker'
@@ -68,7 +69,7 @@ import {
FileText, Pilcrow, MessageSquare, AlignLeft, AlignCenter, AlignRight,
Superscript as SuperscriptIcon, Subscript as SubscriptIcon, Expand, Plus,
SpellCheck, Languages, BookOpen, Presentation, BarChart3, Database,
- ChevronsRightLeft, MessageSquareWarning, ListTree, FunctionSquare
+ ChevronsRightLeft, MessageSquareWarning, ListTree, FunctionSquare, Columns3
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { toast } from 'sonner'
@@ -231,6 +232,10 @@ const slashCommands: SlashItem[] = [
title: 'Math', description: 'LaTeX equation block', icon: FunctionSquare, category: 'Basic blocks', shortcut: '$$',
command: (e) => { insertMathEquation(e) },
},
+ {
+ title: 'Columns', description: 'Side-by-side layout', icon: Columns3, category: 'Basic blocks', shortcut: '/cols',
+ command: (e) => { insertColumnsBlock(e, 2) },
+ },
]
async function aiReformulate(text: string, option: string, t: any, language?: string): Promise {
@@ -480,6 +485,8 @@ export const RichTextEditor = forwardRef Number(el.getAttribute('index')) || 0,
+ renderHTML: (attrs) => ({ index: attrs.index }),
+ },
+ }
+ },
+
+ parseHTML() {
+ return [{ tag: 'div[data-type="column"]' }]
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return [
+ 'div',
+ mergeAttributes(HTMLAttributes, {
+ 'data-type': 'column',
+ class: 'tiptap-column',
+ }),
+ 0,
+ ]
+ },
+})
+
+export const ColumnsExtension = Node.create({
+ name: 'columns',
+ group: 'block',
+ content: 'column{1,}',
+ isolating: true,
+ defining: true,
+ allowGapCursor: false,
+
+ addAttributes() {
+ return {
+ cols: {
+ default: 2,
+ parseHTML: (el) => Number(el.getAttribute('cols')) || 2,
+ renderHTML: (attrs) => ({ cols: attrs.cols }),
+ },
+ }
+ },
+
+ parseHTML() {
+ return [{ tag: 'div[data-type="columns"]' }]
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return [
+ 'div',
+ mergeAttributes(HTMLAttributes, {
+ 'data-type': 'columns',
+ class: 'tiptap-columns',
+ }),
+ 0,
+ ]
+ },
+
+ addCommands() {
+ return {
+ addColumnAfter: () => ({ state, dispatch }: { state: EditorState; dispatch?: (tr: Transaction) => void }) => {
+ return addOrDeleteCol(state, dispatch, 'addAfter')
+ },
+ addColumnBefore: () => ({ state, dispatch }: { state: EditorState; dispatch?: (tr: Transaction) => void }) => {
+ return addOrDeleteCol(state, dispatch, 'addBefore')
+ },
+ deleteColumn: () => ({ state, dispatch }: { state: EditorState; dispatch?: (tr: Transaction) => void }) => {
+ return addOrDeleteCol(state, dispatch, 'delete')
+ },
+ }
+ },
+
+ addKeyboardShortcuts() {
+ return {
+ 'Mod-Shift-L': () => this.editor.commands.insertContent({
+ type: this.name,
+ attrs: { cols: 2 },
+ content: [
+ { type: 'column', attrs: { index: 0 }, content: [{ type: 'paragraph' }] },
+ { type: 'column', attrs: { index: 1 }, content: [{ type: 'paragraph' }] },
+ ],
+ }),
+ }
+ },
+})
+
+function addOrDeleteCol(state: EditorState, dispatch?: (tr: Transaction) => void, type?: 'addBefore' | 'addAfter' | 'delete'): boolean {
+ const maybeColumns = findParentNode((node: PMNode) => node.type.name === 'columns')(state.selection)
+ const maybeColumn = findParentNode((node: PMNode) => node.type.name === 'column')(state.selection)
+
+ if (!maybeColumns || !maybeColumn) return false
+
+ const cols = maybeColumns.node
+ const colIndex = maybeColumn.node.attrs.index
+ const colsJSON = cols.toJSON()
+
+ let nextIndex = colIndex
+
+ if (type === 'delete') {
+ if (colsJSON.content.length <= 1) return false
+ nextIndex = Math.max(0, colIndex - 1)
+ colsJSON.content.splice(colIndex, 1)
+ } else {
+ nextIndex = type === 'addBefore' ? colIndex : colIndex + 1
+ colsJSON.content.splice(nextIndex, 0, {
+ type: 'column',
+ attrs: { index: colIndex },
+ content: [{ type: 'paragraph' }],
+ })
+ }
+
+ colsJSON.attrs.cols = colsJSON.content.length
+ colsJSON.content.forEach((colJSON: any, index: number) => {
+ colJSON.attrs.index = index
+ })
+
+ const nextCols = PMNode.fromJSON(state.schema, colsJSON)
+
+ let nextSelectPos = maybeColumns.pos + 1
+ nextCols.content.forEach((col: PMNode, _pos: number, index: number) => {
+ if (index < nextIndex) nextSelectPos += col.nodeSize
+ })
+
+ if (dispatch) {
+ const tr = state.tr
+ tr.replaceWith(maybeColumns.pos, maybeColumns.pos + maybeColumns.node.nodeSize, nextCols)
+ tr.setSelection(TextSelection.near(tr.doc.resolve(nextSelectPos + 1)))
+ dispatch(tr)
+ }
+
+ return true
+}
+
+export function insertColumnsBlock(editor: any, columns: number = 2) {
+ const content = Array.from({ length: columns }, (_, i) => ({
+ type: 'column',
+ attrs: { index: i },
+ content: [{ type: 'paragraph' }],
+ }))
+ editor.chain().focus().insertContent({
+ type: 'columns',
+ attrs: { cols: columns },
+ content,
+ }).run()
+}
diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json
index c3e2fe8..8cd651e 100644
--- a/memento-note/locales/en.json
+++ b/memento-note/locales/en.json
@@ -2445,6 +2445,12 @@
"mathCancel": "Cancel",
"mathAi": "Write with AI",
"mathAiPlaceholder": "Describe the equation in words... (e.g: quadratic formula)",
+ "slashColumns": "Columns",
+ "slashColumnsDesc": "Put content side by side",
+ "columnsRemove": "Remove a column",
+ "columnsAdd": "Add a column",
+ "columnsDelete": "Delete columns",
+ "columnsLabel": "columns",
"calloutDelete": "Delete callout",
"calloutUnwrap": "Disable callout",
"calloutInfo": "Information",
diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json
index 84b6d11..01bd3eb 100644
--- a/memento-note/locales/fr.json
+++ b/memento-note/locales/fr.json
@@ -2449,6 +2449,12 @@
"mathCancel": "Annuler",
"mathAi": "Écrire avec l'IA",
"mathAiPlaceholder": "Décris l'équation en mots... (ex: formule quadratique)",
+ "slashColumns": "Colonnes",
+ "slashColumnsDesc": "Mettre du contenu côte à côte",
+ "columnsRemove": "Retirer une colonne",
+ "columnsAdd": "Ajouter une colonne",
+ "columnsDelete": "Supprimer les colonnes",
+ "columnsLabel": "colonnes",
"calloutDelete": "Supprimer l'encadré",
"calloutUnwrap": "Désactiver l'encadré",
"calloutInfo": "Information",