reviewkit 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/CODE_OF_CONDUCT.md +123 -0
- data/CONTRIBUTING.md +44 -0
- data/MIT-LICENSE +20 -0
- data/README.md +335 -0
- data/Rakefile +7 -0
- data/SECURITY.md +18 -0
- data/app/assets/builds/reviewkit/application.css +2 -0
- data/app/assets/javascripts/reviewkit/application.js +12 -0
- data/app/assets/javascripts/reviewkit/controllers/file_nav_controller.js +24 -0
- data/app/assets/javascripts/reviewkit/controllers/review_index_controller.js +84 -0
- data/app/assets/tailwind/reviewkit/application.css +865 -0
- data/app/controllers/reviewkit/application_controller.rb +80 -0
- data/app/controllers/reviewkit/comments_controller.rb +147 -0
- data/app/controllers/reviewkit/review_threads_controller.rb +277 -0
- data/app/controllers/reviewkit/reviews_controller.rb +142 -0
- data/app/helpers/reviewkit/application_helper.rb +12 -0
- data/app/helpers/reviewkit/asset_helper.rb +39 -0
- data/app/helpers/reviewkit/diff_helper.rb +230 -0
- data/app/helpers/reviewkit/flash_helper.rb +36 -0
- data/app/helpers/reviewkit/frame_helper.rb +37 -0
- data/app/helpers/reviewkit/icon_helper.rb +107 -0
- data/app/helpers/reviewkit/review_thread_helper.rb +54 -0
- data/app/models/concerns/reviewkit/notifies_lifecycle_events.rb +39 -0
- data/app/models/reviewkit/application_record.rb +7 -0
- data/app/models/reviewkit/comment.rb +29 -0
- data/app/models/reviewkit/current.rb +7 -0
- data/app/models/reviewkit/document.rb +79 -0
- data/app/models/reviewkit/review.rb +66 -0
- data/app/models/reviewkit/review_thread.rb +75 -0
- data/app/services/reviewkit/diffs/intraline_budget.rb +40 -0
- data/app/services/reviewkit/diffs/intraline_diff.rb +220 -0
- data/app/services/reviewkit/diffs/split_diff.rb +112 -0
- data/app/services/reviewkit/reviews/create.rb +57 -0
- data/app/views/layouts/reviewkit/application.html.erb +15 -0
- data/app/views/reviewkit/comments/_comment.html.erb +53 -0
- data/app/views/reviewkit/comments/_edit_form.html.erb +26 -0
- data/app/views/reviewkit/comments/_form.html.erb +16 -0
- data/app/views/reviewkit/review_threads/_bucket.html.erb +53 -0
- data/app/views/reviewkit/review_threads/_bucket_frame.html.erb +13 -0
- data/app/views/reviewkit/review_threads/_bucket_row.html.erb +55 -0
- data/app/views/reviewkit/review_threads/_edit_form.html.erb +29 -0
- data/app/views/reviewkit/review_threads/_thread.html.erb +87 -0
- data/app/views/reviewkit/reviews/_document.html.erb +41 -0
- data/app/views/reviewkit/reviews/_document_split.html.erb +73 -0
- data/app/views/reviewkit/reviews/_document_unified.html.erb +57 -0
- data/app/views/reviewkit/reviews/_edit_form.html.erb +35 -0
- data/app/views/reviewkit/reviews/_index_content.html.erb +160 -0
- data/app/views/reviewkit/reviews/_review_sidebar.html.erb +70 -0
- data/app/views/reviewkit/reviews/_show_content.html.erb +164 -0
- data/app/views/reviewkit/reviews/index.html.erb +11 -0
- data/app/views/reviewkit/reviews/show.html.erb +11 -0
- data/app/views/reviewkit/shared/_flash.html.erb +10 -0
- data/bin/console +4 -0
- data/bin/lint +4 -0
- data/bin/rails +14 -0
- data/bin/setup +9 -0
- data/bin/test +4 -0
- data/config/importmap.rb +6 -0
- data/config/routes.rb +24 -0
- data/db/migrate/20260331181500_create_reviewkit_reviews.rb +19 -0
- data/db/migrate/20260331181600_create_reviewkit_documents.rb +23 -0
- data/db/migrate/20260331181700_create_reviewkit_review_threads.rb +23 -0
- data/db/migrate/20260331181800_create_reviewkit_comments.rb +15 -0
- data/db/migrate/20260401093000_add_description_to_reviewkit_reviews.rb +7 -0
- data/lib/generators/reviewkit/controllers/controllers_generator.rb +24 -0
- data/lib/generators/reviewkit/controllers/templates/comments_controller_extension.rb +13 -0
- data/lib/generators/reviewkit/controllers/templates/review_threads_controller_extension.rb +13 -0
- data/lib/generators/reviewkit/controllers/templates/reviews_controller_extension.rb +19 -0
- data/lib/generators/reviewkit/install/install_generator.rb +52 -0
- data/lib/generators/reviewkit/install/templates/importmap.rb +3 -0
- data/lib/generators/reviewkit/install/templates/reviewkit.rb +19 -0
- data/lib/generators/reviewkit/models/models_generator.rb +24 -0
- data/lib/generators/reviewkit/models/templates/comment_extension.rb +21 -0
- data/lib/generators/reviewkit/models/templates/review_extension.rb +22 -0
- data/lib/generators/reviewkit/models/templates/review_thread_extension.rb +21 -0
- data/lib/generators/reviewkit/views/views_generator.rb +15 -0
- data/lib/reviewkit/configuration.rb +33 -0
- data/lib/reviewkit/engine.rb +67 -0
- data/lib/reviewkit/version.rb +5 -0
- data/lib/reviewkit.rb +26 -0
- data/lib/tasks/reviewkit_tasks.rake +12 -0
- data/sig/reviewkit.rbs +129 -0
- metadata +238 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<% show_bucket = composer_open || thread_errors.present? || threads.present? %>
|
|
2
|
+
|
|
3
|
+
<tr
|
|
4
|
+
id="<%= reviewkit_thread_bucket_row_id(document, line_code) %>"
|
|
5
|
+
class="<%= [("hidden" unless show_bucket), "border-t border-slate-200 bg-slate-50/40"].compact.join(" ") %>"
|
|
6
|
+
>
|
|
7
|
+
<% if view_mode == "split" %>
|
|
8
|
+
<% default_side = row&.dig("new_line").present? ? "new" : "old" %>
|
|
9
|
+
<% old_threads = threads.select { |thread| thread.side == "old" } %>
|
|
10
|
+
<% new_threads = threads.select { |thread| thread.side == "new" } %>
|
|
11
|
+
<% active_side = composer_side.presence || default_side %>
|
|
12
|
+
|
|
13
|
+
<td colspan="3" class="reviewkit-thread-bucket-cell reviewkit-thread-bucket-cell--split reviewkit-thread-bucket-cell--old">
|
|
14
|
+
<%= render "reviewkit/review_threads/bucket",
|
|
15
|
+
composer_open: composer_open && active_side == "old",
|
|
16
|
+
composer_side: "old",
|
|
17
|
+
document: document,
|
|
18
|
+
frame_id: frame_id,
|
|
19
|
+
line_code: line_code,
|
|
20
|
+
review: review,
|
|
21
|
+
row: row,
|
|
22
|
+
thread_errors: active_side == "old" ? thread_errors : [],
|
|
23
|
+
threads: old_threads,
|
|
24
|
+
view_mode: view_mode %>
|
|
25
|
+
</td>
|
|
26
|
+
|
|
27
|
+
<td colspan="3" class="reviewkit-thread-bucket-cell reviewkit-thread-bucket-cell--split reviewkit-thread-bucket-cell--new">
|
|
28
|
+
<%= render "reviewkit/review_threads/bucket",
|
|
29
|
+
composer_open: composer_open && active_side == "new",
|
|
30
|
+
composer_side: "new",
|
|
31
|
+
document: document,
|
|
32
|
+
frame_id: frame_id,
|
|
33
|
+
line_code: line_code,
|
|
34
|
+
review: review,
|
|
35
|
+
row: row,
|
|
36
|
+
thread_errors: active_side == "new" ? thread_errors : [],
|
|
37
|
+
threads: new_threads,
|
|
38
|
+
view_mode: view_mode %>
|
|
39
|
+
</td>
|
|
40
|
+
<% else %>
|
|
41
|
+
<td colspan="<%= colspan %>" class="reviewkit-thread-bucket-cell">
|
|
42
|
+
<%= render "reviewkit/review_threads/bucket",
|
|
43
|
+
composer_open: composer_open,
|
|
44
|
+
composer_side: composer_side,
|
|
45
|
+
document: document,
|
|
46
|
+
frame_id: frame_id,
|
|
47
|
+
line_code: line_code,
|
|
48
|
+
review: review,
|
|
49
|
+
row: row,
|
|
50
|
+
thread_errors: thread_errors,
|
|
51
|
+
threads: threads,
|
|
52
|
+
view_mode: view_mode %>
|
|
53
|
+
</td>
|
|
54
|
+
<% end %>
|
|
55
|
+
</tr>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<%= turbo_frame_tag dom_id(thread) do %>
|
|
2
|
+
<%= form_with model: thread, scope: :review_thread, class: "reviewkit-comment-edit-form" do |form| %>
|
|
3
|
+
<%= form.hidden_field :frame_id, value: local_assigns[:frame_id] %>
|
|
4
|
+
<%= form.hidden_field :view, value: local_assigns[:view_mode] %>
|
|
5
|
+
<% if comment.errors.any? %>
|
|
6
|
+
<div class="reviewkit-comment-error">
|
|
7
|
+
<%= safe_join(comment.errors.full_messages.map { |error| content_tag(:div, error) }) %>
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<label class="block">
|
|
12
|
+
<span class="mb-1 block text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Edit thread</span>
|
|
13
|
+
<%= form.text_area :body,
|
|
14
|
+
value: comment.body,
|
|
15
|
+
rows: 3,
|
|
16
|
+
class: "reviewkit-textarea",
|
|
17
|
+
autofocus: true %>
|
|
18
|
+
</label>
|
|
19
|
+
|
|
20
|
+
<div class="flex items-center justify-end gap-2">
|
|
21
|
+
<%= link_to "Cancel",
|
|
22
|
+
review_thread_path(thread, frame_id: local_assigns[:frame_id], view: local_assigns[:view_mode]),
|
|
23
|
+
class: "reviewkit-button reviewkit-button--ghost reviewkit-button--small",
|
|
24
|
+
data: { turbo_frame: dom_id(thread) } %>
|
|
25
|
+
<%= form.submit "Save",
|
|
26
|
+
class: "reviewkit-button reviewkit-button--primary reviewkit-button--small" %>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<% review_frame_id = local_assigns[:frame_id] || dom_id(review, :review) %>
|
|
2
|
+
<% starter_comment = reviewkit_thread_starter_comment(thread) %>
|
|
3
|
+
|
|
4
|
+
<%= turbo_frame_tag dom_id(thread) do %>
|
|
5
|
+
<details class="reviewkit-thread-accordion" <%= "open" if local_assigns[:expanded] %>>
|
|
6
|
+
<summary class="reviewkit-thread-accordion__summary">
|
|
7
|
+
<div class="reviewkit-thread-accordion__summary-main">
|
|
8
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
9
|
+
<span class="font-semibold text-slate-900">
|
|
10
|
+
<%= reviewkit_actor_label(starter_comment&.author) %>
|
|
11
|
+
</span>
|
|
12
|
+
<span class="<%= reviewkit_status_pill_class(thread.status) %>">
|
|
13
|
+
<%= thread.status %>
|
|
14
|
+
</span>
|
|
15
|
+
<span class="text-slate-500">line <%= thread.new_line || thread.old_line %></span>
|
|
16
|
+
<span class="text-slate-400"><%= pluralize(thread.comments.size, "comment") %></span>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<p class="reviewkit-thread-accordion__preview">
|
|
20
|
+
<%= reviewkit_thread_preview(thread) %>
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<span class="reviewkit-thread-accordion__chevron" aria-hidden="true">
|
|
25
|
+
<%= reviewkit_chevron_right_icon %>
|
|
26
|
+
</span>
|
|
27
|
+
</summary>
|
|
28
|
+
|
|
29
|
+
<article class="reviewkit-thread-card">
|
|
30
|
+
<header class="reviewkit-thread-card__header">
|
|
31
|
+
<div class="flex flex-wrap items-center gap-1.5">
|
|
32
|
+
<% if thread.open? %>
|
|
33
|
+
<%= button_to "Resolve", resolve_review_thread_path(thread), method: :patch,
|
|
34
|
+
class: "reviewkit-button reviewkit-button--ghost reviewkit-button--small",
|
|
35
|
+
params: { frame_id: review_frame_id, view: local_assigns[:view_mode] } %>
|
|
36
|
+
<%= button_to "Outdated", mark_outdated_review_thread_path(thread), method: :patch,
|
|
37
|
+
class: "reviewkit-button reviewkit-button--ghost reviewkit-button--small",
|
|
38
|
+
params: { frame_id: review_frame_id, view: local_assigns[:view_mode] } %>
|
|
39
|
+
<% else %>
|
|
40
|
+
<%= button_to "Reopen", reopen_review_thread_path(thread), method: :patch,
|
|
41
|
+
class: "reviewkit-button reviewkit-button--ghost reviewkit-button--small",
|
|
42
|
+
params: { frame_id: review_frame_id, view: local_assigns[:view_mode] } %>
|
|
43
|
+
<% end %>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<% if reviewkit_thread_manageable?(thread) %>
|
|
47
|
+
<div class="reviewkit-inline-actions" aria-label="Thread actions">
|
|
48
|
+
<%= link_to edit_review_thread_path(thread, frame_id: review_frame_id, view: local_assigns[:view_mode]),
|
|
49
|
+
class: "reviewkit-inline-icon-button",
|
|
50
|
+
data: { turbo_frame: dom_id(thread) },
|
|
51
|
+
aria: { label: "Edit thread" },
|
|
52
|
+
title: "Edit thread" do %>
|
|
53
|
+
<%= reviewkit_pencil_icon %>
|
|
54
|
+
<% end %>
|
|
55
|
+
|
|
56
|
+
<%= link_to review_thread_path(thread, frame_id: review_frame_id, view: local_assigns[:view_mode]),
|
|
57
|
+
class: "reviewkit-inline-icon-button reviewkit-inline-icon-button--danger",
|
|
58
|
+
data: {
|
|
59
|
+
turbo_method: :delete,
|
|
60
|
+
turbo_frame: review_frame_id,
|
|
61
|
+
turbo_confirm: "Delete this thread?"
|
|
62
|
+
},
|
|
63
|
+
aria: { label: "Delete thread" },
|
|
64
|
+
title: "Delete thread" do %>
|
|
65
|
+
<%= reviewkit_trash_icon %>
|
|
66
|
+
<% end %>
|
|
67
|
+
</div>
|
|
68
|
+
<% end %>
|
|
69
|
+
</header>
|
|
70
|
+
|
|
71
|
+
<div class="divide-y divide-slate-200">
|
|
72
|
+
<% thread.comments.each do |comment| %>
|
|
73
|
+
<%= render "reviewkit/comments/comment",
|
|
74
|
+
comment: comment,
|
|
75
|
+
frame_id: review_frame_id,
|
|
76
|
+
view_mode: local_assigns[:view_mode] %>
|
|
77
|
+
<% end %>
|
|
78
|
+
|
|
79
|
+
<%= render "reviewkit/comments/form",
|
|
80
|
+
frame_id: review_frame_id,
|
|
81
|
+
review: review,
|
|
82
|
+
thread: thread,
|
|
83
|
+
view_mode: local_assigns[:view_mode] %>
|
|
84
|
+
</div>
|
|
85
|
+
</article>
|
|
86
|
+
</details>
|
|
87
|
+
<% end %>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<% directory, basename = reviewkit_document_path_parts(document) %>
|
|
2
|
+
|
|
3
|
+
<article
|
|
4
|
+
id="<%= reviewkit_document_anchor(document) %>"
|
|
5
|
+
class="reviewkit-file reviewkit-file--selected"
|
|
6
|
+
>
|
|
7
|
+
<header class="reviewkit-file-header">
|
|
8
|
+
<div class="reviewkit-file-heading">
|
|
9
|
+
<%= reviewkit_file_icon %>
|
|
10
|
+
|
|
11
|
+
<p class="reviewkit-file-path" title="<%= document.path %>" aria-label="<%= document.path %>">
|
|
12
|
+
<% if directory.present? %>
|
|
13
|
+
<span class="reviewkit-file-path__directory"><%= directory %></span>
|
|
14
|
+
<% end %>
|
|
15
|
+
<span class="reviewkit-file-path__basename"><%= basename %></span>
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="reviewkit-file-meta">
|
|
20
|
+
<span><%= document.language %></span>
|
|
21
|
+
<span class="font-medium text-emerald-700">+<%= document.additions_count %></span>
|
|
22
|
+
<span class="font-medium text-rose-700">-<%= document.deletions_count %></span>
|
|
23
|
+
<span class="rounded-md px-2 py-0.5 text-[11px] font-semibold <%= reviewkit_document_badge_class(document) %>">
|
|
24
|
+
<%= document.status %>
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
</header>
|
|
28
|
+
|
|
29
|
+
<div class="reviewkit-file-table-wrap">
|
|
30
|
+
<div class="reviewkit-file-table-scroll">
|
|
31
|
+
<%= render "reviewkit/reviews/document_#{view_mode}",
|
|
32
|
+
document: document,
|
|
33
|
+
frame_id: frame_id,
|
|
34
|
+
open_thread_line_code: open_thread_line_code,
|
|
35
|
+
open_thread_side: open_thread_side,
|
|
36
|
+
review: review,
|
|
37
|
+
view_mode: view_mode,
|
|
38
|
+
thread_index: thread_index %>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</article>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<table class="reviewkit-diff-table reviewkit-diff-table--split">
|
|
2
|
+
<colgroup>
|
|
3
|
+
<col class="w-8">
|
|
4
|
+
<col class="w-[1%]">
|
|
5
|
+
<col>
|
|
6
|
+
<col class="w-8">
|
|
7
|
+
<col class="w-[1%]">
|
|
8
|
+
<col>
|
|
9
|
+
</colgroup>
|
|
10
|
+
|
|
11
|
+
<% document.diff_rows.each do |row| %>
|
|
12
|
+
<% threads = reviewkit_threads_for(thread_index, document, row) %>
|
|
13
|
+
<% line_code = row.fetch("line_code") %>
|
|
14
|
+
<% composer_open = open_thread_line_code == line_code %>
|
|
15
|
+
<tbody class="reviewkit-line-group">
|
|
16
|
+
<tr class="<%= reviewkit_diff_row_class(row) %>">
|
|
17
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--old reviewkit-diff-cell--action">
|
|
18
|
+
<% if row["old_line"].present? %>
|
|
19
|
+
<%= link_to "+",
|
|
20
|
+
review_path(review, document_id: document.id, open_thread: line_code, thread_side: "old", view: view_mode),
|
|
21
|
+
class: "reviewkit-line-action",
|
|
22
|
+
data: { turbo_frame: frame_id },
|
|
23
|
+
aria: { label: "Start a thread on the old version of this line" } %>
|
|
24
|
+
<% end %>
|
|
25
|
+
</td>
|
|
26
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--old reviewkit-diff-cell--line">
|
|
27
|
+
<%= reviewkit_line_number(row["old_line"]) %>
|
|
28
|
+
</td>
|
|
29
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--old reviewkit-diff-cell--code reviewkit-diff-cell--split-divider">
|
|
30
|
+
<%= reviewkit_highlight_line(
|
|
31
|
+
row["old_text"],
|
|
32
|
+
document.language,
|
|
33
|
+
inline_ranges: reviewkit_inline_ranges(row, side: "old", document: document),
|
|
34
|
+
inline_side: "old"
|
|
35
|
+
) %>
|
|
36
|
+
</td>
|
|
37
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--new reviewkit-diff-cell--action">
|
|
38
|
+
<% if row["new_line"].present? %>
|
|
39
|
+
<%= link_to "+",
|
|
40
|
+
review_path(review, document_id: document.id, open_thread: line_code, thread_side: "new", view: view_mode),
|
|
41
|
+
class: "reviewkit-line-action",
|
|
42
|
+
data: { turbo_frame: frame_id },
|
|
43
|
+
aria: { label: "Start a thread on the new version of this line" } %>
|
|
44
|
+
<% end %>
|
|
45
|
+
</td>
|
|
46
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--new reviewkit-diff-cell--line">
|
|
47
|
+
<%= reviewkit_line_number(row["new_line"]) %>
|
|
48
|
+
</td>
|
|
49
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--new reviewkit-diff-cell--code">
|
|
50
|
+
<%= reviewkit_highlight_line(
|
|
51
|
+
row["new_text"],
|
|
52
|
+
document.language,
|
|
53
|
+
inline_ranges: reviewkit_inline_ranges(row, side: "new", document: document),
|
|
54
|
+
inline_side: "new"
|
|
55
|
+
) %>
|
|
56
|
+
</td>
|
|
57
|
+
</tr>
|
|
58
|
+
|
|
59
|
+
<%= render "reviewkit/review_threads/bucket_row",
|
|
60
|
+
colspan: 6,
|
|
61
|
+
composer_open: composer_open,
|
|
62
|
+
composer_side: open_thread_side,
|
|
63
|
+
document: document,
|
|
64
|
+
frame_id: frame_id,
|
|
65
|
+
line_code: line_code,
|
|
66
|
+
review: review,
|
|
67
|
+
row: row,
|
|
68
|
+
thread_errors: [],
|
|
69
|
+
threads: threads,
|
|
70
|
+
view_mode: view_mode %>
|
|
71
|
+
</tbody>
|
|
72
|
+
<% end %>
|
|
73
|
+
</table>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<table class="reviewkit-diff-table reviewkit-diff-table--unified">
|
|
2
|
+
<colgroup>
|
|
3
|
+
<col class="w-8">
|
|
4
|
+
<col class="w-[1%]">
|
|
5
|
+
<col class="w-[1%]">
|
|
6
|
+
<col>
|
|
7
|
+
</colgroup>
|
|
8
|
+
|
|
9
|
+
<% document.diff_rows.each do |row| %>
|
|
10
|
+
<% threads = reviewkit_threads_for(thread_index, document, row) %>
|
|
11
|
+
<% unified_rows = reviewkit_unified_group_rows(row, document: document) %>
|
|
12
|
+
<% line_code = row.fetch("line_code") %>
|
|
13
|
+
<% composer_open = open_thread_line_code == line_code %>
|
|
14
|
+
<tbody class="reviewkit-line-group">
|
|
15
|
+
<% unified_rows.each do |unified_row| %>
|
|
16
|
+
<tr class="<%= reviewkit_diff_row_class(unified_row) %>">
|
|
17
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--action">
|
|
18
|
+
<% if unified_row["line"].present? %>
|
|
19
|
+
<%= link_to "+",
|
|
20
|
+
review_path(review, document_id: document.id, open_thread: line_code, thread_side: (unified_row["side"] == "old" ? "old" : "new"), view: view_mode),
|
|
21
|
+
class: "reviewkit-line-action",
|
|
22
|
+
data: { turbo_frame: frame_id },
|
|
23
|
+
aria: { label: "Start a thread on this line" } %>
|
|
24
|
+
<% end %>
|
|
25
|
+
</td>
|
|
26
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--line">
|
|
27
|
+
<%= reviewkit_line_number(unified_row["old_line"]) %>
|
|
28
|
+
</td>
|
|
29
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--line">
|
|
30
|
+
<%= reviewkit_line_number(unified_row["new_line"]) %>
|
|
31
|
+
</td>
|
|
32
|
+
<td class="reviewkit-diff-cell reviewkit-diff-cell--code">
|
|
33
|
+
<%= reviewkit_highlight_line(
|
|
34
|
+
unified_row["text"],
|
|
35
|
+
document.language,
|
|
36
|
+
inline_ranges: reviewkit_inline_ranges(unified_row, side: unified_row["side"], document: document),
|
|
37
|
+
inline_side: unified_row["side"]
|
|
38
|
+
) %>
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
41
|
+
<% end %>
|
|
42
|
+
|
|
43
|
+
<%= render "reviewkit/review_threads/bucket_row",
|
|
44
|
+
colspan: 4,
|
|
45
|
+
composer_open: composer_open,
|
|
46
|
+
composer_side: open_thread_side,
|
|
47
|
+
document: document,
|
|
48
|
+
frame_id: frame_id,
|
|
49
|
+
line_code: line_code,
|
|
50
|
+
review: review,
|
|
51
|
+
row: row,
|
|
52
|
+
thread_errors: [],
|
|
53
|
+
threads: threads,
|
|
54
|
+
view_mode: view_mode %>
|
|
55
|
+
</tbody>
|
|
56
|
+
<% end %>
|
|
57
|
+
</table>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<%= form_with model: review,
|
|
2
|
+
url: review_path(review, document_id: selected_document&.id, view: view_mode),
|
|
3
|
+
method: :patch,
|
|
4
|
+
class: "reviewkit-review-edit-form",
|
|
5
|
+
data: { turbo_frame: frame_id } do |form| %>
|
|
6
|
+
<% if review.errors.any? %>
|
|
7
|
+
<div class="reviewkit-comment-error">
|
|
8
|
+
<%= safe_join(review.errors.full_messages.map { |error| content_tag(:div, error) }) %>
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<label class="block">
|
|
13
|
+
<span class="mb-1 block text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Title</span>
|
|
14
|
+
<%= form.text_field :title,
|
|
15
|
+
class: "reviewkit-textarea",
|
|
16
|
+
autofocus: true %>
|
|
17
|
+
</label>
|
|
18
|
+
|
|
19
|
+
<label class="block">
|
|
20
|
+
<span class="mb-1 block text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Description</span>
|
|
21
|
+
<%= form.text_area :description,
|
|
22
|
+
rows: 3,
|
|
23
|
+
class: "reviewkit-textarea",
|
|
24
|
+
placeholder: "Explain what this review is for." %>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<div class="flex items-center justify-end gap-2">
|
|
28
|
+
<%= link_to "Cancel",
|
|
29
|
+
review_path(review, document_id: selected_document&.id, view: view_mode),
|
|
30
|
+
class: "reviewkit-button reviewkit-button--ghost reviewkit-button--small",
|
|
31
|
+
data: { turbo_frame: frame_id } %>
|
|
32
|
+
<%= form.submit "Save",
|
|
33
|
+
class: "reviewkit-button reviewkit-button--primary reviewkit-button--small" %>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<main class="<%= reviewkit_frame_request? ? "reviewkit-page reviewkit-page--embedded" : "reviewkit-page" %>">
|
|
2
|
+
<section class="reviewkit-index-shell" data-controller="reviewkit--review-index">
|
|
3
|
+
<% status_options = {
|
|
4
|
+
"all" => { label: "All", count: reviews.size },
|
|
5
|
+
"open" => { label: "Open", count: reviews.count { |review| review.status == "open" } },
|
|
6
|
+
"approved" => { label: "Approved", count: reviews.count { |review| review.status == "approved" } },
|
|
7
|
+
"rejected" => { label: "Rejected", count: reviews.count { |review| review.status == "rejected" } },
|
|
8
|
+
"closed" => { label: "Closed", count: reviews.count { |review| review.status == "closed" } }
|
|
9
|
+
} %>
|
|
10
|
+
|
|
11
|
+
<header class="reviewkit-index-heading">
|
|
12
|
+
<div class="reviewkit-index-heading__copy">
|
|
13
|
+
<h1 class="reviewkit-title reviewkit-title--compact">Reviews</h1>
|
|
14
|
+
<p class="reviewkit-subtitle reviewkit-subtitle--compact">
|
|
15
|
+
Search open work, filter by status, and jump directly into a review.
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="reviewkit-summary-pill">
|
|
20
|
+
<span class="reviewkit-summary-pill__value" data-reviewkit--review-index-target="count"><%= reviews.size %></span>
|
|
21
|
+
<span>visible</span>
|
|
22
|
+
</div>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<% if reviews.any? %>
|
|
26
|
+
<section class="reviewkit-panel reviewkit-index-table-shell">
|
|
27
|
+
<div class="reviewkit-index-toolbar">
|
|
28
|
+
<div class="reviewkit-index-toolbar__search">
|
|
29
|
+
<label class="reviewkit-index-search">
|
|
30
|
+
<span class="sr-only">Search reviews</span>
|
|
31
|
+
<input
|
|
32
|
+
type="search"
|
|
33
|
+
name="reviewkit_review_search"
|
|
34
|
+
placeholder="Filter reviews by title, reference, or description"
|
|
35
|
+
autocomplete="off"
|
|
36
|
+
data-action="input->reviewkit--review-index#filter"
|
|
37
|
+
data-reviewkit--review-index-target="search"
|
|
38
|
+
>
|
|
39
|
+
</label>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="reviewkit-index-tabs">
|
|
43
|
+
<div class="reviewkit-index-tabs__select-wrap">
|
|
44
|
+
<label class="sr-only" for="reviewkit-review-status-filter">Filter reviews by status</label>
|
|
45
|
+
<select
|
|
46
|
+
id="reviewkit-review-status-filter"
|
|
47
|
+
class="reviewkit-index-tabs__select"
|
|
48
|
+
aria-label="Filter reviews by status"
|
|
49
|
+
data-action="change->reviewkit--review-index#selectStatusFilter"
|
|
50
|
+
data-reviewkit--review-index-target="select"
|
|
51
|
+
>
|
|
52
|
+
<% status_options.each do |status, option| %>
|
|
53
|
+
<option value="<%= status %>"<%= status == "all" ? " selected" : "" %>><%= option[:label] %></option>
|
|
54
|
+
<% end %>
|
|
55
|
+
</select>
|
|
56
|
+
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="reviewkit-index-tabs__select-icon">
|
|
57
|
+
<path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
|
|
58
|
+
</svg>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="reviewkit-index-tabs__desktop">
|
|
62
|
+
<div class="reviewkit-index-tabs__nav-wrap">
|
|
63
|
+
<nav class="reviewkit-index-tabs__nav" aria-label="Filter reviews by status">
|
|
64
|
+
<% status_options.each do |status, option| %>
|
|
65
|
+
<% active = status == "all" %>
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
role="tab"
|
|
69
|
+
class="reviewkit-index-tab<%= active ? " reviewkit-index-tab--active" : "" %>"
|
|
70
|
+
data-action="reviewkit--review-index#applyStatusFilter"
|
|
71
|
+
data-reviewkit--review-index-target="filter"
|
|
72
|
+
data-status="<%= status %>"
|
|
73
|
+
aria-selected="<%= active %>"
|
|
74
|
+
tabindex="<%= active ? 0 : -1 %>"
|
|
75
|
+
>
|
|
76
|
+
<span><%= option[:label] %></span>
|
|
77
|
+
<span class="reviewkit-index-tab__count<%= active ? " reviewkit-index-tab__count--active" : "" %>"><%= option[:count] %></span>
|
|
78
|
+
</button>
|
|
79
|
+
<% end %>
|
|
80
|
+
</nav>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="reviewkit-index-table-wrap">
|
|
87
|
+
<table class="reviewkit-index-table">
|
|
88
|
+
<thead>
|
|
89
|
+
<tr>
|
|
90
|
+
<th scope="col">Review</th>
|
|
91
|
+
<th scope="col">Status</th>
|
|
92
|
+
<th scope="col">Threads</th>
|
|
93
|
+
<th scope="col">Updated</th>
|
|
94
|
+
</tr>
|
|
95
|
+
</thead>
|
|
96
|
+
<tbody>
|
|
97
|
+
<% reviews.each do |review| %>
|
|
98
|
+
<% search_text = [review.title, review.external_reference, review.description].compact.join(" ") %>
|
|
99
|
+
<tr
|
|
100
|
+
tabindex="0"
|
|
101
|
+
role="link"
|
|
102
|
+
aria-label="Open review <%= review.title %>"
|
|
103
|
+
data-action="click->reviewkit--review-index#openRow keydown->reviewkit--review-index#openRowFromKeyboard"
|
|
104
|
+
data-reviewkit--review-index-target="row"
|
|
105
|
+
data-search="<%= search_text %>"
|
|
106
|
+
data-status="<%= review.status %>"
|
|
107
|
+
data-url="<%= review_path(review) %>"
|
|
108
|
+
>
|
|
109
|
+
<td>
|
|
110
|
+
<div class="reviewkit-index-row__primary">
|
|
111
|
+
<div class="reviewkit-index-row__title-group">
|
|
112
|
+
<%= link_to review.title, review_path(review), class: "reviewkit-index-row__title" %>
|
|
113
|
+
<% if review.external_reference.present? %>
|
|
114
|
+
<span class="reviewkit-index-row__reference"><%= review.external_reference %></span>
|
|
115
|
+
<% end %>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<% if review.description.present? %>
|
|
119
|
+
<p class="reviewkit-index-row__description"><%= truncate(review.description, length: 180) %></p>
|
|
120
|
+
<% end %>
|
|
121
|
+
|
|
122
|
+
<div class="reviewkit-index-row__meta">
|
|
123
|
+
<span><%= pluralize(review.documents.size, "document") %></span>
|
|
124
|
+
<% if review.reviewable_type.present? %>
|
|
125
|
+
<span><%= review.reviewable_type %></span>
|
|
126
|
+
<% end %>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</td>
|
|
130
|
+
<td class="reviewkit-index-row__status">
|
|
131
|
+
<span class="<%= reviewkit_status_pill_class(review) %>"><%= review.status %></span>
|
|
132
|
+
</td>
|
|
133
|
+
<td class="reviewkit-index-row__threads">
|
|
134
|
+
<span class="reviewkit-index-row__thread-count"><%= review.review_threads.size %></span>
|
|
135
|
+
<span class="reviewkit-index-row__thread-label">total</span>
|
|
136
|
+
</td>
|
|
137
|
+
<td class="reviewkit-index-row__updated">
|
|
138
|
+
<time datetime="<%= review.updated_at.iso8601 %>"><%= time_ago_in_words(review.updated_at) %> ago</time>
|
|
139
|
+
</td>
|
|
140
|
+
</tr>
|
|
141
|
+
<% end %>
|
|
142
|
+
</tbody>
|
|
143
|
+
</table>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="reviewkit-index-empty hidden" data-reviewkit--review-index-target="empty">
|
|
147
|
+
<p>No reviews match the current filter.</p>
|
|
148
|
+
</div>
|
|
149
|
+
</section>
|
|
150
|
+
<% else %>
|
|
151
|
+
<section class="reviewkit-panel reviewkit-empty-state">
|
|
152
|
+
<p class="reviewkit-kicker">Ready for your first review</p>
|
|
153
|
+
<h2 class="text-2xl font-semibold tracking-tight text-slate-950">No reviews have been created yet.</h2>
|
|
154
|
+
<p class="max-w-2xl text-sm leading-7 text-slate-600">
|
|
155
|
+
Once your host application creates reviews through <code>Reviewkit::Reviews::Create.call</code>, they will appear here automatically.
|
|
156
|
+
</p>
|
|
157
|
+
</section>
|
|
158
|
+
<% end %>
|
|
159
|
+
</section>
|
|
160
|
+
</main>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<aside class="reviewkit-review-sidebar">
|
|
2
|
+
<div class="reviewkit-review-sidebar__rail">
|
|
3
|
+
<div class="space-y-1">
|
|
4
|
+
<p class="reviewkit-kicker">Files changed</p>
|
|
5
|
+
<p class="text-sm text-slate-600">
|
|
6
|
+
<span class="font-semibold text-slate-900"><%= review.documents.size %></span> files,
|
|
7
|
+
<span class="font-semibold text-slate-900"><%= review.open_threads_count %></span> open thread<%= "s" unless review.open_threads_count == 1 %>
|
|
8
|
+
</p>
|
|
9
|
+
<% if review.external_reference.present? %>
|
|
10
|
+
<p class="text-xs text-slate-500"><span class="font-medium text-slate-700">Reference:</span> <%= review.external_reference %></p>
|
|
11
|
+
<% end %>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<label class="reviewkit-document-search">
|
|
15
|
+
<span class="sr-only">Search changed files</span>
|
|
16
|
+
<input
|
|
17
|
+
type="search"
|
|
18
|
+
name="reviewkit_document_search"
|
|
19
|
+
placeholder="Jump to file"
|
|
20
|
+
autocomplete="off"
|
|
21
|
+
data-action="input->reviewkit--file-nav#filter"
|
|
22
|
+
data-reviewkit--file-nav-target="search"
|
|
23
|
+
>
|
|
24
|
+
</label>
|
|
25
|
+
|
|
26
|
+
<div class="reviewkit-document-nav-wrap">
|
|
27
|
+
<nav class="reviewkit-document-nav" aria-label="Changed files">
|
|
28
|
+
<% review.documents.each do |document| %>
|
|
29
|
+
<% directory, basename = reviewkit_document_path_parts(document) %>
|
|
30
|
+
<%= link_to review_path(review, document_id: document.id, view: view_mode),
|
|
31
|
+
class: [ "reviewkit-document-link", ("reviewkit-document-link--active" if document == selected_document) ].compact.join(" "),
|
|
32
|
+
title: document.path,
|
|
33
|
+
aria: { label: document.path },
|
|
34
|
+
data: {
|
|
35
|
+
turbo_frame: frame_id,
|
|
36
|
+
"reviewkit--file-nav-target": "link",
|
|
37
|
+
path: document.path
|
|
38
|
+
} do %>
|
|
39
|
+
<div class="reviewkit-document-link__content">
|
|
40
|
+
<%= reviewkit_file_icon(class_name: "reviewkit-file-icon reviewkit-file-icon--nav") %>
|
|
41
|
+
|
|
42
|
+
<p class="reviewkit-document-link__path" title="<%= document.path %>">
|
|
43
|
+
<% if directory.present? %>
|
|
44
|
+
<span class="text-slate-500"><%= directory %></span>
|
|
45
|
+
<% end %>
|
|
46
|
+
<span class="font-semibold text-slate-900"><%= basename %></span>
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<span class="reviewkit-document-link__stats">
|
|
51
|
+
<span class="font-medium text-emerald-700">+<%= document.additions_count %></span>
|
|
52
|
+
<span class="font-medium text-rose-700">-<%= document.deletions_count %></span>
|
|
53
|
+
</span>
|
|
54
|
+
<% end %>
|
|
55
|
+
<% end %>
|
|
56
|
+
</nav>
|
|
57
|
+
|
|
58
|
+
<p class="reviewkit-document-empty hidden" data-reviewkit--file-nav-target="empty">
|
|
59
|
+
No changed files match that search.
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<% if review.metadata.present? %>
|
|
64
|
+
<details class="reviewkit-sidebar-details">
|
|
65
|
+
<summary>Review metadata</summary>
|
|
66
|
+
<pre class="reviewkit-code-panel"><%= JSON.pretty_generate(review.metadata) %></pre>
|
|
67
|
+
</details>
|
|
68
|
+
<% end %>
|
|
69
|
+
</div>
|
|
70
|
+
</aside>
|