Skip to content

Commit 32a0ace

Browse files
authored
Merge pull request #2107 from basecamp/adjust-paragraph-spacing
Adjusting text content spacing between paragraphs
2 parents 79d2ecb + 6b427ad commit 32a0ace

3 files changed

Lines changed: 125 additions & 0 deletions

File tree

app/assets/stylesheets/rich-text-content.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
}
2929
}
3030

31+
p:has(+ p) {
32+
margin: 0;
33+
}
34+
3135
ol, ul {
3236
padding-inline-start: 3ch;
3337
}

app/models/account/seeder.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def populate
6969
<li>Make another called "Working on"</li>
7070
</ol>
7171
<p>Go back to the Board view, click the little “+” to the right of the DONE column, name the column, pick a color, then do it again.</p>
72+
<p><br></p>
7273
<p>After that, drag this card to “DONE” or select “DONE” in the sidebar.</p>
7374
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/images/make-columns.gif" alt="Demo of adding columns" caption="Make two more columns" content-type="image/*" filename="make-columns.gif" presentation="gallery"></action-text-attachment>
7475
HTML
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env ruby
2+
3+
BACKFILL_TIMESTAMP = Time.parse("2025-12-19 00:07:00 UTC")
4+
ACCOUNT_ID = nil # restrict to an account_id
5+
6+
# Split sibling <p> tags with content by inserting <p><br</p> to replicaate previous view.
7+
# Run for the time range before paragraphs were not spaced
8+
# See https://app.fizzy.do/5986089/cards/3472
9+
# and https://github.com/basecamp/fizzy/pull/2107
10+
#
11+
# MUST BE RUN AFTER `decrypt!` when using ActiveRecord Encryption
12+
#
13+
# Run locally:
14+
# bin/rails runner script/migrations/split-sibling-paragraphs-with-p-br.rb
15+
#
16+
# Run via Kamal:
17+
# kamal app exec -d <stage> -p --reuse "bin/rails runner script/migrations/split-sibling-paragraphs-with-p-br.rb"
18+
#
19+
# Safe to re-run for a time range: won't re-detect unsplit paragraphs and updated_at will be outside time window
20+
class SeparateSiblingParagraphs
21+
attr_reader :updated_at, :account_id
22+
23+
def initialize(updated_at, account_id: nil)
24+
@updated_at = updated_at
25+
@account_id = account_id
26+
end
27+
28+
def run
29+
puts "Separating non-blank sibling paragraphs"
30+
31+
puts "Updated at: #{updated_at}"
32+
puts account_id ? "Only account id: #{account_id}" : "For **ALL ACCOUNTS**"
33+
34+
puts "\nPress ENTER to continue running or CTRL-C to bail..."
35+
gets
36+
37+
puts "\nRunning..."
38+
39+
# Suppress SQL logs
40+
Rails.event.debug_mode = false
41+
42+
seconds = Benchmark.realtime do
43+
suppressing_turbo_broadcasts do
44+
separate_nonblank_paragraphs
45+
end
46+
end
47+
48+
puts "\n\n"
49+
puts "Finished splitting non-blank <p>s in %.2f seconds." % seconds
50+
end
51+
52+
private
53+
def suppressing_turbo_broadcasts
54+
Board.suppressing_turbo_broadcasts do
55+
Card.suppressing_turbo_broadcasts do
56+
yield
57+
end
58+
end
59+
end
60+
61+
def separate_nonblank_paragraphs
62+
scanned = 0
63+
fixed = 0
64+
insertions = 0
65+
66+
action_texts_scope.find_each(**batch_options) do |rich_text|
67+
next if account_id && rich_text.record.account.external_account_id != account_id
68+
69+
scanned += 1
70+
edited = false
71+
72+
rich_text.body&.fragment.tap do |fragment|
73+
next unless fragment
74+
75+
fragment.find_all("p + p").each do |node|
76+
unless empty_node?(node) || empty_node?(node.previous_sibling)
77+
node.add_previous_sibling empty_node_markup
78+
edited = true
79+
insertions += 1
80+
end
81+
end
82+
83+
if edited
84+
puts " - modifying #{rich_text.record.class.name} #{rich_text.record.to_param} (account: #{rich_text.record.account.external_account_id})" unless demo_card?(rich_text.record)
85+
# allow implicit touching to invalidate caches
86+
rich_text.update! body: fragment.to_html
87+
fixed +=1
88+
end
89+
end
90+
end
91+
92+
puts "\n\Separation complete!"
93+
puts " Rich texts examined: #{scanned}"
94+
puts " Rich texts modified: #{fixed}"
95+
puts " Paragraphs inserted: #{insertions}"
96+
fixed
97+
end
98+
99+
def action_texts_scope
100+
ActionText::RichText.where(updated_at: updated_at)
101+
end
102+
103+
def batch_options
104+
{ batch_size: 20, order: :desc }
105+
end
106+
107+
def empty_node?(node)
108+
node.to_html == empty_node_markup
109+
end
110+
111+
def empty_node_markup
112+
"<p><br></p>"
113+
end
114+
115+
def demo_card?(record)
116+
record.is_a?(Card) && record.number <= 8
117+
end
118+
end
119+
120+
SeparateSiblingParagraphs.new(..BACKFILL_TIMESTAMP, account_id: ACCOUNT_ID).run

0 commit comments

Comments
 (0)