collapsables
This commit is contained in:
@@ -19,6 +19,9 @@
|
|||||||
"@tiptap/core": "^3.14.0",
|
"@tiptap/core": "^3.14.0",
|
||||||
"@tiptap/extension-code-block-lowlight": "^3.14.0",
|
"@tiptap/extension-code-block-lowlight": "^3.14.0",
|
||||||
"@tiptap/extension-color": "^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-image": "^3.14.0",
|
||||||
"@tiptap/extension-link": "^3.14.0",
|
"@tiptap/extension-link": "^3.14.0",
|
||||||
"@tiptap/extension-list-item": "^3.14.0",
|
"@tiptap/extension-list-item": "^3.14.0",
|
||||||
|
|||||||
100
src/app.css
100
src/app.css
@@ -810,7 +810,6 @@ a.hover-underline-animation:hover::after {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Task list styles */
|
|
||||||
ul[data-type="taskList"] {
|
ul[data-type="taskList"] {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -820,51 +819,80 @@ ul[data-type="taskList"] {
|
|||||||
ul[data-type="taskList"] li {
|
ul[data-type="taskList"] li {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0.5rem 0;
|
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul[data-type="taskList"] li > label {
|
/* Collapsible section (details/summary) styles */
|
||||||
flex: 0 0 auto;
|
details {
|
||||||
user-select: none;
|
margin: 1.5rem 0;
|
||||||
display: flex;
|
padding: 1rem;
|
||||||
align-items: center;
|
border: 1px solid var(--color-surface2);
|
||||||
margin: 0;
|
border-radius: 0.5rem;
|
||||||
padding: 0;
|
background-color: var(--color-surface0);
|
||||||
line-height: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul[data-type="taskList"] li > div {
|
summary {
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul[data-type="taskList"] input[type="checkbox"] {
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 1em;
|
font-weight: 600;
|
||||||
height: 1em;
|
user-select: none;
|
||||||
margin: 0;
|
padding: 0.5rem;
|
||||||
padding: 0;
|
margin: -1rem -1rem 0 -1rem;
|
||||||
flex-shrink: 0;
|
background-color: var(--color-surface1);
|
||||||
accent-color: var(--color-blue);
|
border-radius: 0.5rem 0.5rem 0 0;
|
||||||
transform: none;
|
transition: background-color 0.2s;
|
||||||
vertical-align: middle;
|
list-style: none;
|
||||||
-webkit-appearance: checkbox;
|
|
||||||
appearance: checkbox;
|
|
||||||
border: 2px solid var(--color-blue);
|
|
||||||
border-radius: 0.15em;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul[data-type="taskList"] input[type="checkbox"]:checked {
|
summary::-webkit-details-marker {
|
||||||
background-color: var(--color-blue);
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul[data-type="taskList"] li[data-checked="true"] > div {
|
summary::before {
|
||||||
text-decoration: line-through;
|
content: "▶ ";
|
||||||
opacity: 0.6;
|
color: var(--color-blue);
|
||||||
|
display: inline-block;
|
||||||
|
transition: transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Nested task lists */
|
details[open] summary::before {
|
||||||
ul[data-type="taskList"] ul[data-type="taskList"] {
|
transform: rotate(90deg);
|
||||||
margin: 0.5rem 0 0.5rem 1.5rem;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import { TableHeader } from "@tiptap/extension-table-header";
|
|||||||
import { TableCell } from "@tiptap/extension-table-cell";
|
import { TableCell } from "@tiptap/extension-table-cell";
|
||||||
import TaskList from "@tiptap/extension-task-list";
|
import TaskList from "@tiptap/extension-task-list";
|
||||||
import TaskItem from "@tiptap/extension-task-item";
|
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 { Node } from "@tiptap/core";
|
||||||
import { createLowlight, common } from "lowlight";
|
import { createLowlight, common } from "lowlight";
|
||||||
import css from "highlight.js/lib/languages/css";
|
import css from "highlight.js/lib/languages/css";
|
||||||
@@ -225,6 +228,9 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
class: "task-item"
|
class: "task-item"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
Details,
|
||||||
|
DetailsSummary,
|
||||||
|
DetailsContent,
|
||||||
Table.configure({
|
Table.configure({
|
||||||
resizable: true,
|
resizable: true,
|
||||||
HTMLAttributes: {
|
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 insertCodeBlock = (language: string | null) => {
|
||||||
const instance = editor();
|
const instance = editor();
|
||||||
if (!instance) return;
|
if (!instance) return;
|
||||||
@@ -1013,6 +1051,14 @@ export default function TextEditor(props: TextEditorProps) {
|
|||||||
>
|
>
|
||||||
" Quote
|
" Quote
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={insertCollapsibleSection}
|
||||||
|
class="hover:bg-surface1 rounded px-2 py-1 text-xs"
|
||||||
|
title="Insert Collapsible Section"
|
||||||
|
>
|
||||||
|
▼ Details
|
||||||
|
</button>
|
||||||
<div class="border-surface2 mx-1 border-l"></div>
|
<div class="border-surface2 mx-1 border-l"></div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user