Class: CoPlan::CommentThread
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- CoPlan::CommentThread
- Defined in:
- app/models/coplan/comment_thread.rb
Constant Summary collapse
- STATUSES =
%w[open resolved accepted dismissed].freeze
Instance Attribute Summary collapse
-
#anchor_occurrence ⇒ Object
Returns the value of attribute anchor_occurrence.
Class Method Summary collapse
-
.mark_out_of_date_for_new_version!(new_version) ⇒ Object
Transforms anchor positions through intervening version edits using OT.
Instance Method Summary collapse
- #accept!(user) ⇒ Object
- #anchor_context_with_highlight(chars: 100) ⇒ Object
-
#anchor_occurrence_index ⇒ Object
Returns the 0-based occurrence index of anchor_text in the raw markdown, computed from anchor_start.
- #anchor_preview(max_length: 80) ⇒ Object
- #anchor_valid? ⇒ Boolean
- #anchored? ⇒ Boolean
- #dismiss!(user) ⇒ Object
- #line_range_text ⇒ Object
- #line_specific? ⇒ Boolean
- #resolve!(user) ⇒ Object
Instance Attribute Details
#anchor_occurrence ⇒ Object
Returns the value of attribute anchor_occurrence.
5 6 7 |
# File 'app/models/coplan/comment_thread.rb', line 5 def anchor_occurrence @anchor_occurrence end |
Class Method Details
.mark_out_of_date_for_new_version!(new_version) ⇒ Object
Transforms anchor positions through intervening version edits using OT. Threads without positional data (anchor_start/anchor_end/anchor_revision) are marked out-of-date unconditionally — all new threads resolve positions on creation via resolve_anchor_position.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'app/models/coplan/comment_thread.rb', line 28 def self.mark_out_of_date_for_new_version!(new_version) threads = where(out_of_date: false).where.not(plan_version_id: new_version.id) anchored_threads = threads.select(&:anchored?) # Pre-fetch all versions that any thread might need (from the oldest # anchor_revision to the new version) in a single query. min_anchor_rev = anchored_threads .filter_map { |t| t.anchor_revision if t.anchor_start.present? && t.anchor_end.present? && t.anchor_revision.present? } .min all_versions = if min_anchor_rev new_version.plan.plan_versions .where("revision > ? AND revision <= ?", min_anchor_rev, new_version.revision) .order(revision: :asc) .to_a else [] end anchored_threads.each do |thread| unless thread.anchor_start.present? && thread.anchor_end.present? && thread.anchor_revision.present? thread.update_columns(out_of_date: true, out_of_date_since_version_id: new_version.id) next end intervening = all_versions.select { |v| v.revision > thread.anchor_revision && v.revision <= new_version.revision } begin new_range = Plans::TransformRange.transform_through_versions( [thread.anchor_start, thread.anchor_end], intervening ) thread.update_columns( anchor_start: new_range[0], anchor_end: new_range[1], anchor_revision: new_version.revision ) rescue Plans::TransformRange::Conflict thread.update_columns( out_of_date: true, out_of_date_since_version_id: new_version.id ) end end end |
Instance Method Details
#accept!(user) ⇒ Object
96 97 98 |
# File 'app/models/coplan/comment_thread.rb', line 96 def accept!(user) update!(status: "accepted", resolved_by_user: user) end |
#anchor_context_with_highlight(chars: 100) ⇒ Object
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/coplan/comment_thread.rb', line 128 def anchor_context_with_highlight(chars: 100) return nil unless anchored? && anchor_start.present? content = plan.current_content return nil unless content.present? context_start = [anchor_start - chars, 0].max context_end = [anchor_end + chars, content.length].min before = content[context_start...anchor_start] anchor = content[anchor_start...anchor_end] after = content[anchor_end...context_end] "#{before}**#{anchor}**#{after}" end |
#anchor_occurrence_index ⇒ Object
Returns the 0-based occurrence index of anchor_text in the raw markdown, computed from anchor_start. The frontend uses this to find the correct occurrence in the rendered DOM text instead of relying on context matching.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'app/models/coplan/comment_thread.rb', line 112 def anchor_occurrence_index return nil unless anchored? && anchor_start.present? content = plan.current_content return nil unless content.present? count = 0 pos = 0 while (idx = content.index(anchor_text, pos)) break if idx >= anchor_start count += 1 pos = idx + 1 end count end |
#anchor_preview(max_length: 80) ⇒ Object
87 88 89 90 |
# File 'app/models/coplan/comment_thread.rb', line 87 def anchor_preview(max_length: 80) return nil unless anchored? anchor_text.length > max_length ? "#{anchor_text[0...max_length]}…" : anchor_text end |
#anchor_valid? ⇒ Boolean
104 105 106 107 |
# File 'app/models/coplan/comment_thread.rb', line 104 def anchor_valid? return true unless anchored? !out_of_date end |
#anchored? ⇒ Boolean
74 75 76 |
# File 'app/models/coplan/comment_thread.rb', line 74 def anchored? anchor_text.present? end |
#dismiss!(user) ⇒ Object
100 101 102 |
# File 'app/models/coplan/comment_thread.rb', line 100 def dismiss!(user) update!(status: "dismissed", resolved_by_user: user) end |
#line_range_text ⇒ Object
82 83 84 85 |
# File 'app/models/coplan/comment_thread.rb', line 82 def line_range_text return nil unless line_specific? start_line == end_line ? "Line #{start_line}" : "Lines #{start_line}–#{end_line}" end |
#line_specific? ⇒ Boolean
78 79 80 |
# File 'app/models/coplan/comment_thread.rb', line 78 def line_specific? start_line.present? end |
#resolve!(user) ⇒ Object
92 93 94 |
# File 'app/models/coplan/comment_thread.rb', line 92 def resolve!(user) update!(status: "resolved", resolved_by_user: user) end |