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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.rubocop.yml +8 -0
  4. data/.yardopts +11 -0
  5. data/CLAUDE.md +141 -0
  6. data/README.md +409 -0
  7. data/Rakefile +19 -0
  8. data/app/assets/javascripts/inkpen/controllers/editor_controller.js +2050 -0
  9. data/app/assets/javascripts/inkpen/controllers/sticky_toolbar_controller.js +667 -0
  10. data/app/assets/javascripts/inkpen/controllers/toolbar_controller.js +693 -0
  11. data/app/assets/javascripts/inkpen/export/html.js +637 -0
  12. data/app/assets/javascripts/inkpen/export/index.js +30 -0
  13. data/app/assets/javascripts/inkpen/export/markdown.js +697 -0
  14. data/app/assets/javascripts/inkpen/export/pdf.js +372 -0
  15. data/app/assets/javascripts/inkpen/extensions/advanced_table.js +640 -0
  16. data/app/assets/javascripts/inkpen/extensions/block_commands.js +300 -0
  17. data/app/assets/javascripts/inkpen/extensions/block_gutter.js +338 -0
  18. data/app/assets/javascripts/inkpen/extensions/callout.js +303 -0
  19. data/app/assets/javascripts/inkpen/extensions/columns.js +403 -0
  20. data/app/assets/javascripts/inkpen/extensions/database.js +990 -0
  21. data/app/assets/javascripts/inkpen/extensions/document_section.js +352 -0
  22. data/app/assets/javascripts/inkpen/extensions/drag_handle.js +407 -0
  23. data/app/assets/javascripts/inkpen/extensions/embed.js +629 -0
  24. data/app/assets/javascripts/inkpen/extensions/enhanced_image.js +566 -0
  25. data/app/assets/javascripts/inkpen/extensions/export_commands.js +271 -0
  26. data/app/assets/javascripts/inkpen/extensions/file_attachment.js +593 -0
  27. data/app/assets/javascripts/inkpen/extensions/inkpen_table/index.js +58 -0
  28. data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table.js +638 -0
  29. data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table_cell.js +100 -0
  30. data/app/assets/javascripts/inkpen/extensions/inkpen_table/inkpen_table_header.js +100 -0
  31. data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_constants.js +152 -0
  32. data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_helpers.js +254 -0
  33. data/app/assets/javascripts/inkpen/extensions/inkpen_table/table_menu.js +282 -0
  34. data/app/assets/javascripts/inkpen/extensions/preformatted.js +239 -0
  35. data/app/assets/javascripts/inkpen/extensions/section.js +281 -0
  36. data/app/assets/javascripts/inkpen/extensions/section_title.js +126 -0
  37. data/app/assets/javascripts/inkpen/extensions/slash_commands.js +439 -0
  38. data/app/assets/javascripts/inkpen/extensions/table_of_contents.js +474 -0
  39. data/app/assets/javascripts/inkpen/extensions/toggle_block.js +332 -0
  40. data/app/assets/javascripts/inkpen/index.js +87 -0
  41. data/app/assets/stylesheets/inkpen/advanced_table.css +514 -0
  42. data/app/assets/stylesheets/inkpen/animations.css +626 -0
  43. data/app/assets/stylesheets/inkpen/block_gutter.css +265 -0
  44. data/app/assets/stylesheets/inkpen/callout.css +359 -0
  45. data/app/assets/stylesheets/inkpen/columns.css +314 -0
  46. data/app/assets/stylesheets/inkpen/database.css +658 -0
  47. data/app/assets/stylesheets/inkpen/document_section.css +305 -0
  48. data/app/assets/stylesheets/inkpen/drag_drop.css +220 -0
  49. data/app/assets/stylesheets/inkpen/editor.css +652 -0
  50. data/app/assets/stylesheets/inkpen/embed.css +468 -0
  51. data/app/assets/stylesheets/inkpen/enhanced_image.css +453 -0
  52. data/app/assets/stylesheets/inkpen/export.css +499 -0
  53. data/app/assets/stylesheets/inkpen/file_attachment.css +347 -0
  54. data/app/assets/stylesheets/inkpen/footnotes.css +136 -0
  55. data/app/assets/stylesheets/inkpen/inkpen_table.css +608 -0
  56. data/app/assets/stylesheets/inkpen/preformatted.css +215 -0
  57. data/app/assets/stylesheets/inkpen/search_replace.css +58 -0
  58. data/app/assets/stylesheets/inkpen/section.css +236 -0
  59. data/app/assets/stylesheets/inkpen/slash_menu.css +252 -0
  60. data/app/assets/stylesheets/inkpen/sticky_toolbar.css +314 -0
  61. data/app/assets/stylesheets/inkpen/toc.css +386 -0
  62. data/app/assets/stylesheets/inkpen/toggle.css +260 -0
  63. data/app/helpers/inkpen/editor_helper.rb +114 -0
  64. data/app/views/inkpen/_editor.html.erb +139 -0
  65. data/config/importmap.rb +170 -0
  66. data/docs/.DS_Store +0 -0
  67. data/docs/CHANGELOG.md +571 -0
  68. data/docs/FEATURES.md +436 -0
  69. data/docs/ROADMAP.md +3029 -0
  70. data/docs/VISION.md +235 -0
  71. data/docs/extensions/INKPEN_TABLE.md +482 -0
  72. data/docs/thinking/CORRECTED_NO_VUE.md +756 -0
  73. data/docs/thinking/EXECUTIVE_SUMMARY.md +403 -0
  74. data/docs/thinking/INKPEN_CODE_SAMPLES.md +1479 -0
  75. data/docs/thinking/INKPEN_MASTER_GUIDE.md +891 -0
  76. data/docs/thinking/README_START_HERE.md +341 -0
  77. data/lib/inkpen/configuration.rb +175 -0
  78. data/lib/inkpen/editor.rb +204 -0
  79. data/lib/inkpen/engine.rb +32 -0
  80. data/lib/inkpen/extensions/base.rb +109 -0
  81. data/lib/inkpen/extensions/code_block_syntax.rb +177 -0
  82. data/lib/inkpen/extensions/document_section.rb +111 -0
  83. data/lib/inkpen/extensions/forced_document.rb +183 -0
  84. data/lib/inkpen/extensions/mention.rb +155 -0
  85. data/lib/inkpen/extensions/preformatted.rb +111 -0
  86. data/lib/inkpen/extensions/section.rb +139 -0
  87. data/lib/inkpen/extensions/slash_commands.rb +100 -0
  88. data/lib/inkpen/extensions/table.rb +182 -0
  89. data/lib/inkpen/extensions/task_list.rb +145 -0
  90. data/lib/inkpen/sticky_toolbar.rb +157 -0
  91. data/lib/inkpen/toolbar.rb +145 -0
  92. data/lib/inkpen/version.rb +5 -0
  93. data/lib/inkpen.rb +101 -0
  94. data/sig/inkpen.rbs +4 -0
  95. metadata +165 -0
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inkpen
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Inkpen
6
+
7
+ initializer "inkpen.assets" do |app|
8
+ # Add stylesheets to asset pipeline
9
+ app.config.assets.paths << root.join("app/assets/stylesheets")
10
+ app.config.assets.paths << root.join("app/assets/javascripts")
11
+ end
12
+
13
+ initializer "inkpen.importmap", before: "importmap" do |app|
14
+ # Hook into importmap if available
15
+ if app.config.respond_to?(:importmap)
16
+ app.config.importmap.paths << root.join("config/importmap.rb")
17
+ app.config.importmap.cache_sweepers << root.join("app/assets/javascripts")
18
+ end
19
+ end
20
+
21
+ initializer "inkpen.helpers" do |app|
22
+ app.config.to_prepare do
23
+ ActionView::Base.include Inkpen::EditorHelper
24
+ end
25
+ end
26
+
27
+ # Allow precompilation of inkpen assets
28
+ initializer "inkpen.precompile" do |app|
29
+ app.config.assets.precompile += %w[inkpen/editor.css]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inkpen
4
+ module Extensions
5
+ ##
6
+ # Base class for all Inkpen TipTap extensions.
7
+ #
8
+ # Extensions follow the TipTap extension pattern and are configured
9
+ # through this Ruby PORO layer before being passed to the JavaScript
10
+ # TipTap editor.
11
+ #
12
+ # @abstract Subclass and override {#name} and {#to_config} to implement
13
+ # a custom extension.
14
+ #
15
+ # @example Creating a custom extension
16
+ # class MyExtension < Inkpen::Extensions::Base
17
+ # def name
18
+ # :my_extension
19
+ # end
20
+ #
21
+ # def to_config
22
+ # { custom_option: true }
23
+ # end
24
+ # end
25
+ #
26
+ # @author Inkpen Team
27
+ # @since 0.2.0
28
+ #
29
+ class Base
30
+ ##
31
+ # @return [Hash] the options passed during initialization
32
+ attr_reader :options
33
+
34
+ ##
35
+ # Initialize a new extension with optional configuration.
36
+ #
37
+ # @param options [Hash] configuration options for the extension
38
+ # @option options [Boolean] :enabled whether the extension is enabled (default: true)
39
+ #
40
+ def initialize(**options)
41
+ @options = default_options.merge(options)
42
+ end
43
+
44
+ ##
45
+ # The unique name of the extension.
46
+ #
47
+ # @abstract Subclasses must override this method.
48
+ # @return [Symbol] the extension name
49
+ # @raise [NotImplementedError] if not overridden in subclass
50
+ #
51
+ def name
52
+ raise NotImplementedError, "#{self.class} must implement #name"
53
+ end
54
+
55
+ ##
56
+ # Whether this extension is currently enabled.
57
+ #
58
+ # @return [Boolean] true if enabled
59
+ #
60
+ def enabled?
61
+ options.fetch(:enabled, true)
62
+ end
63
+
64
+ ##
65
+ # Convert the extension configuration to a hash for JSON serialization.
66
+ #
67
+ # This hash is passed to the JavaScript TipTap extension.
68
+ #
69
+ # @return [Hash] configuration hash
70
+ #
71
+ def to_config
72
+ {}
73
+ end
74
+
75
+ ##
76
+ # Full configuration including name and enabled status.
77
+ #
78
+ # @return [Hash] complete extension configuration
79
+ #
80
+ def to_h
81
+ {
82
+ name: name,
83
+ enabled: enabled?,
84
+ config: to_config
85
+ }
86
+ end
87
+
88
+ ##
89
+ # JSON representation of the extension.
90
+ #
91
+ # @return [String] JSON string
92
+ #
93
+ def to_json(*args)
94
+ to_h.to_json(*args)
95
+ end
96
+
97
+ private
98
+
99
+ ##
100
+ # Default options for this extension.
101
+ #
102
+ # @return [Hash] default options
103
+ #
104
+ def default_options
105
+ { enabled: true }
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inkpen
4
+ module Extensions
5
+ ##
6
+ # Code Block with Syntax Highlighting extension.
7
+ #
8
+ # Enhances the standard code block with syntax highlighting using
9
+ # lowlight (highlight.js). Supports multiple programming languages
10
+ # with automatic language detection.
11
+ #
12
+ # @example Basic usage
13
+ # extension = Inkpen::Extensions::CodeBlockSyntax.new
14
+ #
15
+ # @example With custom languages
16
+ # extension = Inkpen::Extensions::CodeBlockSyntax.new(
17
+ # languages: [:ruby, :javascript, :python, :sql],
18
+ # default_language: :ruby
19
+ # )
20
+ #
21
+ # @see https://tiptap.dev/docs/examples/advanced/syntax-highlighting
22
+ # @author Inkpen Team
23
+ # @since 0.2.0
24
+ #
25
+ class CodeBlockSyntax < Base
26
+ ##
27
+ # All available programming languages for syntax highlighting.
28
+ # These are loaded from highlight.js via lowlight.
29
+ AVAILABLE_LANGUAGES = %i[
30
+ javascript
31
+ typescript
32
+ ruby
33
+ python
34
+ css
35
+ xml
36
+ html
37
+ json
38
+ bash
39
+ shell
40
+ sql
41
+ markdown
42
+ yaml
43
+ go
44
+ rust
45
+ java
46
+ kotlin
47
+ swift
48
+ php
49
+ c
50
+ cpp
51
+ csharp
52
+ elixir
53
+ erlang
54
+ haskell
55
+ scala
56
+ r
57
+ matlab
58
+ dockerfile
59
+ nginx
60
+ apache
61
+ graphql
62
+ ].freeze
63
+
64
+ ##
65
+ # Default languages to load (for performance).
66
+ DEFAULT_LANGUAGES = %i[
67
+ javascript
68
+ typescript
69
+ ruby
70
+ python
71
+ css
72
+ xml
73
+ json
74
+ bash
75
+ sql
76
+ markdown
77
+ ].freeze
78
+
79
+ ##
80
+ # The unique name of this extension.
81
+ #
82
+ # @return [Symbol] :code_block_syntax
83
+ #
84
+ def name
85
+ :code_block_syntax
86
+ end
87
+
88
+ ##
89
+ # Languages to enable for syntax highlighting.
90
+ #
91
+ # Only these languages will be loaded from highlight.js to
92
+ # reduce bundle size.
93
+ #
94
+ # @return [Array<Symbol>] enabled language identifiers
95
+ #
96
+ def languages
97
+ options.fetch(:languages, DEFAULT_LANGUAGES)
98
+ end
99
+
100
+ ##
101
+ # Default language when none is specified.
102
+ #
103
+ # @return [Symbol, nil] default language or nil for auto-detect
104
+ #
105
+ def default_language
106
+ options[:default_language]
107
+ end
108
+
109
+ ##
110
+ # Whether to show line numbers in code blocks.
111
+ #
112
+ # @return [Boolean] true to show line numbers
113
+ #
114
+ def line_numbers?
115
+ options.fetch(:line_numbers, false)
116
+ end
117
+
118
+ ##
119
+ # Whether to show a language selector dropdown.
120
+ #
121
+ # @return [Boolean] true to show language selector
122
+ #
123
+ def show_language_selector?
124
+ options.fetch(:language_selector, true)
125
+ end
126
+
127
+ ##
128
+ # Whether to enable copy-to-clipboard button.
129
+ #
130
+ # @return [Boolean] true to show copy button
131
+ #
132
+ def copy_button?
133
+ options.fetch(:copy_button, true)
134
+ end
135
+
136
+ ##
137
+ # CSS theme for syntax highlighting.
138
+ #
139
+ # @return [String] theme name (e.g., "github", "monokai", "dracula")
140
+ #
141
+ def theme
142
+ options.fetch(:theme, "github")
143
+ end
144
+
145
+ ##
146
+ # Convert to configuration hash for JavaScript.
147
+ #
148
+ # @return [Hash] configuration for the TipTap extension
149
+ #
150
+ def to_config
151
+ {
152
+ languages: languages,
153
+ defaultLanguage: default_language,
154
+ lineNumbers: line_numbers?,
155
+ languageSelector: show_language_selector?,
156
+ copyButton: copy_button?,
157
+ theme: theme,
158
+ lowlight: {
159
+ languages: languages
160
+ }
161
+ }
162
+ end
163
+
164
+ private
165
+
166
+ def default_options
167
+ super.merge(
168
+ languages: DEFAULT_LANGUAGES,
169
+ line_numbers: false,
170
+ language_selector: true,
171
+ copy_button: true,
172
+ theme: "github"
173
+ )
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inkpen
4
+ module Extensions
5
+ ##
6
+ # DocumentSection extension for TipTap.
7
+ #
8
+ # Provides true content grouping with a semantic H2 title and collapsible
9
+ # content. Unlike the layout Section extension (which controls width/spacing),
10
+ # DocumentSection groups related blocks under a heading for document structure.
11
+ #
12
+ # Features:
13
+ # - Semantic H2 title (integrates with Table of Contents)
14
+ # - Collapsible content with left-gutter toggle
15
+ # - Nesting support (sections within sections, up to 3 levels)
16
+ # - Draggable as a group
17
+ # - Auto-generated IDs for deep linking
18
+ #
19
+ # @example Basic usage
20
+ # extension = Inkpen::Extensions::DocumentSection.new
21
+ #
22
+ # @example With custom options
23
+ # extension = Inkpen::Extensions::DocumentSection.new(
24
+ # default_collapsed: false,
25
+ # allow_nesting: true,
26
+ # max_depth: 3,
27
+ # show_controls: true
28
+ # )
29
+ #
30
+ # @see https://tiptap.dev/docs/editor/api/nodes
31
+ # @author Inkpen Team
32
+ # @since 0.8.0
33
+ #
34
+ class DocumentSection < Base
35
+ ##
36
+ # Default maximum nesting depth for sections.
37
+ DEFAULT_MAX_DEPTH = 3
38
+
39
+ ##
40
+ # The unique name of this extension.
41
+ #
42
+ # @return [Symbol] :document_section
43
+ #
44
+ def name
45
+ :document_section
46
+ end
47
+
48
+ ##
49
+ # Default collapsed state for new sections.
50
+ #
51
+ # @return [Boolean] true if sections start collapsed
52
+ #
53
+ def default_collapsed
54
+ options.fetch(:default_collapsed, false)
55
+ end
56
+
57
+ ##
58
+ # Whether sections can be nested within each other.
59
+ #
60
+ # @return [Boolean] true to allow nesting
61
+ #
62
+ def allow_nesting?
63
+ options.fetch(:allow_nesting, true)
64
+ end
65
+
66
+ ##
67
+ # Maximum nesting depth for sections.
68
+ #
69
+ # @return [Integer] maximum depth allowed (1-3)
70
+ #
71
+ def max_depth
72
+ depth = options.fetch(:max_depth, DEFAULT_MAX_DEPTH)
73
+ [[depth, 1].max, 3].min # Clamp between 1 and 3
74
+ end
75
+
76
+ ##
77
+ # Whether to show the collapse toggle control.
78
+ #
79
+ # @return [Boolean] true to show controls
80
+ #
81
+ def show_controls?
82
+ options.fetch(:show_controls, true)
83
+ end
84
+
85
+ ##
86
+ # Convert to configuration hash for JavaScript.
87
+ #
88
+ # @return [Hash] configuration for the TipTap extension
89
+ #
90
+ def to_config
91
+ {
92
+ defaultCollapsed: default_collapsed,
93
+ allowNesting: allow_nesting?,
94
+ maxDepth: max_depth,
95
+ showControls: show_controls?
96
+ }
97
+ end
98
+
99
+ private
100
+
101
+ def default_options
102
+ super.merge(
103
+ default_collapsed: false,
104
+ allow_nesting: true,
105
+ max_depth: DEFAULT_MAX_DEPTH,
106
+ show_controls: true
107
+ )
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inkpen
4
+ module Extensions
5
+ ##
6
+ # Forced Document Structure extension.
7
+ #
8
+ # Enforces a specific document structure where the first element must be
9
+ # a title heading (H1), optionally followed by a subtitle heading (H2).
10
+ # This prevents users from deleting required headings and ensures
11
+ # consistent document structure similar to Medium, Notion, or Dropbox Paper.
12
+ #
13
+ # @example Basic usage (title only)
14
+ # extension = Inkpen::Extensions::ForcedDocument.new
15
+ #
16
+ # @example With subtitle enabled
17
+ # extension = Inkpen::Extensions::ForcedDocument.new(
18
+ # subtitle: true,
19
+ # title_placeholder: "Your title here...",
20
+ # subtitle_placeholder: "Add a subtitle..."
21
+ # )
22
+ #
23
+ # @example Custom heading levels
24
+ # extension = Inkpen::Extensions::ForcedDocument.new(
25
+ # title_level: 1,
26
+ # subtitle_level: 2,
27
+ # subtitle: true
28
+ # )
29
+ #
30
+ # @see https://tiptap.dev/docs/examples/advanced/forced-content-structure
31
+ # @author Inkpen Team
32
+ # @since 0.2.0
33
+ #
34
+ class ForcedDocument < Base
35
+ ##
36
+ # Default heading level for the title (H1).
37
+ DEFAULT_TITLE_LEVEL = 1
38
+
39
+ ##
40
+ # Default heading level for the subtitle (H2).
41
+ DEFAULT_SUBTITLE_LEVEL = 2
42
+
43
+ ##
44
+ # Default placeholder text for the title heading.
45
+ DEFAULT_TITLE_PLACEHOLDER = "Untitled"
46
+
47
+ ##
48
+ # Default placeholder text for the subtitle heading.
49
+ DEFAULT_SUBTITLE_PLACEHOLDER = "Add a subtitle..."
50
+
51
+ ##
52
+ # The unique name of this extension.
53
+ #
54
+ # @return [Symbol] :forced_document
55
+ #
56
+ def name
57
+ :forced_document
58
+ end
59
+
60
+ ##
61
+ # The heading level for the title (first heading).
62
+ #
63
+ # @return [Integer] heading level (1-6), default 1
64
+ #
65
+ def title_level
66
+ options.fetch(:title_level, options.fetch(:heading_level, DEFAULT_TITLE_LEVEL))
67
+ end
68
+
69
+ ##
70
+ # The heading level for the subtitle (second heading).
71
+ #
72
+ # @return [Integer] heading level (1-6), default 2
73
+ #
74
+ def subtitle_level
75
+ options.fetch(:subtitle_level, DEFAULT_SUBTITLE_LEVEL)
76
+ end
77
+
78
+ ##
79
+ # Whether subtitle is enabled.
80
+ #
81
+ # @return [Boolean] true if subtitle heading should be enforced
82
+ #
83
+ def subtitle?
84
+ options.fetch(:subtitle, false)
85
+ end
86
+
87
+ ##
88
+ # Placeholder text shown in the title heading when empty.
89
+ #
90
+ # @return [String] placeholder text
91
+ #
92
+ def title_placeholder
93
+ options.fetch(:title_placeholder, options.fetch(:placeholder, DEFAULT_TITLE_PLACEHOLDER))
94
+ end
95
+
96
+ ##
97
+ # Placeholder text shown in the subtitle heading when empty.
98
+ #
99
+ # @return [String] placeholder text
100
+ #
101
+ def subtitle_placeholder
102
+ options.fetch(:subtitle_placeholder, DEFAULT_SUBTITLE_PLACEHOLDER)
103
+ end
104
+
105
+ ##
106
+ # Whether to allow the title heading to be deleted.
107
+ #
108
+ # @return [Boolean] false by default (title cannot be deleted)
109
+ #
110
+ def allow_title_deletion?
111
+ options.fetch(:allow_deletion, false)
112
+ end
113
+
114
+ ##
115
+ # Whether to allow the subtitle heading to be deleted.
116
+ #
117
+ # @return [Boolean] true by default (subtitle can be deleted/cleared)
118
+ #
119
+ def allow_subtitle_deletion?
120
+ options.fetch(:allow_subtitle_deletion, true)
121
+ end
122
+
123
+ ##
124
+ # Document structure schema.
125
+ #
126
+ # Defines the required structure:
127
+ # - Without subtitle: heading block*
128
+ # - With subtitle: heading heading? block*
129
+ #
130
+ # @return [String] ProseMirror content expression
131
+ #
132
+ def content_expression
133
+ if subtitle?
134
+ "heading heading? block*"
135
+ else
136
+ "heading block*"
137
+ end
138
+ end
139
+
140
+ ##
141
+ # Convert to configuration hash for JavaScript.
142
+ #
143
+ # @return [Hash] configuration for the TipTap extension
144
+ #
145
+ def to_config
146
+ config = {
147
+ titleLevel: title_level,
148
+ titlePlaceholder: title_placeholder,
149
+ allowDeletion: allow_title_deletion?,
150
+ contentExpression: content_expression,
151
+ subtitle: subtitle?
152
+ }
153
+
154
+ if subtitle?
155
+ config.merge!(
156
+ subtitleLevel: subtitle_level,
157
+ subtitlePlaceholder: subtitle_placeholder,
158
+ allowSubtitleDeletion: allow_subtitle_deletion?
159
+ )
160
+ end
161
+
162
+ # Legacy support for headingLevel
163
+ config[:headingLevel] = title_level
164
+
165
+ config
166
+ end
167
+
168
+ private
169
+
170
+ def default_options
171
+ super.merge(
172
+ title_level: DEFAULT_TITLE_LEVEL,
173
+ subtitle_level: DEFAULT_SUBTITLE_LEVEL,
174
+ title_placeholder: DEFAULT_TITLE_PLACEHOLDER,
175
+ subtitle_placeholder: DEFAULT_SUBTITLE_PLACEHOLDER,
176
+ subtitle: false,
177
+ allow_deletion: false,
178
+ allow_subtitle_deletion: true
179
+ )
180
+ end
181
+ end
182
+ end
183
+ end