activeadmin_annotations 0.1.1 → 0.1.2

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/CODE_OF_CONDUCT.md +14 -19
  4. data/CONTRIBUTING.md +9 -15
  5. data/GOVERNANCE.md +10 -14
  6. data/README.md +24 -7
  7. data/SECURITY.md +14 -16
  8. data/activeadmin_annotations.gemspec +2 -2
  9. data/admin/annotation_reviews.rb +14 -6
  10. data/admin/annotations.rb +33 -13
  11. data/app/assets/controllers/activeadmin_annotations/annotation_client.js +44 -0
  12. data/app/assets/controllers/activeadmin_annotations/annotation_list.js +29 -0
  13. data/app/assets/controllers/activeadmin_annotations/annotator_controller.js +45 -370
  14. data/app/assets/controllers/activeadmin_annotations/composer.js +179 -0
  15. data/app/assets/controllers/activeadmin_annotations/highlights.js +76 -0
  16. data/app/assets/controllers/activeadmin_annotations/selection.js +73 -0
  17. data/app/assets/javascripts/activeadmin_annotations/annotator_logic.mjs +73 -0
  18. data/app/assets/stylesheets/activeadmin_annotations.css +15 -0
  19. data/app/helpers/active_admin/annotations/panel_helper.rb +15 -8
  20. data/app/models/active_admin/annotations/review.rb +7 -2
  21. data/app/services/active_admin/annotations/review_service.rb +34 -27
  22. data/app/views/active_admin/annotations/_panel.html.erb +15 -2
  23. data/db/migrate/20260617120000_create_activeadmin_annotations_tables.rb +27 -10
  24. data/db/migrate/20260706150000_add_metadata_json_gin_index_to_annotation_reviews.rb +1 -1
  25. data/db/migrate/20260710120000_add_panel_query_index_to_annotation_spans.rb +1 -1
  26. data/lib/activeadmin/annotations/copy_text.rb +0 -1
  27. data/lib/activeadmin/annotations/engine.rb +16 -1
  28. data/lib/activeadmin/annotations/exporter.rb +1 -1
  29. data/lib/activeadmin/annotations/panel_html.rb +21 -0
  30. data/lib/activeadmin/annotations/review_access.rb +13 -1
  31. data/lib/activeadmin/annotations/review_panel.rb +9 -6
  32. data/lib/activeadmin/annotations/span_create.rb +70 -0
  33. data/lib/activeadmin/annotations/version.rb +1 -1
  34. data/lib/activeadmin_annotations.rb +2 -0
  35. metadata +12 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6894d360b6a6c23d6037fc43d359f3314938190d684cc74b7d31d57443b70e4
4
- data.tar.gz: b99349efb918936290803c2ea2f9ccebd105952e7b891c5c4369c0a615b54a72
3
+ metadata.gz: 552186e90302ae8e259a730bed26b0c7f8de35070f98d60accfcd81f17c95ba8
4
+ data.tar.gz: '048dfedf289691780333a9ab149475f106d2ddc3832cf90d3f6fd153d7192038'
5
5
  SHA512:
6
- metadata.gz: 8ca59dc12c98c45d3f467db454bac3c9f32fcdc0e11a32f45f599b31f26c77e76be9c7c9d1dd7b385a6ab2d0d5f42fa76d439ef420f3c19ef4af44d1edcec6f8
7
- data.tar.gz: 5da69787410fe7c3ad81ee7c952a0a2e58f1f9e43ddcc6ecdeaa3766623bed2417314dd573824527ac82a6c647c16d835bb12a164842f857fe3f4acffefe48d8
6
+ metadata.gz: c8fb4cfa2c652a145ce5ec4e263b9c1d37e2d4f9899cee3c72eab271e18245dbb9780f2f29c595a3fc8be26b6900731d1f6b0d9468860ef3d2a12ae2620559a2
7
+ data.tar.gz: 58c44f9168cbd4e19dd346891a917e408aab9758b98889cbeaa12e1cf40bdaa7053fac0a69b1fcc9f71f02175a7f00fd9513814c6c43ffa65626a3fff1d0f9d3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.1.2 (2026-09-04)
4
+
5
+ - Create the review record when the first annotation is saved.
6
+ - Keep the annotator read-only when browsing a content version other than the one under review.
7
+ - Show This version is read-only. when a save targets a different content version.
8
+ - Show Annotation access denied when a reviewer opens or changes another reviewer's annotation.
9
+ - Limit the review form to status and notes.
10
+ - Omit reviewer identity from copied annotation text.
11
+ - Install annotation tables on SQLite with json columns and on PostgreSQL with jsonb.
12
+ - Escape ordinary panel strings and sanitize html-safe host HTML.
13
+ - Show saved and pending marks when the browser has no custom highlight support.
14
+
3
15
  ## 0.1.1 (2026-07-14)
4
16
 
5
17
  - Return forbidden responses with access-denied messages for cross-reviewer review and annotation mutations instead of not-found wording.
data/CODE_OF_CONDUCT.md CHANGED
@@ -1,31 +1,26 @@
1
1
  # Code of Conduct
2
2
 
3
- ## Our pledge
3
+ ## Pledge
4
4
 
5
- We pledge to make participation in our community a harassment-free experience for everyone. We operate on principles of mutual respect, privacy, and authentic engagement. We value substantive contributions and clarity on intentions.
5
+ Participation is a harassment-free experience. Treat each other with respect. Keep private context private.
6
6
 
7
- ## Our standards
7
+ ## Expected behavior
8
8
 
9
- Examples of behavior that contributes to a positive environment include:
9
+ - Engage with genuine curiosity. Admit uncertainty.
10
+ - Own every line you submit, including generated code.
11
+ - Correct errors politely.
12
+ - Use language that welcomes people and respects identity and privacy.
10
13
 
11
- - Authenticity: engaging with genuine curiosity and admitting uncertainty rather than feigning knowledge.
12
- - Responsible innovation: taking full responsibility for any content or code contributed, whether manually written or generated by automation tools.
13
- - Gentle correction: responding politely to errors. We view mistakes as opportunities for learning, provided they are addressed with humility.
14
- - Inclusive language: using language that welcomes diverse perspectives and respects the privacy and identity of all participants.
14
+ ## Unacceptable behavior
15
15
 
16
- Examples of unacceptable behavior include:
16
+ - Harassment, trolling, or insulting comments, public or private.
17
+ - Using jargon or volume, including automated spam, to silence others.
18
+ - Sharing others' data or personal context without permission.
17
19
 
18
- - Harassment: public or private harassment, trolling, or insulting comments.
19
- - Weaponized complexity: using jargon or overwhelming volume (including automated spam) to silence others.
20
- - Publishing private information: sharing others' data or personal context without explicit permission.
20
+ ## Automation
21
21
 
22
- ## Artificial intelligence and automation
23
-
24
- In accordance with our commitment to collective awareness:
25
-
26
- - Contributors are responsible for the accuracy and security of any AI-generated artifacts they submit.
27
- - "The AI wrote it" is not a valid excuse for introducing bugs, security vulnerabilities, or bias.
22
+ You remain responsible for bugs, security holes, and bias in generated artifacts you submit.
28
23
 
29
24
  ## Enforcement
30
25
 
31
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@kiskolabs.com. All complaints will be reviewed and investigated promptly and fairly.
26
+ Report abusive or harassing behavior to contact@kiskolabs.com. Reports are reviewed and investigated.
data/CONTRIBUTING.md CHANGED
@@ -1,29 +1,23 @@
1
1
  # Contributing Guidelines
2
2
 
3
- Thank you for your interest in contributing to activeadmin_annotations. We value learning over perfection but require rigor and responsibility.
3
+ ## Automation authorship
4
4
 
5
- ## The golden rule of automation
5
+ You may use AI and automation tools. You remain the author of every line you submit: review, debug, and understand the change.
6
6
 
7
- We welcome the use of AI and automation tools to reduce toil, but you must strictly adhere to the following:
7
+ Keep pull requests small enough to review. Annotate non-obvious logic.
8
8
 
9
- 1. You are the author. You act as the responsible agent for any code you submit. You must review, debug, and understand every line.
10
- 2. Manage cognitive load. Do not submit massive, unreviewed automated dumps. Respect the reviewers' time by annotating complex logic.
11
- 3. Security. Never feed project secrets or private context into public AI models.
9
+ Never send project secrets or private context to public models.
12
10
 
13
11
  ## How to contribute
14
12
 
15
13
  ### Reporting issues
16
14
 
17
- - Verify accuracy before posting. Avoid generalizations.
18
- - Use issue templates to provide clear goals, constraints, and reproduction steps.
15
+ Verify the facts before filing. Use issue templates for goals, constraints, and reproduction steps.
19
16
 
20
- ### Pull request process
17
+ ### Pull requests
21
18
 
22
- - Scope: keep PRs focused on a single goal.
23
- - Context: explain why the change is necessary.
24
- - Testing: run `make test` locally before opening a PR.
19
+ Keep one pull request to one goal. Explain why the change is needed. Run smoke tests and regression checks locally before opening the request.
25
20
 
26
- ### Review process
21
+ ### Review
27
22
 
28
- - We encourage productive friction. Expect questions about your approach.
29
- - If a reviewer suggests a change, view it as mutual aid, not criticism.
23
+ Reviewers will ask about approach and trade-offs. Treat suggested changes as part of the work.
data/GOVERNANCE.md CHANGED
@@ -1,29 +1,25 @@
1
1
  # Project Governance
2
2
 
3
- ## Overview
4
-
5
- This project balances individual autonomy with collective coordination. We aim for a workflow that supports rapid iteration while maintaining strict guardrails for safety and architectural integrity.
6
-
7
3
  ## Roles
8
4
 
9
5
  ### Contributors
10
6
 
11
- Anyone who submits code, documentation, or participates in discussions. Contributors are expected to exercise direct action—taking ownership of problems they identify—while adhering to our quality standards.
7
+ Anyone who submits code, documentation, or participates in discussions. Contributors take ownership of problems they identify and follow the project's quality standards.
12
8
 
13
- ### Maintainers (Human Oversight)
9
+ ### Maintainers
14
10
 
15
- Maintainers are responsible for:
11
+ Maintainers:
16
12
 
17
- 1. Strategic judgment: defining scope and architectural direction.
18
- 2. Review: verifying that contributions (human or automated) meet security and logic standards.
19
- 3. Consensus building: facilitating decisions when the community is divided.
13
+ 1. Set scope and architectural direction.
14
+ 2. Review contributions (human or automated) against security and logic standards.
15
+ 3. Decide when the community is divided.
20
16
 
21
- ## Decision making process
17
+ ## Decision making
22
18
 
23
19
  ### Lazy consensus
24
20
 
25
- For most routine changes, we operate on lazy consensus. If a proposal is made and no objections are raised within two weeks, it is considered approved.
21
+ For routine changes, a proposal that draws no objection within two weeks is approved.
26
22
 
27
- ### Strategic alignment
23
+ ### Maintainer approval
28
24
 
29
- Major architectural changes, high-risk automation integrations, or changes that affect business logic require explicit approval from the maintainers. We prioritize substance over performance—a change must solve a real problem, not just appear polished.
25
+ Major architectural changes, high-risk automation integrations, or changes that affect business logic need explicit maintainer approval. A change must solve a real problem.
data/README.md CHANGED
@@ -8,10 +8,10 @@ The gem stays domain-agnostic: the host app chooses what review context means, w
8
8
 
9
9
  ## Requirements
10
10
 
11
- - Ruby 3.2+
11
+ - Ruby 3.4+
12
12
  - Rails 7.1+
13
13
  - ActiveAdmin 3.0+
14
- - PostgreSQL recommended for production (SQLite works for development; some metadata filters use Postgres JSON operators)
14
+ - SQLite (`json` columns) or PostgreSQL (`jsonb`, plus GIN on review metadata). CI appraisals start at Rails 7.2.
15
15
 
16
16
  ## Host app setup
17
17
 
@@ -89,7 +89,9 @@ text_node helpers.activeadmin_annotations_panel(
89
89
  )
90
90
  ```
91
91
 
92
- The gem stores annotations against the live subject text. If the reviewed text changes, stale detection uses `context_digest` only. No version UI is shown.
92
+ Ordinary strings are escaped. html-safe host HTML is sanitized so paragraphs stay and scripts and event handlers are stripped.
93
+
94
+ The gem stores annotations against the live subject text. The review row is created on the first annotation save, not when the panel is shown. If the reviewed text changes, stale detection uses `context_digest` only. No version UI is shown.
93
95
 
94
96
  ## Content revisions (optional)
95
97
 
@@ -127,7 +129,7 @@ class Article < ApplicationRecord
127
129
  end
128
130
  ```
129
131
 
130
- Then either render the panel through `ActiveAdmin::Annotations::ReviewPanel` (handles pinning, stale state, and version filtering):
132
+ Then either render the panel through `ActiveAdmin::Annotations::ReviewPanel` (handles pinning, stale state, and version filtering). Browsing a content version other than the one under review is read-only. Add notes on the version being reviewed:
131
133
 
132
134
  ```ruby
133
135
  text_node ActiveAdmin::Annotations::ReviewPanel.render(
@@ -231,11 +233,13 @@ Add a host stylesheet that loads after the gem asset (for example in your Active
231
233
  border-color: var(--primary, #2563eb);
232
234
  }
233
235
 
234
- ::highlight(aa-annotations-saved) {
236
+ ::highlight(aa-annotations-saved),
237
+ .aa-annotations-mark--saved {
235
238
  background-color: color-mix(in srgb, var(--primary, #2563eb) 30%, #fef08a);
236
239
  }
237
240
 
238
- ::highlight(aa-annotations-pending) {
241
+ ::highlight(aa-annotations-pending),
242
+ .aa-annotations-mark--pending {
239
243
  background-color: color-mix(in srgb, var(--primary, #2563eb) 40%, #fef08a);
240
244
  text-decoration-color: var(--primary, #2563eb);
241
245
  }
@@ -298,7 +302,7 @@ bundle exec rubocop
298
302
  bundle exec polyrun parallel-rspec --workers 5 --merge-failures
299
303
  ```
300
304
 
301
- Matrixed Rails versions use [Appraisal](https://github.com/thoughtbot/appraisal): `gemfiles/rails72.gemfile`, `rails8ruby34.gemfile`, and `rails8truffleruby.gemfile`. Run `bundle exec appraisal rspec` to execute RSpec in each gemfile context.
305
+ Matrixed Rails versions use [Appraisal](https://github.com/thoughtbot/appraisal): `gemfiles/rails72.gemfile` and `rails8ruby34.gemfile`. Run `bundle exec appraisal rspec` to execute RSpec in each gemfile context.
302
306
 
303
307
  Shared agent guidance is managed with [pray](https://github.com/kiskolabs/pray) via `Prayfile`.
304
308
 
@@ -308,6 +312,19 @@ Shared agent guidance is managed with [pray](https://github.com/kiskolabs/pray)
308
312
 
309
313
  See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and pull requests are welcome at https://github.com/amkisko/activeadmin_annotations.rb/issues
310
314
 
315
+ ## Links
316
+
317
+ - [GitHub](https://github.com/amkisko/activeadmin_annotations.rb)
318
+ - [GitLab](https://gitlab.com/amkisko/activeadmin_annotations.rb)
319
+ - [RubyGems](https://rubygems.org/gems/activeadmin_annotations)
320
+ - [Versions Atom](https://rubygems.org/gems/activeadmin_annotations/versions.atom) (feed id `11581035749940`)
321
+ - [libraries.io](https://libraries.io/rubygems/activeadmin_annotations)
322
+ - [Deps.dev](https://deps.dev/rubygems/activeadmin_annotations)
323
+ - [SonarCloud](https://sonarcloud.io/project/overview?id=amkisko_activeadmin_annotations.rb)
324
+ - [Snyk](https://snyk.io/test/github/amkisko/activeadmin_annotations.rb)
325
+ - [Codecov](https://app.codecov.io/github/amkisko/activeadmin_annotations.rb)
326
+ - [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/amkisko/activeadmin_annotations.rb)
327
+
311
328
  ## License
312
329
 
313
330
  MIT — see [LICENSE.md](LICENSE.md).
data/SECURITY.md CHANGED
@@ -1,27 +1,25 @@
1
1
  # SECURITY
2
2
 
3
- ## Reporting a vulnerability
4
-
5
- Do NOT open a public GitHub issue for security vulnerabilities.
3
+ ## Reporting a Vulnerability
6
4
 
7
5
  Email security details to: security@kiskolabs.com
8
6
 
9
- Include: description, steps to reproduce, potential impact, and suggested fix (if available).
7
+ Send the report only to that address. Include description, steps to reproduce, potential impact, and a suggested fix when you have one.
10
8
 
11
- ### Response timeline
9
+ ### Response Timeline
12
10
 
13
- - We will acknowledge receipt of your report
14
- - We will provide an initial assessment
15
- - We will keep you informed of our progress and resolution timeline
11
+ - We will acknowledge receipt of your report.
12
+ - We will provide an initial assessment.
13
+ - We will keep you informed of our progress and resolution timeline.
16
14
 
17
- ### Disclosure policy
15
+ ### Disclosure Policy
18
16
 
19
- - We will work with you to understand and resolve the issue
20
- - We will credit you for the discovery (unless you prefer to remain anonymous)
21
- - We will publish a security advisory after the vulnerability is patched
22
- - We will coordinate public disclosure with you
17
+ - We will work with you to understand and resolve the issue.
18
+ - We will credit you for the discovery unless you prefer to remain anonymous.
19
+ - We will publish a security advisory after the vulnerability is patched.
20
+ - We will coordinate public disclosure with you.
23
21
 
24
- ## Automation security
22
+ ## Automation Security
25
23
 
26
- - Context isolation: it is strictly forbidden to include production credentials, API keys, or personally identifiable information in prompts sent to third-party LLMs or automation services.
27
- - Supply chain: all automated dependencies must be verified.
24
+ - Keep production credentials, API keys, and personal data out of prompts sent to third-party models or automation services.
25
+ - Verify automated dependencies.
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "Collect text highlights and comments inside a bounded ActiveAdmin block, with optional review context, content revision pinning, and JSONL export."
13
13
  spec.license = "MIT"
14
14
  spec.platform = Gem::Platform::RUBY
15
- spec.required_ruby_version = ">= 3.2.0"
15
+ spec.required_ruby_version = ">= 3.4"
16
16
 
17
17
  repository_url = "https://github.com/amkisko/activeadmin_annotations.rb"
18
18
 
@@ -64,5 +64,5 @@ Gem::Specification.new do |spec|
64
64
  spec.add_development_dependency "standard-performance", "~> 1.8"
65
65
  spec.add_development_dependency "standard-rails", "~> 1.5"
66
66
  spec.add_development_dependency "standard-rspec", "~> 0.3"
67
- spec.add_development_dependency "rbs", "~> 3"
67
+ spec.add_development_dependency "rbs", "~> 4"
68
68
  end
@@ -8,13 +8,16 @@ ActiveAdmin.register ActiveAdmin::Annotations::Review, as: "annotation_reviews"
8
8
 
9
9
  controller do
10
10
  def scoped_collection
11
- super.includes(:annotations)
11
+ annotation_counts = ActiveAdmin::Annotations::Annotation
12
+ .where("annotation_spans.annotation_review_id = annotation_reviews.id")
13
+ .select("COUNT(*)")
14
+ super.select("annotation_reviews.*", "(#{annotation_counts.to_sql}) AS annotations_count")
12
15
  end
13
16
  end
14
17
 
15
18
  actions :index, :show, :create, :update
16
19
 
17
- permit_params :review_status, :notes, :metadata_json
20
+ permit_params :review_status, :notes
18
21
 
19
22
  filter :subject_type, as: :select
20
23
  filter :subject_id
@@ -36,7 +39,7 @@ ActiveAdmin.register ActiveAdmin::Annotations::Review, as: "annotation_reviews"
36
39
  column :subject_id
37
40
  column :reviewer
38
41
  column :review_status
39
- column("Annotations") { |review| review.annotations.size }
42
+ column("Annotations") { |review| review.annotations_count }
40
43
  column :context_digest
41
44
  column :created_at
42
45
  actions
@@ -91,9 +94,14 @@ ActiveAdmin.register ActiveAdmin::Annotations::Review, as: "annotation_reviews"
91
94
 
92
95
  collection_action :export_jsonl, method: :get do
93
96
  exporter = ActiveAdmin::Annotations::Exporter.new(reviews: collection)
94
- send_data exporter.to_jsonl,
95
- filename: "annotation-reviews-#{Time.current.strftime("%Y%m%d%H%M%S")}.jsonl",
96
- type: "application/jsonl"
97
+ filename = "annotation-reviews-#{Time.current.strftime("%Y%m%d%H%M%S")}.jsonl"
98
+ headers["Content-Type"] = "application/jsonl"
99
+ headers["Content-Disposition"] = %(attachment; filename="#{filename}")
100
+ self.response_body = Enumerator.new do |yielder|
101
+ exporter.each_jsonl_row do |row|
102
+ yielder << "#{JSON.generate(row)}\n"
103
+ end
104
+ end
97
105
  end
98
106
 
99
107
  action_item :export_jsonl, only: :index do
data/admin/annotations.rb CHANGED
@@ -59,13 +59,22 @@ ActiveAdmin.register ActiveAdmin::Annotations::Annotation, as: "annotation_spans
59
59
  end
60
60
 
61
61
  controller do
62
- before_action :authorize_annotation_access!, only: %i[update destroy copy_text]
62
+ before_action :authorize_annotation_access!, only: %i[show update destroy copy_text]
63
+
64
+ def scoped_collection
65
+ return super unless action_name == "index"
66
+
67
+ reviewer = current_annotation_reviewer
68
+ return super.none if reviewer.blank?
69
+
70
+ super.joins(:annotation_review).where(annotation_reviews: {reviewer_id: reviewer.id})
71
+ end
63
72
 
64
73
  def create
65
74
  review = authorize_review_for_create!
66
75
  return if performed?
67
76
 
68
- assign_content_revision_version_from_review(review)
77
+ stamp_span_from_review!(review)
69
78
  super do |success, failure|
70
79
  success.html { redirect_to request.referer || resource_path, notice: "Annotation saved." }
71
80
  success.json { render json: annotation_json(resource), status: :created }
@@ -95,12 +104,18 @@ ActiveAdmin.register ActiveAdmin::Annotations::Annotation, as: "annotation_spans
95
104
  private
96
105
 
97
106
  def authorize_review_for_create!
98
- ActiveAdmin::Annotations::ReviewAccess.review_for_span_create!(
99
- review_id: params.dig(:annotation, :annotation_review_id),
107
+ ActiveAdmin::Annotations::SpanCreate.review_for!(
108
+ params: params,
100
109
  reviewer: current_annotation_reviewer
101
110
  )
102
- rescue ActiveAdmin::Annotations::ReviewAccess::Forbidden, ActiveAdmin::Annotations::ReviewAccess::NotFound
103
- respond_to_denied_review_access
111
+ rescue ActiveAdmin::Annotations::SpanCreate::ReadOnly => error
112
+ respond_to_span_error(error, status: :unprocessable_entity)
113
+ nil
114
+ rescue ActiveAdmin::Annotations::ReviewAccess::NotFound => error
115
+ respond_to_span_error(error, status: :not_found)
116
+ nil
117
+ rescue ActiveAdmin::Annotations::ReviewAccess::Forbidden => error
118
+ respond_to_span_error(error, status: :forbidden)
104
119
  nil
105
120
  end
106
121
 
@@ -109,8 +124,10 @@ ActiveAdmin.register ActiveAdmin::Annotations::Annotation, as: "annotation_spans
109
124
  annotation: resource,
110
125
  reviewer: current_annotation_reviewer
111
126
  )
112
- rescue ActiveAdmin::Annotations::ReviewAccess::Forbidden
113
- respond_to_denied_review_access
127
+ rescue ActiveAdmin::Annotations::ReviewAccess::NotFound => error
128
+ respond_to_span_error(error, status: :not_found)
129
+ rescue ActiveAdmin::Annotations::ReviewAccess::Forbidden => error
130
+ respond_to_span_error(error, status: :forbidden)
114
131
  end
115
132
 
116
133
  def current_annotation_reviewer
@@ -119,21 +136,24 @@ ActiveAdmin.register ActiveAdmin::Annotations::Annotation, as: "annotation_spans
119
136
  nil
120
137
  end
121
138
 
122
- def respond_to_denied_review_access
139
+ def respond_to_span_error(error, status:)
123
140
  if request.format.json?
124
- render json: {errors: ["Review not found"]}, status: :forbidden
141
+ render json: {errors: [error.message]}, status: status
125
142
  else
126
- redirect_to request.referer || admin_root_path, alert: "Review not found."
143
+ redirect_to request.referer || admin_root_path, alert: error.message
127
144
  end
128
145
  end
129
146
 
130
- def assign_content_revision_version_from_review(review)
131
- params[:annotation][:content_revision_version] = review.content_revision_version
147
+ def stamp_span_from_review!(review)
148
+ annotation_params = params[:annotation] ||= ActionController::Parameters.new
149
+ annotation_params[:annotation_review_id] = review.id
150
+ annotation_params[:content_revision_version] = review.content_revision_version
132
151
  end
133
152
 
134
153
  def annotation_json(annotation)
135
154
  {
136
155
  id: annotation.id,
156
+ annotation_review_id: annotation.annotation_review_id,
137
157
  field_name: annotation.field_name,
138
158
  selected_text: annotation.selected_text,
139
159
  start_offset: annotation.start_offset,
@@ -0,0 +1,44 @@
1
+ export class AnnotationClient {
2
+ constructor({ csrfToken }) {
3
+ this.csrfToken = csrfToken;
4
+ }
5
+
6
+ async create(url, payload) {
7
+ return this.requestJson(url, { method: "POST", body: payload });
8
+ }
9
+
10
+ async update(url, payload) {
11
+ return this.requestJson(url, { method: "PATCH", body: payload });
12
+ }
13
+
14
+ async destroy(url) {
15
+ await this.requestJson(url, { method: "DELETE" });
16
+ }
17
+
18
+ async requestJson(url, { method, body }) {
19
+ const response = await fetch(url, {
20
+ method,
21
+ credentials: "same-origin",
22
+ headers: {
23
+ Accept: "application/json",
24
+ "X-CSRF-Token": this.csrfToken,
25
+ ...(body ? { "Content-Type": "application/json" } : {}),
26
+ },
27
+ body: body ? JSON.stringify(body) : undefined,
28
+ });
29
+
30
+ if (!response.ok) {
31
+ const error = new Error("request failed");
32
+ error.response = response;
33
+ throw error;
34
+ }
35
+
36
+ if (method === "DELETE") return null;
37
+ return response.json();
38
+ }
39
+ }
40
+
41
+ export function csrfToken() {
42
+ const meta = document.querySelector("meta[name='csrf-token']");
43
+ return meta ? meta.getAttribute("content") : "";
44
+ }
@@ -0,0 +1,29 @@
1
+ import { categoryLabelFor, escapeHtml } from "activeadmin_annotations/annotator_logic";
2
+
3
+ export function emptyAnnotationListItem() {
4
+ const item = document.createElement("p");
5
+ item.className = "aa-annotations-empty";
6
+ item.textContent = "No annotations yet.";
7
+ return item;
8
+ }
9
+
10
+ export function annotationListItem(annotation, { readonly, categoryLabel, categories }) {
11
+ const item = document.createElement("article");
12
+ item.className = "aa-annotations-annotation-item";
13
+ const resolvedCategory = categoryLabelFor(annotation.category, categories);
14
+ const actions = readonly
15
+ ? ""
16
+ : `
17
+ <div class="aa-annotations-annotation-actions">
18
+ <button type="button" class="action-item-button" data-annotation-id="${escapeHtml(String(annotation.id))}" data-action="activeadmin-annotations--annotator#editAnnotation">Edit</button>
19
+ <button type="button" class="action-item-button aa-annotations-delete-button" data-annotation-id="${escapeHtml(String(annotation.id))}" data-action="activeadmin-annotations--annotator#deleteAnnotation">Delete</button>
20
+ </div>
21
+ `;
22
+ item.innerHTML = `
23
+ <blockquote>${escapeHtml(annotation.selected_text)}</blockquote>
24
+ <p>${escapeHtml(annotation.comment)}</p>
25
+ ${annotation.category ? `<p><strong>${escapeHtml(categoryLabel)}:</strong> ${escapeHtml(resolvedCategory)}</p>` : ""}
26
+ ${actions}
27
+ `;
28
+ return item;
29
+ }