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,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inkpen
|
|
4
|
+
module Extensions
|
|
5
|
+
##
|
|
6
|
+
# Task List extension for TipTap.
|
|
7
|
+
#
|
|
8
|
+
# Enables interactive checkbox/task list functionality. Users can
|
|
9
|
+
# create todo lists with checkable items that persist their state.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# extension = Inkpen::Extensions::TaskList.new
|
|
13
|
+
#
|
|
14
|
+
# @example With nested tasks disabled
|
|
15
|
+
# extension = Inkpen::Extensions::TaskList.new(
|
|
16
|
+
# nested: false,
|
|
17
|
+
# item_class: "my-task-item"
|
|
18
|
+
# )
|
|
19
|
+
#
|
|
20
|
+
# @see https://tiptap.dev/docs/examples/tasks
|
|
21
|
+
# @author Inkpen Team
|
|
22
|
+
# @since 0.2.0
|
|
23
|
+
#
|
|
24
|
+
class TaskList < Base
|
|
25
|
+
##
|
|
26
|
+
# The unique name of this extension.
|
|
27
|
+
#
|
|
28
|
+
# @return [Symbol] :task_list
|
|
29
|
+
#
|
|
30
|
+
def name
|
|
31
|
+
:task_list
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Whether task lists can be nested.
|
|
36
|
+
#
|
|
37
|
+
# @return [Boolean] true to allow nested task lists
|
|
38
|
+
#
|
|
39
|
+
def nested?
|
|
40
|
+
options.fetch(:nested, true)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# CSS class applied to task list container.
|
|
45
|
+
#
|
|
46
|
+
# @return [String] CSS class name
|
|
47
|
+
#
|
|
48
|
+
def list_class
|
|
49
|
+
options.fetch(:list_class, "inkpen-task-list")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# CSS class applied to individual task items.
|
|
54
|
+
#
|
|
55
|
+
# @return [String] CSS class name
|
|
56
|
+
#
|
|
57
|
+
def item_class
|
|
58
|
+
options.fetch(:item_class, "inkpen-task-item")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# CSS class applied to checked task items.
|
|
63
|
+
#
|
|
64
|
+
# @return [String] CSS class name
|
|
65
|
+
#
|
|
66
|
+
def checked_class
|
|
67
|
+
options.fetch(:checked_class, "inkpen-task-checked")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# CSS class applied to the checkbox element.
|
|
72
|
+
#
|
|
73
|
+
# @return [String] CSS class name
|
|
74
|
+
#
|
|
75
|
+
def checkbox_class
|
|
76
|
+
options.fetch(:checkbox_class, "inkpen-task-checkbox")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# HTML attributes for the task list element.
|
|
81
|
+
#
|
|
82
|
+
# @return [Hash] HTML attributes
|
|
83
|
+
#
|
|
84
|
+
def html_attributes
|
|
85
|
+
options.fetch(:html_attributes, { class: list_class })
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# HTML attributes for task item elements.
|
|
90
|
+
#
|
|
91
|
+
# @return [Hash] HTML attributes
|
|
92
|
+
#
|
|
93
|
+
def item_html_attributes
|
|
94
|
+
options.fetch(:item_html_attributes, { class: item_class })
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Whether clicking on the text toggles the checkbox.
|
|
99
|
+
#
|
|
100
|
+
# @return [Boolean] true if text click toggles checkbox
|
|
101
|
+
#
|
|
102
|
+
def text_toggle?
|
|
103
|
+
options.fetch(:text_toggle, false)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Keyboard shortcut to create a task list.
|
|
108
|
+
#
|
|
109
|
+
# @return [String] keyboard shortcut
|
|
110
|
+
#
|
|
111
|
+
def keyboard_shortcut
|
|
112
|
+
options.fetch(:keyboard_shortcut, "Mod-Shift-9")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Convert to configuration hash for JavaScript.
|
|
117
|
+
#
|
|
118
|
+
# @return [Hash] configuration for the TipTap extension
|
|
119
|
+
#
|
|
120
|
+
def to_config
|
|
121
|
+
{
|
|
122
|
+
nested: nested?,
|
|
123
|
+
listClass: list_class,
|
|
124
|
+
itemClass: item_class,
|
|
125
|
+
checkedClass: checked_class,
|
|
126
|
+
checkboxClass: checkbox_class,
|
|
127
|
+
HTMLAttributes: html_attributes,
|
|
128
|
+
itemHTMLAttributes: item_html_attributes,
|
|
129
|
+
textToggle: text_toggle?,
|
|
130
|
+
keyboardShortcut: keyboard_shortcut
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def default_options
|
|
137
|
+
super.merge(
|
|
138
|
+
nested: true,
|
|
139
|
+
text_toggle: false,
|
|
140
|
+
keyboard_shortcut: "Mod-Shift-9"
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inkpen
|
|
4
|
+
##
|
|
5
|
+
# PORO representing sticky toolbar configuration.
|
|
6
|
+
#
|
|
7
|
+
# The StickyToolbar provides block and media insertion controls that
|
|
8
|
+
# remain fixed on screen. It's separate from the text formatting
|
|
9
|
+
# toolbar and focused on inserting new content blocks.
|
|
10
|
+
#
|
|
11
|
+
# Supports horizontal (bottom) and vertical (left/right) layouts.
|
|
12
|
+
#
|
|
13
|
+
# @example Basic bottom toolbar
|
|
14
|
+
# sticky_toolbar = Inkpen::StickyToolbar.new(position: :bottom)
|
|
15
|
+
#
|
|
16
|
+
# @example Custom buttons
|
|
17
|
+
# sticky_toolbar = Inkpen::StickyToolbar.new(
|
|
18
|
+
# position: :bottom,
|
|
19
|
+
# buttons: [:table, :code_block, :image, :divider]
|
|
20
|
+
# )
|
|
21
|
+
#
|
|
22
|
+
# @example In editor helper
|
|
23
|
+
# <%= inkpen_editor name: "post[body]",
|
|
24
|
+
# sticky_toolbar: Inkpen::StickyToolbar.new(position: :right) %>
|
|
25
|
+
#
|
|
26
|
+
# @see Inkpen::Toolbar For text formatting toolbar
|
|
27
|
+
#
|
|
28
|
+
# @author Nauman Tariq
|
|
29
|
+
# @since 0.2.1
|
|
30
|
+
#
|
|
31
|
+
class StickyToolbar
|
|
32
|
+
##
|
|
33
|
+
# @!attribute [r] position
|
|
34
|
+
# @return [Symbol] toolbar position (:bottom, :left, :right)
|
|
35
|
+
#
|
|
36
|
+
# @!attribute [r] buttons
|
|
37
|
+
# @return [Array<Symbol>] list of toolbar buttons
|
|
38
|
+
#
|
|
39
|
+
# @!attribute [r] widget_types
|
|
40
|
+
# @return [Array<String>] available widget types for widget picker
|
|
41
|
+
#
|
|
42
|
+
attr_reader :position, :buttons, :widget_types
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Valid toolbar positions.
|
|
46
|
+
# @return [Array<Symbol>]
|
|
47
|
+
POSITIONS = %i[bottom left right].freeze
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Block insertion buttons.
|
|
51
|
+
# @return [Array<Symbol>]
|
|
52
|
+
BLOCK_BUTTONS = %i[table code_block blockquote horizontal_rule task_list].freeze
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Media insertion buttons.
|
|
56
|
+
# @return [Array<Symbol>]
|
|
57
|
+
MEDIA_BUTTONS = %i[image youtube embed].freeze
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Widget insertion buttons (opens modal picker).
|
|
61
|
+
# @return [Array<Symbol>]
|
|
62
|
+
WIDGET_BUTTONS = %i[widget].freeze
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Preset with block buttons only.
|
|
66
|
+
# @return [Array<Symbol>]
|
|
67
|
+
PRESET_BLOCKS = BLOCK_BUTTONS.freeze
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Preset with media buttons only.
|
|
71
|
+
# @return [Array<Symbol>]
|
|
72
|
+
PRESET_MEDIA = MEDIA_BUTTONS.freeze
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Full preset with all buttons.
|
|
76
|
+
# @return [Array<Symbol>]
|
|
77
|
+
PRESET_FULL = (BLOCK_BUTTONS + MEDIA_BUTTONS + WIDGET_BUTTONS).freeze
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Initialize a new sticky toolbar configuration.
|
|
81
|
+
#
|
|
82
|
+
# @param position [Symbol] toolbar position (:bottom, :left, :right)
|
|
83
|
+
# @param buttons [Array<Symbol>, nil] custom button list
|
|
84
|
+
# @param widget_types [Array<String>, nil] widget types for picker
|
|
85
|
+
# @param enabled [Boolean] whether the sticky toolbar is enabled
|
|
86
|
+
#
|
|
87
|
+
def initialize(position: :bottom, buttons: nil, widget_types: nil, enabled: true)
|
|
88
|
+
@position = validate_position(position)
|
|
89
|
+
@buttons = buttons || PRESET_FULL
|
|
90
|
+
@widget_types = widget_types || default_widget_types
|
|
91
|
+
@enabled = enabled
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
# Check if the sticky toolbar is enabled.
|
|
96
|
+
#
|
|
97
|
+
# @return [Boolean] true if enabled
|
|
98
|
+
#
|
|
99
|
+
def enabled?
|
|
100
|
+
@enabled
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
# Check if toolbar uses vertical layout (left or right).
|
|
105
|
+
#
|
|
106
|
+
# @return [Boolean] true if vertical
|
|
107
|
+
#
|
|
108
|
+
def vertical?
|
|
109
|
+
%i[left right].include?(position)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Check if toolbar uses horizontal layout (bottom).
|
|
114
|
+
#
|
|
115
|
+
# @return [Boolean] true if horizontal
|
|
116
|
+
#
|
|
117
|
+
def horizontal?
|
|
118
|
+
position == :bottom
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
##
|
|
122
|
+
# Generate data attributes for Stimulus controller.
|
|
123
|
+
#
|
|
124
|
+
# @return [Hash] data attributes hash
|
|
125
|
+
#
|
|
126
|
+
def data_attributes
|
|
127
|
+
{
|
|
128
|
+
"inkpen--sticky-toolbar-position-value" => position.to_s,
|
|
129
|
+
"inkpen--sticky-toolbar-buttons-value" => buttons.to_json,
|
|
130
|
+
"inkpen--sticky-toolbar-widget-types-value" => widget_types.to_json,
|
|
131
|
+
"inkpen--sticky-toolbar-vertical-value" => vertical?.to_s
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
##
|
|
138
|
+
# Validate and normalize position value.
|
|
139
|
+
#
|
|
140
|
+
# @param pos [Symbol, String] position to validate
|
|
141
|
+
# @return [Symbol] validated position
|
|
142
|
+
#
|
|
143
|
+
def validate_position(pos)
|
|
144
|
+
pos = pos.to_sym
|
|
145
|
+
POSITIONS.include?(pos) ? pos : :bottom
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
##
|
|
149
|
+
# Default widget types for the widget picker.
|
|
150
|
+
#
|
|
151
|
+
# @return [Array<String>] widget type names
|
|
152
|
+
#
|
|
153
|
+
def default_widget_types
|
|
154
|
+
%w[form gallery poll]
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inkpen
|
|
4
|
+
##
|
|
5
|
+
# PORO representing toolbar configuration.
|
|
6
|
+
#
|
|
7
|
+
# The Toolbar class configures the floating or fixed toolbar that
|
|
8
|
+
# appears when text is selected (floating) or at the top of the
|
|
9
|
+
# editor (fixed).
|
|
10
|
+
#
|
|
11
|
+
# @example Basic floating toolbar
|
|
12
|
+
# toolbar = Inkpen::Toolbar.new(style: :floating)
|
|
13
|
+
#
|
|
14
|
+
# @example Custom button set
|
|
15
|
+
# toolbar = Inkpen::Toolbar.new(
|
|
16
|
+
# style: :fixed,
|
|
17
|
+
# buttons: [:bold, :italic, :link, :heading]
|
|
18
|
+
# )
|
|
19
|
+
#
|
|
20
|
+
# @example Using presets
|
|
21
|
+
# toolbar = Inkpen::Toolbar.new(
|
|
22
|
+
# style: :floating,
|
|
23
|
+
# buttons: Inkpen::Toolbar::PRESET_MINIMAL
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
# @see Inkpen::StickyToolbar For block insertion toolbar
|
|
27
|
+
#
|
|
28
|
+
# @author Nauman Tariq
|
|
29
|
+
# @since 0.1.0
|
|
30
|
+
#
|
|
31
|
+
class Toolbar
|
|
32
|
+
##
|
|
33
|
+
# @!attribute [r] style
|
|
34
|
+
# @return [Symbol] toolbar display style (:floating, :fixed, :none)
|
|
35
|
+
#
|
|
36
|
+
# @!attribute [r] buttons
|
|
37
|
+
# @return [Array<Symbol>] list of toolbar buttons
|
|
38
|
+
#
|
|
39
|
+
# @!attribute [r] position
|
|
40
|
+
# @return [Symbol] toolbar position (:top, :bottom)
|
|
41
|
+
#
|
|
42
|
+
attr_reader :style, :buttons, :position
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Text formatting buttons (bold, italic, strike, underline).
|
|
46
|
+
# @return [Array<Symbol>]
|
|
47
|
+
FORMATTING_BUTTONS = %i[bold italic strike underline].freeze
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Block-level formatting buttons.
|
|
51
|
+
# @return [Array<Symbol>]
|
|
52
|
+
BLOCK_BUTTONS = %i[heading bullet_list ordered_list blockquote code_block].freeze
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Content insertion buttons.
|
|
56
|
+
# @return [Array<Symbol>]
|
|
57
|
+
INSERT_BUTTONS = %i[link image table horizontal_rule].freeze
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Minimal preset (bold, italic, link).
|
|
61
|
+
# @return [Array<Symbol>]
|
|
62
|
+
PRESET_MINIMAL = %i[bold italic link].freeze
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Standard preset with common formatting options.
|
|
66
|
+
# @return [Array<Symbol>]
|
|
67
|
+
PRESET_STANDARD = (FORMATTING_BUTTONS + %i[link heading bullet_list ordered_list blockquote]).freeze
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Full preset with all available buttons.
|
|
71
|
+
# @return [Array<Symbol>]
|
|
72
|
+
PRESET_FULL = (FORMATTING_BUTTONS + BLOCK_BUTTONS + INSERT_BUTTONS).freeze
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Initialize a new toolbar configuration.
|
|
76
|
+
#
|
|
77
|
+
# @param style [Symbol] toolbar style (:floating, :fixed, :none)
|
|
78
|
+
# @param buttons [Array<Symbol>, nil] custom button list (defaults to preset)
|
|
79
|
+
# @param position [Symbol] toolbar position (:top, :bottom)
|
|
80
|
+
#
|
|
81
|
+
def initialize(style: :floating, buttons: nil, position: :top)
|
|
82
|
+
@style = style.to_sym
|
|
83
|
+
@buttons = buttons || default_buttons_for_style
|
|
84
|
+
@position = position.to_sym
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Check if toolbar uses floating (bubble menu) style.
|
|
89
|
+
#
|
|
90
|
+
# @return [Boolean] true if floating style
|
|
91
|
+
#
|
|
92
|
+
def floating?
|
|
93
|
+
style == :floating
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# Check if toolbar uses fixed style.
|
|
98
|
+
#
|
|
99
|
+
# @return [Boolean] true if fixed style
|
|
100
|
+
#
|
|
101
|
+
def fixed?
|
|
102
|
+
style == :fixed
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# Check if toolbar is hidden.
|
|
107
|
+
#
|
|
108
|
+
# @return [Boolean] true if toolbar is hidden
|
|
109
|
+
#
|
|
110
|
+
def hidden?
|
|
111
|
+
style == :none
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
##
|
|
115
|
+
# Generate data attributes for Stimulus controller.
|
|
116
|
+
#
|
|
117
|
+
# @return [Hash] data attributes hash
|
|
118
|
+
#
|
|
119
|
+
def data_attributes
|
|
120
|
+
{
|
|
121
|
+
"inkpen--toolbar-style-value" => style.to_s,
|
|
122
|
+
"inkpen--toolbar-buttons-value" => buttons.to_json,
|
|
123
|
+
"inkpen--toolbar-position-value" => position.to_s
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# Get default buttons based on toolbar style.
|
|
131
|
+
#
|
|
132
|
+
# @return [Array<Symbol>] default button list
|
|
133
|
+
#
|
|
134
|
+
def default_buttons_for_style
|
|
135
|
+
case style
|
|
136
|
+
when :floating
|
|
137
|
+
PRESET_STANDARD
|
|
138
|
+
when :fixed
|
|
139
|
+
PRESET_FULL
|
|
140
|
+
else
|
|
141
|
+
[]
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
data/lib/inkpen.rb
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "inkpen/version"
|
|
4
|
+
require_relative "inkpen/engine"
|
|
5
|
+
require_relative "inkpen/configuration"
|
|
6
|
+
require_relative "inkpen/editor"
|
|
7
|
+
require_relative "inkpen/toolbar"
|
|
8
|
+
require_relative "inkpen/sticky_toolbar"
|
|
9
|
+
require_relative "inkpen/markdown_mode"
|
|
10
|
+
|
|
11
|
+
# Extensions
|
|
12
|
+
require_relative "inkpen/extensions/base"
|
|
13
|
+
require_relative "inkpen/extensions/forced_document"
|
|
14
|
+
require_relative "inkpen/extensions/mention"
|
|
15
|
+
require_relative "inkpen/extensions/code_block_syntax"
|
|
16
|
+
require_relative "inkpen/extensions/table"
|
|
17
|
+
require_relative "inkpen/extensions/task_list"
|
|
18
|
+
require_relative "inkpen/extensions/slash_commands"
|
|
19
|
+
require_relative "inkpen/extensions/section"
|
|
20
|
+
require_relative "inkpen/extensions/preformatted"
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Inkpen is a TipTap-based rich text editor gem for Rails applications.
|
|
24
|
+
#
|
|
25
|
+
# It provides a customizable WYSIWYG editor with Notion-like features including:
|
|
26
|
+
# - Block-based editing with drag & drop
|
|
27
|
+
# - Slash commands for quick block insertion
|
|
28
|
+
# - Multiple toolbar options (floating, fixed, sticky)
|
|
29
|
+
# - 16+ custom TipTap extensions
|
|
30
|
+
# - Export to Markdown, HTML, and PDF
|
|
31
|
+
#
|
|
32
|
+
# @example Basic usage with Rails form
|
|
33
|
+
# <%= inkpen_editor form, :content %>
|
|
34
|
+
#
|
|
35
|
+
# @example With configuration
|
|
36
|
+
# Inkpen.configure do |config|
|
|
37
|
+
# config.toolbar = :floating
|
|
38
|
+
# config.extensions = [:bold, :italic, :link, :slash_commands]
|
|
39
|
+
# config.placeholder = "Start writing..."
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# @example Full-featured editor
|
|
43
|
+
# <%= inkpen_editor form, :content,
|
|
44
|
+
# toolbar: :sticky,
|
|
45
|
+
# extensions: [:slash_commands, :block_gutter, :drag_handle, :export_commands] %>
|
|
46
|
+
#
|
|
47
|
+
# @see Inkpen::Configuration Global configuration options
|
|
48
|
+
# @see Inkpen::Editor Editor instance configuration
|
|
49
|
+
# @see Inkpen::Extensions Extension system
|
|
50
|
+
#
|
|
51
|
+
# @author Nauman Tariq
|
|
52
|
+
# @since 0.1.0
|
|
53
|
+
#
|
|
54
|
+
module Inkpen
|
|
55
|
+
##
|
|
56
|
+
# Base error class for all Inkpen errors.
|
|
57
|
+
#
|
|
58
|
+
class Error < StandardError; end
|
|
59
|
+
|
|
60
|
+
class << self
|
|
61
|
+
##
|
|
62
|
+
# @return [Configuration] the global configuration instance
|
|
63
|
+
attr_writer :configuration
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Returns the global configuration instance.
|
|
67
|
+
#
|
|
68
|
+
# @return [Configuration] the configuration object
|
|
69
|
+
#
|
|
70
|
+
def configuration
|
|
71
|
+
@configuration ||= Configuration.new
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Configure Inkpen globally.
|
|
76
|
+
#
|
|
77
|
+
# @yield [config] Yields the configuration object for modification
|
|
78
|
+
# @yieldparam config [Configuration] the configuration object
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# Inkpen.configure do |config|
|
|
82
|
+
# config.toolbar = :floating
|
|
83
|
+
# config.extensions = [:bold, :italic, :link]
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
def configure
|
|
87
|
+
yield(configuration)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Reset configuration to defaults.
|
|
92
|
+
#
|
|
93
|
+
# Useful for testing or reinitializing the editor configuration.
|
|
94
|
+
#
|
|
95
|
+
# @return [Configuration] the new configuration object
|
|
96
|
+
#
|
|
97
|
+
def reset_configuration!
|
|
98
|
+
@configuration = Configuration.new
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
data/sig/inkpen.rbs
ADDED