Class: CoPlan::CommentThread
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- CoPlan::CommentThread
- Defined in:
- app/models/coplan/comment_thread.rb
Constant Summary collapse
- STATUSES =
%w[pending todo resolved discarded].freeze
- OPEN_STATUSES =
%w[pending todo].freeze
- CLOSED_STATUSES =
%w[resolved discarded].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
- #discard!(user) ⇒ Object
- #line_range_text ⇒ Object
- #line_specific? ⇒ Boolean
- #open? ⇒ Boolean
- #resolve!(user) ⇒ Object
Instance Attribute Details
#anchor_occurrence ⇒ Object
Returns the value of attribute anchor_occurrence.
7 8 9 |
# File 'app/models/coplan/comment_thread.rb', line 7 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.
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 73 74 |
# File 'app/models/coplan/comment_thread.rb', line 30 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
98 99 100 |
# File 'app/models/coplan/comment_thread.rb', line 98 def accept!(user) update!(status: "todo", resolved_by_user: user) end |
#anchor_context_with_highlight(chars: 100) ⇒ Object
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'app/models/coplan/comment_thread.rb', line 134 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.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'app/models/coplan/comment_thread.rb', line 118 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
89 90 91 92 |
# File 'app/models/coplan/comment_thread.rb', line 89 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
110 111 112 113 |
# File 'app/models/coplan/comment_thread.rb', line 110 def anchor_valid? return true unless anchored? !out_of_date end |
#anchored? ⇒ Boolean
76 77 78 |
# File 'app/models/coplan/comment_thread.rb', line 76 def anchored? anchor_text.present? end |
#discard!(user) ⇒ Object
102 103 104 |
# File 'app/models/coplan/comment_thread.rb', line 102 def discard!(user) update!(status: "discarded", resolved_by_user: user) end |
#line_range_text ⇒ Object
84 85 86 87 |
# File 'app/models/coplan/comment_thread.rb', line 84 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
80 81 82 |
# File 'app/models/coplan/comment_thread.rb', line 80 def line_specific? start_line.present? end |
#open? ⇒ Boolean
106 107 108 |
# File 'app/models/coplan/comment_thread.rb', line 106 def open? OPEN_STATUSES.include?(status) end |
#resolve!(user) ⇒ Object
94 95 96 |
# File 'app/models/coplan/comment_thread.rb', line 94 def resolve!(user) update!(status: "resolved", resolved_by_user: user) end |