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,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