collavre 0.1.1 → 0.2.0
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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/collavre/comments_popup.css +293 -8
- data/app/assets/stylesheets/collavre/mention_menu.css +26 -0
- data/app/assets/stylesheets/collavre/popup.css +7 -0
- data/app/assets/stylesheets/collavre/print.css +18 -0
- data/app/channels/collavre/comments_presence_channel.rb +33 -0
- data/app/components/collavre/autocomplete_popup_component.html.erb +3 -0
- data/app/components/collavre/autocomplete_popup_component.rb +18 -0
- data/app/components/collavre/command_menu_component.rb +7 -0
- data/app/components/collavre/plans_timeline_component.html.erb +1 -1
- data/app/components/collavre/plans_timeline_component.rb +29 -32
- data/app/components/collavre/user_mention_menu_component.rb +4 -5
- data/app/controllers/collavre/comments_controller.rb +111 -10
- data/app/controllers/collavre/creatives_controller.rb +8 -0
- data/app/controllers/collavre/google_auth_controller.rb +5 -1
- data/app/controllers/collavre/plans_controller.rb +65 -9
- data/app/controllers/collavre/topics_controller.rb +42 -0
- data/app/controllers/collavre/users_controller.rb +4 -14
- data/app/errors/collavre/approval_pending_error.rb +54 -0
- data/app/errors/collavre/cancelled_error.rb +9 -0
- data/app/helpers/collavre/navigation_helper.rb +3 -1
- data/app/javascript/collavre.js +1 -0
- data/app/javascript/controllers/comments/__tests__/popup_controller.test.js +2 -1
- data/app/javascript/controllers/comments/form_controller.js +2 -1
- data/app/javascript/controllers/comments/list_controller.js +185 -2
- data/app/javascript/controllers/comments/popup_controller.js +95 -20
- data/app/javascript/controllers/comments/presence_controller.js +30 -1
- data/app/javascript/controllers/comments/topics_controller.js +314 -4
- data/app/javascript/modules/__tests__/creative_progress.test.js +50 -0
- data/app/javascript/modules/command_menu.js +116 -0
- data/app/javascript/modules/creative_progress.js +14 -0
- data/app/javascript/modules/creative_row_editor.js +104 -20
- data/app/javascript/modules/plans_timeline.js +15 -4
- data/app/javascript/modules/share_modal.js +3 -0
- data/app/jobs/collavre/ai_agent_job.rb +35 -21
- data/app/models/collavre/calendar_event.rb +7 -1
- data/app/models/collavre/comment.rb +35 -2
- data/app/models/collavre/creative.rb +1 -3
- data/app/models/collavre/mcp_tool.rb +4 -0
- data/app/models/collavre/plan.rb +23 -0
- data/app/models/collavre/topic.rb +12 -0
- data/app/models/collavre/user.rb +15 -1
- data/app/services/collavre/ai_agent_service.rb +174 -66
- data/app/services/collavre/ai_client.rb +31 -2
- data/app/services/collavre/comments/action_executor.rb +47 -1
- data/app/services/collavre/comments/calendar_command.rb +117 -18
- data/app/services/collavre/google_calendar_service.rb +38 -15
- data/app/services/collavre/markdown_importer.rb +47 -8
- data/app/services/collavre/mcp_service.rb +23 -10
- data/app/services/collavre/system_events/router.rb +50 -26
- data/app/services/collavre/tools/creative_create_service.rb +97 -0
- data/app/services/collavre/tools/creative_update_service.rb +116 -0
- data/app/views/collavre/comments/_comment.html.erb +2 -2
- data/app/views/collavre/comments/_comments_popup.html.erb +40 -6
- data/app/views/collavre/comments/fullscreen.html.erb +5 -0
- data/app/views/collavre/creatives/_inline_edit_form.html.erb +11 -3
- data/app/views/collavre/creatives/_integration_modals.html.erb +6 -0
- data/app/views/collavre/creatives/_integration_triggers.html.erb +8 -0
- data/app/views/collavre/creatives/_integrations_menu.html.erb +12 -0
- data/app/views/collavre/creatives/_mobile_actions_menu.html.erb +13 -1
- data/app/views/collavre/creatives/_share_button.html.erb +1 -1
- data/app/views/collavre/creatives/index.html.erb +22 -4
- data/app/views/collavre/users/edit_ai.html.erb +15 -0
- data/app/views/collavre/users/new_ai.html.erb +15 -0
- data/app/views/layouts/collavre/chat.html.erb +46 -0
- data/config/locales/ai_agent.en.yml +15 -0
- data/config/locales/ai_agent.ko.yml +15 -0
- data/config/locales/comments.en.yml +15 -3
- data/config/locales/comments.ko.yml +15 -3
- data/config/locales/creatives.en.yml +3 -31
- data/config/locales/creatives.ko.yml +3 -27
- data/config/locales/plans.en.yml +4 -0
- data/config/locales/plans.ko.yml +4 -0
- data/config/locales/users.en.yml +3 -0
- data/config/locales/users.ko.yml +3 -0
- data/config/routes.rb +8 -3
- data/db/migrate/20260120045354_encrypt_oauth_tokens.rb +1 -1
- data/db/migrate/20260131100000_migrate_active_storage_attachment_record_types.rb +21 -0
- data/db/migrate/20260201100000_make_google_event_id_nullable.rb +5 -0
- data/lib/collavre/engine.rb +171 -6
- data/lib/collavre/integration_registry.rb +129 -0
- data/lib/collavre/version.rb +1 -1
- data/lib/collavre.rb +2 -0
- data/lib/navigation/registry.rb +130 -0
- metadata +22 -15
- data/app/components/collavre/user_mention_menu_component.html.erb +0 -3
- data/app/controllers/collavre/notion_auth_controller.rb +0 -25
- data/app/jobs/collavre/notion_export_job.rb +0 -30
- data/app/jobs/collavre/notion_sync_job.rb +0 -48
- data/app/models/collavre/notion_account.rb +0 -17
- data/app/models/collavre/notion_block_link.rb +0 -10
- data/app/models/collavre/notion_page_link.rb +0 -19
- data/app/services/collavre/notion_client.rb +0 -231
- data/app/services/collavre/notion_creative_exporter.rb +0 -296
- data/app/services/collavre/notion_service.rb +0 -249
- data/app/views/collavre/creatives/_notion_integration_modal.html.erb +0 -90
- data/db/migrate/20241201000000_create_notion_integrations.rb +0 -29
- data/db/migrate/20250312000000_create_notion_block_links.rb +0 -16
- data/db/migrate/20250312010000_allow_multiple_notion_blocks_per_creative.rb +0 -5
data/lib/collavre/engine.rb
CHANGED
|
@@ -41,12 +41,6 @@ module Collavre
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
# Add engine locales to I18n load path
|
|
45
|
-
# Host app can override these translations by defining the same keys
|
|
46
|
-
initializer "collavre.i18n" do |app|
|
|
47
|
-
config.i18n.load_path += Dir[root.join("config/locales/**/*.yml")]
|
|
48
|
-
end
|
|
49
|
-
|
|
50
44
|
# Allow engine controllers to fall back to host app views during migration
|
|
51
45
|
# This enables gradual view migration - views can stay in host app until moved to engine
|
|
52
46
|
initializer "collavre.view_paths" do
|
|
@@ -73,5 +67,176 @@ module Collavre
|
|
|
73
67
|
end
|
|
74
68
|
end
|
|
75
69
|
end
|
|
70
|
+
|
|
71
|
+
# Reset navigation registry before any registrations (runs first)
|
|
72
|
+
initializer "collavre.navigation_reset" do
|
|
73
|
+
ActiveSupport::Reloader.to_prepare(prepend: true) do
|
|
74
|
+
Navigation::Registry.instance.reset!
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Register core navigation items
|
|
79
|
+
# Host app or other engines can add/modify items in their own initializers
|
|
80
|
+
initializer "collavre.navigation", after: "collavre.navigation_reset" do
|
|
81
|
+
Rails.application.config.to_prepare do
|
|
82
|
+
# ============================================
|
|
83
|
+
# Search Section
|
|
84
|
+
# ============================================
|
|
85
|
+
Navigation::Registry.instance.register(
|
|
86
|
+
key: :search,
|
|
87
|
+
label: "app.search_placeholder",
|
|
88
|
+
section: :search,
|
|
89
|
+
type: :partial,
|
|
90
|
+
partial: "collavre/shared/navigation/search_form",
|
|
91
|
+
priority: 10
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# ============================================
|
|
95
|
+
# Main Section - Mobile Only
|
|
96
|
+
# ============================================
|
|
97
|
+
Navigation::Registry.instance.register(
|
|
98
|
+
key: :mobile_plans,
|
|
99
|
+
label: "app.plans",
|
|
100
|
+
type: :partial,
|
|
101
|
+
partial: "collavre/shared/navigation/mobile_plans_button",
|
|
102
|
+
priority: 100,
|
|
103
|
+
requires_auth: true,
|
|
104
|
+
desktop: false,
|
|
105
|
+
mobile: true
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# ============================================
|
|
109
|
+
# Main Section - Desktop Navigation
|
|
110
|
+
# ============================================
|
|
111
|
+
Navigation::Registry.instance.register(
|
|
112
|
+
key: :home,
|
|
113
|
+
label: "app.home",
|
|
114
|
+
type: :button,
|
|
115
|
+
path: -> { main_app.root_path },
|
|
116
|
+
priority: 110
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
Navigation::Registry.instance.register(
|
|
120
|
+
key: :plans,
|
|
121
|
+
label: "app.plans",
|
|
122
|
+
type: :partial,
|
|
123
|
+
partial: "collavre/shared/navigation/plans_button",
|
|
124
|
+
priority: 120,
|
|
125
|
+
requires_auth: true,
|
|
126
|
+
mobile: false
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
Navigation::Registry.instance.register(
|
|
130
|
+
key: :progress_filter,
|
|
131
|
+
label: "",
|
|
132
|
+
type: :component,
|
|
133
|
+
component: Collavre::ProgressFilterComponent,
|
|
134
|
+
component_args: {
|
|
135
|
+
current_state: -> {
|
|
136
|
+
if params[:min_progress] == "1" && params[:max_progress] == "1"
|
|
137
|
+
:complete
|
|
138
|
+
elsif params[:min_progress] == "0" && params[:max_progress] == "0.99"
|
|
139
|
+
:incomplete
|
|
140
|
+
else
|
|
141
|
+
:all
|
|
142
|
+
end
|
|
143
|
+
},
|
|
144
|
+
states: [
|
|
145
|
+
{ name: -> { I18n.t("app.filter_complete") }, value: :complete },
|
|
146
|
+
{ name: -> { I18n.t("app.filter_incomplete") }, value: :incomplete },
|
|
147
|
+
{ name: -> { I18n.t("app.filter_all") }, value: :all }
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
priority: 130,
|
|
151
|
+
requires_auth: true
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
Navigation::Registry.instance.register(
|
|
155
|
+
key: :comment_filter,
|
|
156
|
+
label: "",
|
|
157
|
+
type: :component,
|
|
158
|
+
component: Collavre::ProgressFilterComponent,
|
|
159
|
+
component_args: {
|
|
160
|
+
current_state: -> { params[:comment] == "true" ? :comment : nil },
|
|
161
|
+
states: [
|
|
162
|
+
{ name: -> { I18n.t("app.filter_comments") }, value: :comment }
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
priority: 140,
|
|
166
|
+
requires_auth: true
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
Navigation::Registry.instance.register(
|
|
170
|
+
key: :inbox,
|
|
171
|
+
label: "app.inbox",
|
|
172
|
+
type: :partial,
|
|
173
|
+
partial: "collavre/shared/navigation/inbox_button",
|
|
174
|
+
priority: 150,
|
|
175
|
+
requires_user: true,
|
|
176
|
+
mobile: false
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
Navigation::Registry.instance.register(
|
|
180
|
+
key: :mobile_inbox,
|
|
181
|
+
label: "app.inbox",
|
|
182
|
+
type: :partial,
|
|
183
|
+
partial: "collavre/shared/navigation/mobile_inbox_button",
|
|
184
|
+
priority: 155,
|
|
185
|
+
requires_user: true,
|
|
186
|
+
desktop: false,
|
|
187
|
+
mobile: true
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
Navigation::Registry.instance.register(
|
|
191
|
+
key: :sign_in,
|
|
192
|
+
label: "app.sign_in",
|
|
193
|
+
type: :button,
|
|
194
|
+
path: -> { Collavre::Engine.routes.url_helpers.new_session_path },
|
|
195
|
+
priority: 160,
|
|
196
|
+
visible: -> { !authenticated? }
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
Navigation::Registry.instance.register(
|
|
200
|
+
key: :help,
|
|
201
|
+
label: "?",
|
|
202
|
+
type: :partial,
|
|
203
|
+
partial: "collavre/shared/navigation/help_button",
|
|
204
|
+
priority: 170
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# ============================================
|
|
208
|
+
# User Section
|
|
209
|
+
# ============================================
|
|
210
|
+
Navigation::Registry.instance.register(
|
|
211
|
+
key: :user_menu,
|
|
212
|
+
label: "",
|
|
213
|
+
section: :user,
|
|
214
|
+
type: :raw,
|
|
215
|
+
button_content: -> { render(AvatarComponent.new(user: Current.user, size: 32, classes: "nav-avatar avatar")) },
|
|
216
|
+
menu_id: "user-menu",
|
|
217
|
+
align: :right,
|
|
218
|
+
priority: 100,
|
|
219
|
+
requires_user: true,
|
|
220
|
+
children: [
|
|
221
|
+
{
|
|
222
|
+
key: :profile,
|
|
223
|
+
label: "collavre.users.profile",
|
|
224
|
+
type: :button,
|
|
225
|
+
path: -> { Collavre::Engine.routes.url_helpers.user_path(Current.user) },
|
|
226
|
+
html_class: "popup-menu-item",
|
|
227
|
+
priority: 100
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
key: :sign_out,
|
|
231
|
+
label: "app.sign_out",
|
|
232
|
+
type: :button,
|
|
233
|
+
path: -> { Collavre::Engine.routes.url_helpers.session_path },
|
|
234
|
+
method: :delete,
|
|
235
|
+
priority: 900
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
76
241
|
end
|
|
77
242
|
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Collavre
|
|
4
|
+
# Registry for third-party integrations (Slack, GitHub, Notion, etc.)
|
|
5
|
+
# Allows extensions to register themselves and provide UI components
|
|
6
|
+
# without modifying the core Collavre engine.
|
|
7
|
+
#
|
|
8
|
+
# @example Registering an integration
|
|
9
|
+
# Collavre::IntegrationRegistry.register(:slack, {
|
|
10
|
+
# label: "Slack",
|
|
11
|
+
# icon: "slack",
|
|
12
|
+
# description: "Sync chat messages with Slack channels",
|
|
13
|
+
# routes: CollavreSlack::Engine.routes.url_helpers,
|
|
14
|
+
# creative_menu_partial: "collavre_slack/integrations/creative_menu",
|
|
15
|
+
# settings_partial: "collavre_slack/integrations/settings"
|
|
16
|
+
# })
|
|
17
|
+
#
|
|
18
|
+
class IntegrationRegistry
|
|
19
|
+
include Singleton
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@integrations = {}
|
|
23
|
+
@mutex = Mutex.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Register a new integration
|
|
27
|
+
# @param name [Symbol] Unique identifier for the integration
|
|
28
|
+
# @param config [Hash] Configuration options
|
|
29
|
+
# @option config [String] :label Display name
|
|
30
|
+
# @option config [String] :icon Icon identifier (optional)
|
|
31
|
+
# @option config [String] :description Brief description (optional)
|
|
32
|
+
# @option config [Object] :routes Engine routes url_helpers
|
|
33
|
+
# @option config [String] :creative_menu_partial Partial for creative action menu
|
|
34
|
+
# @option config [String] :settings_partial Partial for settings page (optional)
|
|
35
|
+
# @option config [Proc] :enabled_for Lambda to check if enabled for a creative (optional)
|
|
36
|
+
def register(name, config)
|
|
37
|
+
integration = Integration.new(name, config)
|
|
38
|
+
@mutex.synchronize do
|
|
39
|
+
@integrations[name.to_sym] = integration
|
|
40
|
+
end
|
|
41
|
+
Rails.logger.info("[Collavre] Integration registered: #{name}")
|
|
42
|
+
integration
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Unregister an integration
|
|
46
|
+
def unregister(name)
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
@integrations.delete(name.to_sym)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Get a specific integration by name
|
|
53
|
+
def find(name)
|
|
54
|
+
@mutex.synchronize do
|
|
55
|
+
@integrations[name.to_sym]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get all registered integrations
|
|
60
|
+
def all
|
|
61
|
+
@mutex.synchronize do
|
|
62
|
+
@integrations.values
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Iterate over all integrations
|
|
67
|
+
def each(&block)
|
|
68
|
+
all.each(&block)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if any integrations are registered
|
|
72
|
+
def any?
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
@integrations.any?
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Clear all integrations (useful for testing)
|
|
79
|
+
def reset!
|
|
80
|
+
@mutex.synchronize do
|
|
81
|
+
@integrations = {}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Class methods for convenient access
|
|
86
|
+
class << self
|
|
87
|
+
delegate :register, :unregister, :find, :all, :each, :any?, :reset!, to: :instance
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Represents a registered integration
|
|
92
|
+
class Integration
|
|
93
|
+
attr_reader :name, :label, :icon, :description, :routes,
|
|
94
|
+
:creative_menu_partial, :settings_partial, :enabled_for
|
|
95
|
+
|
|
96
|
+
def initialize(name, config)
|
|
97
|
+
@name = name.to_sym
|
|
98
|
+
@label = config[:label] || name.to_s.titleize
|
|
99
|
+
@icon = config[:icon]
|
|
100
|
+
@description = config[:description]
|
|
101
|
+
@routes = config[:routes]
|
|
102
|
+
@creative_menu_partial = config[:creative_menu_partial]
|
|
103
|
+
@settings_partial = config[:settings_partial]
|
|
104
|
+
@enabled_for = config[:enabled_for] || ->(_creative) { true }
|
|
105
|
+
|
|
106
|
+
validate!
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Check if this integration is enabled for a specific creative
|
|
110
|
+
def enabled_for?(creative)
|
|
111
|
+
return true unless @enabled_for.respond_to?(:call)
|
|
112
|
+
@enabled_for.call(creative)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Generate a path using the integration's routes
|
|
116
|
+
def path_for(method_name, **args)
|
|
117
|
+
return nil unless @routes
|
|
118
|
+
return nil unless @routes.respond_to?(method_name)
|
|
119
|
+
@routes.public_send(method_name, **args)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def validate!
|
|
125
|
+
raise ArgumentError, "Integration must have a :label" unless @label.present?
|
|
126
|
+
raise ArgumentError, "Integration must have a :creative_menu_partial" unless @creative_menu_partial.present?
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
data/lib/collavre/version.rb
CHANGED
data/lib/collavre.rb
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Navigation
|
|
4
|
+
class Registry
|
|
5
|
+
include Singleton
|
|
6
|
+
|
|
7
|
+
DEFAULT_PRIORITY = 500
|
|
8
|
+
DEFAULT_SECTION = :main
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@items = []
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Register a new navigation item or replace an existing one with the same key
|
|
16
|
+
def register(item)
|
|
17
|
+
item = normalize_item(item)
|
|
18
|
+
validate_item!(item)
|
|
19
|
+
|
|
20
|
+
@mutex.synchronize do
|
|
21
|
+
# Remove existing item with same key if present
|
|
22
|
+
@items.reject! { |i| i[:key] == item[:key] }
|
|
23
|
+
@items << item
|
|
24
|
+
end
|
|
25
|
+
item
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Remove an item by key
|
|
29
|
+
def unregister(key)
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
@items.reject! { |i| i[:key] == key.to_sym }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Modify an existing item
|
|
36
|
+
def modify(key, **changes)
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
item = @items.find { |i| i[:key] == key.to_sym }
|
|
39
|
+
raise ArgumentError, "Navigation item not found: #{key}" unless item
|
|
40
|
+
|
|
41
|
+
item.merge!(changes)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Add a child item to a parent
|
|
46
|
+
def add_child(parent_key, child)
|
|
47
|
+
child = normalize_item(child)
|
|
48
|
+
validate_item!(child)
|
|
49
|
+
|
|
50
|
+
@mutex.synchronize do
|
|
51
|
+
parent = @items.find { |i| i[:key] == parent_key.to_sym }
|
|
52
|
+
raise ArgumentError, "Parent navigation item not found: #{parent_key}" unless parent
|
|
53
|
+
|
|
54
|
+
parent[:children] ||= []
|
|
55
|
+
# Remove existing child with same key if present
|
|
56
|
+
parent[:children].reject! { |c| c[:key] == child[:key] }
|
|
57
|
+
parent[:children] << child
|
|
58
|
+
parent[:children].sort_by! { |c| c[:priority] }
|
|
59
|
+
end
|
|
60
|
+
child
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get all items for a specific section, sorted by priority
|
|
64
|
+
def items_for_section(section)
|
|
65
|
+
@mutex.synchronize do
|
|
66
|
+
@items
|
|
67
|
+
.select { |i| i[:section] == section.to_sym }
|
|
68
|
+
.sort_by { |i| i[:priority] }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get all items, sorted by priority
|
|
73
|
+
def all
|
|
74
|
+
@mutex.synchronize do
|
|
75
|
+
@items.sort_by { |i| i[:priority] }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get a specific item by key
|
|
80
|
+
def find(key)
|
|
81
|
+
@mutex.synchronize do
|
|
82
|
+
@items.find { |i| i[:key] == key.to_sym }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Clear all items (useful for testing or reloading)
|
|
87
|
+
def reset!
|
|
88
|
+
@mutex.synchronize do
|
|
89
|
+
@items = []
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def normalize_item(item)
|
|
96
|
+
item = item.dup
|
|
97
|
+
item[:key] = item[:key]&.to_sym
|
|
98
|
+
item[:section] ||= DEFAULT_SECTION
|
|
99
|
+
item[:section] = item[:section].to_sym
|
|
100
|
+
item[:priority] ||= DEFAULT_PRIORITY
|
|
101
|
+
item[:type] ||= :button
|
|
102
|
+
item[:type] = item[:type].to_sym
|
|
103
|
+
item[:desktop] = true unless item.key?(:desktop)
|
|
104
|
+
item[:mobile] = true unless item.key?(:mobile)
|
|
105
|
+
item[:requires_auth] ||= false
|
|
106
|
+
item[:requires_user] ||= false
|
|
107
|
+
if item[:children]
|
|
108
|
+
item[:children].map! { |c| normalize_item(c) }
|
|
109
|
+
item[:children].sort_by! { |c| c[:priority] }
|
|
110
|
+
end
|
|
111
|
+
item
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def validate_item!(item, parent_key: nil)
|
|
115
|
+
context = parent_key ? " (child of #{parent_key})" : ""
|
|
116
|
+
raise ArgumentError, "Navigation item must have a :key#{context}" unless item[:key]
|
|
117
|
+
raise ArgumentError, "Navigation item must have a :label#{context}" unless item[:label]
|
|
118
|
+
raise ArgumentError, "Navigation item must have a :key" unless item[:key]
|
|
119
|
+
raise ArgumentError, "Navigation item must have a :label" unless item[:label]
|
|
120
|
+
|
|
121
|
+
valid_types = %i[button link component partial divider raw popup]
|
|
122
|
+
unless valid_types.include?(item[:type])
|
|
123
|
+
raise ArgumentError, "Invalid navigation item type: #{item[:type]}#{context}. Valid types: #{valid_types.join(', ')}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Validate children recursively
|
|
127
|
+
item[:children]&.each { |child| validate_item!(child, parent_key: item[:key]) }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: collavre
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Collavre
|
|
@@ -172,8 +172,11 @@ files:
|
|
|
172
172
|
- app/channels/collavre/comments_presence_channel.rb
|
|
173
173
|
- app/channels/collavre/slide_view_channel.rb
|
|
174
174
|
- app/channels/collavre/topics_channel.rb
|
|
175
|
+
- app/components/collavre/autocomplete_popup_component.html.erb
|
|
176
|
+
- app/components/collavre/autocomplete_popup_component.rb
|
|
175
177
|
- app/components/collavre/avatar_component.html.erb
|
|
176
178
|
- app/components/collavre/avatar_component.rb
|
|
179
|
+
- app/components/collavre/command_menu_component.rb
|
|
177
180
|
- app/components/collavre/inbox/badge_component.html.erb
|
|
178
181
|
- app/components/collavre/inbox/badge_component.rb
|
|
179
182
|
- app/components/collavre/plans_timeline_component.html.erb
|
|
@@ -182,7 +185,6 @@ files:
|
|
|
182
185
|
- app/components/collavre/popup_menu_component.rb
|
|
183
186
|
- app/components/collavre/progress_filter_component.html.erb
|
|
184
187
|
- app/components/collavre/progress_filter_component.rb
|
|
185
|
-
- app/components/collavre/user_mention_menu_component.html.erb
|
|
186
188
|
- app/components/collavre/user_mention_menu_component.rb
|
|
187
189
|
- app/controllers/collavre/application_controller.rb
|
|
188
190
|
- app/controllers/collavre/attachments_controller.rb
|
|
@@ -204,13 +206,14 @@ files:
|
|
|
204
206
|
- app/controllers/collavre/google_auth_controller.rb
|
|
205
207
|
- app/controllers/collavre/inbox_items_controller.rb
|
|
206
208
|
- app/controllers/collavre/invites_controller.rb
|
|
207
|
-
- app/controllers/collavre/notion_auth_controller.rb
|
|
208
209
|
- app/controllers/collavre/passwords_controller.rb
|
|
209
210
|
- app/controllers/collavre/plans_controller.rb
|
|
210
211
|
- app/controllers/collavre/sessions_controller.rb
|
|
211
212
|
- app/controllers/collavre/topics_controller.rb
|
|
212
213
|
- app/controllers/collavre/user_themes_controller.rb
|
|
213
214
|
- app/controllers/collavre/users_controller.rb
|
|
215
|
+
- app/errors/collavre/approval_pending_error.rb
|
|
216
|
+
- app/errors/collavre/cancelled_error.rb
|
|
214
217
|
- app/helpers/collavre/application_helper.rb
|
|
215
218
|
- app/helpers/collavre/comments_helper.rb
|
|
216
219
|
- app/helpers/collavre/creatives_helper.rb
|
|
@@ -271,7 +274,10 @@ files:
|
|
|
271
274
|
- app/javascript/lib/responsive_images.js
|
|
272
275
|
- app/javascript/lib/turbo_stream_actions.js
|
|
273
276
|
- app/javascript/lib/utils/markdown.js
|
|
277
|
+
- app/javascript/modules/__tests__/creative_progress.test.js
|
|
278
|
+
- app/javascript/modules/command_menu.js
|
|
274
279
|
- app/javascript/modules/creative_guide.js
|
|
280
|
+
- app/javascript/modules/creative_progress.js
|
|
275
281
|
- app/javascript/modules/creative_row_editor.js
|
|
276
282
|
- app/javascript/modules/creative_row_swipe.js
|
|
277
283
|
- app/javascript/modules/creatives.js
|
|
@@ -290,8 +296,6 @@ files:
|
|
|
290
296
|
- app/javascript/utils/clipboard.js
|
|
291
297
|
- app/jobs/collavre/ai_agent_job.rb
|
|
292
298
|
- app/jobs/collavre/inbox_summary_job.rb
|
|
293
|
-
- app/jobs/collavre/notion_export_job.rb
|
|
294
|
-
- app/jobs/collavre/notion_sync_job.rb
|
|
295
299
|
- app/jobs/collavre/permission_cache_cleanup_job.rb
|
|
296
300
|
- app/jobs/collavre/permission_cache_job.rb
|
|
297
301
|
- app/jobs/collavre/push_notification_job.rb
|
|
@@ -322,9 +326,6 @@ files:
|
|
|
322
326
|
- app/models/collavre/invitation.rb
|
|
323
327
|
- app/models/collavre/label.rb
|
|
324
328
|
- app/models/collavre/mcp_tool.rb
|
|
325
|
-
- app/models/collavre/notion_account.rb
|
|
326
|
-
- app/models/collavre/notion_block_link.rb
|
|
327
|
-
- app/models/collavre/notion_page_link.rb
|
|
328
329
|
- app/models/collavre/plan.rb
|
|
329
330
|
- app/models/collavre/session.rb
|
|
330
331
|
- app/models/collavre/system_setting.rb
|
|
@@ -374,15 +375,14 @@ files:
|
|
|
374
375
|
- app/services/collavre/link_preview_fetcher.rb
|
|
375
376
|
- app/services/collavre/markdown_importer.rb
|
|
376
377
|
- app/services/collavre/mcp_service.rb
|
|
377
|
-
- app/services/collavre/notion_client.rb
|
|
378
|
-
- app/services/collavre/notion_creative_exporter.rb
|
|
379
|
-
- app/services/collavre/notion_service.rb
|
|
380
378
|
- app/services/collavre/ppt_importer.rb
|
|
381
379
|
- app/services/collavre/ruby_llm_interaction_logger.rb
|
|
382
380
|
- app/services/collavre/system_events/context_builder.rb
|
|
383
381
|
- app/services/collavre/system_events/dispatcher.rb
|
|
384
382
|
- app/services/collavre/system_events/router.rb
|
|
383
|
+
- app/services/collavre/tools/creative_create_service.rb
|
|
385
384
|
- app/services/collavre/tools/creative_retrieval_service.rb
|
|
385
|
+
- app/services/collavre/tools/creative_update_service.rb
|
|
386
386
|
- app/views/admin/shared/_tabs.html.erb
|
|
387
387
|
- app/views/collavre/comments/_activity_log_details.html.erb
|
|
388
388
|
- app/views/collavre/comments/_comment.html.erb
|
|
@@ -391,13 +391,16 @@ files:
|
|
|
391
391
|
- app/views/collavre/comments/_presence_avatars.html.erb
|
|
392
392
|
- app/views/collavre/comments/_reaction_picker.html.erb
|
|
393
393
|
- app/views/collavre/comments/_read_receipts.html.erb
|
|
394
|
+
- app/views/collavre/comments/fullscreen.html.erb
|
|
394
395
|
- app/views/collavre/creatives/_add_button.html.erb
|
|
395
396
|
- app/views/collavre/creatives/_delete_button.html.erb
|
|
396
397
|
- app/views/collavre/creatives/_github_integration_modal.html.erb
|
|
397
398
|
- app/views/collavre/creatives/_import_upload_zone.html.erb
|
|
398
399
|
- app/views/collavre/creatives/_inline_edit_form.html.erb
|
|
400
|
+
- app/views/collavre/creatives/_integration_modals.html.erb
|
|
401
|
+
- app/views/collavre/creatives/_integration_triggers.html.erb
|
|
402
|
+
- app/views/collavre/creatives/_integrations_menu.html.erb
|
|
399
403
|
- app/views/collavre/creatives/_mobile_actions_menu.html.erb
|
|
400
|
-
- app/views/collavre/creatives/_notion_integration_modal.html.erb
|
|
401
404
|
- app/views/collavre/creatives/_set_plan_modal.html.erb
|
|
402
405
|
- app/views/collavre/creatives/_share_button.html.erb
|
|
403
406
|
- app/views/collavre/creatives/edit.html.erb
|
|
@@ -446,7 +449,10 @@ files:
|
|
|
446
449
|
- app/views/collavre/users/passkeys.html.erb
|
|
447
450
|
- app/views/collavre/users/show.html.erb
|
|
448
451
|
- app/views/inbox/badge_component/_count.html.erb
|
|
452
|
+
- app/views/layouts/collavre/chat.html.erb
|
|
449
453
|
- app/views/layouts/collavre/slide.html.erb
|
|
454
|
+
- config/locales/ai_agent.en.yml
|
|
455
|
+
- config/locales/ai_agent.ko.yml
|
|
450
456
|
- config/locales/comments.en.yml
|
|
451
457
|
- config/locales/comments.ko.yml
|
|
452
458
|
- config/locales/contacts.en.yml
|
|
@@ -464,13 +470,10 @@ files:
|
|
|
464
470
|
- config/locales/users.en.yml
|
|
465
471
|
- config/locales/users.ko.yml
|
|
466
472
|
- config/routes.rb
|
|
467
|
-
- db/migrate/20241201000000_create_notion_integrations.rb
|
|
468
473
|
- db/migrate/20250128110017_create_creatives.rb
|
|
469
474
|
- db/migrate/20250128120122_create_users.rb
|
|
470
475
|
- db/migrate/20250128120123_create_sessions.rb
|
|
471
476
|
- db/migrate/20250128123633_create_subscribers.rb
|
|
472
|
-
- db/migrate/20250312000000_create_notion_block_links.rb
|
|
473
|
-
- db/migrate/20250312010000_allow_multiple_notion_blocks_per_creative.rb
|
|
474
477
|
- db/migrate/20250522115048_remove_name_from_creatives.rb
|
|
475
478
|
- db/migrate/20250522190651_add_parent_id_to_creatives.rb
|
|
476
479
|
- db/migrate/20250523133100_rename_inventory_count_to_progress.rb
|
|
@@ -557,13 +560,17 @@ files:
|
|
|
557
560
|
- db/migrate/20260120045354_encrypt_oauth_tokens.rb
|
|
558
561
|
- db/migrate/20260120162259_remove_fk_from_creative_shares_caches.rb
|
|
559
562
|
- db/migrate/20260120163856_remove_timestamps_from_creative_shares_caches.rb
|
|
563
|
+
- db/migrate/20260131100000_migrate_active_storage_attachment_record_types.rb
|
|
564
|
+
- db/migrate/20260201100000_make_google_event_id_nullable.rb
|
|
560
565
|
- lib/collavre.rb
|
|
561
566
|
- lib/collavre/configuration.rb
|
|
562
567
|
- lib/collavre/engine.rb
|
|
568
|
+
- lib/collavre/integration_registry.rb
|
|
563
569
|
- lib/collavre/user_extensions.rb
|
|
564
570
|
- lib/collavre/version.rb
|
|
565
571
|
- lib/generators/collavre/install/install_generator.rb
|
|
566
572
|
- lib/generators/collavre/install/templates/build.cjs.tt
|
|
573
|
+
- lib/navigation/registry.rb
|
|
567
574
|
- lib/tasks/collavre_assets.rake
|
|
568
575
|
homepage: https://collavre.com
|
|
569
576
|
licenses:
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
module Collavre
|
|
2
|
-
class NotionAuthController < ApplicationController
|
|
3
|
-
allow_unauthenticated_access only: :callback
|
|
4
|
-
before_action -> { enforce_auth_provider!(:notion) }, only: :callback
|
|
5
|
-
|
|
6
|
-
def callback
|
|
7
|
-
auth = request.env["omniauth.auth"]
|
|
8
|
-
notion = Collavre::NotionAccount.find_or_initialize_by(notion_uid: auth.uid)
|
|
9
|
-
|
|
10
|
-
if notion.new_record?
|
|
11
|
-
unless Current.user
|
|
12
|
-
redirect_to collavre.new_session_path, alert: I18n.t("collavre.notion_auth.login_first")
|
|
13
|
-
return
|
|
14
|
-
end
|
|
15
|
-
notion.user = Current.user
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
notion.token = auth.credentials.token
|
|
19
|
-
notion.workspace_name = auth.info.name
|
|
20
|
-
notion.save!
|
|
21
|
-
|
|
22
|
-
redirect_to creatives_path, notice: I18n.t("collavre.notion_auth.connected")
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module Collavre
|
|
2
|
-
class NotionExportJob < ApplicationJob
|
|
3
|
-
queue_as :default
|
|
4
|
-
|
|
5
|
-
def perform(creative, notion_account, parent_page_id = nil)
|
|
6
|
-
service = NotionService.new(user: notion_account.user)
|
|
7
|
-
|
|
8
|
-
begin
|
|
9
|
-
# Export the creative tree to Notion
|
|
10
|
-
link = service.sync_creative(creative, parent_page_id: parent_page_id)
|
|
11
|
-
|
|
12
|
-
Rails.logger.info("Successfully exported creative #{creative.id} to Notion page #{link.page_id}")
|
|
13
|
-
|
|
14
|
-
# You could add broadcast/notification logic here if needed
|
|
15
|
-
# ActionCable.server.broadcast("user_#{notion_account.user.id}", {
|
|
16
|
-
# type: 'notion_export_complete',
|
|
17
|
-
# creative_id: creative.id,
|
|
18
|
-
# page_url: link.page_url
|
|
19
|
-
# })
|
|
20
|
-
|
|
21
|
-
rescue NotionError => e
|
|
22
|
-
Rails.logger.error("Notion export failed for creative #{creative.id}: #{e.message}")
|
|
23
|
-
raise e
|
|
24
|
-
rescue StandardError => e
|
|
25
|
-
Rails.logger.error("Unexpected error during Notion export for creative #{creative.id}: #{e.message}")
|
|
26
|
-
raise e
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|