inkpen 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.rubocop.yml +8 -0
- data/.yardopts +11 -0
- data/CLAUDE.md +141 -0
- data/README.md +409 -0
- data/Rakefile +19 -0
- data/app/assets/javascripts/inkpen/controllers/editor_controller.js +2050 -0
- data/app/assets/javascripts/inkpen/controllers/sticky_toolbar_controller.js +667 -0
- data/app/assets/javascripts/inkpen/controllers/toolbar_controller.js +693 -0
- data/app/assets/javascripts/inkpen/export/html.js +637 -0
- data/app/assets/javascripts/inkpen/export/index.js +30 -0
- data/app/assets/javascripts/inkpen/export/markdown.js +697 -0
- data/app/assets/javascripts/inkpen/export/pdf.js +372 -0
- data/app/assets/javascripts/inkpen/extensions/advanced_table.js +640 -0
- data/app/assets/javascripts/inkpen/extensions/block_commands.js +300 -0
- data/app/assets/javascripts/inkpen/extensions/block_gutter.js +338 -0
- data/app/assets/javascripts/inkpen/extensions/callout.js +303 -0
- data/app/assets/javascripts/inkpen/extensions/columns.js +403 -0
- data/app/assets/javascripts/inkpen/extensions/database.js +990 -0
- data/app/assets/javascripts/inkpen/extensions/document_section.js +352 -0
- data/app/assets/javascripts/inkpen/extensions/drag_handle.js +407 -0
- data/app/assets/javascripts/inkpen/extensions/embed.js +629 -0
- data/app/assets/javascripts/inkpen/extensions/enhanced_image.js +566 -0
- data/app/assets/javascripts/inkpen/extensions/export_commands.js +271 -0
- data/app/assets/javascripts/inkpen/extensions/file_attachment.js +593 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/index.js +58 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table.js +638 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table_cell.js +100 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table_header.js +100 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_constants.js +152 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_helpers.js +254 -0
- data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_menu.js +282 -0
- data/app/assets/javascripts/inkpen/extensions/preformatted.js +239 -0
- data/app/assets/javascripts/inkpen/extensions/section.js +281 -0
- data/app/assets/javascripts/inkpen/extensions/section_title.js +126 -0
- data/app/assets/javascripts/inkpen/extensions/slash_commands.js +439 -0
- data/app/assets/javascripts/inkpen/extensions/table_of_contents.js +474 -0
- data/app/assets/javascripts/inkpen/extensions/toggle_block.js +332 -0
- data/app/assets/javascripts/inkpen/index.js +87 -0
- data/app/assets/stylesheets/inkpen/advanced_table.css +514 -0
- data/app/assets/stylesheets/inkpen/animations.css +626 -0
- data/app/assets/stylesheets/inkpen/block_gutter.css +265 -0
- data/app/assets/stylesheets/inkpen/callout.css +359 -0
- data/app/assets/stylesheets/inkpen/columns.css +314 -0
- data/app/assets/stylesheets/inkpen/database.css +658 -0
- data/app/assets/stylesheets/inkpen/document_section.css +305 -0
- data/app/assets/stylesheets/inkpen/drag_drop.css +220 -0
- data/app/assets/stylesheets/inkpen/editor.css +652 -0
- data/app/assets/stylesheets/inkpen/embed.css +468 -0
- data/app/assets/stylesheets/inkpen/enhanced_image.css +453 -0
- data/app/assets/stylesheets/inkpen/export.css +499 -0
- data/app/assets/stylesheets/inkpen/file_attachment.css +347 -0
- data/app/assets/stylesheets/inkpen/footnotes.css +136 -0
- data/app/assets/stylesheets/inkpen/inkpen_table.css +608 -0
- data/app/assets/stylesheets/inkpen/preformatted.css +215 -0
- data/app/assets/stylesheets/inkpen/search_replace.css +58 -0
- data/app/assets/stylesheets/inkpen/section.css +236 -0
- data/app/assets/stylesheets/inkpen/slash_menu.css +252 -0
- data/app/assets/stylesheets/inkpen/sticky_toolbar.css +314 -0
- data/app/assets/stylesheets/inkpen/toc.css +386 -0
- data/app/assets/stylesheets/inkpen/toggle.css +260 -0
- data/app/helpers/inkpen/editor_helper.rb +114 -0
- data/app/views/inkpen/_editor.html.erb +139 -0
- data/config/importmap.rb +170 -0
- data/docs/.DS_Store +0 -0
- data/docs/CHANGELOG.md +571 -0
- data/docs/FEATURES.md +436 -0
- data/docs/ROADMAP.md +3029 -0
- data/docs/VISION.md +235 -0
- data/docs/extensions/INKPEN_TABLE.md +482 -0
- data/docs/thinking/CORRECTED_NO_VUE.md +756 -0
- data/docs/thinking/EXECUTIVE_SUMMARY.md +403 -0
- data/docs/thinking/INKPEN_CODE_SAMPLES.md +1479 -0
- data/docs/thinking/INKPEN_MASTER_GUIDE.md +891 -0
- data/docs/thinking/README_START_HERE.md +341 -0
- data/lib/inkpen/configuration.rb +175 -0
- data/lib/inkpen/editor.rb +204 -0
- data/lib/inkpen/engine.rb +32 -0
- data/lib/inkpen/extensions/base.rb +109 -0
- data/lib/inkpen/extensions/code_block_syntax.rb +177 -0
- data/lib/inkpen/extensions/document_section.rb +111 -0
- data/lib/inkpen/extensions/forced_document.rb +183 -0
- data/lib/inkpen/extensions/mention.rb +155 -0
- data/lib/inkpen/extensions/preformatted.rb +111 -0
- data/lib/inkpen/extensions/section.rb +139 -0
- data/lib/inkpen/extensions/slash_commands.rb +100 -0
- data/lib/inkpen/extensions/table.rb +182 -0
- data/lib/inkpen/extensions/task_list.rb +145 -0
- data/lib/inkpen/sticky_toolbar.rb +157 -0
- data/lib/inkpen/toolbar.rb +145 -0
- data/lib/inkpen/version.rb +5 -0
- data/lib/inkpen.rb +101 -0
- data/sig/inkpen.rbs +4 -0
- metadata +165 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { Node, mergeAttributes } from "@tiptap/core"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Section Extension for TipTap
|
|
5
|
+
*
|
|
6
|
+
* Block-level container that controls content width and spacing.
|
|
7
|
+
* Renders with an interactive NodeView for editing controls.
|
|
8
|
+
*
|
|
9
|
+
* Follows Fizzy patterns:
|
|
10
|
+
* - Section comments for organization
|
|
11
|
+
* - Clean command API
|
|
12
|
+
* - Event-driven updates
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* editor.commands.insertSection({ width: 'wide', spacing: 'spacious' })
|
|
16
|
+
* editor.commands.setSectionWidth('narrow')
|
|
17
|
+
*
|
|
18
|
+
* @since 0.3.0
|
|
19
|
+
*/
|
|
20
|
+
export const Section = Node.create({
|
|
21
|
+
name: "section",
|
|
22
|
+
|
|
23
|
+
group: "block",
|
|
24
|
+
content: "block+",
|
|
25
|
+
defining: true,
|
|
26
|
+
isolating: true,
|
|
27
|
+
|
|
28
|
+
// Options
|
|
29
|
+
|
|
30
|
+
addOptions() {
|
|
31
|
+
return {
|
|
32
|
+
defaultWidth: "default",
|
|
33
|
+
defaultSpacing: "normal",
|
|
34
|
+
showControls: true,
|
|
35
|
+
widthPresets: {
|
|
36
|
+
narrow: { maxWidth: "560px", label: "Narrow" },
|
|
37
|
+
default: { maxWidth: "680px", label: "Default" },
|
|
38
|
+
wide: { maxWidth: "900px", label: "Wide" },
|
|
39
|
+
full: { maxWidth: "100%", label: "Full" }
|
|
40
|
+
},
|
|
41
|
+
spacingPresets: {
|
|
42
|
+
compact: { padding: "1rem 0", label: "Compact" },
|
|
43
|
+
normal: { padding: "2rem 0", label: "Normal" },
|
|
44
|
+
spacious: { padding: "4rem 0", label: "Spacious" }
|
|
45
|
+
},
|
|
46
|
+
HTMLAttributes: {
|
|
47
|
+
class: "inkpen-section"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Attributes
|
|
53
|
+
|
|
54
|
+
addAttributes() {
|
|
55
|
+
return {
|
|
56
|
+
width: {
|
|
57
|
+
default: this.options.defaultWidth,
|
|
58
|
+
parseHTML: element => element.getAttribute("data-width") || this.options.defaultWidth,
|
|
59
|
+
renderHTML: attributes => ({ "data-width": attributes.width })
|
|
60
|
+
},
|
|
61
|
+
spacing: {
|
|
62
|
+
default: this.options.defaultSpacing,
|
|
63
|
+
parseHTML: element => element.getAttribute("data-spacing") || this.options.defaultSpacing,
|
|
64
|
+
renderHTML: attributes => ({ "data-spacing": attributes.spacing })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Parse & Render
|
|
70
|
+
|
|
71
|
+
parseHTML() {
|
|
72
|
+
return [
|
|
73
|
+
{ tag: "section[data-type='section']" },
|
|
74
|
+
{ tag: "div.inkpen-section" }
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
79
|
+
return [
|
|
80
|
+
"section",
|
|
81
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
|
82
|
+
"data-type": "section"
|
|
83
|
+
}),
|
|
84
|
+
0 // Content placeholder
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// NodeView (interactive controls)
|
|
89
|
+
|
|
90
|
+
addNodeView() {
|
|
91
|
+
return ({ node, getPos, editor }) => {
|
|
92
|
+
const dom = document.createElement("section")
|
|
93
|
+
dom.className = "inkpen-section"
|
|
94
|
+
dom.setAttribute("data-type", "section")
|
|
95
|
+
dom.setAttribute("data-width", node.attrs.width)
|
|
96
|
+
dom.setAttribute("data-spacing", node.attrs.spacing)
|
|
97
|
+
|
|
98
|
+
// Controls container (only in edit mode)
|
|
99
|
+
let controls = null
|
|
100
|
+
if (this.options.showControls && editor.isEditable) {
|
|
101
|
+
controls = this.createControls(node.attrs, (attr, value) => {
|
|
102
|
+
if (typeof getPos === "function") {
|
|
103
|
+
editor.chain()
|
|
104
|
+
.focus()
|
|
105
|
+
.command(({ tr }) => {
|
|
106
|
+
const pos = getPos()
|
|
107
|
+
if (pos !== undefined) {
|
|
108
|
+
tr.setNodeMarkup(pos, undefined, { ...node.attrs, [attr]: value })
|
|
109
|
+
}
|
|
110
|
+
return true
|
|
111
|
+
})
|
|
112
|
+
.run()
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
dom.appendChild(controls)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Content container
|
|
119
|
+
const content = document.createElement("div")
|
|
120
|
+
content.className = "inkpen-section__content"
|
|
121
|
+
dom.appendChild(content)
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
dom,
|
|
125
|
+
contentDOM: content,
|
|
126
|
+
update: (updatedNode) => {
|
|
127
|
+
if (updatedNode.type !== this.type) return false
|
|
128
|
+
|
|
129
|
+
dom.setAttribute("data-width", updatedNode.attrs.width)
|
|
130
|
+
dom.setAttribute("data-spacing", updatedNode.attrs.spacing)
|
|
131
|
+
|
|
132
|
+
// Update control button states
|
|
133
|
+
if (controls) {
|
|
134
|
+
this.updateControlStates(controls, updatedNode.attrs)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true
|
|
138
|
+
},
|
|
139
|
+
destroy: () => {
|
|
140
|
+
// Cleanup if needed
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
// Commands
|
|
147
|
+
|
|
148
|
+
addCommands() {
|
|
149
|
+
return {
|
|
150
|
+
insertSection: (attributes = {}) => ({ commands }) => {
|
|
151
|
+
return commands.insertContent({
|
|
152
|
+
type: this.name,
|
|
153
|
+
attrs: {
|
|
154
|
+
width: attributes.width || this.options.defaultWidth,
|
|
155
|
+
spacing: attributes.spacing || this.options.defaultSpacing
|
|
156
|
+
},
|
|
157
|
+
content: [{ type: "paragraph" }]
|
|
158
|
+
})
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
setSectionWidth: (width) => ({ commands }) => {
|
|
162
|
+
return commands.updateAttributes(this.name, { width })
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
setSectionSpacing: (spacing) => ({ commands }) => {
|
|
166
|
+
return commands.updateAttributes(this.name, { spacing })
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
wrapInSection: (attributes = {}) => ({ commands }) => {
|
|
170
|
+
return commands.wrapIn(this.name, attributes)
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
unwrapSection: () => ({ commands }) => {
|
|
174
|
+
return commands.lift(this.name)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
// Keyboard Shortcuts
|
|
180
|
+
|
|
181
|
+
addKeyboardShortcuts() {
|
|
182
|
+
return {
|
|
183
|
+
"Mod-Shift-s": () => this.editor.commands.insertSection()
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// Private Helpers
|
|
188
|
+
|
|
189
|
+
createControls(attrs, onChange) {
|
|
190
|
+
const controls = document.createElement("div")
|
|
191
|
+
controls.className = "inkpen-section-controls"
|
|
192
|
+
controls.contentEditable = "false"
|
|
193
|
+
|
|
194
|
+
// Width buttons
|
|
195
|
+
const widthGroup = document.createElement("div")
|
|
196
|
+
widthGroup.className = "inkpen-section-controls__group"
|
|
197
|
+
|
|
198
|
+
const widthLabel = document.createElement("span")
|
|
199
|
+
widthLabel.className = "inkpen-section-controls__label"
|
|
200
|
+
widthLabel.textContent = "Width"
|
|
201
|
+
widthGroup.appendChild(widthLabel)
|
|
202
|
+
|
|
203
|
+
Object.entries(this.options.widthPresets).forEach(([key, preset]) => {
|
|
204
|
+
const btn = document.createElement("button")
|
|
205
|
+
btn.type = "button"
|
|
206
|
+
btn.className = "inkpen-section-controls__btn"
|
|
207
|
+
btn.dataset.width = key
|
|
208
|
+
btn.textContent = preset.label
|
|
209
|
+
btn.title = `Set width to ${preset.label}`
|
|
210
|
+
|
|
211
|
+
if (attrs.width === key) {
|
|
212
|
+
btn.classList.add("is-active")
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
btn.addEventListener("mousedown", (e) => e.preventDefault())
|
|
216
|
+
btn.addEventListener("click", (e) => {
|
|
217
|
+
e.preventDefault()
|
|
218
|
+
e.stopPropagation()
|
|
219
|
+
onChange("width", key)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
widthGroup.appendChild(btn)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
controls.appendChild(widthGroup)
|
|
226
|
+
|
|
227
|
+
// Divider
|
|
228
|
+
const divider = document.createElement("span")
|
|
229
|
+
divider.className = "inkpen-section-controls__divider"
|
|
230
|
+
controls.appendChild(divider)
|
|
231
|
+
|
|
232
|
+
// Spacing buttons
|
|
233
|
+
const spacingGroup = document.createElement("div")
|
|
234
|
+
spacingGroup.className = "inkpen-section-controls__group"
|
|
235
|
+
|
|
236
|
+
const spacingLabel = document.createElement("span")
|
|
237
|
+
spacingLabel.className = "inkpen-section-controls__label"
|
|
238
|
+
spacingLabel.textContent = "Spacing"
|
|
239
|
+
spacingGroup.appendChild(spacingLabel)
|
|
240
|
+
|
|
241
|
+
Object.entries(this.options.spacingPresets).forEach(([key, preset]) => {
|
|
242
|
+
const btn = document.createElement("button")
|
|
243
|
+
btn.type = "button"
|
|
244
|
+
btn.className = "inkpen-section-controls__btn"
|
|
245
|
+
btn.dataset.spacing = key
|
|
246
|
+
btn.textContent = preset.label
|
|
247
|
+
btn.title = `Set spacing to ${preset.label}`
|
|
248
|
+
|
|
249
|
+
if (attrs.spacing === key) {
|
|
250
|
+
btn.classList.add("is-active")
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
btn.addEventListener("mousedown", (e) => e.preventDefault())
|
|
254
|
+
btn.addEventListener("click", (e) => {
|
|
255
|
+
e.preventDefault()
|
|
256
|
+
e.stopPropagation()
|
|
257
|
+
onChange("spacing", key)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
spacingGroup.appendChild(btn)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
controls.appendChild(spacingGroup)
|
|
264
|
+
|
|
265
|
+
return controls
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
updateControlStates(controls, attrs) {
|
|
269
|
+
// Update width buttons
|
|
270
|
+
controls.querySelectorAll("[data-width]").forEach(btn => {
|
|
271
|
+
btn.classList.toggle("is-active", btn.dataset.width === attrs.width)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// Update spacing buttons
|
|
275
|
+
controls.querySelectorAll("[data-spacing]").forEach(btn => {
|
|
276
|
+
btn.classList.toggle("is-active", btn.dataset.spacing === attrs.spacing)
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
export default Section
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Node, mergeAttributes } from "@tiptap/core"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Section Title Extension for TipTap
|
|
5
|
+
*
|
|
6
|
+
* Semantic H2 heading for document sections. Integrates with Table of Contents
|
|
7
|
+
* and provides the title/header for DocumentSection nodes.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Renders as semantic H2 element
|
|
11
|
+
* - Auto-generates ID for deep linking
|
|
12
|
+
* - Integrates with TOC extension
|
|
13
|
+
* - Enter key creates paragraph below (not new title)
|
|
14
|
+
* - Backspace at start of empty title deletes parent section
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Used within DocumentSection, not standalone
|
|
18
|
+
* { type: "documentSection", content: [{ type: "sectionTitle" }, ...blocks] }
|
|
19
|
+
*
|
|
20
|
+
* @since 0.8.0
|
|
21
|
+
*/
|
|
22
|
+
export const SectionTitle = Node.create({
|
|
23
|
+
name: "sectionTitle",
|
|
24
|
+
|
|
25
|
+
content: "inline*",
|
|
26
|
+
defining: true,
|
|
27
|
+
selectable: false,
|
|
28
|
+
|
|
29
|
+
// Options
|
|
30
|
+
|
|
31
|
+
addOptions() {
|
|
32
|
+
return {
|
|
33
|
+
HTMLAttributes: {
|
|
34
|
+
class: "inkpen-doc-section__title"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Attributes
|
|
40
|
+
|
|
41
|
+
addAttributes() {
|
|
42
|
+
return {
|
|
43
|
+
id: {
|
|
44
|
+
default: null,
|
|
45
|
+
parseHTML: element => element.getAttribute("id"),
|
|
46
|
+
renderHTML: attributes => (attributes.id ? { id: attributes.id } : {})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// Parse & Render
|
|
52
|
+
|
|
53
|
+
parseHTML() {
|
|
54
|
+
return [
|
|
55
|
+
{ tag: 'h2[data-type="section-title"]' },
|
|
56
|
+
{ tag: "h2.inkpen-doc-section__title" }
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
renderHTML({ HTMLAttributes }) {
|
|
61
|
+
return [
|
|
62
|
+
"h2",
|
|
63
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
|
64
|
+
"data-type": "section-title"
|
|
65
|
+
}),
|
|
66
|
+
0
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Keyboard Shortcuts
|
|
71
|
+
|
|
72
|
+
addKeyboardShortcuts() {
|
|
73
|
+
return {
|
|
74
|
+
// Enter in title creates content below, not new title
|
|
75
|
+
Enter: ({ editor }) => {
|
|
76
|
+
const { state } = editor
|
|
77
|
+
const { $from } = state.selection
|
|
78
|
+
|
|
79
|
+
if ($from.parent.type.name !== "sectionTitle") {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Find the documentSection parent
|
|
84
|
+
const sectionPos = $from.before($from.depth - 1)
|
|
85
|
+
const sectionNode = state.doc.nodeAt(sectionPos)
|
|
86
|
+
|
|
87
|
+
if (sectionNode && sectionNode.type.name === "documentSection") {
|
|
88
|
+
// Insert paragraph after title, inside section content
|
|
89
|
+
const titleEnd = sectionPos + 1 + $from.parent.nodeSize
|
|
90
|
+
return editor.chain()
|
|
91
|
+
.insertContentAt(titleEnd, { type: "paragraph" })
|
|
92
|
+
.focus(titleEnd + 1)
|
|
93
|
+
.run()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// Backspace at start of empty title deletes parent section
|
|
100
|
+
Backspace: ({ editor }) => {
|
|
101
|
+
const { state } = editor
|
|
102
|
+
const { $from, empty } = state.selection
|
|
103
|
+
|
|
104
|
+
if (!empty) return false
|
|
105
|
+
|
|
106
|
+
if ($from.parent.type.name === "sectionTitle" && $from.parentOffset === 0) {
|
|
107
|
+
// If title is empty, delete the whole document section
|
|
108
|
+
if ($from.parent.textContent === "") {
|
|
109
|
+
const sectionPos = $from.before($from.depth - 1)
|
|
110
|
+
const sectionNode = state.doc.nodeAt(sectionPos)
|
|
111
|
+
|
|
112
|
+
if (sectionNode && sectionNode.type.name === "documentSection") {
|
|
113
|
+
return editor.chain()
|
|
114
|
+
.deleteRange({ from: sectionPos, to: sectionPos + sectionNode.nodeSize })
|
|
115
|
+
.run()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
export default SectionTitle
|