diff --git a/bun.lockb b/bun.lockb index a9d19e1..27d31fe 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 2b102c7..839d3b5 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ "@tiptap/core": "^3.14.0", "@tiptap/extension-code-block-lowlight": "^3.14.0", "@tiptap/extension-color": "^3.14.0", + "@tiptap/extension-details": "^3.14.0", + "@tiptap/extension-details-content": "^2.26.2", + "@tiptap/extension-details-summary": "^2.26.2", "@tiptap/extension-image": "^3.14.0", "@tiptap/extension-link": "^3.14.0", "@tiptap/extension-list-item": "^3.14.0", diff --git a/src/app.css b/src/app.css index 841f1e5..db5a622 100644 --- a/src/app.css +++ b/src/app.css @@ -810,7 +810,6 @@ a.hover-underline-animation:hover::after { pointer-events: none; } -/* Task list styles */ ul[data-type="taskList"] { list-style: none; padding: 0; @@ -820,51 +819,80 @@ ul[data-type="taskList"] { ul[data-type="taskList"] li { display: flex; align-items: center; - margin: 0.5rem 0; gap: 0.5rem; + line-height: 0; } -ul[data-type="taskList"] li > label { - flex: 0 0 auto; - user-select: none; - display: flex; - align-items: center; - margin: 0; - padding: 0; - line-height: 1; +/* Collapsible section (details/summary) styles */ +details { + margin: 1.5rem 0; + padding: 1rem; + border: 1px solid var(--color-surface2); + border-radius: 0.5rem; + background-color: var(--color-surface0); } -ul[data-type="taskList"] li > div { - flex: 1 1 auto; -} - -ul[data-type="taskList"] input[type="checkbox"] { +summary { cursor: pointer; - width: 1em; - height: 1em; - margin: 0; - padding: 0; - flex-shrink: 0; - accent-color: var(--color-blue); - transform: none; - vertical-align: middle; - -webkit-appearance: checkbox; - appearance: checkbox; - border: 2px solid var(--color-blue); - border-radius: 0.15em; - background-color: transparent; + 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; } -ul[data-type="taskList"] input[type="checkbox"]:checked { - background-color: var(--color-blue); +summary::-webkit-details-marker { + display: none; } -ul[data-type="taskList"] li[data-checked="true"] > div { - text-decoration: line-through; - opacity: 0.6; +summary::before { + content: "▶ "; + color: var(--color-blue); + display: inline-block; + transition: transform 0.2s; } -/* Nested task lists */ -ul[data-type="taskList"] ul[data-type="taskList"] { - margin: 0.5rem 0 0.5rem 1.5rem; +details[open] summary::before { + transform: rotate(90deg); + color: var(--color-green); +} + +summary:hover { + background-color: var(--color-surface2); +} + +details[open] summary { + margin-bottom: 1rem; + border-bottom: 1px solid var(--color-surface2); + border-radius: 0.5rem 0.5rem 0 0; +} + +/* Content inside details */ +details div[data-type="details-content"] { + padding: 0.5rem; +} + +/* Nested details */ +details details { + margin: 1rem 0; + border-color: var(--color-surface1); +} + +/* Animation */ +details[open] div[data-type="details-content"] { + animation: slideDown 0.2s ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } } diff --git a/src/components/blog/TextEditor.tsx b/src/components/blog/TextEditor.tsx index 174e2c0..1a72e0c 100644 --- a/src/components/blog/TextEditor.tsx +++ b/src/components/blog/TextEditor.tsx @@ -10,6 +10,9 @@ import { TableHeader } from "@tiptap/extension-table-header"; import { TableCell } from "@tiptap/extension-table-cell"; import TaskList from "@tiptap/extension-task-list"; import TaskItem from "@tiptap/extension-task-item"; +import Details from "@tiptap/extension-details"; +import DetailsSummary from "@tiptap/extension-details-summary"; +import DetailsContent from "@tiptap/extension-details-content"; import { Node } from "@tiptap/core"; import { createLowlight, common } from "lowlight"; import css from "highlight.js/lib/languages/css"; @@ -225,6 +228,9 @@ export default function TextEditor(props: TextEditorProps) { class: "task-item" } }), + Details, + DetailsSummary, + DetailsContent, Table.configure({ resizable: true, HTMLAttributes: { @@ -336,6 +342,38 @@ export default function TextEditor(props: TextEditorProps) { } }; + const insertCollapsibleSection = () => { + const instance = editor(); + if (!instance) return; + + const title = window.prompt("Section title:", "Click to expand"); + + if (title !== null) { + instance + .chain() + .focus() + .insertContent({ + type: "details", + content: [ + { + type: "detailsSummary", + content: [{ type: "text", text: title }] + }, + { + type: "detailsContent", + content: [ + { + type: "paragraph", + content: [{ type: "text", text: "Add your content here..." }] + } + ] + } + ] + }) + .run(); + } + }; + const insertCodeBlock = (language: string | null) => { const instance = editor(); if (!instance) return; @@ -1013,6 +1051,14 @@ export default function TextEditor(props: TextEditorProps) { > " Quote +