Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(docs): toggle blocks (collapsible sections)

Add native <details>/<summary> based toggle blocks to the document
editor. Available via /toggle slash command and command palette.
Sections are collapsible, collaborative via Yjs, and styled with
disclosure markers.

Closes #131

+109
+24
src/css/app.css
··· 2924 2924 font-size: 13px; 2925 2925 } 2926 2926 2927 + /* --- Toggle Block (collapsible section) #131 --- */ 2928 + .tiptap .toggle-block { 2929 + border: 1px solid var(--color-border); 2930 + border-radius: var(--radius-md); 2931 + padding: 0.5rem 0.75rem; 2932 + margin: 0.75em 0; 2933 + } 2934 + 2935 + .tiptap .toggle-block[open] { 2936 + padding-bottom: 0.75rem; 2937 + } 2938 + 2939 + .tiptap .toggle-summary { 2940 + cursor: pointer; 2941 + font-weight: 600; 2942 + padding: 0.25rem 0; 2943 + list-style: disclosure-closed; 2944 + } 2945 + 2946 + .tiptap .toggle-block[open] > .toggle-summary { 2947 + list-style: disclosure-open; 2948 + margin-bottom: 0.5rem; 2949 + } 2950 + 2927 2951 /* --- Print styles --- */ 2928 2952 @media print { 2929 2953 .app-topbar,
+3
src/docs/extensions/slash-commands.ts
··· 127 127 pageBreak: (editor: Editor) => { 128 128 editor.chain().focus().insertPageBreak().run(); 129 129 }, 130 + toggle: (editor: Editor) => { 131 + editor.chain().focus().insertToggleBlock().run(); 132 + }, 130 133 link: (editor: Editor) => { 131 134 const url = prompt('Link URL:'); 132 135 if (url) {
+70
src/docs/extensions/toggle-block.ts
··· 1 + import { Node, mergeAttributes } from '@tiptap/core'; 2 + 3 + /** 4 + * Toggle Block (collapsible section) — uses native <details>/<summary>. 5 + * 6 + * Creates a collapsible section in the document that the reader can 7 + * expand/collapse by clicking the summary. Content inside is fully 8 + * editable and collaborative via Yjs. 9 + * 10 + * Slash command: /toggle 11 + */ 12 + 13 + export const ToggleBlock = Node.create({ 14 + name: 'details', 15 + 16 + group: 'block', 17 + content: 'detailsSummary block+', 18 + defining: true, 19 + 20 + addAttributes() { 21 + return { 22 + open: { 23 + default: true, 24 + parseHTML: (el) => el.hasAttribute('open'), 25 + renderHTML: (attrs) => (attrs.open ? { open: '' } : {}), 26 + }, 27 + }; 28 + }, 29 + 30 + parseHTML() { 31 + return [{ tag: 'details' }]; 32 + }, 33 + 34 + renderHTML({ HTMLAttributes }) { 35 + return ['details', mergeAttributes(HTMLAttributes, { class: 'toggle-block' }), 0]; 36 + }, 37 + 38 + addCommands() { 39 + return { 40 + insertToggleBlock: 41 + () => 42 + ({ commands }) => { 43 + return commands.insertContent({ 44 + type: 'details', 45 + attrs: { open: true }, 46 + content: [ 47 + { type: 'detailsSummary', content: [{ type: 'text', text: 'Toggle heading' }] }, 48 + { type: 'paragraph' }, 49 + ], 50 + }); 51 + }, 52 + }; 53 + }, 54 + }); 55 + 56 + export const ToggleSummary = Node.create({ 57 + name: 'detailsSummary', 58 + 59 + group: 'block', 60 + content: 'inline*', 61 + defining: true, 62 + 63 + parseHTML() { 64 + return [{ tag: 'summary' }]; 65 + }, 66 + 67 + renderHTML({ HTMLAttributes }) { 68 + return ['summary', mergeAttributes(HTMLAttributes, { class: 'toggle-summary' }), 0]; 69 + }, 70 + });
+4
src/docs/main.ts
··· 36 36 import { LineSpacing, LINE_SPACING_PRESETS } from './extensions/line-spacing.js'; 37 37 import { ParagraphSpacing, PARAGRAPH_SPACING_PRESETS } from './extensions/paragraph-spacing.js'; 38 38 import { PageBreak } from './extensions/page-break.js'; 39 + import { ToggleBlock, ToggleSummary } from './extensions/toggle-block.js'; 39 40 import { SuggestionInsert } from './extensions/suggestion-insert.js'; 40 41 import { SuggestionDelete } from './extensions/suggestion-delete.js'; 41 42 import { SearchReplace } from './search-replace.js'; ··· 143 144 LineSpacing, 144 145 ParagraphSpacing, 145 146 PageBreak, 147 + ToggleBlock, 148 + ToggleSummary, 146 149 SuggestionInsert, 147 150 SuggestionDelete, 148 151 SearchReplace.configure({ ··· 2177 2180 { id: 'export-pdf', label: 'Export PDF', category: 'action', icon: '\u2193', shortcut: '\u2318\u21e7P', action: () => doExportPdf() }, 2178 2181 { id: 'import', label: 'Import File', category: 'action', icon: '\u2191', action: () => importFile() }, 2179 2182 { id: 'find', label: 'Find & Replace', category: 'action', icon: '\u2315', shortcut: '\u2318F', action: () => { editor.commands.openSearch(); updateFindBar(); } }, 2183 + { id: 'toggle', label: 'Insert Toggle Block', category: 'action', icon: '\u25B6', action: () => editor.chain().focus().insertToggleBlock().run() }, 2180 2184 ], 2181 2185 fetchDocuments: async (): Promise<PaletteAction[]> => { 2182 2186 try {
+8
src/docs/slash-menu.ts
··· 158 158 159 159 // --- Advanced --- 160 160 { 161 + id: 'toggle', 162 + name: 'Toggle', 163 + description: 'Collapsible section', 164 + category: 'advanced', 165 + icon: '\u25B6', 166 + shortcut: null, 167 + }, 168 + { 161 169 id: 'pageBreak', 162 170 name: 'Page Break', 163 171 description: 'Insert a page break',