hotsheet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +17 -0
  3. data/LICENSE +21 -0
  4. data/README.md +61 -0
  5. data/app/assets/config/hotsheet.js +2 -0
  6. data/app/assets/javascripts/hotsheet/application.js +1 -0
  7. data/app/assets/javascripts/hotsheet/channels/consumer.js +3 -0
  8. data/app/assets/javascripts/hotsheet/channels/inline_edit_channel.js +3 -0
  9. data/app/assets/javascripts/hotsheet/controllers/application.js +6 -0
  10. data/app/assets/javascripts/hotsheet/controllers/editable_attribute_controller.js +51 -0
  11. data/app/assets/stylesheets/hotsheet/application.css +86 -0
  12. data/app/channels/application_cable/channel.rb +6 -0
  13. data/app/channels/application_cable/connection.rb +6 -0
  14. data/app/channels/inline_edit_channel.rb +9 -0
  15. data/app/controllers/hotsheet/application_controller.rb +7 -0
  16. data/app/controllers/hotsheet/pages_controller.rb +43 -0
  17. data/app/helpers/hotsheet/application_helper.rb +7 -0
  18. data/app/views/hotsheet/pages/_editable_attribute.html.erb +24 -0
  19. data/app/views/hotsheet/pages/index.html.erb +27 -0
  20. data/app/views/layouts/hotsheet/_flash.html.erb +7 -0
  21. data/app/views/layouts/hotsheet/_sidebar.html.erb +11 -0
  22. data/app/views/layouts/hotsheet/application.html.erb +22 -0
  23. data/config/initializers/hotsheet/content_security_policy.rb +14 -0
  24. data/config/initializers/hotsheet/editable_attributes.rb +43 -0
  25. data/config/initializers/hotsheet/pagy.rb +7 -0
  26. data/config/locales/en.yml +5 -0
  27. data/config/routes.rb +11 -0
  28. data/lib/generators/hotsheet/install_generator.rb +19 -0
  29. data/lib/generators/templates/hotsheet.rb +9 -0
  30. data/lib/hotsheet/configuration.rb +19 -0
  31. data/lib/hotsheet/engine.rb +9 -0
  32. data/lib/hotsheet/version.rb +5 -0
  33. data/lib/hotsheet.rb +26 -0
  34. data/vendor/assets/stylesheets/hotsheet/pagy.css +37 -0
  35. metadata +155 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ebb74d345ffa27e7320c021ce0108b934414cc5e93fae4e58893a5e556e389b4
4
+ data.tar.gz: c45e89321ccd3da4ad7354ae2d6695035d31451a2c38990a6c14f6cc4d793173
5
+ SHA512:
6
+ metadata.gz: d7d422c0bfe713d39d3e2d4566b116e1e5cc75825bbca187bae4e9d73fb75ebd35a442020b8ad1b2f3e05796cd1726be86b9b7ff9f28838ef4bae6c86bfcefcc
7
+ data.tar.gz: 82a933e5d47a1b60806dca93db6b5e041fc46a39fc2e0f57955c5bac3cf5dc45b7c3218bfd16a18e685faa1cbac4a2160daef3a30333ecafda78400d57737414
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ ## [Unreleased](https://github.com/renuo/hotsheet/compare/v0.1.0..HEAD)
2
+
3
+ -
4
+
5
+ ## [0.1.0](https://github.com/renuo/hotsheet/releases/tag/v0.1.0) (2024-11-05)
6
+
7
+ - Gem structure and initial configuration ([@ignaciosy])
8
+ - Inline editing table (([@simon-isler]) and ([@edmunteanu]))
9
+ - Dummy app for development and test ([@hunchr])
10
+ - Configuration for initializer, CI, lint, tests ([@hunchr])
11
+ - Generator `bin/rails g hotsheet:install` for copying initializer and mounting engine in routes ([@hunchr])
12
+ - Pagination for data tables ([@hunchr])
13
+
14
+ [@ignaciosy]: https://github.com/ignaciosy
15
+ [@hunchr]: https://github.com/hunchr
16
+ [@simon-isler]: https://github.com/simon-isler
17
+ [@edmunteanu]: https://github.com/edmunteanu
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Renuo AG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Hotsheet
2
+
3
+ **Manage your database with a simple and familiar web interface**
4
+
5
+ This gem allows you to mount a view to manage your database using a table view
6
+ where you can edit database records inline.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```rb
13
+ gem "hotsheet"
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ ```sh
19
+ bundle
20
+ bin/rails g hotsheet:install
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ You can configure which models this gem should manage, and specify which
26
+ attributes (database columns) should be editable in the Hotsheet. This can be
27
+ done by configuring the initializer file created by the install command:
28
+
29
+ ```rb
30
+ # config/initializers/hotsheet.rb
31
+
32
+ Hotsheet.configure do |config|
33
+ config.model :Author do |model|
34
+ model.included_attributes = %i[name birthdate gender] # mutually exclusive with "excluded_attributes"
35
+ # model.excluded_attributes = %i[created_at updated_at] # mutually exclusive with "included_attributes"
36
+ end
37
+ end
38
+ ```
39
+
40
+ ## Contributing
41
+
42
+ See [Contributing Guide] for information on how to set up hotsheet locally.
43
+
44
+ ## Roadmap / Planned Features
45
+
46
+ - Fetch all models in the application by default
47
+ - Support live updates (show when someone has the intention to edit a resource) via ActionCable
48
+ - Conflict resolution strategy (locking / merging / latest change etc.)
49
+ - Fine grained access / permissions (cancancan)
50
+ - Type-specific input fields
51
+ - Undo feature (or confirm / discard changes icons)
52
+ - Configure visibility of non-editable (excluded) fields
53
+ - Use importmap-rails for JS dependencies
54
+
55
+ ## License
56
+
57
+ Hotsheet is available as open source under the terms of the [MIT License].\
58
+ Copyright © 2024 [Renuo AG](https://www.renuo.ch).
59
+
60
+ [Contributing Guide]: https://github.com/renuo/hotsheet/blob/main/CONTRIBUTING.md
61
+ [MIT License]: https://github.com/renuo/hotsheet/blob/main/LICENSE
@@ -0,0 +1,2 @@
1
+ //= link_directory ../stylesheets/hotsheet .css
2
+ //= link_directory ../javascripts/hotsheet .js
@@ -0,0 +1 @@
1
+ //= require ./controllers/application
@@ -0,0 +1,3 @@
1
+ import { createConsumer } from "https://unpkg.com/@rails/actioncable@7.1.3-4/app/assets/javascripts/actioncable.esm.js"
2
+
3
+ export default createConsumer()
@@ -0,0 +1,3 @@
1
+ import consumer from "channels/consumer"
2
+
3
+ consumer.subscriptions.create({ channel: "InlineEditChannel" })
@@ -0,0 +1,6 @@
1
+ import { Application } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
2
+ import EditableAttributeController from "./controllers/editable_attribute_controller"
3
+
4
+ window.Stimulus = Application.start()
5
+
6
+ Stimulus.register("editable-attribute", EditableAttributeController)
@@ -0,0 +1,51 @@
1
+ import { Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ broadcastUrl: String,
6
+ resourceName: String,
7
+ resourceId: Number,
8
+ }
9
+
10
+ static targets = ["readonlyAttribute", "attributeForm", "attributeFormInput"]
11
+
12
+ displayInputField() {
13
+ // this.broadcastEditIntent()
14
+ this.readonlyAttributeTarget.style.display = "none"
15
+ this.attributeFormTarget.style.display = "block"
16
+ this.attributeFormInputTarget.focus()
17
+ }
18
+
19
+ broadcastEditIntent() {
20
+ const headers = {
21
+ "Content-Type": "application/json",
22
+ "X-CSRF-Token": document.querySelector("meta[name=csrf-token]").content,
23
+ }
24
+ const body = JSON.stringify({
25
+ broadcast: {
26
+ resource_name: this.resourceNameValue,
27
+ resource_id: this.resourceIdValue,
28
+ },
29
+ })
30
+
31
+ fetch(this.broadcastUrlValue, { method: "POST", headers, body })
32
+ }
33
+
34
+ submitForm(event) {
35
+ // Prevent standard submission triggered by Enter press
36
+ event.preventDefault()
37
+
38
+ const previousValue = this.readonlyAttributeTarget.innerText.trim()
39
+ const newValue = this.attributeFormInputTarget.value
40
+
41
+ if (previousValue && previousValue === newValue) {
42
+ this.readonlyAttributeTarget.style.display = "block"
43
+ this.attributeFormTarget.style.display = "none"
44
+ return
45
+ }
46
+
47
+ // It's important to use requestSubmit() instead of simply submit() as the latter will circumvent the
48
+ // Turbo mechanism, causing the PATCH request to be submitted as HTML instead of TURBO_STREAM
49
+ this.attributeFormInputTarget.form.requestSubmit()
50
+ }
51
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ *= require_tree .
6
+ *= require_self
7
+ *= require hotsheet/pagy
8
+ */
9
+
10
+ :root {
11
+ --bg-color: lightgray;
12
+ --sidebar-width: 12rem;
13
+ }
14
+
15
+ body {
16
+ font: 400 16px system-ui, Roboto, Helvetica, Arial, sans-serif;
17
+ margin: 0;
18
+ }
19
+
20
+ main {
21
+ margin-left: var(--sidebar-width);
22
+ padding: 1rem;
23
+ }
24
+
25
+ aside {
26
+ background-color: var(--bg-color);
27
+ border-right: 1px solid gray;
28
+ height: 100%;
29
+ left: 0;
30
+ overflow-y: auto;
31
+ position: fixed;
32
+ top: 0;
33
+ width: var(--sidebar-width);
34
+
35
+ ul {
36
+ display: flex;
37
+ flex-direction: column;
38
+ margin: 0;
39
+ padding: 0.25rem;
40
+
41
+ a {
42
+ border-radius: 0.5rem;
43
+ color: green;
44
+ font-weight: 700;
45
+ overflow: hidden;
46
+ padding: 0.5rem;
47
+ text-decoration: none;
48
+ text-overflow: ellipsis;
49
+ white-space: nowrap;
50
+
51
+ &:hover {
52
+ background-color: darkgray;
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ h1 {
59
+ margin: 0 0 1rem 0;
60
+ }
61
+
62
+ table {
63
+ border-collapse: collapse;
64
+
65
+ th,
66
+ td {
67
+ border: 1px solid var(--bg-color);
68
+ padding: 0.5rem;
69
+ text-align: left;
70
+ }
71
+
72
+ th {
73
+ background-color: var(--bg-color);
74
+ }
75
+
76
+ .readonly-attribute {
77
+ min-height: 1rem;
78
+ min-width: 1rem;
79
+ }
80
+
81
+ .editable-input {
82
+ background-color: transparent;
83
+ border: none;
84
+ width: 100%;
85
+ }
86
+ }
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationCable
4
+ class Channel < ActionCable::Channel::Base
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationCable
4
+ class Connection < ActionCable::Connection::Base
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class InlineEditChannel < ApplicationCable::Channel
4
+ STREAM_NAME = "inline_edit"
5
+
6
+ def subscribed
7
+ stream_from STREAM_NAME
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotsheet
4
+ class ApplicationController < ActionController::Base
5
+ include Pagy::Backend
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotsheet
4
+ class PagesController < ApplicationController
5
+ def index
6
+ @pagy, @records = pagy model.all if model
7
+ end
8
+
9
+ def broadcast_edit_intent
10
+ ActionCable.server.broadcast InlineEditChannel::STREAM_NAME, {
11
+ resource_name: broadcast_params[:resource_name],
12
+ resource_id: broadcast_params[:resource_id]
13
+ }
14
+ end
15
+
16
+ def update # rubocop:disable Metrics/AbcSize
17
+ record = model.find params[:id]
18
+
19
+ if record.update model_params
20
+ flash[:notice] = t("hotsheet.success", record: model.model_name.human)
21
+ else
22
+ flash[:alert] = t("hotsheet.error", record: model.model_name.human,
23
+ errors: record.errors.full_messages.join(", "))
24
+ end
25
+
26
+ redirect_to "#{root_path}#{model.table_name}"
27
+ end
28
+
29
+ private
30
+
31
+ def broadcast_params
32
+ params.require(:broadcast).permit :resource_name, :resource_id
33
+ end
34
+
35
+ def model_params
36
+ params.require(model.name.underscore).permit(*model.editable_attributes)
37
+ end
38
+
39
+ def model
40
+ @model ||= params[:model]&.constantize
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotsheet
4
+ module ApplicationHelper
5
+ include Pagy::Frontend
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ <%# locals: (attribute:, model:, record:) %>
2
+
3
+ <%= turbo_frame_tag "#{dom_id record}-#{attribute}" do %>
4
+ <div data-controller="editable-attribute"
5
+ data-editable-attribute-broadcast-url-value="<%= broadcast_edit_intent_path %>"
6
+ data-editable-attribute-resource-name-value="<%= model.table_name %>"
7
+ data-editable-attribute-resource-id-value="<%= record.id %>">
8
+ <div class="readonly-attribute"
9
+ data-editable-attribute-target="readonlyAttribute"
10
+ data-action="click->editable-attribute#displayInputField"
11
+ role="button">
12
+ <%= record.public_send attribute %>
13
+ </div>
14
+
15
+ <div data-editable-attribute-target="attributeForm" style="display:none">
16
+ <%= form_for record, url: "#{root_path}#{model.table_name}/#{record.id}" do |f| %>
17
+ <%= f.text_field attribute, class: "editable-input", data: {
18
+ editable_attribute_target: "attributeFormInput",
19
+ action: "keydown.enter->editable-attribute#submitForm blur->editable-attribute#submitForm"
20
+ } %>
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <% if @model.nil? %>
2
+ <h1>Hotsheet</h1>
3
+ <p>Welcome to the hottest sheet around. Select a model from the sidebar to view the spreadsheet.</p>
4
+ <% else %>
5
+ <h1><%= @model.model_name.human %></h1>
6
+ <table>
7
+ <thead>
8
+ <tr>
9
+ <% @model.editable_attributes.each do |attribute| %>
10
+ <th><%= attribute %></th>
11
+ <% end %>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <% @records.each do |record| %>
16
+ <tr>
17
+ <% @model.editable_attributes.each do |attribute| %>
18
+ <td>
19
+ <%= render "editable_attribute", model: @model, record: record, attribute: attribute %>
20
+ </td>
21
+ <% end %>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+ <%== pagy_nav @pagy %>
27
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <%# locals: () %>
2
+
3
+ <% flash.each do |type, msg| %>
4
+ <div class='flash <%= type %>'>
5
+ <span><%= msg %></span>
6
+ </div>
7
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <%# locals: () %>
2
+
3
+ <aside>
4
+ <nav>
5
+ <ul>
6
+ <% Hotsheet.models.each do |model| %>
7
+ <%= link_to model.model_name.human, "#{root_path}#{model.table_name}" %>
8
+ <% end %>
9
+ </ul>
10
+ </nav>
11
+ </aside>
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= I18n.locale %>">
3
+ <head>
4
+ <title>Hotsheet</title>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width,initial-scale=1">
7
+ <meta name="robots" content="noindex,nofollow">
8
+ <meta name="turbo-prefetch" content="false">
9
+ <%= csrf_meta_tags %>
10
+ <%= csp_meta_tag %>
11
+ <%= stylesheet_link_tag "hotsheet/application" %>
12
+ <%= javascript_include_tag "hotsheet/application", "data-turbolinks-track": :reload,
13
+ type: :module, nonce: content_security_policy_nonce, defer: true %>
14
+ </head>
15
+ <body>
16
+ <%= render "layouts/hotsheet/sidebar" %>
17
+ <%= render "layouts/hotsheet/flash" %>
18
+ <main>
19
+ <%= yield %>
20
+ </main>
21
+ </body>
22
+ </html>
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.configure do
4
+ config.content_security_policy do |csp|
5
+ csp.default_src :none
6
+ csp.connect_src :self
7
+ csp.img_src :self
8
+ csp.script_src :strict_dynamic
9
+ csp.style_src :self, :unsafe_inline
10
+ end
11
+
12
+ config.content_security_policy_nonce_directives = %w[script-src]
13
+ config.content_security_policy_nonce_generator = ->(_) { SecureRandom.base64 18 }
14
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.config.after_initialize do # rubocop:disable Metrics/BlockLength
4
+ # Only run this initializer when running the Rails server
5
+ next unless Rails.env.test? || defined? Rails::Server
6
+
7
+ Hotsheet.configuration.models.each_key do |model| # rubocop:disable Metrics/BlockLength
8
+ model.constantize.class_eval do
9
+ class << self
10
+ def editable_attributes
11
+ @editable_attributes ||= fetch_editable_attributes
12
+ end
13
+
14
+ private
15
+
16
+ def fetch_editable_attributes
17
+ config = Hotsheet.configuration.models[name]
18
+ excluded_attributes = config.excluded_attributes
19
+
20
+ if config.included_attributes.present?
21
+ raise "Can only specify either included or excluded attributes" if excluded_attributes.present?
22
+
23
+ attrs_to_s(config.included_attributes)
24
+ elsif excluded_attributes.present?
25
+ column_names - attrs_to_s(excluded_attributes)
26
+ else
27
+ column_names
28
+ end
29
+ end
30
+
31
+ def attrs_to_s(attrs)
32
+ attrs.map do |attr|
33
+ raise "Attribute '#{attr}' doesn't exist on model '#{name}'" if column_names.exclude? attr.to_s
34
+
35
+ attr.to_s
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ model.constantize.editable_attributes
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pagy"
4
+
5
+ # Pagy Variables (https://ddnexus.github.io/pagy/docs/api/pagy#variables)
6
+ Pagy::DEFAULT[:limit] = 100
7
+ Pagy::DEFAULT.freeze
@@ -0,0 +1,5 @@
1
+ en:
2
+ home: Home
3
+ hotsheet:
4
+ success: '%{record} updated successfully'
5
+ error: '%{record} update failed: %{errors}'
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ Hotsheet::Engine.routes.draw do
4
+ root to: "pages#index"
5
+
6
+ post :broadcast_edit_intent, to: "pages#broadcast_edit_intent"
7
+
8
+ Hotsheet.models.each do |model|
9
+ resources model.table_name, controller: :pages, only: %i[index update], model: model.name
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Hotsheet
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path "../templates", __dir__
9
+
10
+ def copy_initializer
11
+ template "hotsheet.rb", "config/initializers/hotsheet.rb"
12
+ end
13
+
14
+ def mount_engine
15
+ route "mount Hotsheet::Engine, at: :hotsheet"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Hotsheet.configure do |config|
4
+ # Configure the models to be used by Hotsheet (see https://github.com/renuo/hotsheet?tab=readme-ov-file#usage)
5
+
6
+ # config.model :User do |model|
7
+ # model.excluded_attributes = %i[created_at updated_at]
8
+ # end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotsheet
4
+ class Configuration
5
+ include ActiveSupport::Configurable
6
+
7
+ config_accessor(:models) { {} }
8
+
9
+ def model(name, &block)
10
+ model_config = ModelConfig.new
11
+ yield model_config if block
12
+ models[name.to_s] = model_config
13
+ end
14
+
15
+ class ModelConfig
16
+ attr_accessor :included_attributes, :excluded_attributes
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotsheet
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Hotsheet
6
+
7
+ config.assets.precompile += %w[hotsheet/application.css hotsheet/application.js]
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotsheet
4
+ VERSION = "0.1.0"
5
+ end
data/lib/hotsheet.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sprockets/railtie"
4
+ require "turbo-rails"
5
+
6
+ require "hotsheet/configuration"
7
+ require "hotsheet/engine"
8
+ require "hotsheet/version"
9
+
10
+ module Hotsheet
11
+ class Error < StandardError; end
12
+
13
+ class << self
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def configure
19
+ yield configuration
20
+ end
21
+
22
+ def models
23
+ @models ||= configuration.models.each_key.map(&:constantize)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ /* https://ddnexus.github.io/pagy/docs/api/stylesheets */
2
+
3
+ .pagy {
4
+ color: #6b7280;
5
+ display: flex;
6
+ font-size: .875rem;
7
+ font-weight: 600;
8
+ margin-top: 1rem;
9
+
10
+ a ~ a {
11
+ margin-left: .25rem;
12
+ }
13
+
14
+ a:not(.gap) {
15
+ background-color: #e5e7eb;
16
+ border-radius: .5rem;
17
+ color: inherit;
18
+ display: block;
19
+ padding: .25rem .75rem;
20
+ text-decoration: none;
21
+
22
+ &:hover {
23
+ background-color: #d1d5db;
24
+ }
25
+
26
+ &:not([href]) {
27
+ background-color: #f3f4f6;
28
+ color: #d1d5db;
29
+ cursor: default;
30
+ }
31
+
32
+ &.current {
33
+ background-color: #9ca3af;
34
+ color: white;
35
+ }
36
+ }
37
+ }
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hotsheet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Renuo AG
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-11-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: pagy
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sprockets-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: stimulus-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: turbo-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: This gem allows you to mount a view to manage your database using atable
84
+ view where you can edit DB records inline.
85
+ email:
86
+ - ignacio.sfeir@renuo.ch
87
+ - simon.isler@renuo.ch
88
+ - eduard.munteanu@renuo.ch
89
+ - chris.hunziker@renuo.ch
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - CHANGELOG.md
95
+ - LICENSE
96
+ - README.md
97
+ - app/assets/config/hotsheet.js
98
+ - app/assets/javascripts/hotsheet/application.js
99
+ - app/assets/javascripts/hotsheet/channels/consumer.js
100
+ - app/assets/javascripts/hotsheet/channels/inline_edit_channel.js
101
+ - app/assets/javascripts/hotsheet/controllers/application.js
102
+ - app/assets/javascripts/hotsheet/controllers/editable_attribute_controller.js
103
+ - app/assets/stylesheets/hotsheet/application.css
104
+ - app/channels/application_cable/channel.rb
105
+ - app/channels/application_cable/connection.rb
106
+ - app/channels/inline_edit_channel.rb
107
+ - app/controllers/hotsheet/application_controller.rb
108
+ - app/controllers/hotsheet/pages_controller.rb
109
+ - app/helpers/hotsheet/application_helper.rb
110
+ - app/views/hotsheet/pages/_editable_attribute.html.erb
111
+ - app/views/hotsheet/pages/index.html.erb
112
+ - app/views/layouts/hotsheet/_flash.html.erb
113
+ - app/views/layouts/hotsheet/_sidebar.html.erb
114
+ - app/views/layouts/hotsheet/application.html.erb
115
+ - config/initializers/hotsheet/content_security_policy.rb
116
+ - config/initializers/hotsheet/editable_attributes.rb
117
+ - config/initializers/hotsheet/pagy.rb
118
+ - config/locales/en.yml
119
+ - config/routes.rb
120
+ - lib/generators/hotsheet/install_generator.rb
121
+ - lib/generators/templates/hotsheet.rb
122
+ - lib/hotsheet.rb
123
+ - lib/hotsheet/configuration.rb
124
+ - lib/hotsheet/engine.rb
125
+ - lib/hotsheet/version.rb
126
+ - vendor/assets/stylesheets/hotsheet/pagy.css
127
+ homepage: https://github.com/renuo/hotsheet
128
+ licenses:
129
+ - MIT
130
+ metadata:
131
+ homepage_uri: https://github.com/renuo/hotsheet
132
+ source_code_uri: https://github.com/renuo/hotsheet
133
+ changelog_uri: https://github.com/renuo/hotsheet/blob/main/CHANGELOG.md
134
+ documentation_uri: https://github.com/renuo/hotsheet/blob/main/README.md
135
+ rubygems_mfa_required: 'true'
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: 3.0.0
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubygems_version: 3.5.16
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: Manage your database with a simple and familiar web interface
155
+ test_files: []