diff --git a/src/app.css b/src/app.css index 70779d6..fa91ccd 100644 --- a/src/app.css +++ b/src/app.css @@ -890,6 +890,75 @@ details[open] div[data-type="details-content"] { animation: slideDown 0.2s ease-out; } +/* TipTap Details/Summary nodes - using data-type attributes */ +.ProseMirror [data-type="details"] { + margin: 1.5rem 0; + padding: 1rem; + border: 1px solid var(--color-surface2); + border-radius: 0.5rem; + background-color: var(--color-surface0); + position: relative; +} + +/* Hide the empty button that TipTap creates */ +.ProseMirror [data-type="details"] > button { + display: none; +} + +.ProseMirror [data-type="details"] summary { + cursor: pointer; + font-weight: 600; + user-select: none; + padding: 0.5rem; + margin: -1rem -1rem 0 -1rem; + background-color: var(--color-surface1); + border-radius: 0.5rem 0.5rem 0 0; + transition: background-color 0.2s; + list-style: none; +} + +.ProseMirror [data-type="details"] summary::-webkit-details-marker { + display: none; +} + +.ProseMirror [data-type="details"] summary::before { + content: "▶ "; + color: var(--color-blue); + display: inline-block; + transition: transform 0.2s; + margin-right: 0.25rem; +} + +.ProseMirror [data-type="details"][open] summary::before { + transform: rotate(90deg); + color: var(--color-green); +} + +.ProseMirror [data-type="details"] summary:hover { + background-color: var(--color-surface2); +} + +.ProseMirror [data-type="details"][open] summary { + margin-bottom: 1rem; + border-bottom: 1px solid var(--color-surface2); + border-radius: 0.5rem 0.5rem 0 0; +} + +.ProseMirror [data-type="detailsContent"] { + padding: 0.5rem; +} + +.ProseMirror [data-type="detailsContent"][hidden] { + display: none; +} + +.ProseMirror + [data-type="details"][open] + [data-type="detailsContent"]:not([hidden]) { + display: block; + animation: slideDown 0.2s ease-out; +} + @keyframes slideDown { from { opacity: 0; diff --git a/src/components/blog/TextEditor.tsx b/src/components/blog/TextEditor.tsx index 1655603..9aaf1fa 100644 --- a/src/components/blog/TextEditor.tsx +++ b/src/components/blog/TextEditor.tsx @@ -231,9 +231,17 @@ export default function TextEditor(props: TextEditorProps) { class: "task-item" } }), - Details, + Details.configure({ + HTMLAttributes: { + class: "tiptap-details" + } + }), DetailsSummary, - DetailsContent, + DetailsContent.configure({ + HTMLAttributes: { + class: "details-content" + } + }), Table.configure({ resizable: true, HTMLAttributes: { @@ -260,6 +268,37 @@ export default function TextEditor(props: TextEditorProps) { editorProps: { attributes: { class: "focus:outline-none" + }, + handleClickOn(view, pos, node, nodePos, event) { + const target = event.target as HTMLElement; + + // Check if click is on a summary element inside details + const summary = target.closest("summary"); + if (summary) { + const details = summary.closest('[data-type="details"]'); + if (details) { + // Toggle the open attribute + const isOpen = details.hasAttribute("open"); + if (isOpen) { + details.removeAttribute("open"); + } else { + details.setAttribute("open", ""); + } + // Also toggle hidden attribute on details content + const content = details.querySelector( + '[data-type="detailsContent"]' + ); + if (content) { + if (isOpen) { + content.setAttribute("hidden", "hidden"); + } else { + content.removeAttribute("hidden"); + } + } + return true; // Prevent default behavior + } + } + return false; } }, onUpdate: ({ editor }) => {