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,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inkpen
|
|
4
|
+
module Extensions
|
|
5
|
+
##
|
|
6
|
+
# Mention extension for @mentions functionality.
|
|
7
|
+
#
|
|
8
|
+
# Enables users to mention other users or entities using the @ symbol.
|
|
9
|
+
# When triggered, a popup appears with suggestions that can be filtered
|
|
10
|
+
# by typing.
|
|
11
|
+
#
|
|
12
|
+
# The suggestion data is fetched from a configurable endpoint.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# extension = Inkpen::Extensions::Mention.new(
|
|
16
|
+
# search_url: "/api/users/search"
|
|
17
|
+
# )
|
|
18
|
+
#
|
|
19
|
+
# @example With custom trigger and styling
|
|
20
|
+
# extension = Inkpen::Extensions::Mention.new(
|
|
21
|
+
# search_url: "/api/users/search",
|
|
22
|
+
# trigger: "@",
|
|
23
|
+
# suggestion_class: "mention-suggestion",
|
|
24
|
+
# item_class: "mention-item"
|
|
25
|
+
# )
|
|
26
|
+
#
|
|
27
|
+
# @see https://tiptap.dev/docs/examples/advanced/mentions
|
|
28
|
+
# @author Inkpen Team
|
|
29
|
+
# @since 0.2.0
|
|
30
|
+
#
|
|
31
|
+
class Mention < Base
|
|
32
|
+
##
|
|
33
|
+
# Default trigger character for mentions.
|
|
34
|
+
DEFAULT_TRIGGER = "@"
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Default minimum characters to start searching.
|
|
38
|
+
DEFAULT_MIN_CHARS = 1
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# The unique name of this extension.
|
|
42
|
+
#
|
|
43
|
+
# @return [Symbol] :mention
|
|
44
|
+
#
|
|
45
|
+
def name
|
|
46
|
+
:mention
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# The character that triggers the mention popup.
|
|
51
|
+
#
|
|
52
|
+
# @return [String] trigger character (default: "@")
|
|
53
|
+
#
|
|
54
|
+
def trigger
|
|
55
|
+
options.fetch(:trigger, DEFAULT_TRIGGER)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# URL endpoint for fetching mention suggestions.
|
|
60
|
+
#
|
|
61
|
+
# The endpoint receives a `query` parameter with the search text.
|
|
62
|
+
# Expected response format: [{ id: 1, label: "Username", ... }]
|
|
63
|
+
#
|
|
64
|
+
# @return [String, nil] search URL or nil if using static items
|
|
65
|
+
#
|
|
66
|
+
def search_url
|
|
67
|
+
options[:search_url]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Static items to show in the suggestion popup.
|
|
72
|
+
#
|
|
73
|
+
# Use this instead of search_url for fixed suggestion lists.
|
|
74
|
+
#
|
|
75
|
+
# @return [Array<Hash>, nil] array of suggestion items
|
|
76
|
+
#
|
|
77
|
+
def items
|
|
78
|
+
options[:items]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# Minimum characters before triggering search.
|
|
83
|
+
#
|
|
84
|
+
# @return [Integer] minimum character count
|
|
85
|
+
#
|
|
86
|
+
def min_chars
|
|
87
|
+
options.fetch(:min_chars, DEFAULT_MIN_CHARS)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# CSS class for the suggestion popup container.
|
|
92
|
+
#
|
|
93
|
+
# @return [String] CSS class name
|
|
94
|
+
#
|
|
95
|
+
def suggestion_class
|
|
96
|
+
options.fetch(:suggestion_class, "inkpen-mention-suggestions")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
# CSS class for individual suggestion items.
|
|
101
|
+
#
|
|
102
|
+
# @return [String] CSS class name
|
|
103
|
+
#
|
|
104
|
+
def item_class
|
|
105
|
+
options.fetch(:item_class, "inkpen-mention-item")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Whether to allow creating new mentions not in the suggestion list.
|
|
110
|
+
#
|
|
111
|
+
# @return [Boolean] true to allow custom mentions
|
|
112
|
+
#
|
|
113
|
+
def allow_custom?
|
|
114
|
+
options.fetch(:allow_custom, false)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
# HTML attributes rendered on the mention node.
|
|
119
|
+
#
|
|
120
|
+
# @return [Hash] HTML attributes
|
|
121
|
+
#
|
|
122
|
+
def html_attributes
|
|
123
|
+
options.fetch(:html_attributes, { class: "inkpen-mention" })
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# Convert to configuration hash for JavaScript.
|
|
128
|
+
#
|
|
129
|
+
# @return [Hash] configuration for the TipTap extension
|
|
130
|
+
#
|
|
131
|
+
def to_config
|
|
132
|
+
{
|
|
133
|
+
trigger: trigger,
|
|
134
|
+
searchUrl: search_url,
|
|
135
|
+
items: items,
|
|
136
|
+
minChars: min_chars,
|
|
137
|
+
suggestionClass: suggestion_class,
|
|
138
|
+
itemClass: item_class,
|
|
139
|
+
allowCustom: allow_custom?,
|
|
140
|
+
HTMLAttributes: html_attributes
|
|
141
|
+
}.compact
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
def default_options
|
|
147
|
+
super.merge(
|
|
148
|
+
trigger: DEFAULT_TRIGGER,
|
|
149
|
+
min_chars: DEFAULT_MIN_CHARS,
|
|
150
|
+
allow_custom: false
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inkpen
|
|
4
|
+
module Extensions
|
|
5
|
+
##
|
|
6
|
+
# Preformatted text extension for TipTap.
|
|
7
|
+
#
|
|
8
|
+
# Plain text block that preserves whitespace exactly as typed.
|
|
9
|
+
# Perfect for ASCII art, ASCII tables, diagrams, and any content
|
|
10
|
+
# that requires precise character alignment.
|
|
11
|
+
#
|
|
12
|
+
# Unlike CodeBlock, this has NO syntax highlighting - just pure
|
|
13
|
+
# monospace text with preserved whitespace.
|
|
14
|
+
#
|
|
15
|
+
# @example Basic usage
|
|
16
|
+
# extension = Inkpen::Extensions::Preformatted.new
|
|
17
|
+
#
|
|
18
|
+
# @example With custom options
|
|
19
|
+
# extension = Inkpen::Extensions::Preformatted.new(
|
|
20
|
+
# show_line_numbers: false,
|
|
21
|
+
# wrap_lines: false
|
|
22
|
+
# )
|
|
23
|
+
#
|
|
24
|
+
# @see https://tiptap.dev/docs/editor/api/nodes
|
|
25
|
+
# @author Inkpen Team
|
|
26
|
+
# @since 0.3.0
|
|
27
|
+
#
|
|
28
|
+
class Preformatted < Base
|
|
29
|
+
##
|
|
30
|
+
# The unique name of this extension.
|
|
31
|
+
#
|
|
32
|
+
# @return [Symbol] :preformatted
|
|
33
|
+
#
|
|
34
|
+
def name
|
|
35
|
+
:preformatted
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Whether to show line numbers.
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean] true to show line numbers
|
|
42
|
+
#
|
|
43
|
+
def show_line_numbers?
|
|
44
|
+
options.fetch(:show_line_numbers, false)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Whether to wrap long lines.
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean] true to wrap lines
|
|
51
|
+
#
|
|
52
|
+
def wrap_lines?
|
|
53
|
+
options.fetch(:wrap_lines, false)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# Tab size in spaces.
|
|
58
|
+
#
|
|
59
|
+
# @return [Integer] number of spaces per tab
|
|
60
|
+
#
|
|
61
|
+
def tab_size
|
|
62
|
+
options.fetch(:tab_size, 4)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Whether to show a label/badge.
|
|
67
|
+
#
|
|
68
|
+
# @return [Boolean] true to show label
|
|
69
|
+
#
|
|
70
|
+
def show_label?
|
|
71
|
+
options.fetch(:show_label, true)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Custom label text.
|
|
76
|
+
#
|
|
77
|
+
# @return [String] the label text
|
|
78
|
+
#
|
|
79
|
+
def label_text
|
|
80
|
+
options.fetch(:label_text, "Plain Text")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Convert to configuration hash for JavaScript.
|
|
85
|
+
#
|
|
86
|
+
# @return [Hash] configuration for the TipTap extension
|
|
87
|
+
#
|
|
88
|
+
def to_config
|
|
89
|
+
{
|
|
90
|
+
showLineNumbers: show_line_numbers?,
|
|
91
|
+
wrapLines: wrap_lines?,
|
|
92
|
+
tabSize: tab_size,
|
|
93
|
+
showLabel: show_label?,
|
|
94
|
+
labelText: label_text
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def default_options
|
|
101
|
+
super.merge(
|
|
102
|
+
show_line_numbers: false,
|
|
103
|
+
wrap_lines: false,
|
|
104
|
+
tab_size: 4,
|
|
105
|
+
show_label: true,
|
|
106
|
+
label_text: "Plain Text"
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inkpen
|
|
4
|
+
module Extensions
|
|
5
|
+
##
|
|
6
|
+
# Section extension for TipTap.
|
|
7
|
+
#
|
|
8
|
+
# Block-level container that controls content width and vertical spacing.
|
|
9
|
+
# Useful for page-builder style layouts where different sections need
|
|
10
|
+
# different widths (narrow for reading, wide for media, full for heroes).
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage
|
|
13
|
+
# extension = Inkpen::Extensions::Section.new
|
|
14
|
+
#
|
|
15
|
+
# @example With custom options
|
|
16
|
+
# extension = Inkpen::Extensions::Section.new(
|
|
17
|
+
# default_width: "wide",
|
|
18
|
+
# default_spacing: "spacious",
|
|
19
|
+
# show_controls: true
|
|
20
|
+
# )
|
|
21
|
+
#
|
|
22
|
+
# @see https://tiptap.dev/docs/editor/api/nodes
|
|
23
|
+
# @author Inkpen Team
|
|
24
|
+
# @since 0.3.0
|
|
25
|
+
#
|
|
26
|
+
class Section < Base
|
|
27
|
+
##
|
|
28
|
+
# Available width presets with their CSS values.
|
|
29
|
+
WIDTH_PRESETS = {
|
|
30
|
+
narrow: { max_width: "560px", label: "Narrow" },
|
|
31
|
+
default: { max_width: "680px", label: "Default" },
|
|
32
|
+
wide: { max_width: "900px", label: "Wide" },
|
|
33
|
+
full: { max_width: "100%", label: "Full" }
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Available spacing presets with their CSS values.
|
|
38
|
+
SPACING_PRESETS = {
|
|
39
|
+
compact: { padding: "1rem 0", label: "Compact" },
|
|
40
|
+
normal: { padding: "2rem 0", label: "Normal" },
|
|
41
|
+
spacious: { padding: "4rem 0", label: "Spacious" }
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# The unique name of this extension.
|
|
46
|
+
#
|
|
47
|
+
# @return [Symbol] :section
|
|
48
|
+
#
|
|
49
|
+
def name
|
|
50
|
+
:section
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Default width preset for new sections.
|
|
55
|
+
#
|
|
56
|
+
# @return [String] the default width preset key
|
|
57
|
+
#
|
|
58
|
+
def default_width
|
|
59
|
+
options.fetch(:default_width, "default")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Default spacing preset for new sections.
|
|
64
|
+
#
|
|
65
|
+
# @return [String] the default spacing preset key
|
|
66
|
+
#
|
|
67
|
+
def default_spacing
|
|
68
|
+
options.fetch(:default_spacing, "normal")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Whether to show the section controls UI.
|
|
73
|
+
#
|
|
74
|
+
# @return [Boolean] true to show controls
|
|
75
|
+
#
|
|
76
|
+
def show_controls?
|
|
77
|
+
options.fetch(:show_controls, true)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Available width presets.
|
|
82
|
+
#
|
|
83
|
+
# @return [Hash] width presets with max_width and label
|
|
84
|
+
#
|
|
85
|
+
def width_presets
|
|
86
|
+
custom_presets = options[:width_presets] || {}
|
|
87
|
+
WIDTH_PRESETS.merge(custom_presets.transform_keys(&:to_sym))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Available spacing presets.
|
|
92
|
+
#
|
|
93
|
+
# @return [Hash] spacing presets with padding and label
|
|
94
|
+
#
|
|
95
|
+
def spacing_presets
|
|
96
|
+
custom_presets = options[:spacing_presets] || {}
|
|
97
|
+
SPACING_PRESETS.merge(custom_presets.transform_keys(&:to_sym))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# Convert to configuration hash for JavaScript.
|
|
102
|
+
#
|
|
103
|
+
# @return [Hash] configuration for the TipTap extension
|
|
104
|
+
#
|
|
105
|
+
def to_config
|
|
106
|
+
{
|
|
107
|
+
defaultWidth: default_width,
|
|
108
|
+
defaultSpacing: default_spacing,
|
|
109
|
+
showControls: show_controls?,
|
|
110
|
+
widthPresets: serialize_presets(width_presets),
|
|
111
|
+
spacingPresets: serialize_presets(spacing_presets)
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def default_options
|
|
118
|
+
super.merge(
|
|
119
|
+
default_width: "default",
|
|
120
|
+
default_spacing: "normal",
|
|
121
|
+
show_controls: true
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
##
|
|
126
|
+
# Convert Ruby-style presets to JavaScript camelCase.
|
|
127
|
+
#
|
|
128
|
+
def serialize_presets(presets)
|
|
129
|
+
presets.transform_values do |preset|
|
|
130
|
+
preset.transform_keys { |k| camelize(k.to_s) }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def camelize(string)
|
|
135
|
+
string.gsub(/_([a-z])/) { ::Regexp.last_match(1).upcase }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inkpen
|
|
4
|
+
module Extensions
|
|
5
|
+
# SlashCommands Extension
|
|
6
|
+
#
|
|
7
|
+
# Adds "/" command palette for quick insertion of blocks and formatting.
|
|
8
|
+
# Similar to Notion's slash commands or Dropbox Paper's "/" menu.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# extension = Inkpen::Extensions::SlashCommands.new
|
|
12
|
+
#
|
|
13
|
+
# @example With custom commands
|
|
14
|
+
# extension = Inkpen::Extensions::SlashCommands.new(
|
|
15
|
+
# trigger: "/",
|
|
16
|
+
# commands: [
|
|
17
|
+
# { name: "heading1", label: "Heading 1", icon: "h1", group: "Basic" },
|
|
18
|
+
# { name: "heading2", label: "Heading 2", icon: "h2", group: "Basic" },
|
|
19
|
+
# { name: "bullet_list", label: "Bullet List", icon: "list", group: "Lists" },
|
|
20
|
+
# { name: "code_block", label: "Code Block", icon: "code", group: "Advanced" }
|
|
21
|
+
# ],
|
|
22
|
+
# groups: ["Basic", "Lists", "Media", "Advanced"],
|
|
23
|
+
# max_suggestions: 10,
|
|
24
|
+
# allow_filtering: true
|
|
25
|
+
# )
|
|
26
|
+
#
|
|
27
|
+
class SlashCommands < Base
|
|
28
|
+
# Default command set matching Notion/Paper experience
|
|
29
|
+
DEFAULT_COMMANDS = [
|
|
30
|
+
# Text blocks
|
|
31
|
+
{ name: "paragraph", label: "Text", description: "Plain text block", icon: "text", group: "Basic", shortcut: nil },
|
|
32
|
+
{ name: "heading1", label: "Heading 1", description: "Large heading", icon: "h1", group: "Basic", shortcut: "#" },
|
|
33
|
+
{ name: "heading2", label: "Heading 2", description: "Medium heading", icon: "h2", group: "Basic", shortcut: "##" },
|
|
34
|
+
{ name: "heading3", label: "Heading 3", description: "Small heading", icon: "h3", group: "Basic", shortcut: "###" },
|
|
35
|
+
|
|
36
|
+
# Lists
|
|
37
|
+
{ name: "bullet_list", label: "Bullet List", description: "Unordered list", icon: "list", group: "Lists", shortcut: "-" },
|
|
38
|
+
{ name: "ordered_list", label: "Numbered List", description: "Ordered list", icon: "list-ordered", group: "Lists", shortcut: "1." },
|
|
39
|
+
{ name: "task_list", label: "Task List", description: "Checklist with checkboxes", icon: "check-square", group: "Lists", shortcut: "[]" },
|
|
40
|
+
|
|
41
|
+
# Blocks
|
|
42
|
+
{ name: "blockquote", label: "Quote", description: "Quote block", icon: "quote", group: "Blocks", shortcut: ">" },
|
|
43
|
+
{ name: "code_block", label: "Code Block", description: "Code with syntax highlighting", icon: "code", group: "Blocks", shortcut: "```" },
|
|
44
|
+
{ name: "horizontal_rule", label: "Divider", description: "Horizontal line", icon: "minus", group: "Blocks", shortcut: "---" },
|
|
45
|
+
|
|
46
|
+
# Media
|
|
47
|
+
{ name: "image", label: "Image", description: "Upload or embed an image", icon: "image", group: "Media", shortcut: nil },
|
|
48
|
+
{ name: "youtube", label: "YouTube", description: "Embed a YouTube video", icon: "youtube", group: "Media", shortcut: nil },
|
|
49
|
+
|
|
50
|
+
# Advanced
|
|
51
|
+
{ name: "table", label: "Table", description: "Insert a table", icon: "table", group: "Advanced", shortcut: nil },
|
|
52
|
+
{ name: "callout", label: "Callout", description: "Highlighted callout box", icon: "alert-circle", group: "Advanced", shortcut: nil },
|
|
53
|
+
{ name: "section", label: "Section", description: "Page section with width control", icon: "layout", group: "Advanced", shortcut: nil },
|
|
54
|
+
{ name: "preformatted", label: "Plain Text", description: "Preformatted text for ASCII art", icon: "file-text", group: "Advanced", shortcut: nil }
|
|
55
|
+
].freeze
|
|
56
|
+
|
|
57
|
+
DEFAULT_GROUPS = %w[Basic Lists Blocks Media Advanced].freeze
|
|
58
|
+
|
|
59
|
+
def name
|
|
60
|
+
:slash_commands
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def to_config
|
|
64
|
+
{
|
|
65
|
+
trigger: options[:trigger],
|
|
66
|
+
commands: options[:commands],
|
|
67
|
+
groups: options[:groups],
|
|
68
|
+
maxSuggestions: options[:max_suggestions],
|
|
69
|
+
allowFiltering: options[:allow_filtering],
|
|
70
|
+
showIcons: options[:show_icons],
|
|
71
|
+
showDescriptions: options[:show_descriptions],
|
|
72
|
+
showShortcuts: options[:show_shortcuts],
|
|
73
|
+
suggestionClass: options[:suggestion_class],
|
|
74
|
+
itemClass: options[:item_class],
|
|
75
|
+
activeClass: options[:active_class],
|
|
76
|
+
groupClass: options[:group_class]
|
|
77
|
+
}.compact
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def default_options
|
|
83
|
+
super.merge(
|
|
84
|
+
trigger: "/",
|
|
85
|
+
commands: DEFAULT_COMMANDS,
|
|
86
|
+
groups: DEFAULT_GROUPS,
|
|
87
|
+
max_suggestions: 10,
|
|
88
|
+
allow_filtering: true,
|
|
89
|
+
show_icons: true,
|
|
90
|
+
show_descriptions: true,
|
|
91
|
+
show_shortcuts: false,
|
|
92
|
+
suggestion_class: "inkpen-slash-menu",
|
|
93
|
+
item_class: "inkpen-slash-item",
|
|
94
|
+
active_class: "is-selected",
|
|
95
|
+
group_class: "inkpen-slash-group"
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inkpen
|
|
4
|
+
module Extensions
|
|
5
|
+
##
|
|
6
|
+
# Table extension for TipTap.
|
|
7
|
+
#
|
|
8
|
+
# Enables table functionality including headers, cells, row/column
|
|
9
|
+
# operations, and cell merging. Uses ProseMirror's table system
|
|
10
|
+
# under the hood.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage
|
|
13
|
+
# extension = Inkpen::Extensions::Table.new
|
|
14
|
+
#
|
|
15
|
+
# @example With custom options
|
|
16
|
+
# extension = Inkpen::Extensions::Table.new(
|
|
17
|
+
# resizable: true,
|
|
18
|
+
# header_row: true,
|
|
19
|
+
# cell_min_width: 100
|
|
20
|
+
# )
|
|
21
|
+
#
|
|
22
|
+
# @see https://tiptap.dev/docs/examples/basics/tables
|
|
23
|
+
# @author Inkpen Team
|
|
24
|
+
# @since 0.2.0
|
|
25
|
+
#
|
|
26
|
+
class Table < Base
|
|
27
|
+
##
|
|
28
|
+
# Default minimum cell width in pixels.
|
|
29
|
+
DEFAULT_CELL_MIN_WIDTH = 25
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Default maximum cell width in pixels.
|
|
33
|
+
DEFAULT_CELL_MAX_WIDTH = 500
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# The unique name of this extension.
|
|
37
|
+
#
|
|
38
|
+
# @return [Symbol] :table
|
|
39
|
+
#
|
|
40
|
+
def name
|
|
41
|
+
:table
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Whether table columns can be resized.
|
|
46
|
+
#
|
|
47
|
+
# @return [Boolean] true to enable column resizing
|
|
48
|
+
#
|
|
49
|
+
def resizable?
|
|
50
|
+
options.fetch(:resizable, true)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Whether the first row is treated as a header row.
|
|
55
|
+
#
|
|
56
|
+
# @return [Boolean] true if first row is header
|
|
57
|
+
#
|
|
58
|
+
def header_row?
|
|
59
|
+
options.fetch(:header_row, true)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Whether the first column is treated as a header column.
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean] true if first column is header
|
|
66
|
+
#
|
|
67
|
+
def header_column?
|
|
68
|
+
options.fetch(:header_column, false)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Minimum cell width in pixels.
|
|
73
|
+
#
|
|
74
|
+
# @return [Integer] minimum width
|
|
75
|
+
#
|
|
76
|
+
def cell_min_width
|
|
77
|
+
options.fetch(:cell_min_width, DEFAULT_CELL_MIN_WIDTH)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Maximum cell width in pixels (optional).
|
|
82
|
+
#
|
|
83
|
+
# @return [Integer, nil] maximum width or nil for unlimited
|
|
84
|
+
#
|
|
85
|
+
def cell_max_width
|
|
86
|
+
options[:cell_max_width]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Whether to show the table toolbar.
|
|
91
|
+
#
|
|
92
|
+
# @return [Boolean] true to show toolbar
|
|
93
|
+
#
|
|
94
|
+
def toolbar?
|
|
95
|
+
options.fetch(:toolbar, true)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# Whether to allow cell background colors.
|
|
100
|
+
#
|
|
101
|
+
# @return [Boolean] true to enable cell backgrounds
|
|
102
|
+
#
|
|
103
|
+
def cell_backgrounds?
|
|
104
|
+
options.fetch(:cell_backgrounds, true)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
##
|
|
108
|
+
# Whether to allow cell merging.
|
|
109
|
+
#
|
|
110
|
+
# @return [Boolean] true to enable cell merging
|
|
111
|
+
#
|
|
112
|
+
def allow_merge?
|
|
113
|
+
options.fetch(:allow_merge, true)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# Default number of rows when inserting a new table.
|
|
118
|
+
#
|
|
119
|
+
# @return [Integer] default row count
|
|
120
|
+
#
|
|
121
|
+
def default_rows
|
|
122
|
+
options.fetch(:default_rows, 3)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
##
|
|
126
|
+
# Default number of columns when inserting a new table.
|
|
127
|
+
#
|
|
128
|
+
# @return [Integer] default column count
|
|
129
|
+
#
|
|
130
|
+
def default_cols
|
|
131
|
+
options.fetch(:default_cols, 3)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# Whether to include headers when inserting a new table.
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean] true to include header row
|
|
138
|
+
#
|
|
139
|
+
def with_header_row?
|
|
140
|
+
options.fetch(:with_header_row, true)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# Convert to configuration hash for JavaScript.
|
|
145
|
+
#
|
|
146
|
+
# @return [Hash] configuration for the TipTap extension
|
|
147
|
+
#
|
|
148
|
+
def to_config
|
|
149
|
+
{
|
|
150
|
+
resizable: resizable?,
|
|
151
|
+
headerRow: header_row?,
|
|
152
|
+
headerColumn: header_column?,
|
|
153
|
+
cellMinWidth: cell_min_width,
|
|
154
|
+
cellMaxWidth: cell_max_width,
|
|
155
|
+
toolbar: toolbar?,
|
|
156
|
+
cellBackgrounds: cell_backgrounds?,
|
|
157
|
+
allowMerge: allow_merge?,
|
|
158
|
+
defaultRows: default_rows,
|
|
159
|
+
defaultCols: default_cols,
|
|
160
|
+
withHeaderRow: with_header_row?
|
|
161
|
+
}.compact
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def default_options
|
|
167
|
+
super.merge(
|
|
168
|
+
resizable: true,
|
|
169
|
+
header_row: true,
|
|
170
|
+
header_column: false,
|
|
171
|
+
cell_min_width: DEFAULT_CELL_MIN_WIDTH,
|
|
172
|
+
toolbar: true,
|
|
173
|
+
cell_backgrounds: true,
|
|
174
|
+
allow_merge: true,
|
|
175
|
+
default_rows: 3,
|
|
176
|
+
default_cols: 3,
|
|
177
|
+
with_header_row: true
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|