collavre 0.20.3 β†’ 0.22.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.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/collavre/actiontext.css +92 -2
  3. data/app/assets/stylesheets/collavre/code_highlight.css +144 -26
  4. data/app/assets/stylesheets/collavre/comments_popup.css +133 -2
  5. data/app/assets/stylesheets/collavre/landing.css +507 -0
  6. data/app/assets/stylesheets/collavre/popup.css +148 -0
  7. data/app/channels/collavre/agent_channel.rb +205 -0
  8. data/app/channels/collavre/comments_presence_channel.rb +7 -0
  9. data/app/controllers/collavre/admin/integrations_controller.rb +93 -0
  10. data/app/controllers/collavre/admin/settings_controller.rb +22 -17
  11. data/app/controllers/collavre/api/v1/agents_controller.rb +777 -0
  12. data/app/controllers/collavre/api/v1/base_controller.rb +46 -0
  13. data/app/controllers/collavre/application_controller.rb +27 -0
  14. data/app/controllers/collavre/attachments_controller.rb +30 -2
  15. data/app/controllers/collavre/channels_controller.rb +23 -0
  16. data/app/controllers/collavre/comments_controller.rb +1 -1
  17. data/app/controllers/collavre/creatives/attachments_controller.rb +79 -0
  18. data/app/controllers/collavre/creatives_controller.rb +141 -7
  19. data/app/controllers/collavre/landing_controller.rb +8 -0
  20. data/app/controllers/collavre/public_assets_controller.rb +24 -0
  21. data/app/controllers/collavre/tasks_controller.rb +12 -4
  22. data/app/controllers/collavre/topics_controller.rb +36 -30
  23. data/app/controllers/concerns/collavre/comments/approval_actions.rb +57 -14
  24. data/app/helpers/collavre/comments_helper.rb +7 -0
  25. data/app/helpers/collavre/public_assets_helper.rb +14 -0
  26. data/app/javascript/components/InlineLexicalEditor.jsx +42 -59
  27. data/app/javascript/components/plugins/attachment_cleanup_plugin.jsx +3 -0
  28. data/app/javascript/components/plugins/image_upload_plugin.jsx +12 -0
  29. data/app/javascript/controllers/__tests__/link_creative_controller.test.js +447 -0
  30. data/app/javascript/controllers/comment_controller.js +15 -1
  31. data/app/javascript/controllers/comments/__tests__/presence_controller.test.js +108 -0
  32. data/app/javascript/controllers/comments/form_controller.js +4 -0
  33. data/app/javascript/controllers/comments/list_controller.js +27 -9
  34. data/app/javascript/controllers/comments/popup_controller.js +9 -0
  35. data/app/javascript/controllers/comments/presence_controller.js +137 -4
  36. data/app/javascript/controllers/comments/topics_controller.js +15 -0
  37. data/app/javascript/controllers/creatives/__tests__/sync_controller.test.js +89 -0
  38. data/app/javascript/controllers/creatives/__tests__/tree_controller.test.js +120 -0
  39. data/app/javascript/controllers/creatives/sync_controller.js +30 -9
  40. data/app/javascript/controllers/creatives/tree_controller.js +23 -0
  41. data/app/javascript/controllers/index.js +4 -1
  42. data/app/javascript/controllers/landing_video_controller.js +53 -0
  43. data/app/javascript/controllers/link_creative_controller.js +451 -29
  44. data/app/javascript/creatives/tree_renderer.js +6 -0
  45. data/app/javascript/lib/api/__tests__/queue_manager.test.js +27 -0
  46. data/app/javascript/lib/api/creatives.js +13 -0
  47. data/app/javascript/lib/api/queue_manager.js +17 -5
  48. data/app/javascript/lib/lexical/__tests__/color_import.test.js +318 -0
  49. data/app/javascript/lib/lexical/__tests__/minimize_html.test.js +259 -0
  50. data/app/javascript/lib/lexical/color_import.js +186 -0
  51. data/app/javascript/lib/lexical/minimize_html.js +182 -0
  52. data/app/javascript/lib/lexical/video_node.jsx +96 -0
  53. data/app/javascript/modules/__tests__/command_args_form.test.js +103 -0
  54. data/app/javascript/modules/__tests__/html_content_empty.test.js +41 -0
  55. data/app/javascript/modules/__tests__/markdown_source_reconcile.test.js +70 -0
  56. data/app/javascript/modules/command_args_form.js +22 -4
  57. data/app/javascript/modules/command_menu.js +27 -0
  58. data/app/javascript/modules/creative_row_editor.js +227 -17
  59. data/app/javascript/modules/html_content_empty.js +12 -0
  60. data/app/javascript/modules/markdown_source_reconcile.js +53 -0
  61. data/app/jobs/collavre/ai_agent_job.rb +89 -3
  62. data/app/jobs/collavre/cancel_offline_delegated_tasks_job.rb +134 -0
  63. data/app/jobs/collavre/claude_channel_presence_job.rb +91 -0
  64. data/app/jobs/collavre/drop_trigger_job.rb +37 -8
  65. data/app/mailers/collavre/application_mailer.rb +1 -1
  66. data/app/models/collavre/agent_subscription.rb +52 -0
  67. data/app/models/collavre/channel/injected_message.rb +5 -0
  68. data/app/models/collavre/channel.rb +87 -0
  69. data/app/models/collavre/comment/claude_channel_permission.rb +145 -0
  70. data/app/models/collavre/comment.rb +70 -5
  71. data/app/models/collavre/creative/describable.rb +202 -3
  72. data/app/models/collavre/creative.rb +2 -0
  73. data/app/models/collavre/creative_share.rb +1 -0
  74. data/app/models/collavre/integration_setting.rb +35 -0
  75. data/app/models/collavre/preview_channel.rb +93 -0
  76. data/app/models/collavre/system_setting.rb +13 -2
  77. data/app/models/collavre/task.rb +34 -5
  78. data/app/models/collavre/topic.rb +8 -25
  79. data/app/models/collavre/user.rb +4 -0
  80. data/app/models/concerns/collavre/ai_agent_resolvable.rb +12 -3
  81. data/app/services/collavre/agent_session_abort.rb +28 -0
  82. data/app/services/collavre/ai_agent/claude_channel_adapter.rb +79 -0
  83. data/app/services/collavre/ai_agent/response_finalizer.rb +4 -2
  84. data/app/services/collavre/ai_agent_service.rb +68 -49
  85. data/app/services/collavre/ai_client.rb +3 -3
  86. data/app/services/collavre/attachment_backfill.rb +26 -0
  87. data/app/services/collavre/channel_attacher.rb +58 -0
  88. data/app/services/collavre/comments/mcp_command.rb +31 -1
  89. data/app/services/collavre/creatives/breadcrumb_resolver.rb +91 -0
  90. data/app/services/collavre/creatives/filter_pipeline.rb +26 -42
  91. data/app/services/collavre/creatives/filters/search_filter.rb +12 -2
  92. data/app/services/collavre/creatives/index_query.rb +110 -8
  93. data/app/services/collavre/creatives/permission_filter.rb +50 -0
  94. data/app/services/collavre/creatives/reveal_path_resolver.rb +118 -0
  95. data/app/services/collavre/creatives/tree_builder.rb +7 -3
  96. data/app/services/collavre/crons/recurring_task_arguments.rb +28 -0
  97. data/app/services/collavre/google_calendar_service.rb +4 -2
  98. data/app/services/collavre/markdown_converter.rb +130 -15
  99. data/app/services/collavre/markdown_importer.rb +7 -2
  100. data/app/services/collavre/orchestration/policy_resolver.rb +11 -1
  101. data/app/services/collavre/orchestration/stuck_detector.rb +22 -2
  102. data/app/services/collavre/tools/creative_attach_files_service.rb +62 -0
  103. data/app/services/collavre/tools/creative_list_attachments_service.rb +42 -0
  104. data/app/services/collavre/tools/creative_remove_attachment_service.rb +37 -0
  105. data/app/services/collavre/tools/cron_list_service.rb +1 -14
  106. data/app/services/collavre/tools/permission_denied_error.rb +9 -0
  107. data/app/services/collavre/tools/preview_attach_service.rb +128 -0
  108. data/app/services/collavre/tools/preview_detach_service.rb +61 -0
  109. data/app/services/collavre/tools/topic_authorizer.rb +24 -0
  110. data/app/services/collavre/topic_branch_service.rb +34 -26
  111. data/app/services/collavre/topics/orphaned_cron_notifier.rb +68 -0
  112. data/app/views/admin/shared/_tabs.html.erb +1 -0
  113. data/app/views/collavre/admin/integrations/_category.html.erb +22 -0
  114. data/app/views/collavre/admin/integrations/_setting_row.html.erb +70 -0
  115. data/app/views/collavre/admin/integrations/index.html.erb +42 -0
  116. data/app/views/collavre/admin/settings/_system_tab.html.erb +8 -0
  117. data/app/views/collavre/comments/_channel_chips.html.erb +33 -0
  118. data/app/views/collavre/comments/_comment.html.erb +16 -2
  119. data/app/views/collavre/comments/_comments_popup.html.erb +4 -1
  120. data/app/views/collavre/creatives/_inline_edit_form.html.erb +19 -0
  121. data/app/views/collavre/creatives/index.html.erb +10 -2
  122. data/app/views/collavre/landing/show.html.erb +130 -0
  123. data/app/views/collavre/shared/_link_creative_modal.html.erb +6 -2
  124. data/app/views/layouts/collavre/landing.html.erb +33 -0
  125. data/config/locales/admin.en.yml +4 -2
  126. data/config/locales/admin.ko.yml +4 -2
  127. data/config/locales/channels.en.yml +13 -0
  128. data/config/locales/channels.ko.yml +13 -0
  129. data/config/locales/claude_channel.en.yml +16 -0
  130. data/config/locales/claude_channel.ko.yml +16 -0
  131. data/config/locales/comments.en.yml +5 -0
  132. data/config/locales/comments.ko.yml +5 -0
  133. data/config/locales/creatives.en.yml +11 -0
  134. data/config/locales/creatives.ko.yml +10 -0
  135. data/config/locales/integrations.en.yml +55 -0
  136. data/config/locales/integrations.ko.yml +55 -0
  137. data/config/locales/landing.en.yml +51 -0
  138. data/config/locales/landing.ko.yml +51 -0
  139. data/config/routes.rb +30 -0
  140. data/db/migrate/20260526000000_create_channels.rb +42 -0
  141. data/db/migrate/20260527000000_add_dismissed_at_to_channels.rb +6 -0
  142. data/db/migrate/20260527000100_backfill_dismissed_at_for_legacy_detached_channels.rb +28 -0
  143. data/db/migrate/20260528000000_add_preview_channel_unique_index.rb +31 -0
  144. data/db/migrate/20260529000000_add_primary_agent_id_to_topics.rb +40 -0
  145. data/db/migrate/20260529100000_create_integration_settings.rb +15 -0
  146. data/db/migrate/20260609000000_drop_action_text_rich_texts.rb +20 -0
  147. data/db/migrate/20260609005000_add_session_id_to_topics.rb +16 -0
  148. data/db/migrate/20260609010000_create_agent_subscriptions.rb +19 -0
  149. data/db/migrate/20260609020000_add_last_seen_at_to_agent_subscriptions.rb +23 -0
  150. data/db/migrate/20260609030000_add_session_id_to_agent_subscriptions.rb +17 -0
  151. data/db/migrate/20260609190659_backfill_creative_files_into_description.rb +24 -0
  152. data/db/seeds.rb +19 -0
  153. data/lib/collavre/aws_credentials.rb +75 -0
  154. data/lib/collavre/engine.rb +50 -0
  155. data/lib/collavre/integration_settings/key_definition.rb +35 -0
  156. data/lib/collavre/integration_settings/registry.rb +60 -0
  157. data/lib/collavre/integration_settings/resolver.rb +71 -0
  158. data/lib/collavre/integration_settings.rb +46 -0
  159. data/lib/collavre/ses_settings_interceptor.rb +72 -0
  160. data/lib/collavre/version.rb +1 -1
  161. data/lib/collavre.rb +3 -0
  162. metadata +82 -2
  163. data/app/services/collavre/openclaw_abort_service.rb +0 -45
@@ -0,0 +1,42 @@
1
+ <h1 class="no-top-margin"><%= t("collavre.admin.integrations.title") %></h1>
2
+
3
+ <%= render "admin/shared/tabs" %>
4
+
5
+ <div class="tab-panels">
6
+ <section class="tab-panel active">
7
+ <%= tag.div(flash[:alert], class: "flash-alert") if flash[:alert] %>
8
+ <%= tag.div(flash[:warning], class: "flash-warning") if flash[:warning] %>
9
+ <%= tag.div(flash[:notice], class: "flash-notice") if flash[:notice] %>
10
+
11
+ <p style="color: var(--color-muted); margin-bottom: 1.5em;">
12
+ <%= t("collavre.admin.integrations.subtitle") %>
13
+ </p>
14
+
15
+ <% if @grouped_settings.empty? %>
16
+ <p class="empty-state" style="padding: 2em; text-align: center; color: var(--color-muted);">
17
+ <%= t("collavre.admin.integrations.empty_state") %>
18
+ </p>
19
+ <% else %>
20
+ <%# Bulk-save form is an empty container. Row inputs reference it via the HTML5
21
+ `form` attribute; per-row Reset/Seed `button_to` forms sit as siblings,
22
+ so we never nest <form> inside <form>. %>
23
+ <%= form_with url: collavre.bulk_update_admin_integrations_path,
24
+ method: :patch,
25
+ local: true,
26
+ id: "integrations-bulk-form",
27
+ html: { id: "integrations-bulk-form", class: "profile-form" } do %>
28
+ <% end %>
29
+
30
+ <% @grouped_settings.each do |category, rows| %>
31
+ <%= render partial: "category", locals: { category: category, rows: rows, bulk_form_id: "integrations-bulk-form" } %>
32
+ <% end %>
33
+
34
+ <div style="margin-top: 2em;">
35
+ <%= button_tag t("collavre.admin.integrations.actions.save"),
36
+ type: :submit,
37
+ form: "integrations-bulk-form",
38
+ class: "btn btn-primary" %>
39
+ </div>
40
+ <% end %>
41
+ </section>
42
+ </div>
@@ -38,6 +38,14 @@
38
38
  </div>
39
39
  </div>
40
40
 
41
+ <div style="margin-top: 1.5em;">
42
+ <%= f.label :home_page_path_authenticated, t('admin.settings.home_page_path_authenticated') %>
43
+ <%= f.text_field :home_page_path_authenticated, value: @home_page_path_authenticated, placeholder: "/creatives" %>
44
+ <div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
45
+ <%= t('admin.settings.home_page_path_authenticated_hint') %>
46
+ </div>
47
+ </div>
48
+
41
49
  <div style="margin-top: 2em; border-top: 1px solid var(--color-border); padding-top: 1.5em;">
42
50
  <h3 style="font-size: 1.1em; margin-bottom: 1em;"><%= t('admin.settings.account_lockout') %></h3>
43
51
  <div style="display: flex; gap: 2em; flex-wrap: wrap;">
@@ -0,0 +1,33 @@
1
+ <%# locals: { topic: Collavre::Topic } %>
2
+ <div class="channel-chips" data-comments--presence-target="channelChips" data-topic-id="<%= topic.id %>">
3
+ <% topic.channels.not_dismissed.each do |channel| %>
4
+ <%# Chip is kept after detach so the final closed/stopped badge stays
5
+ visible until the user dismisses it with the X button. Subclasses
6
+ drive the colored badge via badge_state / badge_title; returning nil
7
+ from badge_state hides the badge entirely. %>
8
+ <% badge_state = channel.badge_state %>
9
+ <% badge_title = channel.badge_title %>
10
+ <% chip_classes = [ "channel-chip" ]
11
+ chip_classes << "channel-chip--detached" if channel.detached? %>
12
+ <% chip_label = channel.latest_label.presence || channel.default_label.presence || channel.class.name.demodulize %>
13
+ <% chip_link = channel.latest_link.presence || channel.default_link.presence %>
14
+ <span class="<%= chip_classes.join(' ') %>" data-channel-id="<%= channel.id %>">
15
+ <% if badge_state %>
16
+ <span class="channel-chip-badge channel-chip-badge--<%= badge_state %>"
17
+ title="<%= badge_title %>"
18
+ aria-label="<%= badge_title %>"></span>
19
+ <% end %>
20
+ <% if chip_link %>
21
+ <a href="<%= chip_link %>" target="_blank" rel="noopener">
22
+ <%= chip_label %>
23
+ </a>
24
+ <% else %>
25
+ <%= chip_label %>
26
+ <% end %>
27
+ <button type="button"
28
+ data-action="comments--presence#detachChannel"
29
+ data-channel-id="<%= channel.id %>"
30
+ aria-label="<%= t('collavre.channels.detach', default: 'Detach') %>">&times;</button>
31
+ </span>
32
+ <% end %>
33
+ </div>
@@ -36,7 +36,11 @@
36
36
  <span class="comment-status-label private-label">πŸ”’ <%= t('collavre.comments.private') %></span>
37
37
  <% end %>
38
38
  <% if comment.action_executed_at.present? %>
39
- <span class="comment-status-label approved-label">βœ… <%= t('collavre.comments.approved_label') %></span>
39
+ <% if comment.claude_channel_permission_denied? %>
40
+ <span class="comment-status-label denied-label">🚫 <%= t('collavre.comments.denied_label') %></span>
41
+ <% else %>
42
+ <span class="comment-status-label approved-label">βœ… <%= t('collavre.comments.approved_label') %></span>
43
+ <% end %>
40
44
  <% end %>
41
45
  <% can_convert_comment = comment.user == Current.user || comment.creative.has_permission?(Current.user, :admin) %>
42
46
  </div>
@@ -57,6 +61,11 @@
57
61
  <button class="approve-comment-btn comment-approve-hidden" data-comment-target="approveButton" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.approve_button') %>">
58
62
  <%= t('collavre.comments.approve_button') %>
59
63
  </button>
64
+ <% if comment.claude_channel_permission? %>
65
+ <button class="deny-comment-btn comment-approve-hidden" data-comment-target="denyButton" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.deny_button') %>">
66
+ <%= t('collavre.comments.deny_button') %>
67
+ </button>
68
+ <% end %>
60
69
  <% end %>
61
70
  <button class="edit-comment-btn comment-owner-only" data-comment-target="ownerButton" data-comment-id="<%= comment.id %>" data-comment-content="<%= comment.content %>" data-comment-private="<%= comment.private? %>" title="<%= t('collavre.comments.update_comment') %>">
62
71
  <%= t('collavre.comments.edit_button') %>
@@ -164,7 +173,12 @@
164
173
  <details class="comment-action-details">
165
174
  <summary class="comment-action-summary"><%= t("collavre.comments.action_summary") %></summary>
166
175
  <div class="comment-action-body">
167
- <pre class="comment-action-json" data-comment-action-json><%= formatted_comment_action(comment) %></pre>
176
+ <% action_md = comment_action_markdown(comment) %>
177
+ <% if action_md && !has_pending_action %>
178
+ <div class="comment-content comment-action-markdown"><%= action_md %></div>
179
+ <% else %>
180
+ <pre class="comment-action-json" data-comment-action-json><%= formatted_comment_action(comment) %></pre>
181
+ <% end %>
168
182
  <% if has_pending_action %>
169
183
  <div class="comment-action-approve-controls comment-approve-hidden" data-comment-target="actionApproveControls">
170
184
  <button class="edit-comment-action-btn" type="button" data-comment-id="<%= comment.id %>"><%= t("collavre.comments.edit_action_button") %></button>
@@ -120,7 +120,10 @@
120
120
  <div id="comments-list" data-comments--popup-target="list" data-comments--list-target="list"><%= t('app.loading') %></div>
121
121
  <div id="typing-indicator-row">
122
122
  <button type="button" id="scroll-prev-msg-btn" class="scroll-prev-msg-btn" data-action="click->comments--list#scrollToPreviousMessage" title="<%= t('collavre.comments.scroll_to_prev', default: 'Previous message') %>" aria-label="<%= t('collavre.comments.scroll_to_prev', default: 'Previous message') %>">&#8963;</button>
123
- <div id="typing-indicator" data-comments--presence-target="typingIndicator" data-comments--popup-target="typingIndicator" data-stop-agent-text="<%= t('collavre.comments.stop_agent') %>"></div>
123
+ <div id="typing-scroll-viewport" data-comments--presence-target="scrollRow">
124
+ <div id="channel-chips-container" data-comments--presence-target="channelChips"></div>
125
+ <div id="typing-indicator" data-comments--presence-target="typingIndicator" data-comments--popup-target="typingIndicator" data-stop-agent-text="<%= t('collavre.comments.stop_agent') %>"></div>
126
+ </div>
124
127
  </div>
125
128
  <form id="new-comment-form" data-comments--popup-target="form" data-comments--form-target="form" style="display:none;">
126
129
  <input type="hidden" name="comment[quoted_comment_id]" data-comments--form-target="quotedCommentId" value="" />
@@ -3,6 +3,8 @@
3
3
  <input type="hidden" id="inline-method" name="_method" value="patch" />
4
4
  <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" />
5
5
  <input type="hidden" id="inline-creative-description" name="creative[description]" />
6
+ <input type="hidden" id="inline-content-type" name="creative[content_type_input]" value="html" />
7
+ <input type="hidden" id="inline-markdown-source" name="creative[markdown_source]" />
6
8
  <input type="hidden" id="inline-parent-id" name="creative[parent_id]" />
7
9
  <input type="hidden" id="inline-before-id" name="before_id" />
8
10
  <input type="hidden" id="inline-after-id" name="after_id" />
@@ -14,6 +16,14 @@
14
16
  data-direct-upload-url="<%= main_app.rails_direct_uploads_path %>"
15
17
  data-blob-url-template="<%= main_app.rails_service_blob_path(':signed_id', ':filename') %>"
16
18
  data-placeholder="<%= t('collavre.creatives.inline_editor.placeholder') %>"></div>
19
+ <div id="markdown-editor-wrapper" class="markdown-editor-wrapper" style="display:none;">
20
+ <textarea id="markdown-editor-textarea"
21
+ class="markdown-editor-textarea"
22
+ rows="10"
23
+ placeholder="<%= t('collavre.creatives.inline_editor.markdown_placeholder') %>"></textarea>
24
+ <div id="markdown-preview" class="markdown-preview"
25
+ data-placeholder="<%= t('collavre.creatives.inline_editor.markdown_preview_placeholder') %>"></div>
26
+ </div>
17
27
  <div id="actions-inline-editor" style="padding: 0px 8px;">
18
28
  <div style="margin-top:0.5em;display:flex;align-items:center;gap:0.5em;">
19
29
  <input type="hidden" name="creative[progress]" value="0">
@@ -30,6 +40,15 @@
30
40
  <button type="button" id="inline-metadata-btn" class="creative-action-btn" title="<%= t('collavre.creatives.index.metadata_tooltip') %>" style="margin-left:0.5em;font-family:monospace;font-size:0.9em;">
31
41
  { }
32
42
  </button>
43
+ <button type="button" id="inline-toggle-markdown" class="creative-action-btn"
44
+ title="<%= t('collavre.creatives.index.toggle_markdown') %>"
45
+ data-label-markdown="<%= t('collavre.creatives.index.toggle_markdown') %>"
46
+ data-label-richtext="<%= t('collavre.creatives.index.toggle_richtext') %>"
47
+ data-confirm-to-richtext="<%= t('collavre.creatives.index.markdown_to_richtext_confirm') %>"
48
+ data-confirm-to-markdown="<%= t('collavre.creatives.index.richtext_to_markdown_confirm') %>"
49
+ style="margin-left:0.5em;font-family:monospace;font-size:0.9em;">
50
+ <%= t('collavre.creatives.index.toggle_markdown') %>
51
+ </button>
33
52
  </div>
34
53
  <div style="margin-top:0.5em;">
35
54
  <button type="button" id="inline-move-up" class="creative-action-btn" title="<%= t('collavre.creatives.index.inline_move_up_tooltip') %>">
@@ -154,17 +154,25 @@
154
154
  title_row_content += content_tag(:template, data: { part: "edit-icon" }) { svg_tag("edit.svg", class: "icon-edit") }
155
155
  title_row_content += content_tag(:template, data: { part: "edit-off-icon" }) { svg_tag("edit-off.svg", class: "icon-edit") }
156
156
  %>
157
+ <%
158
+ title_can_write = @parent_creative.has_permission?(Current.user, :write)
159
+ title_effective_origin = @parent_creative.effective_origin(Set.new)
160
+ title_content_type = title_effective_origin.data&.dig("content_type")
161
+ title_markdown_source = title_can_write ? title_effective_origin.data&.dig("markdown_source") : nil
162
+ %>
157
163
  <%= content_tag(
158
164
  "creative-tree-row",
159
165
  title_row_content,
160
166
  "dom-id": "creative-markdown-block",
161
167
  "creative-id": @parent_creative.id,
162
168
  "parent-id": @parent_creative.parent_id,
163
- "can-write": @parent_creative.has_permission?(Current.user, :write) ? "true" : "false",
169
+ "can-write": title_can_write ? "true" : "false",
164
170
  "is-title": "",
165
171
  "data-description-raw-html": @parent_creative.description,
166
172
  "data-progress-value": @parent_creative.progress,
167
- "data-origin-id": @parent_creative.origin_id
173
+ "data-origin-id": @parent_creative.origin_id,
174
+ "data-content-type": title_content_type,
175
+ "data-markdown-source": title_markdown_source
168
176
  ) %>
169
177
  <% else %>
170
178
  <%= content_tag(
@@ -0,0 +1,130 @@
1
+ <%# Landing Page β€” Collavre %>
2
+
3
+ <div class="landing">
4
+ <%# ═══════════════════════════════════════════ HERO ══════════════════════════════════════════ %>
5
+ <section class="landing-hero">
6
+ <div class="landing-hero-glow"></div>
7
+ <div class="landing-container">
8
+ <h1 class="landing-hero-title"><%= t('collavre.landing.hero.headline_html') %></h1>
9
+ <p class="landing-hero-sub"><%= t('collavre.landing.hero.subline') %></p>
10
+ <p class="landing-hero-brand"><%= t('app.name') %></p>
11
+ <div class="landing-hero-actions">
12
+ <a href="#problem" class="landing-btn landing-btn-lg"><%= t('collavre.landing.hero.learn_more') %></a>
13
+ <%= link_to t('app.sign_in'), collavre.new_session_path, class: "landing-btn landing-btn-ghost landing-btn-lg" %>
14
+ </div>
15
+ </div>
16
+ </section>
17
+
18
+ <%# ══════════════════════════════════════ PROBLEM STATEMENT ══════════════════════════════════ %>
19
+ <section id="problem" class="landing-section landing-problem">
20
+ <div class="landing-container">
21
+ <h2 class="landing-section-title"><%= t('collavre.landing.problem.title') %></h2>
22
+ <div class="landing-problem-grid">
23
+ <div class="landing-problem-card">
24
+ <span class="landing-problem-icon">πŸ“</span>
25
+ <h3><%= t('collavre.landing.problem.notion.title') %></h3>
26
+ <p><%= t('collavre.landing.problem.notion.desc') %></p>
27
+ </div>
28
+ <div class="landing-problem-card">
29
+ <span class="landing-problem-icon">πŸ“‹</span>
30
+ <h3><%= t('collavre.landing.problem.jira.title') %></h3>
31
+ <p><%= t('collavre.landing.problem.jira.desc') %></p>
32
+ </div>
33
+ <div class="landing-problem-card">
34
+ <span class="landing-problem-icon">πŸ’¬</span>
35
+ <h3><%= t('collavre.landing.problem.slack.title') %></h3>
36
+ <p><%= t('collavre.landing.problem.slack.desc') %></p>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </section>
41
+
42
+ <%# ═══════════════════════════════════════ FEATURES ═════════════════════════════════════════ %>
43
+ <section id="features" class="landing-section landing-features">
44
+ <div class="landing-container">
45
+ <h2 class="landing-section-title"><%= t('collavre.landing.features.title') %></h2>
46
+ <p class="landing-section-sub"><%= t('collavre.landing.features.subtitle') %></p>
47
+
48
+ <div class="landing-features-grid">
49
+ <div class="landing-feature-card">
50
+ <div class="landing-feature-icon">🌳</div>
51
+ <h3><%= t('collavre.landing.features.tree.title') %></h3>
52
+ <p><%= t('collavre.landing.features.tree.desc') %></p>
53
+ </div>
54
+
55
+ <div class="landing-feature-card">
56
+ <div class="landing-feature-icon">πŸ€–</div>
57
+ <h3><%= t('collavre.landing.features.ai.title') %></h3>
58
+ <p><%= t('collavre.landing.features.ai.desc') %></p>
59
+ </div>
60
+
61
+ <div class="landing-feature-card">
62
+ <div class="landing-feature-icon">πŸ’‘</div>
63
+ <h3><%= t('collavre.landing.features.context.title') %></h3>
64
+ <p><%= t('collavre.landing.features.context.desc') %></p>
65
+ </div>
66
+
67
+ <div class="landing-feature-card">
68
+ <div class="landing-feature-icon">πŸ’¬</div>
69
+ <h3><%= t('collavre.landing.features.chat.title') %></h3>
70
+ <p><%= t('collavre.landing.features.chat.desc') %></p>
71
+ </div>
72
+
73
+
74
+ </div>
75
+ </div>
76
+ </section>
77
+
78
+ <%# ══════════════════════════════════════════ DEMO ══════════════════════════════════════════ %>
79
+ <section id="demo" class="landing-section landing-demo">
80
+ <div class="landing-container">
81
+ <h2 class="landing-section-title"><%= t('collavre.landing.demo.title') %></h2>
82
+ <%# Light mode video %>
83
+ <div class="landing-demo-frame landing-demo-light" data-controller="landing-video">
84
+ <video class="landing-demo-video"
85
+ data-landing-video-target="video"
86
+ autoplay muted loop playsinline
87
+ poster="/public-assets/blobs/eyJfcmFpbHMiOnsiZGF0YSI6NDEyLCJwdXIiOiJibG9iX2lkIn19--33811afcd451cef0cf10313fa1bfecca15b41299/demo-poster.jpg">
88
+ <source src="/public-assets/blobs/eyJfcmFpbHMiOnsiZGF0YSI6NDEwLCJwdXIiOiJibG9iX2lkIn19--c272341a19ed8e847208c52da0617ebc801e2e67/demo.mp4" type="video/mp4">
89
+ </video>
90
+ <div class="landing-demo-progress" data-landing-video-target="progressBar">
91
+ <div class="landing-demo-progress-fill" data-landing-video-target="progressFill"></div>
92
+ </div>
93
+ <button class="landing-demo-toggle" data-landing-video-target="toggle" data-action="click->landing-video#togglePlay">
94
+ <span class="landing-demo-toggle-icon" data-landing-video-target="icon">❚❚</span>
95
+ </button>
96
+ </div>
97
+ <%# Dark mode video %>
98
+ <div class="landing-demo-frame landing-demo-dark" data-controller="landing-video">
99
+ <video class="landing-demo-video"
100
+ data-landing-video-target="video"
101
+ autoplay muted loop playsinline
102
+ poster="/public-assets/blobs/eyJfcmFpbHMiOnsiZGF0YSI6NDEzLCJwdXIiOiJibG9iX2lkIn19--53c3c505596bca0befbbebb5958764a153ffee5e/demo-dark-poster.jpg">
103
+ <source src="/public-assets/blobs/eyJfcmFpbHMiOnsiZGF0YSI6NDExLCJwdXIiOiJibG9iX2lkIn19--758c90c311bb68f46a2bda87553e3cdfd800420b/demo-dark.mp4" type="video/mp4">
104
+ </video>
105
+ <div class="landing-demo-progress" data-landing-video-target="progressBar">
106
+ <div class="landing-demo-progress-fill" data-landing-video-target="progressFill"></div>
107
+ </div>
108
+ <button class="landing-demo-toggle" data-landing-video-target="toggle" data-action="click->landing-video#togglePlay">
109
+ <span class="landing-demo-toggle-icon" data-landing-video-target="icon">❚❚</span>
110
+ </button>
111
+ </div>
112
+ </div>
113
+ </section>
114
+
115
+ <%# ═══════════════════════════════════════ FINAL CTA ════════════════════════════════════════ %>
116
+ <section class="landing-section landing-cta">
117
+ <div class="landing-container">
118
+ <h2 class="landing-cta-title"><%= t('collavre.landing.cta.title') %></h2>
119
+ <p class="landing-cta-sub"><%= t('collavre.landing.cta.subtitle') %></p>
120
+ <%= link_to t('collavre.landing.cta.start'), collavre.new_user_path, class: "landing-btn landing-btn-lg" %>
121
+ </div>
122
+ </section>
123
+
124
+ <%# ═══════════════════════════════════════ FOOTER ═══════════════════════════════════════════ %>
125
+ <footer class="landing-footer">
126
+ <div class="landing-container landing-footer-inner">
127
+ <p class="landing-footer-copy">Β© <%= Date.today.year %> <%= t('app.name') %>. <%= t('collavre.landing.footer.rights') %></p>
128
+ </div>
129
+ </footer>
130
+ </div>
@@ -1,6 +1,10 @@
1
- <div id="link-creative-modal" class="common-popup" style="display:none;" data-controller="link-creative">
1
+ <div id="link-creative-modal" class="common-popup" style="display:none;" data-controller="link-creative"
2
+ data-link-creative-loading-text="<%= t('collavre.creatives.index.link_loading', default: 'Loading…') %>"
3
+ data-link-creative-no-results-text="<%= t('collavre.creatives.index.no_search_results', default: 'No creatives match your search.') %>"
4
+ data-link-creative-empty-text="<%= t('collavre.creatives.index.no_sub_creatives', default: 'No sub-creatives found.') %>"
5
+ data-link-creative-expand-text="<%= t('collavre.creatives.index.link_expand', default: 'Expand') %>">
2
6
  <button type="button" id="close-link-creative-modal" class="popup-close-btn" data-link-creative-target="close">&times;</button>
3
7
  <h3><%= t('collavre.creatives.index.link', default: 'Link') %></h3>
4
8
  <input type="text" id="link-creative-search" class="shared-input-surface" style="width:100%;margin-bottom:0.5em;" placeholder="<%= t('collavre.creatives.index.search_placeholder', default: 'Search creative') %>" data-link-creative-target="input">
5
- <ul id="link-creative-results" class="common-popup-list" data-popup-list data-link-creative-target="list"></ul>
9
+ <ul id="link-creative-results" class="common-popup-list link-creative-list" data-popup-list data-link-creative-target="list"></ul>
6
10
  </div>
@@ -0,0 +1,33 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= t('app.name') %> β€” <%= t('collavre.landing.meta.title') %></title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <meta name="description" content="<%= t('collavre.landing.meta.description') %>">
7
+ <meta property="og:title" content="<%= t('app.name') %> β€” <%= t('collavre.landing.meta.title') %>">
8
+ <meta property="og:description" content="<%= t('collavre.landing.meta.description') %>">
9
+ <%= csrf_meta_tags %>
10
+ <%= csp_meta_tag %>
11
+
12
+ <link rel="icon" href="/icon-1e3cf549d2.png" type="image/png">
13
+ <link rel="icon" href="/icon-1e3cf549d2.svg" type="image/svg+xml">
14
+ <link rel="apple-touch-icon" href="/icon-1e3cf549d2.png">
15
+
16
+ <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
17
+ <%= collavre_stylesheets %>
18
+ <%= stylesheet_link_tag "collavre/landing", "data-turbo-track": "reload" %>
19
+
20
+ <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true, type: "module" %>
21
+
22
+ <%# Admin-configured default themes for users without personal theme %>
23
+ <% default_styles = default_theme_styles %>
24
+ <% if default_styles.present? %>
25
+ <style id="default-theme-styles" data-turbo-track="reload"><%= default_styles %></style>
26
+ <% end %>
27
+ </head>
28
+ <body class="<%= body_theme_class %> landing-page">
29
+ <main>
30
+ <%= yield %>
31
+ </main>
32
+ </body>
33
+ </html>
@@ -13,8 +13,10 @@ en:
13
13
  mcp_tool_approval_hint: "Require system administrator approval for new MCP tools."
14
14
  creatives_login_required: "Require Login for Creatives"
15
15
  creatives_login_required_hint: "If checked, users must be logged in to view /creatives."
16
- home_page_path: "Home Page Path"
17
- home_page_path_hint: "Path to render when accessing '/'. The URL stays as '/' but shows content from the specified path. Leave empty to use default (/creatives). Examples: /creatives, /user"
16
+ home_page_path: "Home Page Path (Unauthenticated)"
17
+ home_page_path_hint: "Path to render for visitors who hit '/' while signed out. The URL stays as '/' but the response shows content from the specified path. Leave empty to use default (/creatives). Examples: /landing, /creatives"
18
+ home_page_path_authenticated: "Home Page Path (Authenticated)"
19
+ home_page_path_authenticated_hint: "Signed-in users who hit '/' are redirected to this path. Unlike the unauthenticated path, the URL actually changes. Leave empty to use default (/creatives). Set to '/' to disable the redirect and fall back to the unauthenticated path above. Examples: /creatives, /dashboard"
18
20
  home_page_path_invalid_url: "Home page path cannot be a full URL. Please enter a path like /creatives or /user."
19
21
  home_page_path_not_routable: "Home page path '%{path}' is not a valid route. Please enter an existing path."
20
22
  home_page_path_not_html: "Home page path '%{path}' does not serve HTML content. Please enter a path to an HTML page."
@@ -13,8 +13,10 @@ ko:
13
13
  mcp_tool_approval_hint: "μƒˆ MCP 도ꡬ 등둝 μ‹œ μ‹œμŠ€ν…œ κ΄€λ¦¬μžμ˜ 승인이 ν•„μš”ν•©λ‹ˆλ‹€."
14
14
  creatives_login_required: "Creatives 둜그인 ν•„μš”"
15
15
  creatives_login_required_hint: "체크 μ‹œ, λ‘œκ·ΈμΈμ„ ν•œ μ‚¬μš©μžλ§Œ /creatives 에 μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€."
16
- home_page_path: "ν™ˆ νŽ˜μ΄μ§€ 경둜"
17
- home_page_path_hint: "'/' 접속 μ‹œ ν‘œμ‹œν•  κ²½λ‘œμž…λ‹ˆλ‹€. URL은 '/'둜 μœ μ§€λ˜λ©° μ§€μ •λœ 경둜의 μ½˜ν…μΈ κ°€ ν‘œμ‹œλ©λ‹ˆλ‹€. λΉ„μ›Œλ‘λ©΄ κΈ°λ³Έκ°’(/creatives)을 μ‚¬μš©ν•©λ‹ˆλ‹€. 예: /creatives, /user"
16
+ home_page_path: "ν™ˆ νŽ˜μ΄μ§€ 경둜 (λΉ„λ‘œκ·ΈμΈ)"
17
+ home_page_path_hint: "λΉ„λ‘œκ·ΈμΈ μ‚¬μš©μžκ°€ '/' 접속 μ‹œ ν‘œμ‹œν•  κ²½λ‘œμž…λ‹ˆλ‹€. URL은 '/'둜 μœ μ§€λ˜λ©° μ§€μ •λœ 경둜의 μ½˜ν…μΈ κ°€ ν‘œμ‹œλ©λ‹ˆλ‹€. λΉ„μ›Œλ‘λ©΄ κΈ°λ³Έκ°’(/creatives)을 μ‚¬μš©ν•©λ‹ˆλ‹€. 예: /landing, /creatives"
18
+ home_page_path_authenticated: "ν™ˆ νŽ˜μ΄μ§€ 경둜 (둜그인)"
19
+ home_page_path_authenticated_hint: "둜그인 μ‚¬μš©μžκ°€ '/' 접속 μ‹œ 이 경둜둜 λ¦¬λ‹€μ΄λ ‰νŠΈλ©λ‹ˆλ‹€. λΉ„λ‘œκ·ΈμΈ κ²½λ‘œμ™€ 달리 URL이 μ‹€μ œλ‘œ λ³€κ²½λ©λ‹ˆλ‹€. λΉ„μ›Œλ‘λ©΄ κΈ°λ³Έκ°’(/creatives)을 μ‚¬μš©ν•©λ‹ˆλ‹€. '/'둜 μ„€μ •ν•˜λ©΄ λ¦¬λ‹€μ΄λ ‰νŠΈν•˜μ§€ μ•Šκ³  μœ„μ˜ λΉ„λ‘œκ·ΈμΈ 경둜 섀정을 λ”°λ¦…λ‹ˆλ‹€. 예: /creatives, /dashboard"
18
20
  home_page_path_invalid_url: "ν™ˆ νŽ˜μ΄μ§€ κ²½λ‘œλŠ” 전체 URL이 될 수 μ—†μŠ΅λ‹ˆλ‹€. /creatives λ˜λŠ” /user와 같은 경둜λ₯Ό μž…λ ₯ν•˜μ„Έμš”."
19
21
  home_page_path_not_routable: "ν™ˆ νŽ˜μ΄μ§€ 경둜 '%{path}'λŠ” μœ νš¨ν•œ λΌμš°νŠΈκ°€ μ•„λ‹™λ‹ˆλ‹€. μ‘΄μž¬ν•˜λŠ” 경둜λ₯Ό μž…λ ₯ν•˜μ„Έμš”."
20
22
  home_page_path_not_html: "ν™ˆ νŽ˜μ΄μ§€ 경둜 '%{path}'λŠ” HTML μ½˜ν…μΈ λ₯Ό μ œκ³΅ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. HTML νŽ˜μ΄μ§€ 경둜λ₯Ό μž…λ ₯ν•˜μ„Έμš”."
@@ -0,0 +1,13 @@
1
+ en:
2
+ collavre:
3
+ channels:
4
+ detach: "Detach"
5
+ channel:
6
+ preview:
7
+ label_default: "Preview"
8
+ attached_message: "%{label} server started.\n\n%{url}"
9
+ badge:
10
+ running: "Running"
11
+ stopped: "Stopped"
12
+ claude_channel:
13
+ disconnected: "πŸ”Œ Not connected to Claude Channel β€” your message will be delivered once it reconnects."
@@ -0,0 +1,13 @@
1
+ ko:
2
+ collavre:
3
+ channels:
4
+ detach: "ν•΄μ œ"
5
+ channel:
6
+ preview:
7
+ label_default: "프리뷰"
8
+ attached_message: "%{label} μ„œλ²„κ°€ μ‹œμž‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€.\n\n%{url}"
9
+ badge:
10
+ running: "μ‹€ν–‰ 쀑"
11
+ stopped: "쀑지됨"
12
+ claude_channel:
13
+ disconnected: "πŸ”Œ Claude Channelκ³Ό μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€ β€” λ‹€μ‹œ μ—°κ²°λ˜λ©΄ λ©”μ‹œμ§€κ°€ μ „λ‹¬λ©λ‹ˆλ‹€."
@@ -0,0 +1,16 @@
1
+ en:
2
+ collavre:
3
+ claude_channel:
4
+ permission:
5
+ message: |
6
+ πŸ” **Tool Permission Required**
7
+
8
+ %{description}Claude Code wants to run **%{tool_name}** with the following arguments:
9
+
10
+ ```json
11
+ %{arguments}
12
+ ```
13
+
14
+ Approve or deny to continue.
15
+ description: "> %{text}\n\n"
16
+ no_arguments: "(no arguments)"
@@ -0,0 +1,16 @@
1
+ ko:
2
+ collavre:
3
+ claude_channel:
4
+ permission:
5
+ message: |
6
+ πŸ” **도ꡬ κΆŒν•œ μš”μ²­**
7
+
8
+ %{description}Claude Codeκ°€ λ‹€μŒ 인자둜 **%{tool_name}** 도ꡬλ₯Ό μ‹€ν–‰ν•˜λ €κ³  ν•©λ‹ˆλ‹€:
9
+
10
+ ```json
11
+ %{arguments}
12
+ ```
13
+
14
+ κ³„μ†ν•˜λ €λ©΄ 승인 λ˜λŠ” κ±°λΆ€λ₯Ό μ„ νƒν•˜μ„Έμš”.
15
+ description: "> %{text}\n\n"
16
+ no_arguments: "(인자 μ—†μŒ)"
@@ -13,6 +13,7 @@ en:
13
13
  branch_prefix: "Branch"
14
14
  main_name: "Main"
15
15
  cannot_delete_main: "Cannot delete the Main topic."
16
+ orphaned_cron_notice: "[System] The topic '%{topic_name}' was deleted, but a recurring cron job (%{cron_key}) still targets it and will silently do nothing on each run. Original message: \"%{message}\". Re-point it to another topic or cancel it with cron_cancel."
16
17
  create_and_move: 'Create "%{name}" and move'
17
18
  move:
18
19
  no_target_permission: You don't have write permission on the target creative.
@@ -46,6 +47,7 @@ en:
46
47
  copy_link_success: Link copied to clipboard
47
48
  copy_link_error: Unable to copy link
48
49
  approve_button: Approve
50
+ deny_button: Deny
49
51
  approve_not_allowed: Only the assigned approver can approve this comment.
50
52
  approve_invalid_format: Invalid action format.
51
53
  approve_admin_required: System administrator approval required.
@@ -63,6 +65,7 @@ en:
63
65
  approve_invalid_progress: Comment action progress must be a number.
64
66
  approve_invalid_description: Comment action description must be text.
65
67
  approved_label: Approved
68
+ denied_label: Denied
66
69
  action_summary: Action JSON
67
70
  edit_action_button: Edit action
68
71
  action_update_success: Action updated.
@@ -211,6 +214,8 @@ en:
211
214
  delete: Delete
212
215
  delete_confirm: Delete this image?
213
216
  counter: "%{current} / %{total}"
217
+ channels:
218
+ detach: "Detach"
214
219
  calendar_events:
215
220
  deleted: Calendar event deleted.
216
221
  google_calendar:
@@ -13,6 +13,7 @@ ko:
13
13
  branch_prefix: "λΆ„κΈ°"
14
14
  main_name: "메인"
15
15
  cannot_delete_main: "메인 토픽은 μ‚­μ œν•  수 μ—†μŠ΅λ‹ˆλ‹€."
16
+ orphaned_cron_notice: "[μ‹œμŠ€ν…œ] '%{topic_name}' 토픽이 μ‚­μ œλ˜μ—ˆμ§€λ§Œ, 이 토픽을 λŒ€μƒμœΌλ‘œ ν•˜λŠ” 반볡 크둠 μž‘μ—…(%{cron_key})이 아직 남아 μžˆμ–΄ 싀행될 λ•Œλ§ˆλ‹€ 아무 λ™μž‘λ„ ν•˜μ§€ μ•Šκ³  쑰용히 μ’…λ£Œλ©λ‹ˆλ‹€. μ›λž˜ λ©”μ‹œμ§€: \"%{message}\". λ‹€λ₯Έ ν† ν”½μœΌλ‘œ λ‹€μ‹œ μ§€μ •ν•˜κ±°λ‚˜ cron_cancel둜 μ·¨μ†Œν•˜μ„Έμš”."
16
17
  create_and_move: '"%{name}" 생성후 이동'
17
18
  move:
18
19
  no_target_permission: λŒ€μƒ ν¬λ¦¬μ—μ΄ν‹°λΈŒμ— λŒ€ν•œ μ“°κΈ° κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.
@@ -44,6 +45,7 @@ ko:
44
45
  copy_link_success: 링크가 λ³΅μ‚¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€
45
46
  copy_link_error: 링크λ₯Ό 볡사할 수 μ—†μŠ΅λ‹ˆλ‹€
46
47
  approve_button: 승인
48
+ deny_button: κ±°λΆ€
47
49
  approve_not_allowed: μ§€μ •λœ 승인자만 이 λŒ“κΈ€μ„ μŠΉμΈν•  수 μžˆμŠ΅λ‹ˆλ‹€.
48
50
  approve_invalid_format: μœ νš¨ν•˜μ§€ μ•Šμ€ μ•‘μ…˜ ν˜•μ‹μž…λ‹ˆλ‹€.
49
51
  approve_admin_required: μ‹œμŠ€ν…œ κ΄€λ¦¬μž 승인이 ν•„μš”ν•©λ‹ˆλ‹€.
@@ -61,6 +63,7 @@ ko:
61
63
  approve_invalid_progress: λŒ“κΈ€ μž‘μ—… μ§„ν–‰λ₯ μ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.
62
64
  approve_invalid_description: λŒ“κΈ€ μž‘μ—… μ„€λͺ…은 ν…μŠ€νŠΈμ—¬μ•Ό ν•©λ‹ˆλ‹€.
63
65
  approved_label: 승인됨
66
+ denied_label: 거뢀됨
64
67
  action_summary: Action JSON
65
68
  edit_action_button: Action μˆ˜μ •
66
69
  action_update_success: Action 이 μ—…λ°μ΄νŠΈλ˜μ—ˆμŠ΅λ‹ˆλ‹€.
@@ -208,6 +211,8 @@ ko:
208
211
  delete: μ‚­μ œ
209
212
  delete_confirm: 이 이미지λ₯Ό μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?
210
213
  counter: "%{current} / %{total}"
214
+ channels:
215
+ detach: "ν•΄μ œ"
211
216
  calendar_events:
212
217
  deleted: μΊ˜λ¦°λ” μ΄λ²€νŠΈκ°€ μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.
213
218
  google_calendar:
@@ -44,6 +44,8 @@ en:
44
44
  no_creatives: No creatives found.
45
45
  no_sub_creatives: No sub-creatives found.
46
46
  no_search_results: No creatives match your search.
47
+ link_loading: Loading…
48
+ link_expand: Expand
47
49
  search_placeholder: Search creatives...
48
50
  recommend_parent: Recommend Category
49
51
  import_uploading: Uploading...
@@ -132,6 +134,12 @@ en:
132
134
  metadata_tooltip: Edit metadata
133
135
  metadata_title: Metadata
134
136
  metadata_save: Save
137
+ toggle_markdown: Markdown
138
+ toggle_richtext: Rich Text
139
+ markdown_to_richtext_confirm: Switching to Rich Text will discard the Markdown
140
+ source. Continue?
141
+ richtext_to_markdown_confirm: Switching to Markdown will discard the current
142
+ rich text content. Continue?
135
143
  share:
136
144
  shared: Creative shared successfully.
137
145
  permission_updated: Permission updated successfully.
@@ -147,8 +155,11 @@ en:
147
155
  progress_recalculated: All parent progress recalculated.
148
156
  errors:
149
157
  no_permission: You do not have permission to perform this action.
158
+ metadata_must_be_object: Metadata must be an object.
150
159
  inline_editor:
151
160
  placeholder: Describe the creative…
161
+ markdown_placeholder: Write in Markdown…
162
+ markdown_preview_placeholder: Preview
152
163
  edit_title: Edit creative
153
164
  edit:
154
165
  inline_editor_only_html: Editing creatives now happens directly in the inline
@@ -40,6 +40,8 @@ ko:
40
40
  no_creatives: ν¬λ¦¬μ—μ΄ν‹°λΈŒκ°€ μ—†μŠ΅λ‹ˆλ‹€.
41
41
  no_sub_creatives: ν•˜μœ„ ν¬λ¦¬μ—μ΄ν‹°λΈŒκ°€ μ—†μŠ΅λ‹ˆλ‹€.
42
42
  no_search_results: 검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€.
43
+ link_loading: λΆˆλŸ¬μ˜€λŠ” 쀑…
44
+ link_expand: 펼치기
43
45
  recommend_parent: μΉ΄ν…Œκ³ λ¦¬ μΆ”μ²œ
44
46
  import_uploading: μ—…λ‘œλ“œ 쀑...
45
47
  import_success: κ°€μ Έμ˜€κΈ° 성곡! μƒˆλ‘œκ³ μΉ¨ 쀑...
@@ -120,6 +122,11 @@ ko:
120
122
  metadata_tooltip: 메타데이터 νŽΈμ§‘
121
123
  metadata_title: 메타데이터
122
124
  metadata_save: μ €μž₯
125
+ toggle_markdown: λ§ˆν¬λ‹€μš΄
126
+ toggle_richtext: μ„œμ‹ νŽΈμ§‘
127
+ markdown_to_richtext_confirm: μ„œμ‹ νŽΈμ§‘μœΌλ‘œ μ „ν™˜ν•˜λ©΄ λ§ˆν¬λ‹€μš΄ 원본이 μ‚­μ œλ©λ‹ˆλ‹€.
128
+ κ³„μ†ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?
129
+ richtext_to_markdown_confirm: λ§ˆν¬λ‹€μš΄μœΌλ‘œ μ „ν™˜ν•˜λ©΄ ν˜„μž¬ μ„œμ‹ νŽΈμ§‘ λ‚΄μš©μ΄ μ‚­μ œλ©λ‹ˆλ‹€. κ³„μ†ν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ?
123
130
  share:
124
131
  shared: ν¬λ¦¬μ—μ΄ν‹°λΈŒκ°€ μ„±κ³΅μ μœΌλ‘œ κ³΅μœ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
125
132
  permission_updated: κΆŒν•œμ΄ μ„±κ³΅μ μœΌλ‘œ λ³€κ²½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
@@ -134,8 +141,11 @@ ko:
134
141
  progress_recalculated: μ§„ν–‰λ₯ μ„ λ‹€μ‹œ 계산 ν–ˆμŠ΅λ‹ˆλ‹€.
135
142
  errors:
136
143
  no_permission: 이 μž‘μ—…μ„ μˆ˜ν–‰ν•  κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.
144
+ metadata_must_be_object: λ©”νƒ€λ°μ΄ν„°λŠ” 객체여야 ν•©λ‹ˆλ‹€.
137
145
  inline_editor:
138
146
  placeholder: ν¬λ¦¬μ—μ΄ν‹°λΈŒλ₯Ό μ„€λͺ…ν•΄μ£Όμ„Έμš”β€¦
147
+ markdown_placeholder: λ§ˆν¬λ‹€μš΄μœΌλ‘œ μž‘μ„±β€¦
148
+ markdown_preview_placeholder: 미리보기
139
149
  edit_title: ν¬λ¦¬μ—μ΄ν‹°λΈŒ μˆ˜μ •
140
150
  edit:
141
151
  inline_editor_only_html: ν¬λ¦¬μ—μ΄ν‹°λΈŒ μˆ˜μ •μ€ 인라인 νŽΈμ§‘κΈ°μ—μ„œ λ°”λ‘œ μ§„ν–‰ν•˜μ„Έμš”.