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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 066e1936d8ce5802174354accd02c7e00a958bde47fc5e207463691afb1bc360
|
|
4
|
+
data.tar.gz: a25e8197e62b9227b61e8a7ab9f8ca682372aa7fd08d7a9b15c5d199457d1c15
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e57e7c1a93053612ac75e96d1265a6907e531a1061b5b71be173cf1320ffe5346d0966b4b7099ac6dc0f0b4858c8503894bc68d729bf58d38c4d509a45d5cb04
|
|
7
|
+
data.tar.gz: ca182780b4fcbaebac1def84ed5c8161b23049f2da2e2da98e3a071104a6c6a6a27f59fe6482e6b30875f498f4dffc527c1e1eb472c44724d428d97767295704
|
data/.DS_Store
ADDED
|
Binary file
|
data/.rubocop.yml
ADDED
data/.yardopts
ADDED
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code when working with the Inkpen gem.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Inkpen is a TipTap-based rich text editor gem for Rails. It provides a customizable WYSIWYG editor with:
|
|
8
|
+
- BubbleMenu (floating toolbar on text selection)
|
|
9
|
+
- Fixed toolbar option
|
|
10
|
+
- Extensions system (mentions, tables, code blocks, task lists)
|
|
11
|
+
- Theme support (light/dark)
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Inkpen is used for **Posts only** in MadeMySite. Pages use Monaco editor instead.
|
|
16
|
+
|
|
17
|
+
## JavaScript/Stimulus Patterns (Fizzy Style)
|
|
18
|
+
|
|
19
|
+
**IMPORTANT:** Always follow Fizzy patterns from `/Users/naumantariq/Code/00-source/fizzy`.
|
|
20
|
+
|
|
21
|
+
### Controller Structure
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
import { Controller } from "@hotwired/stimulus"
|
|
25
|
+
import { helperFunction } from "helpers/helper_name"
|
|
26
|
+
|
|
27
|
+
const CONSTANTS_HERE = 3000
|
|
28
|
+
|
|
29
|
+
export default class extends Controller {
|
|
30
|
+
static targets = ["element"]
|
|
31
|
+
static values = { url: String, delay: Number }
|
|
32
|
+
|
|
33
|
+
#privateField // Use # for private fields, NOT underscore
|
|
34
|
+
|
|
35
|
+
// Lifecycle
|
|
36
|
+
|
|
37
|
+
connect() { }
|
|
38
|
+
disconnect() { this.#cleanup() } // Always clean up
|
|
39
|
+
|
|
40
|
+
// Actions
|
|
41
|
+
|
|
42
|
+
actionMethod() { }
|
|
43
|
+
|
|
44
|
+
// Private
|
|
45
|
+
|
|
46
|
+
#privateMethod() { }
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Key Rules
|
|
51
|
+
|
|
52
|
+
- **Private fields**: Use `#field` NOT `_field`
|
|
53
|
+
- **Section comments**: `// Lifecycle`, `// Actions`, `// Private`
|
|
54
|
+
- **Always clean up**: Clear timers/observers in `disconnect()`
|
|
55
|
+
- **Values API**: Use `this.urlValue` NOT `getAttribute()`
|
|
56
|
+
- **Dispatch events**: For controller communication, NOT direct calls
|
|
57
|
+
- **Single responsibility**: Each controller does ONE thing well
|
|
58
|
+
|
|
59
|
+
### Helper Files
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
// ✅ Named function exports
|
|
63
|
+
export function throttle(fn, delay = 1000) { }
|
|
64
|
+
export function debounce(fn, delay = 1000) { }
|
|
65
|
+
|
|
66
|
+
// ❌ DON'T use object exports
|
|
67
|
+
export const Helpers = { throttle() { } }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Import Pattern
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
// ✅ Named imports
|
|
74
|
+
import { throttle, debounce } from "helpers/timing_helpers"
|
|
75
|
+
|
|
76
|
+
// ❌ DON'T import objects
|
|
77
|
+
import { TimingHelpers } from "helpers/timing_helpers"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Ruby Patterns
|
|
81
|
+
|
|
82
|
+
### PORO Structure
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
module Inkpen
|
|
86
|
+
class SomeClass
|
|
87
|
+
attr_reader :option1, :option2
|
|
88
|
+
|
|
89
|
+
def initialize(option1:, option2: default)
|
|
90
|
+
@option1 = option1
|
|
91
|
+
@option2 = option2
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def some_method
|
|
95
|
+
# Implementation
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def private_method
|
|
101
|
+
# Private logic
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## File Structure
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
lib/inkpen/
|
|
111
|
+
├── editor.rb # Main editor PORO
|
|
112
|
+
├── toolbar.rb # Toolbar configuration
|
|
113
|
+
├── configuration.rb # Global config
|
|
114
|
+
├── extensions/ # TipTap extension configs
|
|
115
|
+
│ ├── base.rb
|
|
116
|
+
│ ├── mention.rb
|
|
117
|
+
│ ├── table.rb
|
|
118
|
+
│ └── task_list.rb
|
|
119
|
+
└── engine.rb # Rails engine
|
|
120
|
+
|
|
121
|
+
app/
|
|
122
|
+
├── assets/
|
|
123
|
+
│ ├── javascripts/inkpen/
|
|
124
|
+
│ │ ├── controllers/
|
|
125
|
+
│ │ │ ├── editor_controller.js
|
|
126
|
+
│ │ │ └── toolbar_controller.js
|
|
127
|
+
│ │ └── index.js
|
|
128
|
+
│ └── stylesheets/inkpen/
|
|
129
|
+
└── helpers/inkpen/
|
|
130
|
+
└── editor_helper.rb
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Run tests
|
|
137
|
+
bundle exec rake test
|
|
138
|
+
|
|
139
|
+
# Console
|
|
140
|
+
bin/console
|
|
141
|
+
```
|
data/README.md
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# Inkpen
|
|
2
|
+
|
|
3
|
+
<a href="https://builtonrails.com/@nauman"><img src="https://builtonrails.com/badges/inkpen.svg" alt="Built on Rails"></a>
|
|
4
|
+
|
|
5
|
+
A modern, TipTap-based rich text editor for Ruby on Rails applications. Inkpen provides a clean, extensible editor with a PORO-based architecture that seamlessly integrates with Rails forms and Hotwire/Stimulus.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **TipTap/ProseMirror Foundation**: Built on the powerful TipTap editor framework
|
|
10
|
+
- **Rails Integration**: Works seamlessly with Rails forms, Turbo, and Stimulus
|
|
11
|
+
- **PORO Architecture**: Clean Ruby objects for configuration and extensions
|
|
12
|
+
- **Importmap Compatible**: No Node.js build step required
|
|
13
|
+
- **Extensible**: Modular extension system for adding features
|
|
14
|
+
- **Toolbar Options**: Floating, fixed, or hidden toolbar configurations
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Add to your Gemfile:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem "inkpen", github: "nauman/inkpen"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then run:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bundle install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
Configure Inkpen globally in an initializer:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
# config/initializers/inkpen.rb
|
|
36
|
+
|
|
37
|
+
Inkpen.configure do |config|
|
|
38
|
+
config.toolbar = :floating # :floating, :fixed, :none
|
|
39
|
+
config.placeholder = "Start writing..."
|
|
40
|
+
config.autosave = true
|
|
41
|
+
config.autosave_interval = 5000 # milliseconds
|
|
42
|
+
config.min_height = "200px"
|
|
43
|
+
config.max_height = "600px"
|
|
44
|
+
|
|
45
|
+
# Enable/disable extensions
|
|
46
|
+
config.extensions = [:bold, :italic, :link, :heading, :bullet_list]
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Basic Usage
|
|
51
|
+
|
|
52
|
+
### Creating an Editor Instance
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# In your controller or view
|
|
56
|
+
editor = Inkpen::Editor.new(
|
|
57
|
+
name: "post[body]",
|
|
58
|
+
value: @post.body,
|
|
59
|
+
toolbar: :floating,
|
|
60
|
+
extensions: [:bold, :italic, :link, :heading, :mentions],
|
|
61
|
+
placeholder: "Write your post..."
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### In Views (ERB)
|
|
66
|
+
|
|
67
|
+
```erb
|
|
68
|
+
<%= tag.div editor.data_attributes do %>
|
|
69
|
+
<%= hidden_field_tag editor.input_name, editor.value %>
|
|
70
|
+
<div class="inkpen-editor" style="<%= editor.style_attributes %>"></div>
|
|
71
|
+
<% end %>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Toolbar Configuration
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
toolbar = Inkpen::Toolbar.new(
|
|
78
|
+
style: :floating,
|
|
79
|
+
buttons: [:bold, :italic, :link, :heading],
|
|
80
|
+
position: :top
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Predefined button presets
|
|
84
|
+
Inkpen::Toolbar::PRESET_MINIMAL # [:bold, :italic, :link]
|
|
85
|
+
Inkpen::Toolbar::PRESET_STANDARD # Formatting + common blocks
|
|
86
|
+
Inkpen::Toolbar::PRESET_FULL # All available buttons
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Sticky Toolbar
|
|
90
|
+
|
|
91
|
+
The sticky toolbar provides a fixed-position toolbar for inserting blocks, media, and widgets. It supports horizontal (bottom) and vertical (left/right) positions.
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
# Enable sticky toolbar with default settings
|
|
95
|
+
editor = Inkpen::Editor.new(
|
|
96
|
+
name: "post[body]",
|
|
97
|
+
value: @post.body,
|
|
98
|
+
sticky_toolbar: Inkpen::StickyToolbar.new(
|
|
99
|
+
position: :bottom, # :bottom, :left, :right
|
|
100
|
+
buttons: [:table, :code_block, :image, :youtube, :widget],
|
|
101
|
+
widget_types: %w[form gallery poll]
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Available buttons:**
|
|
107
|
+
|
|
108
|
+
| Button | Description |
|
|
109
|
+
|--------|-------------|
|
|
110
|
+
| `table` | Insert a table |
|
|
111
|
+
| `code_block` | Insert code block |
|
|
112
|
+
| `blockquote` | Insert quote block |
|
|
113
|
+
| `horizontal_rule` | Insert divider line |
|
|
114
|
+
| `task_list` | Insert task list |
|
|
115
|
+
| `image` | Insert image (triggers `inkpen:request-image` event) |
|
|
116
|
+
| `youtube` | Insert YouTube video |
|
|
117
|
+
| `embed` | Insert embed (triggers `inkpen:request-embed` event) |
|
|
118
|
+
| `widget` | Open widget picker modal |
|
|
119
|
+
| `divider` | Visual separator |
|
|
120
|
+
|
|
121
|
+
**Presets:**
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
Inkpen::StickyToolbar::PRESET_BLOCKS # table, code_block, blockquote, etc.
|
|
125
|
+
Inkpen::StickyToolbar::PRESET_MEDIA # image, youtube, embed
|
|
126
|
+
Inkpen::StickyToolbar::PRESET_FULL # All buttons
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Handling widget events:**
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// In your application.js or page-specific controller
|
|
133
|
+
document.addEventListener("inkpen:insert-widget", (event) => {
|
|
134
|
+
const { type, controller } = event.detail
|
|
135
|
+
// type is "form", "gallery", or "poll"
|
|
136
|
+
// Show your widget picker UI
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
document.addEventListener("inkpen:request-image", (event) => {
|
|
140
|
+
const { controller } = event.detail
|
|
141
|
+
// Show image upload modal
|
|
142
|
+
// Then call: controller.insertImage(url, altText)
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Extensions
|
|
147
|
+
|
|
148
|
+
Inkpen uses a modular extension system. Each extension is a PORO that configures TipTap extensions.
|
|
149
|
+
|
|
150
|
+
### Core Extensions
|
|
151
|
+
|
|
152
|
+
Available by default:
|
|
153
|
+
|
|
154
|
+
- `bold`, `italic`, `strike`, `underline`
|
|
155
|
+
- `link`, `heading`
|
|
156
|
+
- `bullet_list`, `ordered_list`
|
|
157
|
+
- `blockquote`, `code_block`
|
|
158
|
+
- `horizontal_rule`, `hard_break`
|
|
159
|
+
|
|
160
|
+
### Advanced Extensions
|
|
161
|
+
|
|
162
|
+
#### Forced Document Structure
|
|
163
|
+
|
|
164
|
+
Enforces a document structure with a required title heading:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
extension = Inkpen::Extensions::ForcedDocument.new(
|
|
168
|
+
heading_level: 1,
|
|
169
|
+
placeholder: "Enter your title...",
|
|
170
|
+
allow_deletion: false
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
extension.to_config
|
|
174
|
+
# => { headingLevel: 1, titlePlaceholder: "Enter your title...", ... }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Mentions
|
|
178
|
+
|
|
179
|
+
Enable @mentions functionality:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
extension = Inkpen::Extensions::Mention.new(
|
|
183
|
+
search_url: "/api/users/search",
|
|
184
|
+
trigger: "@",
|
|
185
|
+
min_chars: 1,
|
|
186
|
+
suggestion_class: "mention-popup",
|
|
187
|
+
allow_custom: false
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Or with static items:
|
|
191
|
+
extension = Inkpen::Extensions::Mention.new(
|
|
192
|
+
items: [
|
|
193
|
+
{ id: 1, label: "John Doe" },
|
|
194
|
+
{ id: 2, label: "Jane Smith" }
|
|
195
|
+
]
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Code Block with Syntax Highlighting
|
|
200
|
+
|
|
201
|
+
Add syntax highlighting to code blocks:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
extension = Inkpen::Extensions::CodeBlockSyntax.new(
|
|
205
|
+
languages: [:ruby, :javascript, :python, :sql],
|
|
206
|
+
default_language: :ruby,
|
|
207
|
+
line_numbers: true,
|
|
208
|
+
language_selector: true,
|
|
209
|
+
copy_button: true,
|
|
210
|
+
theme: "github" # or "monokai", "dracula"
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Available languages: `javascript`, `typescript`, `ruby`, `python`, `css`, `xml`, `html`, `json`, `bash`, `sql`, `markdown`, `go`, `rust`, `java`, `kotlin`, `swift`, `php`, `c`, `cpp`, `csharp`, `elixir`, and more.
|
|
215
|
+
|
|
216
|
+
#### Tables
|
|
217
|
+
|
|
218
|
+
Add table support with resizing and toolbar:
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
extension = Inkpen::Extensions::Table.new(
|
|
222
|
+
resizable: true,
|
|
223
|
+
header_row: true,
|
|
224
|
+
header_column: false,
|
|
225
|
+
cell_min_width: 25,
|
|
226
|
+
toolbar: true,
|
|
227
|
+
allow_merge: true,
|
|
228
|
+
default_rows: 3,
|
|
229
|
+
default_cols: 3
|
|
230
|
+
)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Task Lists
|
|
234
|
+
|
|
235
|
+
Add interactive checkboxes/task lists:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
extension = Inkpen::Extensions::TaskList.new(
|
|
239
|
+
nested: true,
|
|
240
|
+
list_class: "task-list",
|
|
241
|
+
item_class: "task-item",
|
|
242
|
+
checked_class: "task-checked",
|
|
243
|
+
keyboard_shortcut: "Mod-Shift-9"
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Creating Custom Extensions
|
|
248
|
+
|
|
249
|
+
Extend `Inkpen::Extensions::Base`:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
module Inkpen
|
|
253
|
+
module Extensions
|
|
254
|
+
class MyCustomExtension < Base
|
|
255
|
+
def name
|
|
256
|
+
:my_custom
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def to_config
|
|
260
|
+
{
|
|
261
|
+
optionOne: options.fetch(:option_one, "default"),
|
|
262
|
+
optionTwo: options.fetch(:option_two, true)
|
|
263
|
+
}
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
private
|
|
267
|
+
|
|
268
|
+
def default_options
|
|
269
|
+
super.merge(
|
|
270
|
+
option_one: "default",
|
|
271
|
+
option_two: true
|
|
272
|
+
)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## JavaScript Integration
|
|
280
|
+
|
|
281
|
+
Inkpen uses Stimulus controllers and importmaps. The gem automatically registers pins for TipTap and ProseMirror dependencies.
|
|
282
|
+
|
|
283
|
+
### Required Importmap Pins
|
|
284
|
+
|
|
285
|
+
The gem includes pins for:
|
|
286
|
+
|
|
287
|
+
- TipTap core and PM adapters
|
|
288
|
+
- ProseMirror packages
|
|
289
|
+
- TipTap extensions (document, paragraph, text, formatting, etc.)
|
|
290
|
+
- Lowlight for syntax highlighting
|
|
291
|
+
- Highlight.js language definitions
|
|
292
|
+
|
|
293
|
+
### Stimulus Controller
|
|
294
|
+
|
|
295
|
+
The editor is controlled by `inkpen--editor` Stimulus controller. Connect it to your editor container:
|
|
296
|
+
|
|
297
|
+
```html
|
|
298
|
+
<div data-controller="inkpen--editor"
|
|
299
|
+
data-inkpen--editor-extensions-value='["bold","italic","link"]'
|
|
300
|
+
data-inkpen--editor-toolbar-value="floating"
|
|
301
|
+
data-inkpen--editor-placeholder-value="Start writing...">
|
|
302
|
+
<!-- Editor content here -->
|
|
303
|
+
</div>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Architecture
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
inkpen/
|
|
310
|
+
├── lib/
|
|
311
|
+
│ ├── inkpen.rb # Main entry point
|
|
312
|
+
│ ├── inkpen/
|
|
313
|
+
│ │ ├── configuration.rb # Global config PORO
|
|
314
|
+
│ │ ├── editor.rb # Editor instance PORO
|
|
315
|
+
│ │ ├── toolbar.rb # Floating toolbar config PORO
|
|
316
|
+
│ │ ├── sticky_toolbar.rb # Sticky toolbar config PORO
|
|
317
|
+
│ │ ├── engine.rb # Rails engine
|
|
318
|
+
│ │ ├── version.rb
|
|
319
|
+
│ │ └── extensions/
|
|
320
|
+
│ │ ├── base.rb # Extension base class
|
|
321
|
+
│ │ ├── forced_document.rb # Title heading structure
|
|
322
|
+
│ │ ├── mention.rb # @mentions
|
|
323
|
+
│ │ ├── code_block_syntax.rb # Syntax highlighting
|
|
324
|
+
│ │ ├── table.rb # Table support
|
|
325
|
+
│ │ └── task_list.rb # Task/checkbox lists
|
|
326
|
+
├── app/
|
|
327
|
+
│ └── assets/
|
|
328
|
+
│ ├── javascripts/
|
|
329
|
+
│ │ └── inkpen/
|
|
330
|
+
│ │ ├── controllers/
|
|
331
|
+
│ │ │ ├── editor_controller.js # Main TipTap editor
|
|
332
|
+
│ │ │ ├── toolbar_controller.js # Floating toolbar
|
|
333
|
+
│ │ │ └── sticky_toolbar_controller.js # Sticky toolbar
|
|
334
|
+
│ │ └── index.js # Entry point
|
|
335
|
+
│ └── stylesheets/
|
|
336
|
+
│ └── inkpen/
|
|
337
|
+
│ ├── editor.css # Editor styles
|
|
338
|
+
│ └── sticky_toolbar.css # Sticky toolbar styles
|
|
339
|
+
├── config/
|
|
340
|
+
│ └── importmap.rb # TipTap/PM dependencies
|
|
341
|
+
└── README.md
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## API Reference
|
|
345
|
+
|
|
346
|
+
### Inkpen::Editor
|
|
347
|
+
|
|
348
|
+
| Method | Description |
|
|
349
|
+
|--------|-------------|
|
|
350
|
+
| `name` | Form field name |
|
|
351
|
+
| `value` | Current editor content |
|
|
352
|
+
| `toolbar` | Toolbar style (`:floating`, `:fixed`, `:none`) |
|
|
353
|
+
| `extensions` | Array of enabled extension symbols |
|
|
354
|
+
| `data_attributes` | Hash of Stimulus data attributes |
|
|
355
|
+
| `style_attributes` | CSS inline styles string |
|
|
356
|
+
| `extension_enabled?(name)` | Check if extension is enabled |
|
|
357
|
+
| `input_id` | Safe HTML ID from name |
|
|
358
|
+
|
|
359
|
+
### Inkpen::Toolbar
|
|
360
|
+
|
|
361
|
+
| Method | Description |
|
|
362
|
+
|--------|-------------|
|
|
363
|
+
| `style` | Toolbar style |
|
|
364
|
+
| `buttons` | Array of button symbols |
|
|
365
|
+
| `position` | Toolbar position (`:top`, `:bottom`) |
|
|
366
|
+
| `floating?` | Is floating toolbar? |
|
|
367
|
+
| `fixed?` | Is fixed toolbar? |
|
|
368
|
+
| `hidden?` | Is toolbar hidden? |
|
|
369
|
+
|
|
370
|
+
### Inkpen::StickyToolbar
|
|
371
|
+
|
|
372
|
+
| Method | Description |
|
|
373
|
+
|--------|-------------|
|
|
374
|
+
| `position` | Position (`:bottom`, `:left`, `:right`) |
|
|
375
|
+
| `buttons` | Array of button symbols |
|
|
376
|
+
| `widget_types` | Array of widget type strings |
|
|
377
|
+
| `enabled?` | Is sticky toolbar enabled? |
|
|
378
|
+
| `vertical?` | Is vertical layout (left/right)? |
|
|
379
|
+
| `horizontal?` | Is horizontal layout (bottom)? |
|
|
380
|
+
| `data_attributes` | Hash of Stimulus data attributes |
|
|
381
|
+
|
|
382
|
+
### Inkpen::Extensions::Base
|
|
383
|
+
|
|
384
|
+
| Method | Description |
|
|
385
|
+
|--------|-------------|
|
|
386
|
+
| `name` | Extension identifier (Symbol) |
|
|
387
|
+
| `enabled?` | Is extension enabled? |
|
|
388
|
+
| `options` | Configuration options hash |
|
|
389
|
+
| `to_config` | JS configuration hash |
|
|
390
|
+
| `to_h` | Full extension hash |
|
|
391
|
+
| `to_json` | JSON representation |
|
|
392
|
+
|
|
393
|
+
## Development
|
|
394
|
+
|
|
395
|
+
After checking out the repo:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
bin/setup # Install dependencies
|
|
399
|
+
bundle exec rake test # Run tests
|
|
400
|
+
bin/console # Interactive console
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Contributing
|
|
404
|
+
|
|
405
|
+
Bug reports and pull requests are welcome on GitHub.
|
|
406
|
+
|
|
407
|
+
## License
|
|
408
|
+
|
|
409
|
+
MIT License
|
data/Rakefile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "minitest/test_task"
|
|
5
|
+
|
|
6
|
+
Minitest::TestTask.create
|
|
7
|
+
|
|
8
|
+
require "rubocop/rake_task"
|
|
9
|
+
|
|
10
|
+
RuboCop::RakeTask.new
|
|
11
|
+
|
|
12
|
+
require "yard"
|
|
13
|
+
|
|
14
|
+
YARD::Rake::YardocTask.new do |t|
|
|
15
|
+
t.files = ["lib/**/*.rb", "app/helpers/**/*.rb"]
|
|
16
|
+
t.options = ["--title", "Inkpen - TipTap Rich Text Editor for Rails"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
task default: %i[test rubocop]
|