highlite 0.1.0

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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/commands/publish.md +45 -0
  3. data/README.md +176 -0
  4. data/Rakefile +12 -0
  5. data/app/assets/stylesheets/highlite/viewer.css +607 -0
  6. data/app/helpers/highlite/application_helper.rb +20 -0
  7. data/app/javascript/highlite/controllers/.keep +0 -0
  8. data/app/javascript/highlite/controllers/highlight_controller.js +829 -0
  9. data/app/javascript/highlite/controllers/highlights_panel_controller.js +313 -0
  10. data/app/javascript/highlite/controllers/sidebar_controller.js +465 -0
  11. data/app/javascript/highlite/controllers/viewer_controller.js +373 -0
  12. data/app/javascript/highlite/index.js +30 -0
  13. data/app/javascript/highlite/lib/.keep +0 -0
  14. data/app/javascript/highlite/lib/highlight_store.js +235 -0
  15. data/app/javascript/highlite/lib/pdf_renderer.js +212 -0
  16. data/app/views/highlite/_floating_toolbar.html.erb +79 -0
  17. data/app/views/highlite/_left_sidebar.html.erb +43 -0
  18. data/app/views/highlite/_right_sidebar.html.erb +56 -0
  19. data/app/views/highlite/_toolbar.html.erb +63 -0
  20. data/app/views/highlite/_viewer.html.erb +63 -0
  21. data/config/importmap.rb +17 -0
  22. data/lib/generators/highlite/install/install_generator.rb +44 -0
  23. data/lib/generators/highlite/install/templates/_right_sidebar.html.erb.tt +16 -0
  24. data/lib/generators/highlite/install/templates/initializer.rb.tt +20 -0
  25. data/lib/highlite/configuration.rb +27 -0
  26. data/lib/highlite/engine.rb +26 -0
  27. data/lib/highlite/version.rb +5 -0
  28. data/lib/highlite.rb +17 -0
  29. data/sig/highlite.rbs +4 -0
  30. data/tasks/todo.md +129 -0
  31. data/test/dummy/Rakefile +3 -0
  32. data/test/dummy/app/controllers/application_controller.rb +2 -0
  33. data/test/dummy/app/controllers/documents_controller.rb +6 -0
  34. data/test/dummy/app/javascript/application.js +1 -0
  35. data/test/dummy/app/views/documents/show.html.erb +1 -0
  36. data/test/dummy/app/views/layouts/application.html.erb +13 -0
  37. data/test/dummy/bin/rails +4 -0
  38. data/test/dummy/config/application.rb +15 -0
  39. data/test/dummy/config/boot.rb +2 -0
  40. data/test/dummy/config/environment.rb +2 -0
  41. data/test/dummy/config/importmap.rb +3 -0
  42. data/test/dummy/config/routes.rb +3 -0
  43. data/test/dummy/config.ru +2 -0
  44. data/test/dummy/public/convention-over-configuration.pdf +0 -0
  45. metadata +117 -0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Highlite
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Highlite
6
+
7
+ initializer "highlite.assets" do |app|
8
+ if app.config.respond_to?(:assets)
9
+ app.config.assets.paths << root.join("app/assets/stylesheets")
10
+ app.config.assets.paths << root.join("app/javascript")
11
+ end
12
+ end
13
+
14
+ initializer "highlite.importmap", before: "importmap" do |app|
15
+ if app.config.respond_to?(:importmap)
16
+ app.config.importmap.paths << root.join("config/importmap.rb")
17
+ end
18
+ end
19
+
20
+ initializer "highlite.helpers" do
21
+ ActiveSupport.on_load(:action_controller_base) do
22
+ helper Highlite::ApplicationHelper
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Highlite
4
+ VERSION = "0.1.0"
5
+ end
data/lib/highlite.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "highlite/version"
4
+ require_relative "highlite/configuration"
5
+ require_relative "highlite/engine" if defined?(Rails)
6
+
7
+ module Highlite
8
+ class Error < StandardError; end
9
+
10
+ def self.configure
11
+ yield Configuration.instance
12
+ end
13
+
14
+ def self.configuration
15
+ Configuration.instance
16
+ end
17
+ end
data/sig/highlite.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Highlite
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data/tasks/todo.md ADDED
@@ -0,0 +1,129 @@
1
+ # Highlite Gem — Build Plan
2
+
3
+ ## Overview
4
+
5
+ A Ruby gem (Rails engine) that provides a full-featured PDF viewer with highlighting, modeled after [react-pdf-highlighter-plus](https://react-pdf-highlighter-plus-demo.vercel.app/pdf-demo). Built for Rails 8 + Hotwire (Stimulus) + Tailwind CSS + Import Maps.
6
+
7
+ ## Architecture
8
+
9
+ ### Three-panel layout
10
+ - **Left sidebar** (gem-owned): Outline tab (TOC from PDF metadata) + Pages tab (thumbnails)
11
+ - **Center** (gem-owned): PDF.js canvas rendering + text layer + highlight overlay
12
+ - **Right sidebar** (default + overridable): Highlights list with search, grouped by page. Users can replace with their own partial.
13
+
14
+ ### Key design decisions
15
+ - **Stimulus controllers** dispatch custom events (`highlite:highlight-created`, etc.) so host apps can react
16
+ - **Right sidebar override** — ships a default highlights panel, but host apps can provide their own partial at `app/views/highlite/_right_sidebar.html.erb` which takes precedence (Rails engine view override convention)
17
+ - **PDF.js via CDN** (jsdelivr) — pinned in engine's importmap config, no npm required
18
+ - **localStorage by default** for highlight persistence, with an optional `Highlightable` model concern for server-side storage
19
+ - **tailwindcss-ruby** (`flavorjones/tailwindcss-ruby`) — compiles Tailwind CSS within the gem so the host app doesn't need Tailwind installed (but works great if they do)
20
+ - **CSS strategy** — gem ships a pre-compiled CSS file built with tailwindcss-ruby during development; the host app includes it via `stylesheet_link_tag "highlite/viewer"`
21
+
22
+ ### Stimulus event API (dispatched on the viewer element)
23
+ ```
24
+ highlite:document-loaded { detail: { pageCount, title, outline } }
25
+ highlite:page-changed { detail: { page, totalPages } }
26
+ highlite:highlight-created { detail: { id, page, type, color, text, rects } }
27
+ highlite:highlight-removed { detail: { id } }
28
+ highlite:highlight-selected { detail: { id, page } }
29
+ highlite:highlights-cleared { detail: { page } } // null page = all
30
+ ```
31
+
32
+ ## Directory structure
33
+
34
+ ```
35
+ highlite/
36
+ ├── highlite.gemspec
37
+ ├── Gemfile
38
+ ├── Rakefile
39
+ ├── README.md
40
+ ├── lib/
41
+ │ ├── highlite.rb # Main entry + autoload
42
+ │ ├── highlite/
43
+ │ │ ├── version.rb
44
+ │ │ ├── engine.rb # Rails engine config + importmap
45
+ │ │ └── configuration.rb # Config DSL (pdf.js version, colors, etc.)
46
+ │ └── generators/
47
+ │ └── highlite/
48
+ │ └── install/
49
+ │ ├── install_generator.rb # rails g highlite:install
50
+ │ └── templates/
51
+ │ ├── initializer.rb.tt
52
+ │ └── _right_sidebar.html.erb.tt # Override template
53
+ ├── app/
54
+ │ ├── assets/
55
+ │ │ └── stylesheets/
56
+ │ │ └── highlite/
57
+ │ │ └── viewer.css # Minimal required styles (text layer, etc.)
58
+ │ ├── javascript/
59
+ │ │ └── highlite/
60
+ │ │ ├── index.js # Registers all controllers with Stimulus
61
+ │ │ ├── controllers/
62
+ │ │ │ ├── viewer_controller.js # PDF.js rendering, zoom, scroll
63
+ │ │ │ ├── highlight_controller.js # Drawing + managing highlights
64
+ │ │ │ ├── sidebar_controller.js # Left sidebar (outline + pages tabs)
65
+ │ │ │ └── highlights_panel_controller.js # Right sidebar (default highlight list)
66
+ │ │ └── lib/
67
+ │ │ ├── pdf_renderer.js # PDF.js wrapper (load, render, text layer)
68
+ │ │ └── highlight_store.js # State management + localStorage
69
+ │ ├── helpers/
70
+ │ │ └── highlite/
71
+ │ │ └── application_helper.rb # highlite_viewer helper method
72
+ │ └── views/
73
+ │ └── highlite/
74
+ │ ├── _viewer.html.erb # Main 3-panel layout
75
+ │ ├── _toolbar.html.erb # Top bar (zoom, export, change PDF)
76
+ │ ├── _left_sidebar.html.erb # Outline + Pages tabs
77
+ │ ├── _right_sidebar.html.erb # Default highlights panel
78
+ │ └── _floating_toolbar.html.erb # Annotation tool buttons
79
+ └── spec/
80
+ ├── spec_helper.rb
81
+ └── lib/
82
+ └── highlite_spec.rb
83
+ ```
84
+
85
+ ## Build tasks
86
+
87
+ ### Phase 1: Foundation
88
+ - [ ] 1.1 Set up gemspec with correct dependencies (rails >= 7.1, importmap-rails)
89
+ - [ ] 1.2 Create Rails engine (Highlite::Engine) with importmap config
90
+ - [ ] 1.3 Create Configuration class (pdf_js_version, default_colors, toolbar_tools, right_sidebar_partial)
91
+ - [ ] 1.4 Create install generator (initializer + optional sidebar override template)
92
+ - [ ] 1.5 Create viewer helper method: `highlite_viewer(url:, document_id:, **options)`
93
+
94
+ ### Phase 2: Core JavaScript — PDF Viewer
95
+ - [ ] 2.1 Build pdf_renderer.js — PDF.js wrapper (load document, render page to canvas, text layer, get outline, render thumbnail)
96
+ - [ ] 2.2 Build viewer_controller.js — Stimulus controller (renders PDF, handles zoom [Auto/fit/%, +, -], page scroll tracking, keyboard nav)
97
+ - [ ] 2.3 Build highlight_store.js — State manager (add/remove/update/query highlights, localStorage read/write, event dispatch)
98
+
99
+ ### Phase 3: Highlighting
100
+ - [ ] 3.1 Build highlight_controller.js — Text selection highlighting (capture Selection API ranges → rects, apply color)
101
+ - [ ] 3.2 Add area/rectangle highlighting mode (mousedown→drag→mouseup drawing)
102
+ - [ ] 3.3 Highlight rendering (colored overlay divs with mix-blend-mode: multiply)
103
+ - [ ] 3.4 Highlight interaction (click to select, right-click to remove, hover effect)
104
+ - [ ] 3.5 Multiple highlight colors (toolbar color picker)
105
+
106
+ ### Phase 4: Left Sidebar
107
+ - [ ] 4.1 Build sidebar_controller.js — Tab switching (Outline / Pages)
108
+ - [ ] 4.2 Outline tab — Extract TOC from PDF metadata, render hierarchical tree, click-to-navigate, blue dots for pages with highlights
109
+ - [ ] 4.3 Pages tab — Render page thumbnails (small canvas), active page border, click-to-navigate, page counter
110
+
111
+ ### Phase 5: Right Sidebar (Default + Override)
112
+ - [ ] 5.1 Build highlights_panel_controller.js — Lists highlights grouped by page, search/filter, clear all
113
+ - [ ] 5.2 Build _right_sidebar.html.erb — Default partial with highlight cards (type badge, quoted text, page link)
114
+ - [ ] 5.3 Implement override mechanism — check for app/views/highlite/_right_sidebar.html.erb in host app, fall back to gem default
115
+
116
+ ### Phase 6: Toolbar & Polish
117
+ - [ ] 6.1 Build _toolbar.html.erb — Top bar (zoom controls, page info)
118
+ - [ ] 6.2 Build _floating_toolbar.html.erb — Vertical tool buttons (select, text highlight, area highlight, colors)
119
+ - [ ] 6.3 Keyboard shortcuts (Ctrl+Z undo, Escape deselect, arrow keys for pages)
120
+
121
+ ### Phase 7: Views & Integration
122
+ - [ ] 7.1 Build _viewer.html.erb — Main layout composing all panels
123
+ - [ ] 7.2 Build viewer.css — Required styles (text layer positioning, highlight blending, sidebar transitions)
124
+ - [ ] 7.3 Wire helper method to render the viewer partial with all options
125
+ - [ ] 7.4 Write README with usage examples, configuration, and override instructions
126
+
127
+ ### Phase 8: Testing
128
+ - [ ] 8.1 RSpec tests for engine loading, configuration, helper output
129
+ - [ ] 8.2 Generator tests (install generator creates expected files)
@@ -0,0 +1,3 @@
1
+ require_relative "config/boot"
2
+ require "rails/all"
3
+ require "rake"
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,6 @@
1
+ class DocumentsController < ApplicationController
2
+ def show
3
+ @pdf_url = "/convention-over-configuration.pdf"
4
+ @document_id = "convention-over-configuration"
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ import "highlite"
@@ -0,0 +1 @@
1
+ <%= highlite_viewer(url: @pdf_url, document_id: @document_id) %>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Highlite Test</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ <%= stylesheet_link_tag "highlite/viewer" %>
8
+ <%= javascript_importmap_tags %>
9
+ </head>
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path("../config/application", __dir__)
3
+ require_relative "../config/boot"
4
+ require "rails/commands"
@@ -0,0 +1,15 @@
1
+ require_relative "boot"
2
+
3
+ require "action_controller/railtie"
4
+ require "action_view/railtie"
5
+ require "propshaft"
6
+ require "importmap-rails"
7
+ require "highlite"
8
+
9
+ module Dummy
10
+ class Application < Rails::Application
11
+ config.load_defaults Rails::VERSION::STRING.to_f
12
+ config.eager_load = false
13
+ config.secret_key_base = "test-secret-key-base-for-dummy-app"
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)
2
+ require "bundler/setup"
@@ -0,0 +1,2 @@
1
+ require_relative "application"
2
+ Rails.application.initialize!
@@ -0,0 +1,3 @@
1
+ pin "application"
2
+ pin "@hotwired/stimulus", to: "https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.2.2/dist/stimulus.js"
3
+ pin "highlite", to: "highlite/index.js"
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ root "documents#show"
3
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "config/environment"
2
+ run Rails.application
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: highlite
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Jones
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: importmap-rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ description: A Rails engine that wraps PDF.js to provide a full-featured PDF viewer
41
+ with text and area highlighting, powered by Stimulus controllers and Tailwind CSS.
42
+ Includes a three-panel layout with outline/thumbnails sidebar, highlight management,
43
+ and an overridable right sidebar for custom integrations.
44
+ email:
45
+ - natejones@hey.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".claude/commands/publish.md"
51
+ - README.md
52
+ - Rakefile
53
+ - app/assets/stylesheets/highlite/viewer.css
54
+ - app/helpers/highlite/application_helper.rb
55
+ - app/javascript/highlite/controllers/.keep
56
+ - app/javascript/highlite/controllers/highlight_controller.js
57
+ - app/javascript/highlite/controllers/highlights_panel_controller.js
58
+ - app/javascript/highlite/controllers/sidebar_controller.js
59
+ - app/javascript/highlite/controllers/viewer_controller.js
60
+ - app/javascript/highlite/index.js
61
+ - app/javascript/highlite/lib/.keep
62
+ - app/javascript/highlite/lib/highlight_store.js
63
+ - app/javascript/highlite/lib/pdf_renderer.js
64
+ - app/views/highlite/_floating_toolbar.html.erb
65
+ - app/views/highlite/_left_sidebar.html.erb
66
+ - app/views/highlite/_right_sidebar.html.erb
67
+ - app/views/highlite/_toolbar.html.erb
68
+ - app/views/highlite/_viewer.html.erb
69
+ - config/importmap.rb
70
+ - lib/generators/highlite/install/install_generator.rb
71
+ - lib/generators/highlite/install/templates/_right_sidebar.html.erb.tt
72
+ - lib/generators/highlite/install/templates/initializer.rb.tt
73
+ - lib/highlite.rb
74
+ - lib/highlite/configuration.rb
75
+ - lib/highlite/engine.rb
76
+ - lib/highlite/version.rb
77
+ - sig/highlite.rbs
78
+ - tasks/todo.md
79
+ - test/dummy/Rakefile
80
+ - test/dummy/app/controllers/application_controller.rb
81
+ - test/dummy/app/controllers/documents_controller.rb
82
+ - test/dummy/app/javascript/application.js
83
+ - test/dummy/app/views/documents/show.html.erb
84
+ - test/dummy/app/views/layouts/application.html.erb
85
+ - test/dummy/bin/rails
86
+ - test/dummy/config.ru
87
+ - test/dummy/config/application.rb
88
+ - test/dummy/config/boot.rb
89
+ - test/dummy/config/environment.rb
90
+ - test/dummy/config/importmap.rb
91
+ - test/dummy/config/routes.rb
92
+ - test/dummy/public/convention-over-configuration.pdf
93
+ homepage: https://github.com/nathanjones/highlite
94
+ licenses:
95
+ - MIT
96
+ metadata:
97
+ homepage_uri: https://github.com/nathanjones/highlite
98
+ source_code_uri: https://github.com/nathanjones/highlite
99
+ changelog_uri: https://github.com/nathanjones/highlite/blob/main/CHANGELOG.md
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 3.1.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 4.0.3
115
+ specification_version: 4
116
+ summary: Rails engine providing a PDF viewer with text and area highlighting
117
+ test_files: []