paper_view 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.
@@ -0,0 +1,51 @@
1
+ <%= form_with url: versions_path, method: :get, local: true, data: {turbo: false}, class: "pv-search" do %>
2
+ <%= paper_view_hidden_filters(except: %i[item_type item_id event]) %>
3
+
4
+ <div class="pv-field pv-field--grow">
5
+ <label for="pv-item-type">Item type</label>
6
+
7
+ <input
8
+ id="pv-item-type"
9
+ type="text"
10
+ name="item_type"
11
+ value="<%= query.item_type %>"
12
+ class="pv-input"
13
+ list="pv-item-types"
14
+ placeholder="Type to search…"
15
+ autocomplete="off"
16
+ <%= "autofocus" if autofocus %>
17
+ >
18
+
19
+ <datalist id="pv-item-types">
20
+ <%= safe_join(item_types.map { |item_type| tag.option(nil, value: item_type) }) %>
21
+ </datalist>
22
+ </div>
23
+
24
+ <div class="pv-field">
25
+ <label for="pv-item-id">Item ID</label>
26
+
27
+ <input
28
+ id="pv-item-id"
29
+ type="text"
30
+ name="item_id"
31
+ value="<%= query.item_id %>"
32
+ class="pv-input"
33
+ placeholder="any"
34
+ autocomplete="off"
35
+ inputmode="numeric"
36
+ >
37
+ </div>
38
+
39
+ <div class="pv-field">
40
+ <label for="pv-event">Event</label>
41
+
42
+ <select id="pv-event" name="event" class="pv-input">
43
+ <option value="">Any event</option>
44
+ <%= options_for_select(PaperView::VersionQuery::EVENTS, query.event) %>
45
+ </select>
46
+ </div>
47
+
48
+ <div class="pv-field pv-field--actions">
49
+ <button type="submit" class="pv-btn pv-btn--primary">Search</button>
50
+ </div>
51
+ <% end %>
@@ -0,0 +1,41 @@
1
+ <div class="pv-footer">
2
+ <nav class="pv-pagination">
3
+ <% if paginator.total_pages > 1 %>
4
+ <% if paginator.previous_page %>
5
+ <%= link_to "← Previous", versions_path(paper_view_filter_params("page" => paginator.previous_page)), class: "pv-btn" %>
6
+ <% else %>
7
+ <span class="pv-btn pv-btn--disabled">← Previous</span>
8
+ <% end %>
9
+
10
+ <% paginator.page_numbers.each do |number| %>
11
+ <% if number == :gap %>
12
+ <span class="pv-pagination__gap">…</span>
13
+ <% elsif number == paginator.page %>
14
+ <span class="pv-pagination__page pv-pagination__page--current"><%= number %></span>
15
+ <% else %>
16
+ <%= link_to number, versions_path(paper_view_filter_params("page" => number)), class: "pv-pagination__page" %>
17
+ <% end %>
18
+ <% end %>
19
+
20
+ <% if paginator.next_page %>
21
+ <%= link_to "Next →", versions_path(paper_view_filter_params("page" => paginator.next_page)), class: "pv-btn" %>
22
+ <% else %>
23
+ <span class="pv-btn pv-btn--disabled">Next →</span>
24
+ <% end %>
25
+ <% end %>
26
+ </nav>
27
+
28
+ <%= form_with url: versions_path, method: :get, local: true, data: {turbo: false}, class: "pv-footer__end" do %>
29
+ <%= paper_view_hidden_filters(except: :per_page) %>
30
+ <label class="pv-muted" for="pv-per-page">Per page</label>
31
+
32
+ <select
33
+ id="pv-per-page"
34
+ name="per_page"
35
+ class="pv-select-mini"
36
+ data-pv-autosubmit
37
+ >
38
+ <%= options_for_select(paginator.per_page_options, paginator.per_page) %>
39
+ </select>
40
+ <% end %>
41
+ </div>
@@ -0,0 +1,126 @@
1
+ <section
2
+ class="pv-card pv-detail"
3
+ data-pv-panel="<%= row.id %>"
4
+ <%= "hidden" unless active %>
5
+ >
6
+ <div class="pv-detail__head">
7
+ <span class="pv-detail__id">Version #<%= row.id %></span>
8
+ <span class="pv-detail__stamp"><%= paper_view_time(row.version.created_at) %></span>
9
+ </div>
10
+
11
+ <div class="pv-detail__meta<%= " pv-detail__meta--split" if stream %>">
12
+ <% if stream %>
13
+ <div class="pv-detail__field">
14
+ <span class="pv-label">Item</span>
15
+ <%= link_to row.item_label, paper_view_item_path(row.version), class: "pv-detail__item" %>
16
+ </div>
17
+ <% end %>
18
+
19
+ <div class="pv-detail__field">
20
+ <span class="pv-label">Whodunnit</span>
21
+ <span class="pv-detail__who"><%= paper_view_whodunnit(row.version) %></span>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="pv-detail__body">
26
+ <% if row.change_set.unreadable? %>
27
+ <p class="pv-detail__note">
28
+ The <code>object_changes</code> payload is neither readable YAML nor
29
+ JSON, so it cannot be diffed. The stored bytes are below under
30
+ <strong>Raw object_changes</strong>.
31
+ </p>
32
+ <% else %>
33
+ <span class="pv-label"><%= row.change_set.size %> changed <%= "field".pluralize(row.change_set.size) %></span>
34
+
35
+ <% if row.change_set.empty? %>
36
+ <p class="pv-detail__note">
37
+ The <code>object_changes</code> column is empty for this event.
38
+ </p>
39
+ <% end %>
40
+
41
+ <% row.change_set.each_with_index do |change, index| %>
42
+ <div class="pv-attribute">
43
+ <span class="pv-attribute__name"><%= change.name %></span>
44
+
45
+ <% if change.nested? %>
46
+ <% change.leaf_changes.each do |leaf| %>
47
+ <div class="pv-keys">
48
+ <span class="pv-keys__name"><%= leaf.path %></span>
49
+
50
+ <div class="pv-keys__values">
51
+ <span class="pv-value pv-value--old"><%= leaf.old_text %></span>
52
+ <span class="pv-change__arrow">→</span>
53
+ <span class="pv-value pv-value--new"><%= leaf.new_text %></span>
54
+ </div>
55
+ </div>
56
+ <% end %>
57
+ <% if change.unchanged_count.positive? %>
58
+ <% whole_id = "pv-whole-#{row.id}-#{index}" %>
59
+ <button
60
+ type="button"
61
+ class="pv-attribute__unchanged"
62
+ aria-expanded="false"
63
+ aria-controls="<%= whole_id %>"
64
+ data-pv-toggle="<%= whole_id %>"
65
+ >
66
+ <span class="pv-disclosure__arrow">▸</span>
67
+ <%= change.unchanged_count %> unchanged
68
+ <%= "value".pluralize(change.unchanged_count) %>
69
+ </button>
70
+
71
+ <div class="pv-bars" id="<%= whole_id %>" hidden>
72
+ <div class="pv-bar pv-bar--old"><%= change.old_text %></div>
73
+ <div class="pv-bar pv-bar--new"><%= change.new_text %></div>
74
+ </div>
75
+ <% end %>
76
+ <% else %>
77
+ <div class="pv-bars">
78
+ <div class="pv-bar pv-bar--old"><%= change.old_text %></div>
79
+ <div class="pv-bar pv-bar--new"><%= change.new_text %></div>
80
+ </div>
81
+ <% end %>
82
+ </div>
83
+ <% end %>
84
+ <% end %>
85
+
86
+ <div class="pv-disclosures">
87
+ <button
88
+ type="button"
89
+ class="pv-disclosure"
90
+ aria-expanded="false"
91
+ aria-controls="pv-raw-<%= row.id %>"
92
+ data-pv-toggle="pv-raw-<%= row.id %>"
93
+ >
94
+ <span class="pv-disclosure__arrow">▸</span> Raw object_changes
95
+ </button>
96
+
97
+ <button
98
+ type="button"
99
+ class="pv-disclosure pv-disclosure--end"
100
+ aria-expanded="false"
101
+ aria-controls="pv-reify-<%= row.id %>"
102
+ data-pv-toggle="pv-reify-<%= row.id %>"
103
+ >
104
+ <span class="pv-disclosure__arrow">▸</span> Reify
105
+ </button>
106
+
107
+ <pre class="pv-raw" id="pv-raw-<%= row.id %>" hidden><%= row.change_set.raw_text %></pre>
108
+
109
+ <div class="pv-snippet" id="pv-reify-<%= row.id %>" hidden>
110
+ <div class="pv-snippet__head">
111
+ <span class="pv-snippet__label">Rails console</span>
112
+
113
+ <button
114
+ type="button"
115
+ class="pv-snippet__copy"
116
+ data-pv-copy="pv-snippet-<%= row.id %>"
117
+ >
118
+ Copy
119
+ </button>
120
+ </div>
121
+
122
+ <pre class="pv-snippet__code" id="pv-snippet-<%= row.id %>"><%= row.console_snippet %></pre>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </section>
@@ -0,0 +1,50 @@
1
+ <div
2
+ class="pv-version<%= " pv-version--selected" if selected %><%= " pv-version--stream" if stream %>"
3
+ data-pv-version="<%= row.id %>"
4
+ role="button"
5
+ tabindex="0"
6
+ aria-pressed="<%= selected %>"
7
+ >
8
+ <div class="pv-version__time"><%= row.time %></div>
9
+
10
+ <div class="pv-version__rail">
11
+ <span class="pv-version__dot"></span>
12
+ <span class="pv-version__line"></span>
13
+ </div>
14
+
15
+ <% if stream %>
16
+ <div class="pv-version__item">
17
+ <%= link_to row.item_label, paper_view_item_path(row.version), class: "pv-version__item-link" %>
18
+ <span class="pv-pill pv-pill--<%= row.event %>"><%= row.event %></span>
19
+ </div>
20
+ <% end %>
21
+
22
+ <div class="pv-version__content">
23
+ <div class="pv-version__meta">
24
+ <span class="pv-version__actor"><%= paper_view_whodunnit(row.version) %></span>
25
+
26
+ <% unless stream %>
27
+ <span class="pv-pill pv-pill--event"><%= row.event %></span>
28
+ <% end %>
29
+
30
+ <% if row.burst? %>
31
+ <span class="pv-pill pv-pill--burst"><%= row.burst_label %></span>
32
+ <% end %>
33
+ </div>
34
+
35
+ <% row.lines.each do |line| %>
36
+ <div class="pv-change">
37
+ <span class="pv-change__path"><%= line.path %></span>
38
+ <span class="pv-value pv-value--old" title="<%= line.old_text %>"><%= truncate(line.old_text, length: 140) %></span>
39
+ <span class="pv-change__arrow">→</span>
40
+ <span class="pv-value pv-value--new" title="<%= line.new_text %>"><%= truncate(line.new_text, length: 140) %></span>
41
+ </div>
42
+ <% end %>
43
+
44
+ <% row.footnotes.each do |note| %>
45
+ <div class="pv-version__note"><%= note %></div>
46
+ <% end %>
47
+ </div>
48
+
49
+ <div class="pv-version__id">#<%= row.id %></div>
50
+ </div>
@@ -0,0 +1,77 @@
1
+ <% unless @query.submitted? %>
2
+ <section class="pv-panel pv-panel--hero">
3
+ <div class="pv-hero">
4
+ <h1 class="pv-hero__title">Which trail to follow?</h1>
5
+
6
+ <div class="pv-hero__search">
7
+ <%= render "paper_view/versions/filters", query: @query, item_types: @item_types, autofocus: true %>
8
+ </div>
9
+
10
+ <% if @item_types.empty? %>
11
+ <p class="pv-hero__note">No versions have been recorded yet.</p>
12
+ <% end %>
13
+ </div>
14
+ </section>
15
+ <% else %>
16
+ <section class="pv-panel">
17
+ <div class="pv-panel__body">
18
+ <%= render "paper_view/versions/filters", query: @query, item_types: @item_types, autofocus: false %>
19
+ </div>
20
+ </section>
21
+
22
+ <% if @timeline.empty? %>
23
+ <section class="pv-panel">
24
+ <p class="pv-empty">No versions match this filter.</p>
25
+ </section>
26
+ <% else %>
27
+ <div class="pv-layout">
28
+ <section class="pv-card">
29
+ <div class="pv-card__head">
30
+ <span class="pv-card__title"><%= @timeline.title %></span>
31
+
32
+ <span class="pv-card__meta">
33
+ <%= number_with_delimiter(@paginator.total_count) %>
34
+ <%= "version".pluralize(@paginator.total_count) %><%= " · #{@timeline.items.size} items" if @timeline.stream? %>
35
+ </span>
36
+
37
+ <span class="pv-spacer"></span>
38
+
39
+ <%= form_with url: versions_path, method: :get, local: true, data: {turbo: false}, class: "pv-head-form" do %>
40
+ <%= paper_view_hidden_filters(except: :sort) %>
41
+
42
+ <select
43
+ id="pv-sort"
44
+ name="sort"
45
+ class="pv-select-mini"
46
+ data-pv-autosubmit
47
+ aria-label="Sort order"
48
+ >
49
+ <option value="desc" <%= "selected" if @query.sort == "desc" %>>Newest first</option>
50
+ <option value="asc" <%= "selected" if @query.sort == "asc" %>>Oldest first</option>
51
+ </select>
52
+ <% end %>
53
+ </div>
54
+
55
+ <div class="pv-timeline" data-pv-timeline>
56
+ <% @timeline.groups.each do |group| %>
57
+ <div class="pv-timeline__group">
58
+ <div class="pv-timeline__day"><%= group.label %></div>
59
+
60
+ <% group.rows.each do |row| %>
61
+ <%= render "paper_view/versions/timeline_row", row: row, stream: @timeline.stream?, selected: row == @timeline.selected %>
62
+ <% end %>
63
+ </div>
64
+ <% end %>
65
+ </div>
66
+
67
+ <%= render "paper_view/versions/footer", paginator: @paginator %>
68
+ </section>
69
+
70
+ <aside class="pv-side" data-pv-panels>
71
+ <% @timeline.rows.each do |row| %>
72
+ <%= render "paper_view/versions/panel", row: row, stream: @timeline.stream?, active: row == @timeline.selected %>
73
+ <% end %>
74
+ </aside>
75
+ </div>
76
+ <% end %>
77
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ PaperView::Engine.routes.draw do
2
+ root "versions#index"
3
+
4
+ resources :versions, only: :index
5
+ end
@@ -0,0 +1,24 @@
1
+ module PaperView
2
+ class Configuration
3
+ DEFAULT_TIME_FORMAT = "%Y-%m-%d %H:%M:%S".freeze
4
+
5
+ attr_accessor :version_class_name, :parent_controller, :per_page, :whodunnit_label, :time_format
6
+ attr_reader :authentication_block, :authorization_block
7
+
8
+ def initialize
9
+ @version_class_name = "PaperTrail::Version"
10
+ @parent_controller = "ActionController::Base"
11
+ @per_page = 25
12
+ @whodunnit_label = nil
13
+ @time_format = DEFAULT_TIME_FORMAT
14
+ end
15
+
16
+ def authenticate_with(&block)
17
+ @authentication_block = block
18
+ end
19
+
20
+ def authorize_with(&block)
21
+ @authorization_block = block
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ module PaperView
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace PaperView
4
+
5
+ config.generators do |generator|
6
+ generator.test_framework :minitest, fixture: false
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module PaperView
2
+ VERSION = "0.1.0"
3
+ end
data/lib/paper_view.rb ADDED
@@ -0,0 +1,37 @@
1
+ require "paper_view/version"
2
+ require "paper_view/configuration"
3
+ require "paper_view/engine"
4
+
5
+ module PaperView
6
+ EMPTY = "—".freeze
7
+
8
+ class Error < StandardError; end
9
+
10
+ class << self
11
+ def config
12
+ @config ||= Configuration.new
13
+ end
14
+
15
+ def setup
16
+ yield config
17
+ config
18
+ end
19
+
20
+ def version_class
21
+ config.version_class_name.constantize
22
+ rescue NameError => error
23
+ raise unless config.version_class_name.split("::").include?(error.name.to_s)
24
+ raise Error, missing_version_class_message
25
+ end
26
+
27
+ private
28
+
29
+ def missing_version_class_message
30
+ <<~MESSAGE.squish
31
+ PaperView could not find #{config.version_class_name}.
32
+ Add `gem "paper_trail"` to your Gemfile, or point PaperView at your own
33
+ versions model: `PaperView.setup { |config| config.version_class_name = "YourVersion" }`.
34
+ MESSAGE
35
+ end
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: paper_view
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Leon Vogt
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.0'
26
+ description: PaperView is a mountable Rails engine that turns the paper_trail versions
27
+ table into a searchable dashboard
28
+ email:
29
+ - leon.vogt@hey.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - app/controllers/paper_view/application_controller.rb
37
+ - app/controllers/paper_view/versions_controller.rb
38
+ - app/helpers/paper_view/application_helper.rb
39
+ - app/models/paper_view/attribute_change.rb
40
+ - app/models/paper_view/change_set.rb
41
+ - app/models/paper_view/leaf_change.rb
42
+ - app/models/paper_view/paginator.rb
43
+ - app/models/paper_view/payload.rb
44
+ - app/models/paper_view/timeline.rb
45
+ - app/models/paper_view/version_query.rb
46
+ - app/models/paper_view/version_row.rb
47
+ - app/models/paper_view/versions_table.rb
48
+ - app/views/layouts/paper_view/application.html.erb
49
+ - app/views/paper_view/shared/_flash.html.erb
50
+ - app/views/paper_view/shared/_script.html.erb
51
+ - app/views/paper_view/shared/_styles.html.erb
52
+ - app/views/paper_view/versions/_filters.html.erb
53
+ - app/views/paper_view/versions/_footer.html.erb
54
+ - app/views/paper_view/versions/_panel.html.erb
55
+ - app/views/paper_view/versions/_timeline_row.html.erb
56
+ - app/views/paper_view/versions/index.html.erb
57
+ - config/routes.rb
58
+ - lib/paper_view.rb
59
+ - lib/paper_view/configuration.rb
60
+ - lib/paper_view/engine.rb
61
+ - lib/paper_view/version.rb
62
+ homepage: https://github.com/leonvogt/paper_view
63
+ licenses:
64
+ - MIT
65
+ metadata:
66
+ source_code_uri: https://github.com/leonvogt/paper_view
67
+ changelog_uri: https://github.com/leonvogt/paper_view/releases
68
+ rubygems_mfa_required: 'true'
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '2.7'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 4.0.14
84
+ specification_version: 4
85
+ summary: A lightweight, mountable dashboard for paper_trail
86
+ test_files: []