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
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
module Collavre
|
|
2
|
-
require "digest"
|
|
3
|
-
|
|
4
|
-
class NotionService
|
|
5
|
-
def initialize(user:)
|
|
6
|
-
@user = user
|
|
7
|
-
@account = user.notion_account
|
|
8
|
-
raise NotionAuthError, "No Notion account found" unless @account
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def client
|
|
12
|
-
@client ||= NotionClient.new(@account)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def search_pages(query: nil, start_cursor: nil, page_size: 10)
|
|
16
|
-
with_token_refresh { client.search_pages(query: query, start_cursor: start_cursor, page_size: page_size) }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def get_page(page_id)
|
|
20
|
-
with_token_refresh { client.get_page(page_id) }
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def create_page(parent_id:, title:, blocks: [])
|
|
24
|
-
with_token_refresh { client.create_page(parent_id: parent_id, title: title, blocks: blocks) }
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def update_page(page_id, properties: {}, blocks: nil)
|
|
28
|
-
with_token_refresh { client.update_page(page_id, properties: properties, blocks: blocks) }
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def get_page_blocks(page_id, start_cursor: nil, page_size: 100)
|
|
32
|
-
with_token_refresh { client.get_page_blocks(page_id, start_cursor: start_cursor, page_size: page_size) }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def replace_page_blocks(page_id, blocks)
|
|
36
|
-
with_token_refresh { client.replace_page_blocks(page_id, blocks) }
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def append_blocks(parent_id, blocks)
|
|
40
|
-
with_token_refresh { client.append_blocks(parent_id, blocks) }
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def delete_block(block_id)
|
|
44
|
-
with_token_refresh { client.delete_block(block_id) }
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def get_workspace
|
|
48
|
-
with_token_refresh { client.get_workspace }
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Create or update a page for a creative
|
|
52
|
-
def sync_creative(creative, parent_page_id: nil)
|
|
53
|
-
notion_link = find_or_create_page_link(creative, parent_page_id)
|
|
54
|
-
|
|
55
|
-
if notion_link.page_id.present?
|
|
56
|
-
# Update existing page
|
|
57
|
-
update_creative_page(creative, notion_link)
|
|
58
|
-
else
|
|
59
|
-
# Create new page
|
|
60
|
-
create_creative_page(creative, notion_link, parent_page_id)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
notion_link.mark_synced!
|
|
64
|
-
notion_link
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
private
|
|
68
|
-
|
|
69
|
-
def with_token_refresh(&block)
|
|
70
|
-
yield
|
|
71
|
-
rescue NotionAuthError => e
|
|
72
|
-
if refresh_token!
|
|
73
|
-
@client = nil # Reset client with new token
|
|
74
|
-
yield
|
|
75
|
-
else
|
|
76
|
-
raise e
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def refresh_token!
|
|
81
|
-
# Notion uses OAuth 2.0 but doesn't issue refresh tokens in the same way as Google
|
|
82
|
-
# For now, we'll just log the error and return false
|
|
83
|
-
# In a production app, you'd implement proper token refresh logic here
|
|
84
|
-
Rails.logger.error("Notion token refresh needed but not implemented")
|
|
85
|
-
false
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def find_or_create_page_link(creative, parent_page_id)
|
|
89
|
-
@account.notion_page_links.find_or_initialize_by(creative: creative) do |link|
|
|
90
|
-
link.parent_page_id = parent_page_id
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def create_creative_page(creative, notion_link, parent_page_id)
|
|
95
|
-
title = ActionController::Base.helpers.strip_tags(creative.description).strip.presence || "Untitled Creative"
|
|
96
|
-
|
|
97
|
-
# Export only the children - the page title serves as the root creative
|
|
98
|
-
children = creative.children.to_a
|
|
99
|
-
Rails.logger.info("NotionService: Exporting creative #{creative.id} as page title with #{children.count} children as blocks")
|
|
100
|
-
|
|
101
|
-
exporter = NotionCreativeExporter.new(creative)
|
|
102
|
-
blocks = []
|
|
103
|
-
|
|
104
|
-
# If no parent specified, search for a suitable workspace page
|
|
105
|
-
parent_page_id ||= find_default_parent_page
|
|
106
|
-
|
|
107
|
-
response = create_page(
|
|
108
|
-
parent_id: parent_page_id,
|
|
109
|
-
title: title,
|
|
110
|
-
blocks: blocks
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
notion_link.update!(
|
|
114
|
-
page_id: response["id"],
|
|
115
|
-
page_title: title,
|
|
116
|
-
page_url: response["url"],
|
|
117
|
-
parent_page_id: parent_page_id
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
sync_child_blocks(notion_link, creative, children, exporter)
|
|
121
|
-
|
|
122
|
-
response
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def update_creative_page(creative, notion_link)
|
|
126
|
-
title = ActionController::Base.helpers.strip_tags(creative.description).strip.presence || "Untitled Creative"
|
|
127
|
-
|
|
128
|
-
# Update with only the children - page title serves as the root creative
|
|
129
|
-
children = creative.children.to_a
|
|
130
|
-
Rails.logger.info("NotionService: Updating creative #{creative.id} as page title with #{children.count} children as blocks")
|
|
131
|
-
|
|
132
|
-
exporter = NotionCreativeExporter.new(creative)
|
|
133
|
-
|
|
134
|
-
properties = {
|
|
135
|
-
title: {
|
|
136
|
-
title: [ { text: { content: title } } ]
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
update_page(notion_link.page_id, properties: properties)
|
|
141
|
-
notion_link.update!(page_title: title)
|
|
142
|
-
|
|
143
|
-
sync_child_blocks(notion_link, creative, children, exporter)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def find_default_parent_page
|
|
147
|
-
# Search for pages in the workspace to find a suitable parent
|
|
148
|
-
pages = search_pages(page_size: 1)
|
|
149
|
-
pages.dig("results")&.first&.dig("id") || raise(NotionError, "No accessible pages found in workspace")
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def sync_child_blocks(notion_link, creative, children, exporter)
|
|
153
|
-
child_ids = children.map(&:id)
|
|
154
|
-
existing_links = notion_link.notion_block_links.includes(:creative).order(:created_at).to_a
|
|
155
|
-
existing_links_by_creative = existing_links.group_by(&:creative_id)
|
|
156
|
-
|
|
157
|
-
page_blocks = existing_links.any? ? fetch_all_page_blocks(notion_link.page_id) : []
|
|
158
|
-
page_block_ids = page_blocks.map { |block| block["id"] }
|
|
159
|
-
|
|
160
|
-
block_to_creative = {}
|
|
161
|
-
existing_links.each do |link|
|
|
162
|
-
block_to_creative[link.block_id] = link.creative_id if page_block_ids.include?(link.block_id)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
existing_order = page_block_ids.map { |block_id| block_to_creative[block_id] }.compact.uniq
|
|
166
|
-
expected_order = child_ids.select { |id| existing_links_by_creative.key?(id) }
|
|
167
|
-
reorder_detected = existing_order != expected_order
|
|
168
|
-
|
|
169
|
-
removed_ids = existing_links_by_creative.keys - child_ids
|
|
170
|
-
changes_detected = removed_ids.any?
|
|
171
|
-
|
|
172
|
-
child_exports = children.map do |child|
|
|
173
|
-
exported_blocks = exporter.export_tree_blocks([ child ], 1, 0)
|
|
174
|
-
content_hash = Digest::SHA256.hexdigest(exported_blocks.to_json)
|
|
175
|
-
links = existing_links_by_creative[child.id] || []
|
|
176
|
-
missing_blocks = links.any? { |link| !page_block_ids.include?(link.block_id) }
|
|
177
|
-
|
|
178
|
-
if exported_blocks.empty?
|
|
179
|
-
changes_detected ||= links.present?
|
|
180
|
-
else
|
|
181
|
-
changes_detected ||= links.blank?
|
|
182
|
-
changes_detected ||= links.size != exported_blocks.size
|
|
183
|
-
changes_detected ||= links.first.content_hash != content_hash
|
|
184
|
-
changes_detected ||= missing_blocks
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
{
|
|
188
|
-
child: child,
|
|
189
|
-
exported_blocks: exported_blocks,
|
|
190
|
-
content_hash: content_hash
|
|
191
|
-
}
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
unless changes_detected || reorder_detected
|
|
195
|
-
return
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
blocks_to_clear = page_blocks.presence || fetch_all_page_blocks(notion_link.page_id)
|
|
199
|
-
blocks_to_clear.each do |block|
|
|
200
|
-
begin
|
|
201
|
-
delete_block(block["id"])
|
|
202
|
-
rescue NotionError => e
|
|
203
|
-
Rails.logger.warn("NotionService: Failed to delete Notion block #{block['id']} during resync: #{e.message}")
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
NotionBlockLink.transaction do
|
|
208
|
-
notion_link.notion_block_links.delete_all
|
|
209
|
-
|
|
210
|
-
child_exports.each do |data|
|
|
211
|
-
exported_blocks = data[:exported_blocks]
|
|
212
|
-
next if exported_blocks.empty?
|
|
213
|
-
|
|
214
|
-
response = append_blocks(notion_link.page_id, exported_blocks)
|
|
215
|
-
response_results = response.is_a?(Hash) ? response.fetch("results", []) : []
|
|
216
|
-
new_block_ids = response_results.filter_map { |result| result["id"] }
|
|
217
|
-
|
|
218
|
-
if new_block_ids.empty?
|
|
219
|
-
Rails.logger.warn("NotionService: Unable to determine new block ids for creative #{data[:child].id}")
|
|
220
|
-
next
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
new_block_ids.each do |block_id|
|
|
224
|
-
notion_link.notion_block_links.create!(
|
|
225
|
-
creative: data[:child],
|
|
226
|
-
block_id: block_id,
|
|
227
|
-
content_hash: data[:content_hash]
|
|
228
|
-
)
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def fetch_all_page_blocks(page_id)
|
|
235
|
-
blocks = []
|
|
236
|
-
cursor = nil
|
|
237
|
-
|
|
238
|
-
loop do
|
|
239
|
-
response = get_page_blocks(page_id, start_cursor: cursor)
|
|
240
|
-
blocks.concat(response.fetch("results", []))
|
|
241
|
-
break unless response["has_more"]
|
|
242
|
-
|
|
243
|
-
cursor = response["next_cursor"]
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
blocks
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
end
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
<div id="notion-integration-modal"
|
|
2
|
-
data-success-message="<%= t('collavre.creatives.index.notion_integration_saved', default: 'Notion integration saved successfully.') %>"
|
|
3
|
-
data-login-required="<%= t('collavre.creatives.index.notion_integration_login_required', default: 'Sign in with your Notion account to start the integration.') %>"
|
|
4
|
-
data-no-creative="<%= t('collavre.creatives.index.notion_integration_missing_creative', default: 'No Creative selected for integration.') %>"
|
|
5
|
-
data-existing-message="<%= t('collavre.creatives.index.notion_integration_existing_message', default: 'You\'re already connected to Notion pages below.') %>"
|
|
6
|
-
data-delete-confirm="<%= t('collavre.creatives.index.notion_integration_delete_confirm', default: 'Do you want to remove the Notion integration?') %>"
|
|
7
|
-
data-delete-success="<%= t('collavre.creatives.index.notion_integration_delete_success', default: 'Notion integration removed successfully.') %>"
|
|
8
|
-
data-delete-error="<%= t('collavre.creatives.index.notion_integration_delete_error', default: 'Failed to remove the Notion integration.') %>"
|
|
9
|
-
data-delete-button-label="<%= t('collavre.creatives.index.notion_integration_delete_button', default: 'Remove integration') %>"
|
|
10
|
-
data-export-success="<%= t('collavre.creatives.index.notion_export_success', default: 'Creative exported to Notion successfully.') %>"
|
|
11
|
-
data-sync-success="<%= t('collavre.creatives.index.notion_sync_success', default: 'Creative synced to Notion successfully.') %>"
|
|
12
|
-
style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;align-items:center;justify-content:center;">
|
|
13
|
-
<div class="popup-box" style="min-width:360px;max-width:90vw;">
|
|
14
|
-
<button type="button" id="close-notion-modal" class="popup-close-btn">×</button>
|
|
15
|
-
<h2><%= t('collavre.creatives.index.notion_integration_title', default: 'Configure Notion integration') %></h2>
|
|
16
|
-
<p id="notion-integration-status" class="notion-modal-status"></p>
|
|
17
|
-
|
|
18
|
-
<div class="notion-wizard-step" id="notion-step-connect">
|
|
19
|
-
<p id="notion-connect-message" class="notion-modal-subtext"><%= t('collavre.creatives.index.notion_integration_connect', default: 'Sign in with your Notion account to start exporting.') %></p>
|
|
20
|
-
<form id="notion-login-form" action="/auth/notion" method="get" target="notion-auth-window" style="display:none;">
|
|
21
|
-
</form>
|
|
22
|
-
<button type="button" id="notion-login-btn" class="btn btn-primary" data-window-width="620" data-window-height="720">
|
|
23
|
-
<%= t('collavre.creatives.index.notion_login_button', default: 'Sign in with Notion') %>
|
|
24
|
-
</button>
|
|
25
|
-
<div id="notion-existing-connections" style="display:none;margin-top:1.25em;">
|
|
26
|
-
<p style="margin-bottom:0.5em;"><%= t('collavre.creatives.index.notion_integration_existing_intro', default: 'Linked Notion pages:') %></p>
|
|
27
|
-
<ul id="notion-existing-page-list" style="padding-left:1.2em;margin-bottom:0.75em;color:var(--color-text);"></ul>
|
|
28
|
-
<div style="display:flex;gap:0.5em;">
|
|
29
|
-
<button type="button" id="notion-sync-btn" class="btn btn-secondary" style="display:none;">
|
|
30
|
-
<%= t('collavre.creatives.index.notion_sync_button', default: 'Sync to Notion') %>
|
|
31
|
-
</button>
|
|
32
|
-
<button type="button" id="notion-delete-btn" class="btn btn-danger" style="display:none;">
|
|
33
|
-
<%= t('collavre.creatives.index.notion_integration_delete_button', default: 'Remove link') %>
|
|
34
|
-
</button>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
<div class="notion-wizard-step" id="notion-step-workspace" style="display:none;">
|
|
40
|
-
<p class="notion-modal-subtext"><%= t('collavre.creatives.index.notion_integration_workspace', default: 'Your Notion workspace is connected. Choose how to export your creative.') %></p>
|
|
41
|
-
<div id="notion-workspace-info" style="margin-bottom:1em;padding:1em;background:var(--color-bg-alt);border-radius:4px;">
|
|
42
|
-
<strong id="notion-workspace-name"></strong>
|
|
43
|
-
</div>
|
|
44
|
-
<div style="margin-bottom:1em;">
|
|
45
|
-
<label style="display:block;margin-bottom:0.5em;">
|
|
46
|
-
<%= t('collavre.creatives.index.notion_export_option', default: 'Export option:') %>
|
|
47
|
-
</label>
|
|
48
|
-
<div style="display:flex;flex-direction:column;gap:0.5em;">
|
|
49
|
-
<label style="display:flex;align-items:center;gap:0.5em;">
|
|
50
|
-
<input type="radio" name="notion-export-type" value="new-page" checked>
|
|
51
|
-
<%= t('collavre.creatives.index.notion_export_new_page', default: 'Create new page') %>
|
|
52
|
-
</label>
|
|
53
|
-
<label style="display:flex;align-items:center;gap:0.5em;">
|
|
54
|
-
<input type="radio" name="notion-export-type" value="select-parent">
|
|
55
|
-
<%= t('collavre.creatives.index.notion_export_under_page', default: 'Create as subpage under existing page') %>
|
|
56
|
-
</label>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
<div id="notion-parent-page-section" style="display:none;margin-bottom:1em;">
|
|
60
|
-
<label for="notion-parent-page-select" style="display:block;margin-bottom:0.5em;">
|
|
61
|
-
<%= t('collavre.creatives.index.notion_parent_page', default: 'Parent page:') %>
|
|
62
|
-
</label>
|
|
63
|
-
<select id="notion-parent-page-select" style="width:100%;padding:0.5em;border:1px solid var(--color-border);border-radius:4px;">
|
|
64
|
-
<option value=""><%= t('collavre.creatives.index.notion_loading', default: 'Loading pages...') %></option>
|
|
65
|
-
</select>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<div class="notion-wizard-step" id="notion-step-summary" style="display:none;">
|
|
70
|
-
<p class="notion-modal-subtext"><%= t('collavre.creatives.index.notion_integration_summary', default: 'Ready to export your creative tree to Notion:') %></p>
|
|
71
|
-
<div id="notion-export-summary" style="margin:1em 0;padding:1em;background:var(--color-bg-alt);border-radius:4px;">
|
|
72
|
-
<div><strong><%= t('collavre.creatives.index.notion_creative_title', default: 'Root Creative:') %></strong> <span id="notion-creative-title"></span></div>
|
|
73
|
-
<div><strong><%= t('collavre.creatives.index.notion_workspace', default: 'Workspace:') %></strong> <span id="notion-workspace-summary"></span></div>
|
|
74
|
-
<div><strong><%= t('collavre.creatives.index.notion_export_as', default: 'Export as:') %></strong> <span id="notion-export-type-summary"></span></div>
|
|
75
|
-
<div id="notion-parent-summary" style="display:none;"><strong><%= t('collavre.creatives.index.notion_parent_page', default: 'Parent page:') %></strong> <span id="notion-parent-page-summary"></span></div>
|
|
76
|
-
<div style="margin-top:0.5em;color:var(--color-text-secondary);font-size:0.9em;"><%= t('collavre.creatives.index.notion_tree_note', default: 'This will export the selected creative and all its descendants as a structured document.') %></div>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<div id="notion-wizard-error" style="display:none;margin:0.5em 0;color:#c0392b;font-weight:bold;"></div>
|
|
81
|
-
|
|
82
|
-
<div class="notion-wizard-footer" style="display:flex;justify-content:space-between;gap:0.5em;margin-top:1.5em;">
|
|
83
|
-
<button type="button" id="notion-prev-btn" class="btn btn-secondary" style="display:none;"><%= t('app.previous', default: 'Previous') %></button>
|
|
84
|
-
<div style="margin-left:auto;display:flex;gap:0.5em;">
|
|
85
|
-
<button type="button" id="notion-next-btn" class="btn btn-primary" style="display:none;"><%= t('app.next', default: 'Next') %></button>
|
|
86
|
-
<button type="button" id="notion-export-btn" class="btn btn-primary" style="display:none;"><%= t('collavre.creatives.index.notion_export_button', default: 'Export to Notion') %></button>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
class CreateNotionIntegrations < ActiveRecord::Migration[8.0]
|
|
2
|
-
def change
|
|
3
|
-
create_table :notion_accounts do |t|
|
|
4
|
-
t.references :user, null: false, foreign_key: true, index: { unique: true }
|
|
5
|
-
t.string :notion_uid, null: false
|
|
6
|
-
t.string :workspace_name
|
|
7
|
-
t.string :workspace_id
|
|
8
|
-
t.string :bot_id
|
|
9
|
-
t.string :token, null: false
|
|
10
|
-
t.datetime :token_expires_at
|
|
11
|
-
t.timestamps
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
create_table :notion_page_links do |t|
|
|
15
|
-
t.references :creative, null: false, foreign_key: true
|
|
16
|
-
t.references :notion_account, null: false, foreign_key: true
|
|
17
|
-
t.string :page_id, null: false
|
|
18
|
-
t.string :page_title
|
|
19
|
-
t.string :page_url
|
|
20
|
-
t.string :parent_page_id
|
|
21
|
-
t.datetime :last_synced_at
|
|
22
|
-
t.timestamps
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
add_index :notion_accounts, :notion_uid, unique: true
|
|
26
|
-
add_index :notion_page_links, :page_id, unique: true
|
|
27
|
-
add_index :notion_page_links, [ :creative_id, :page_id ], unique: true, name: "index_notion_links_on_creative_and_page"
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
class CreateNotionBlockLinks < ActiveRecord::Migration[8.0]
|
|
2
|
-
def change
|
|
3
|
-
create_table :notion_block_links do |t|
|
|
4
|
-
t.references :notion_page_link, null: false, foreign_key: true, index: false
|
|
5
|
-
t.references :creative, null: false, foreign_key: true, index: false
|
|
6
|
-
t.string :block_id, null: false
|
|
7
|
-
t.string :content_hash
|
|
8
|
-
t.timestamps
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
add_index :notion_block_links, :notion_page_link_id, name: "index_notion_block_links_on_notion_page_link_id"
|
|
12
|
-
add_index :notion_block_links, :creative_id
|
|
13
|
-
add_index :notion_block_links, [ :notion_page_link_id, :creative_id ], unique: true, name: "index_notion_block_links_on_page_link_and_creative"
|
|
14
|
-
add_index :notion_block_links, [ :notion_page_link_id, :block_id ], unique: true, name: "index_notion_block_links_on_page_link_and_block"
|
|
15
|
-
end
|
|
16
|
-
end
|