infotable 1.4.1 β†’ 1.6.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +179 -11
  3. data/app/assets/javascripts/infotable/controllers/drawer_controller.js +74 -378
  4. data/app/assets/javascripts/infotable/controllers/table_controller.js +112 -120
  5. data/app/assets/javascripts/infotable/vendor/sortable.esm.js +3363 -0
  6. data/app/controllers/infotable/tables_controller.rb +16 -5
  7. data/app/helpers/infotable/tables_helper.rb +10 -48
  8. data/app/views/infotable/tables/_column_visibility.html.erb +4 -4
  9. data/app/views/infotable/tables/_end_indicator.html.erb +8 -0
  10. data/app/views/infotable/tables/_filter_drawer.html.erb +6 -6
  11. data/app/views/infotable/tables/_next_page_frame.html.erb +1 -9
  12. data/app/views/infotable/tables/_pagination.html.erb +4 -7
  13. data/app/views/infotable/tables/_table.html.erb +1 -1
  14. data/app/views/infotable/tables/_table_rows.html.erb +9 -2
  15. data/app/views/infotable/tables/filters/_boolean.html.erb +1 -1
  16. data/app/views/infotable/tables/filters/_range.html.erb +1 -1
  17. data/app/views/infotable/tables/filters/_select.html.erb +2 -2
  18. data/app/views/infotable/tables/filters/_text.html.erb +2 -2
  19. data/app/views/infotable/tables/show.html.erb +2 -2
  20. data/config/importmap.rb +2 -2
  21. data/config/locales/de.yml +126 -0
  22. data/config/locales/el.yml +126 -0
  23. data/config/locales/en.yml +18 -4
  24. data/config/locales/es.yml +126 -0
  25. data/config/locales/fr.yml +126 -0
  26. data/config/locales/it.yml +126 -0
  27. data/config/locales/pl.yml +18 -4
  28. data/exe/infotable-mcp +13 -0
  29. data/lib/generators/infotable/install/templates/initializer.rb +5 -3
  30. data/lib/generators/infotable/table/table_generator.rb +23 -1
  31. data/lib/generators/infotable/table/templates/table.rb.tt +8 -1
  32. data/lib/infotable/base.rb +62 -4
  33. data/lib/infotable/configuration.rb +1 -1
  34. data/lib/infotable/engine.rb +1 -8
  35. data/lib/infotable/filter_processor.rb +10 -1
  36. data/lib/infotable/formatters/boolean_formatter.rb +4 -4
  37. data/lib/infotable/formatters/currency_formatter.rb +4 -12
  38. data/lib/infotable/formatters/date_formatter.rb +3 -9
  39. data/lib/infotable/formatters/duration_formatter.rb +51 -0
  40. data/lib/infotable/formatters/filesize_formatter.rb +14 -0
  41. data/lib/infotable/formatters/image_formatter.rb +31 -0
  42. data/lib/infotable/formatters/number_formatter.rb +46 -0
  43. data/lib/infotable/formatters/tags_formatter.rb +44 -0
  44. data/lib/infotable/formatters/truncate_formatter.rb +19 -0
  45. data/lib/infotable/mcp/introspection.rb +94 -0
  46. data/lib/infotable/mcp/server.rb +26 -0
  47. data/lib/infotable/mcp/table_checker.rb +80 -0
  48. data/lib/infotable/mcp/table_writer.rb +66 -0
  49. data/lib/infotable/mcp/tools.rb +122 -0
  50. data/lib/infotable/mcp.rb +30 -0
  51. data/lib/infotable/table_resolver.rb +9 -0
  52. data/lib/infotable/version.rb +1 -1
  53. data/lib/infotable.rb +22 -0
  54. metadata +53 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f00cd7591b4bc03f8f7443771dc1be2abc2e022602a2a2b14a61090d6eea76aa
4
- data.tar.gz: 1c48ace8adb272674a1b0138b7aa1744dfe5154695b87ea5e1773cc918d912fc
3
+ metadata.gz: 31096b54bd71fb8799c287dbd43f91c2e05a3a5d5c8a652bd89f84e97c9fa5ed
4
+ data.tar.gz: 4cec84bed5d53f7599753fa30183e667833278aa4ebd89484cc2495ace2b7262
5
5
  SHA512:
6
- metadata.gz: 9ed45ecaf4c41b844dd6a7df34e35bcecefe593fa146fbe352ec9c6e4b20501f7661464a383f6a6ae386190e13d6a4cb395765c6ea4450f1d2637f442256c148
7
- data.tar.gz: 79fae3ddc36f3c98c826eb18fcb9856f810e5d8fd2681eb9244ba3577efa7a397b384eb8bb170c791cd1668c5c5f5e444f546553d55cffb0cd099813555366bd
6
+ metadata.gz: fa78ae7189b9c5ed8363ecd04c9abe444ed51ba3fcf231644fb14b624d5f7aa776811335efafc8ba521b99cfb9d8724ceccec647e3f3398e27377f5bc0ebaec6
7
+ data.tar.gz: 6bdaf90359935c4a3a1bad5575748b0dd40c47ce01ebd2fc7f8c10c6604d3b7720f9edde5fa387cd5e16868ce48165cc24183d6eeba1474b01b337621bd3829b
data/README.md CHANGED
@@ -12,7 +12,7 @@ Modern, flexible datatable engine for Rails 8+ with Hotwire. Build beautiful, in
12
12
  - πŸ”§ **Highly customizable** - Easy to override defaults and add custom formatters
13
13
  - πŸ“¦ **Works with any bundler** - Importmap, esbuild, webpack, viteβ€”we support them all
14
14
  - πŸš€ **Rails 8+ ready** - Built for modern Rails applications
15
- - 🌍 **i18n Support** - English and Polish translations included
15
+ - 🌍 **i18n Support** - English, Polish, German, French, Spanish, Greek and Italian translations included
16
16
  - 🎯 **Advanced Features** - Infinite scroll, column pinning, drag-to-reorder, row links, and more
17
17
 
18
18
  ## Quick Start
@@ -129,6 +129,15 @@ Infotable.configure do |config|
129
129
  end
130
130
  ```
131
131
 
132
+ ### Security
133
+
134
+ Tables are served by the engine at `/infotable/tables/:name/{data,export,ids}`. A table without an
135
+ `authorize { |controller| ... }` block is readable (and exportable as CSV) by **everyone who can reach the
136
+ route**, unless the engine controllers inherit your app's authentication. The installer therefore sets
137
+ `config.parent_controller = "ApplicationController"`; keep it (or point it at a controller with your
138
+ `before_action :authenticate_user!`) and add `authorize` to tables with restricted data. The engine's own
139
+ default (`ActionController::Base`) applies no authentication at all.
140
+
132
141
  ### Available Global Options
133
142
 
134
143
  | Option | Type | Default | Description |
@@ -137,7 +146,8 @@ end
137
146
  | `per_page_options` | Array | `[10, 25, 50, 100]` | Available rows per page options |
138
147
  | `drawer_position` | Symbol | `:right` | Position of filter/column drawer (`:left` or `:right`) |
139
148
  | `drawer_width` | String | `"26rem"` | Width of the drawer (`px`, `rem`, `em`, `vw` or `%`; anything else falls back to the default) |
140
- | `parent_controller` | String | `"ActionController::Base"` | Controller the engine's controllers inherit from. Set it to `"ApplicationController"` to reuse your authentication |
149
+ | `parent_controller` | String | `"ActionController::Base"` (the installer sets `"ApplicationController"`) | Controller the engine's controllers inherit from. Set it to `"ApplicationController"` to reuse your authentication |
150
+ | `max_select_all` | Integer, nil | `4000` | "Select all N matching" collects at most this many ids (each is sent as `ids[]`; Rack accepts 4096 parameters per request) |
141
151
  | `table_min_height` | String | `"400px"` | Minimum table height |
142
152
  | `table_max_height` | String | `"600px"` | Maximum table height (enables scrolling) |
143
153
  | `autofilter` | Boolean | `true` | Auto-apply filters on change (debounced) vs manual apply |
@@ -155,6 +165,8 @@ Generate a starting point from a model's columns:
155
165
  ```bash
156
166
  rails generate infotable:table User
157
167
  rails generate infotable:table Admin::Order --only=id number status # or --skip=notes
168
+ rails generate infotable:table Order --per-page=50 --sort=placed_at:desc --toolbar=rail --checkbox \
169
+ --authorize="controller.current_user&.admin?" # also --row-click --infinite-scroll --countless
158
170
  ```
159
171
 
160
172
  It writes `app/infotable/users_table.rb` with a column per attribute (types and filters inferred, enums become select filters, passwords/tokens and foreign keys are left out, `belongs_to` associations come as commented hints).
@@ -448,6 +460,30 @@ Features:
448
460
  - Cmd/Ctrl+Click to open in new tab
449
461
  - Ignores clicks on buttons, links, and inputs
450
462
 
463
+ #### Row click, classes and data attributes
464
+
465
+ ```ruby
466
+ # Style rows by their data (write the Tailwind classes out in full so they are generated)
467
+ row_class ->(order) { "bg-red-50" if order.overdue? }
468
+
469
+ # data-* attributes on the <tr>, e.g. for your own Stimulus controller
470
+ row_data ->(post) { { post_id: post.id, status: post.status } } # data-post-id, data-status
471
+
472
+ # Rows fire a cancelable `infotable:row-click` event (needs no row_link)
473
+ row_click
474
+ ```
475
+
476
+ Every row also gets `id="<dom_id>"` (`order_42`), so Turbo Streams can target it (`turbo_stream.replace dom_id(order)`, `broadcasts_to`). Rows without a record id are left without one.
477
+
478
+ Clicking a row (not a link, button or input inside it) first dispatches `infotable:row-click` on the `<tr>`, with `detail: { id, url, data, originalEvent }`. If nothing calls `preventDefault()` and the table has a `row_link`, the click then falls back to following it, so the two compose:
479
+
480
+ ```js
481
+ document.addEventListener("infotable:row-click", event => {
482
+ if (event.detail.data.status === "locked") event.preventDefault() // no navigation for locked rows
483
+ else openPreview(event.detail.data.postId) // ...or handle it yourself
484
+ })
485
+ ```
486
+
451
487
  #### Row Size
452
488
 
453
489
  Control table row padding and text size with three options:
@@ -566,6 +602,100 @@ column :actions,
566
602
 
567
603
  `method:` and `confirm:` use Turbo (`data-turbo-method`, `data-turbo-confirm`). Labels are HTML-escaped.
568
604
 
605
+ #### 6. Link Formatter (`:link`)
606
+
607
+ ```ruby
608
+ column :website, formatter: :link, formatter_options: { external: true } # the value is the URL
609
+
610
+ column :email,
611
+ formatter: :link,
612
+ formatter_options: { url: ->(record) { "mailto:#{record.email}" } } # email / phone: mailto: and tel:
613
+
614
+ column :name,
615
+ formatter: :link,
616
+ formatter_options: { url: ->(record) { user_path(record) } } # Proc (run in the view), Symbol or String
617
+ ```
618
+
619
+ Only relative URLs, `http(s)`, `mailto` and `tel` are linked (`javascript:` and friends render as plain text). Options: `url`, `label`, `external` (`target="_blank"` + `rel="noopener noreferrer"`), `class`.
620
+
621
+ #### 7. Avatar Formatter (`:avatar`)
622
+
623
+ ```ruby
624
+ column :avatar_url,
625
+ formatter: :avatar,
626
+ formatter_options: { name: ->(record) { record.name }, size: :md } # :sm, :md, :lg
627
+ ```
628
+
629
+ The value is the image URL; without one, the initials of `name` are shown on a coloured disc. `label: false` hides the name next to the picture.
630
+
631
+ #### 8. Percent Formatter (`:percent`)
632
+
633
+ ```ruby
634
+ column :completion, formatter: :percent, formatter_options: { precision: 1, bar: true }
635
+ ```
636
+
637
+ Options: `precision` (default 0), `scale: :percent` (0-100, default) or `:ratio` (0-1), `bar` (small progress bar).
638
+
639
+ #### 9. Number Formatter (`:number`)
640
+
641
+ ```ruby
642
+ column :views_count, formatter: :number, formatter_options: { compact: true } # 1.5k, 3.4M
643
+ column :latency, formatter: :number, formatter_options: { unit: "ms", precision: 1 }
644
+ column :stock, formatter: :number, formatter_options: { zero: "-" }
645
+ ```
646
+
647
+ Options: `precision`, `delimiter` (`","`), `separator` (`"."`), `unit` (appended), `compact`, `zero` (shown instead of 0).
648
+
649
+ #### 10. Tags Formatter (`:tags`, alias `:list`)
650
+
651
+ ```ruby
652
+ column :roles,
653
+ formatter: :tags,
654
+ formatter_options: { limit: 3, colors: { "admin" => "red" } }
655
+
656
+ # several values in a cell that is not an attribute: use `value:`
657
+ column :labels, value: ->(post) { post.tags.map(&:name) }, formatter: :tags, formatter_options: { limit: 2 }
658
+ ```
659
+
660
+ The value is an Array, a relation or a comma separated string; each item is a small badge. `limit` shows N items and "+M" (the rest is in its tooltip), `label: ->(item) { item.name }` picks the text, `colors` maps item to badge colour (as in `:badge`), `separator: ", "` renders plain text instead of badges. The CSV export joins the items with `", "`.
661
+
662
+ #### 11. Truncate Formatter (`:truncate`)
663
+
664
+ ```ruby
665
+ column :description, formatter: :truncate, formatter_options: { length: 80 }
666
+ ```
667
+
668
+ Long text is cut to `length` (default 60, `omission` default `"…"`); the full text is in the cell's tooltip.
669
+
670
+ #### 12. Image Formatter (`:image`)
671
+
672
+ ```ruby
673
+ column :photo_url,
674
+ formatter: :image,
675
+ formatter_options: { size: :md, alt: ->(record) { record.name }, link: true } # :sm (32px), :md (48px), :lg (64px)
676
+ ```
677
+
678
+ A square thumbnail (use `:avatar` for round pictures). Only http(s) and relative URLs are rendered. `link: true` opens the full image in a new tab.
679
+
680
+ #### 13. Filesize Formatter (`:filesize`)
681
+
682
+ ```ruby
683
+ column :byte_size, formatter: :filesize # 1536 => "1.5 KB"
684
+ ```
685
+
686
+ `precision` is the number of significant digits (default 2).
687
+
688
+ #### 14. Duration Formatter (`:duration`)
689
+
690
+ ```ruby
691
+ column :duration_seconds, formatter: :duration # 3900 => "1h 05m"
692
+ column :duration_seconds, formatter: :duration, formatter_options: { format: :long } # "1 hour 5 minutes"
693
+ ```
694
+
695
+ The value is in seconds. `parts` limits how many units are shown (default 2). Translated through `infotable.formatters.duration` (Polish plural forms work out of the box).
696
+
697
+ The Date formatter's `format: :relative` keeps the exact time in a tooltip, and the Currency formatter takes `unit`, `precision`, `delimiter` and `separator` from your locale (`number.currency.format`) for whatever you do not pass.
698
+
569
699
  ### Custom Formatters
570
700
 
571
701
  #### Lambda/Proc Formatter
@@ -713,7 +843,7 @@ config.autofilter = false # Require "Apply Filters" button click
713
843
 
714
844
  ## Internationalization (i18n)
715
845
 
716
- InfoTable includes English and Polish translations.
846
+ InfoTable includes English, Polish, German, French, Spanish, Greek and Italian translations. Any key can be overridden in your app.
717
847
 
718
848
  ### Set Locale
719
849
 
@@ -735,7 +865,7 @@ end
735
865
  ### Available Languages
736
866
 
737
867
  - English (`:en`) - Default
738
- - Polish (`:pl`)
868
+ - Polish (`:pl`), German (`:de`), French (`:fr`), Spanish (`:es`), Greek (`:el`), Italian (`:it`)
739
869
 
740
870
  ### Customize Translations
741
871
 
@@ -750,11 +880,11 @@ en:
750
880
  export_csv: "Download CSV"
751
881
  ```
752
882
 
753
- ### Add New Language
883
+ ### Add Another Language
754
884
 
755
885
  ```yaml
756
- # config/locales/infotable.es.yml
757
- es:
886
+ # config/locales/infotable.pt.yml
887
+ pt:
758
888
  infotable:
759
889
  table:
760
890
  buttons:
@@ -804,6 +934,48 @@ Built-in CSV export with:
804
934
 
805
935
  ---
806
936
 
937
+ ## MCP Server (AI assistants)
938
+
939
+ The gem ships an [MCP](https://modelcontextprotocol.io) server, so an AI assistant (Claude Code, Cursor, ...) can create tables in your app: it reads your models, asks you how the table should look, generates `app/infotable/*_table.rb` and checks that it works. It talks to your app over stdio on your machine, no port is opened.
940
+
941
+ Add the `mcp` gem to the app (it is not a dependency of infotable):
942
+
943
+ ```ruby
944
+ # Gemfile
945
+ gem "mcp", group: :development
946
+ ```
947
+
948
+ Register the server in the client, from the app's root. For Claude Code:
949
+
950
+ ```bash
951
+ claude mcp add infotable -- bundle exec infotable-mcp
952
+ ```
953
+
954
+ or commit a `.mcp.json` (Cursor: `.cursor/mcp.json`):
955
+
956
+ ```json
957
+ {
958
+ "mcpServers": {
959
+ "infotable": { "command": "bundle", "args": ["exec", "infotable-mcp"] }
960
+ }
961
+ }
962
+ ```
963
+
964
+ Then ask: *"create an InfoTable table for Order"*. Or use the `new_table` prompt, which makes the assistant ask for the settings first.
965
+
966
+ | Tool | What it does |
967
+ |------|--------------|
968
+ | `list_models` | Models with column types, enums and associations |
969
+ | `list_tables` | Tables that already exist, with their settings (the house style) |
970
+ | `describe_dsl` | Column options, types, filters, formatters and settings of the installed version |
971
+ | `table_options` | Questions about a new table (columns, sort, per page, toolbar, selection, authorization...) with defaults proposed from the model |
972
+ | `generate_table` | Renders the table with the `infotable:table` generator. Only shows the code; `write: true` saves it, an existing file is replaced only with `overwrite: true` |
973
+ | `validate_table` | Loads a table by name or from source and reports missing columns, a bad `default_sort`, a failing query or a missing `authorize`; `preview: 5` returns the first rows |
974
+
975
+ The full README is available to the assistant as the `infotable://readme` resource.
976
+
977
+ Notes: the server boots your app (development environment unless `RAILS_ENV` is set) and reads the development database; it writes only to `app/infotable/`. Tables are generated without `authorize` unless you give one, and `validate_table` warns about that (see [Security](#security)).
978
+
807
979
  ## Examples
808
980
 
809
981
  ### Basic Table
@@ -1046,10 +1218,6 @@ end
1046
1218
  ## Roadmap
1047
1219
 
1048
1220
  - [ ] Server-side persistence of column settings
1049
- - [ ] Table state in the URL
1050
- - [ ] More formatters (link, avatar, percent)
1051
- - [ ] `infotable:table` generator
1052
- - [ ] Tailwind v3 support
1053
1221
 
1054
1222
  ---
1055
1223