diff --git a/app/assets/stylesheets/bubbles.css b/app/assets/stylesheets/bubbles.css
index 91b65091e..9b7f88d5e 100644
--- a/app/assets/stylesheets/bubbles.css
+++ b/app/assets/stylesheets/bubbles.css
@@ -443,6 +443,10 @@
border: var(--bubble-border-width) solid var(--bubble-color);
}
+ .bubble.drafted & {
+ border-style: dashed;
+ }
+
.bubble:has(.bubble__link) & {
z-index: -1;
}
diff --git a/app/controllers/bubbles/publishes_controller.rb b/app/controllers/bubbles/publishes_controller.rb
new file mode 100644
index 000000000..947311bd6
--- /dev/null
+++ b/app/controllers/bubbles/publishes_controller.rb
@@ -0,0 +1,8 @@
+class Bubbles::PublishesController < ApplicationController
+ include BubbleScoped, BucketScoped
+
+ def create
+ @bubble.published!
+ redirect_to @bubble
+ end
+end
diff --git a/app/controllers/bubbles_controller.rb b/app/controllers/bubbles_controller.rb
index 920b8b181..8aa6850d8 100644
--- a/app/controllers/bubbles_controller.rb
+++ b/app/controllers/bubbles_controller.rb
@@ -7,7 +7,7 @@ class BubblesController < ApplicationController
before_action :set_bubble, only: %i[ show edit update destroy ]
def index
- @bubbles = @filter.bubbles
+ @bubbles = @filter.bubbles.published_or_drafted_by(Current.user)
end
def create
diff --git a/app/models/bubble.rb b/app/models/bubble.rb
index 5b6a0ec93..5313038ed 100644
--- a/app/models/bubble.rb
+++ b/app/models/bubble.rb
@@ -1,5 +1,6 @@
class Bubble < ApplicationRecord
- include Assignable, Boostable, Colored, Commentable, Eventable, Messages, Notifiable, Poppable, Searchable, Staged, Taggable
+ include Assignable, Boostable, Colored, Commentable, Draftable,
+ Eventable, Messages, Notifiable, Poppable, Searchable, Staged, Taggable
belongs_to :bucket, touch: true
belongs_to :creator, class_name: "User", default: -> { Current.user }
diff --git a/app/models/bubble/draftable.rb b/app/models/bubble/draftable.rb
new file mode 100644
index 000000000..8d50c6be4
--- /dev/null
+++ b/app/models/bubble/draftable.rb
@@ -0,0 +1,9 @@
+module Bubble::Draftable
+ extend ActiveSupport::Concern
+
+ included do
+ enum :status, %w[ drafted published ].index_by(&:itself)
+
+ scope :published_or_drafted_by, ->(user) { where(status: :published).or(where(creator: user)) }
+ end
+end
diff --git a/app/views/bubbles/_bubble.html.erb b/app/views/bubbles/_bubble.html.erb
index f00d502ff..be53dffda 100644
--- a/app/views/bubbles/_bubble.html.erb
+++ b/app/views/bubbles/_bubble.html.erb
@@ -1,5 +1,5 @@
<% cache bubble do %>
-
"
style="view-transition-name: bubble-<%= bubble.id -%>; --bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %> <%= bubble_size(bubble) %>"
data-controller="animation upload-preview"
data-animation-play-class="bubble--wobble"
diff --git a/app/views/bubbles/_publish.html.erb b/app/views/bubbles/_publish.html.erb
new file mode 100644
index 000000000..0f7af76c3
--- /dev/null
+++ b/app/views/bubbles/_publish.html.erb
@@ -0,0 +1,4 @@
+<%= button_to bucket_bubble_publish_path(bubble.bucket, bubble), class: "btn btn--positive full-width justify-start borderless" do %>
+ <%= image_tag "alert.svg", aria: { hidden: true }, size: 24 %>
+ Publish
+<% end %>
diff --git a/app/views/bubbles/show.html.erb b/app/views/bubbles/show.html.erb
index 8ecd07f4f..2ce7194fe 100644
--- a/app/views/bubbles/show.html.erb
+++ b/app/views/bubbles/show.html.erb
@@ -56,12 +56,16 @@
<% end %>
- <%= render "bubbles/pop_toggle", bubble: @bubble %>
+ <% if @bubble.drafted? %>
+ <%= render "bubbles/publish", bubble: @bubble %>
+ <% else %>
+ <%= render "bubbles/pop_toggle", bubble: @bubble %>
+
+ <% end %>
-
<%= button_to bucket_bubble_path(@bubble.bucket, @bubble),
method: :delete,
diff --git a/config/routes.rb b/config/routes.rb
index ad0d9a2cf..f122bb209 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -29,6 +29,7 @@ Rails.application.routes.draw do
scope module: :bubbles do
resource :image
resource :pop
+ resource :publish
resource :stage_picker
resources :stagings
end
diff --git a/db/migrate/20250107165422_add_status_to_bubbles.rb b/db/migrate/20250107165422_add_status_to_bubbles.rb
new file mode 100644
index 000000000..abe8b481f
--- /dev/null
+++ b/db/migrate/20250107165422_add_status_to_bubbles.rb
@@ -0,0 +1,5 @@
+class AddStatusToBubbles < ActiveRecord::Migration[8.1]
+ def change
+ add_column :bubbles, :status, :text, default: :drafted, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b50200c5c..b10589aaa 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -106,6 +106,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_01_09_153649) do
t.integer "stage_id"
t.integer "comments_count", default: 0, null: false
t.integer "activity_score", default: 0, null: false
+ t.text "status", default: "drafted", null: false
t.index ["bucket_id"], name: "index_bubbles_on_bucket_id"
t.index ["stage_id"], name: "index_bubbles_on_stage_id"
end
diff --git a/test/fixtures/bubbles.yml b/test/fixtures/bubbles.yml
index 556637e7c..8bbde128f 100644
--- a/test/fixtures/bubbles.yml
+++ b/test/fixtures/bubbles.yml
@@ -8,6 +8,7 @@ logo:
boosts_count: 5
comments_count: 2
activity_score: 7
+ status: published
layout:
bucket: writebook
@@ -17,6 +18,7 @@ layout:
created_at: <%= 1.week.ago %>
comments_count: 1
activity_score: 1
+ status: published
text:
bucket: writebook
@@ -24,6 +26,7 @@ text:
title: The text is too small
color: "#3B4B59"
created_at: <%= 1.week.ago %>
+ status: published
shipping:
bucket: writebook
@@ -31,3 +34,4 @@ shipping:
title: We need to ship the app
color: "#ED8008"
created_at: <%= 1.week.ago %>
+ status: published
diff --git a/test/models/bubble/draftable_test.rb b/test/models/bubble/draftable_test.rb
new file mode 100644
index 000000000..5a13f1f66
--- /dev/null
+++ b/test/models/bubble/draftable_test.rb
@@ -0,0 +1,20 @@
+require "test_helper"
+
+class Bubble::DraftableTest < ActiveSupport::TestCase
+ test "bubbles are only visible to the creator by default" do
+ bubble = buckets(:writebook).bubbles.create! creator: users(:kevin), title: "Drafted Bubble"
+
+ assert bubble.drafted?
+ assert_includes Bubble.published_or_drafted_by(users(:kevin)), bubble
+ assert_not_includes Bubble.published_or_drafted_by(users(:jz)), bubble
+ end
+
+ test "bubbles are visible to everyone when published" do
+ bubble = buckets(:writebook).bubbles.create! creator: users(:kevin), title: "Drafted Bubble"
+ bubble.published!
+
+ assert bubble.published?
+ assert_includes Bubble.published_or_drafted_by(users(:kevin)), bubble
+ assert_includes Bubble.published_or_drafted_by(users(:jz)), bubble
+ end
+end