collavre 0.1.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 +7 -0
- data/README.md +221 -0
- data/Rakefile +8 -0
- data/app/assets/stylesheets/collavre/actiontext.css +577 -0
- data/app/assets/stylesheets/collavre/activity_logs.css +99 -0
- data/app/assets/stylesheets/collavre/comments_popup.css +692 -0
- data/app/assets/stylesheets/collavre/creatives.css +559 -0
- data/app/assets/stylesheets/collavre/dark_mode.css +118 -0
- data/app/assets/stylesheets/collavre/mention_menu.css +43 -0
- data/app/assets/stylesheets/collavre/popup.css +160 -0
- data/app/assets/stylesheets/collavre/print.css +37 -0
- data/app/assets/stylesheets/collavre/slide_view.css +79 -0
- data/app/assets/stylesheets/collavre/user_menu.css +34 -0
- data/app/channels/collavre/comments_presence_channel.rb +54 -0
- data/app/channels/collavre/slide_view_channel.rb +11 -0
- data/app/channels/collavre/topics_channel.rb +12 -0
- data/app/components/collavre/avatar_component.html.erb +15 -0
- data/app/components/collavre/avatar_component.rb +59 -0
- data/app/components/collavre/inbox/badge_component.html.erb +6 -0
- data/app/components/collavre/inbox/badge_component.rb +18 -0
- data/app/components/collavre/plans_timeline_component.html.erb +14 -0
- data/app/components/collavre/plans_timeline_component.rb +56 -0
- data/app/components/collavre/popup_menu_component.html.erb +6 -0
- data/app/components/collavre/popup_menu_component.rb +30 -0
- data/app/components/collavre/progress_filter_component.html.erb +5 -0
- data/app/components/collavre/progress_filter_component.rb +10 -0
- data/app/components/collavre/user_mention_menu_component.html.erb +3 -0
- data/app/components/collavre/user_mention_menu_component.rb +8 -0
- data/app/controllers/collavre/application_controller.rb +15 -0
- data/app/controllers/collavre/attachments_controller.rb +44 -0
- data/app/controllers/collavre/calendar_events_controller.rb +15 -0
- data/app/controllers/collavre/comment_read_pointers_controller.rb +80 -0
- data/app/controllers/collavre/comments/activity_logs_controller.rb +26 -0
- data/app/controllers/collavre/comments/reactions_controller.rb +82 -0
- data/app/controllers/collavre/comments_controller.rb +464 -0
- data/app/controllers/collavre/contacts_controller.rb +10 -0
- data/app/controllers/collavre/creative_expanded_states_controller.rb +27 -0
- data/app/controllers/collavre/creative_imports_controller.rb +24 -0
- data/app/controllers/collavre/creative_plans_controller.rb +69 -0
- data/app/controllers/collavre/creative_shares_controller.rb +79 -0
- data/app/controllers/collavre/creatives_controller.rb +535 -0
- data/app/controllers/collavre/devices_controller.rb +19 -0
- data/app/controllers/collavre/email_verifications_controller.rb +16 -0
- data/app/controllers/collavre/emails_controller.rb +11 -0
- data/app/controllers/collavre/github_auth_controller.rb +25 -0
- data/app/controllers/collavre/google_auth_controller.rb +43 -0
- data/app/controllers/collavre/inbox_items_controller.rb +64 -0
- data/app/controllers/collavre/invites_controller.rb +27 -0
- data/app/controllers/collavre/notion_auth_controller.rb +25 -0
- data/app/controllers/collavre/passwords_controller.rb +37 -0
- data/app/controllers/collavre/plans_controller.rb +110 -0
- data/app/controllers/collavre/sessions_controller.rb +57 -0
- data/app/controllers/collavre/topics_controller.rb +58 -0
- data/app/controllers/collavre/user_themes_controller.rb +58 -0
- data/app/controllers/collavre/users_controller.rb +390 -0
- data/app/helpers/collavre/application_helper.rb +4 -0
- data/app/helpers/collavre/comments_helper.rb +9 -0
- data/app/helpers/collavre/creatives_helper.rb +343 -0
- data/app/helpers/collavre/navigation_helper.rb +163 -0
- data/app/helpers/collavre/user_themes_helper.rb +4 -0
- data/app/javascript/collavre.js +26 -0
- data/app/javascript/components/InlineLexicalEditor.jsx +889 -0
- data/app/javascript/components/LinkPopup.jsx +112 -0
- data/app/javascript/components/creative_tree_row.js +503 -0
- data/app/javascript/components/plugins/attachment_cleanup_plugin.jsx +95 -0
- data/app/javascript/components/plugins/image_upload_plugin.jsx +162 -0
- data/app/javascript/controllers/click_target_controller.js +13 -0
- data/app/javascript/controllers/comment_controller.js +162 -0
- data/app/javascript/controllers/comments/__tests__/popup_controller.test.js +68 -0
- data/app/javascript/controllers/comments/form_controller.js +530 -0
- data/app/javascript/controllers/comments/list_controller.js +715 -0
- data/app/javascript/controllers/comments/mention_menu_controller.js +41 -0
- data/app/javascript/controllers/comments/popup_controller.js +385 -0
- data/app/javascript/controllers/comments/presence_controller.js +311 -0
- data/app/javascript/controllers/comments/topics_controller.js +338 -0
- data/app/javascript/controllers/common_popup_controller.js +55 -0
- data/app/javascript/controllers/creatives/drag_drop_controller.js +45 -0
- data/app/javascript/controllers/creatives/expansion_controller.js +222 -0
- data/app/javascript/controllers/creatives/import_controller.js +116 -0
- data/app/javascript/controllers/creatives/row_editor_controller.js +8 -0
- data/app/javascript/controllers/creatives/select_mode_controller.js +231 -0
- data/app/javascript/controllers/creatives/set_plan_modal_controller.js +107 -0
- data/app/javascript/controllers/creatives/tree_controller.js +218 -0
- data/app/javascript/controllers/index.js +79 -0
- data/app/javascript/controllers/link_creative_controller.js +91 -0
- data/app/javascript/controllers/popup_menu_controller.js +82 -0
- data/app/javascript/controllers/progress_filter_controller.js +35 -0
- data/app/javascript/controllers/reaction_picker_controller.js +107 -0
- data/app/javascript/controllers/share_invite_controller.js +15 -0
- data/app/javascript/controllers/share_user_search_controller.js +121 -0
- data/app/javascript/controllers/tabs_controller.js +43 -0
- data/app/javascript/creatives/drag_drop/dom.js +170 -0
- data/app/javascript/creatives/drag_drop/event_handlers.js +846 -0
- data/app/javascript/creatives/drag_drop/indicator.js +35 -0
- data/app/javascript/creatives/drag_drop/operations.js +116 -0
- data/app/javascript/creatives/drag_drop/state.js +31 -0
- data/app/javascript/creatives/tree_renderer.js +248 -0
- data/app/javascript/lib/api/__tests__/queue_manager.test.js +153 -0
- data/app/javascript/lib/api/creatives.js +79 -0
- data/app/javascript/lib/api/csrf_fetch.js +22 -0
- data/app/javascript/lib/api/drag_drop.js +31 -0
- data/app/javascript/lib/api/queue_manager.js +423 -0
- data/app/javascript/lib/apply_lexical_styles.js +15 -0
- data/app/javascript/lib/common_popup.js +195 -0
- data/app/javascript/lib/lexical/__tests__/action_text_attachment_node.test.jsx +91 -0
- data/app/javascript/lib/lexical/__tests__/attachment_payload.test.js +194 -0
- data/app/javascript/lib/lexical/action_text_attachment_node.js +459 -0
- data/app/javascript/lib/lexical/attachment_node.jsx +170 -0
- data/app/javascript/lib/lexical/attachment_payload.js +293 -0
- data/app/javascript/lib/lexical/dom_attachment_utils.js +66 -0
- data/app/javascript/lib/lexical/image_node.jsx +159 -0
- data/app/javascript/lib/lexical/style_attributes.js +40 -0
- data/app/javascript/lib/responsive_images.js +54 -0
- data/app/javascript/lib/turbo_stream_actions.js +33 -0
- data/app/javascript/lib/utils/markdown.js +23 -0
- data/app/javascript/modules/creative_guide.js +53 -0
- data/app/javascript/modules/creative_row_editor.js +1841 -0
- data/app/javascript/modules/creative_row_swipe.js +43 -0
- data/app/javascript/modules/creatives.js +15 -0
- data/app/javascript/modules/export_to_markdown.js +34 -0
- data/app/javascript/modules/inbox_panel.js +226 -0
- data/app/javascript/modules/lexical_inline_editor.jsx +133 -0
- data/app/javascript/modules/mention_menu.js +77 -0
- data/app/javascript/modules/plans_menu.js +39 -0
- data/app/javascript/modules/plans_timeline.js +397 -0
- data/app/javascript/modules/share_modal.js +73 -0
- data/app/javascript/modules/share_user_popup.js +77 -0
- data/app/javascript/modules/slide_view.js +163 -0
- data/app/javascript/services/cable.js +32 -0
- data/app/javascript/slide_view.js +2 -0
- data/app/javascript/utils/caret_position.js +42 -0
- data/app/javascript/utils/clipboard.js +40 -0
- data/app/jobs/collavre/ai_agent_job.rb +27 -0
- data/app/jobs/collavre/inbox_summary_job.rb +24 -0
- data/app/jobs/collavre/notion_export_job.rb +30 -0
- data/app/jobs/collavre/notion_sync_job.rb +48 -0
- data/app/jobs/collavre/permission_cache_cleanup_job.rb +36 -0
- data/app/jobs/collavre/permission_cache_job.rb +71 -0
- data/app/jobs/collavre/push_notification_job.rb +86 -0
- data/app/mailers/collavre/application_mailer.rb +17 -0
- data/app/mailers/collavre/creative_mailer.rb +9 -0
- data/app/mailers/collavre/email_verification_mailer.rb +20 -0
- data/app/mailers/collavre/inbox_mailer.rb +19 -0
- data/app/mailers/collavre/invitation_mailer.rb +16 -0
- data/app/mailers/collavre/passwords_mailer.rb +10 -0
- data/app/models/collavre/activity_log.rb +13 -0
- data/app/models/collavre/application_record.rb +5 -0
- data/app/models/collavre/calendar_event.rb +20 -0
- data/app/models/collavre/comment.rb +307 -0
- data/app/models/collavre/comment_presence_store.rb +30 -0
- data/app/models/collavre/comment_reaction.rb +11 -0
- data/app/models/collavre/comment_read_pointer.rb +26 -0
- data/app/models/collavre/contact.rb +23 -0
- data/app/models/collavre/creative.rb +413 -0
- data/app/models/collavre/creative_expanded_state.rb +11 -0
- data/app/models/collavre/creative_share.rb +122 -0
- data/app/models/collavre/creative_shares_cache.rb +18 -0
- data/app/models/collavre/current.rb +14 -0
- data/app/models/collavre/device.rb +13 -0
- data/app/models/collavre/email.rb +14 -0
- data/app/models/collavre/github_account.rb +10 -0
- data/app/models/collavre/github_repository_link.rb +19 -0
- data/app/models/collavre/inbox_item.rb +95 -0
- data/app/models/collavre/invitation.rb +22 -0
- data/app/models/collavre/label.rb +47 -0
- data/app/models/collavre/mcp_tool.rb +30 -0
- data/app/models/collavre/notion_account.rb +17 -0
- data/app/models/collavre/notion_block_link.rb +10 -0
- data/app/models/collavre/notion_page_link.rb +19 -0
- data/app/models/collavre/plan.rb +20 -0
- data/app/models/collavre/session.rb +24 -0
- data/app/models/collavre/system_setting.rb +144 -0
- data/app/models/collavre/tag.rb +10 -0
- data/app/models/collavre/task.rb +10 -0
- data/app/models/collavre/task_action.rb +10 -0
- data/app/models/collavre/topic.rb +12 -0
- data/app/models/collavre/user.rb +174 -0
- data/app/models/collavre/user_theme.rb +10 -0
- data/app/models/collavre/variation.rb +5 -0
- data/app/models/collavre/webauthn_credential.rb +11 -0
- data/app/services/collavre/ai_agent_service.rb +193 -0
- data/app/services/collavre/ai_client.rb +183 -0
- data/app/services/collavre/ai_system_prompt_renderer.rb +38 -0
- data/app/services/collavre/auto_theme_generator.rb +198 -0
- data/app/services/collavre/comment_link_formatter.rb +60 -0
- data/app/services/collavre/comments/action_executor.rb +262 -0
- data/app/services/collavre/comments/action_validator.rb +58 -0
- data/app/services/collavre/comments/calendar_command.rb +97 -0
- data/app/services/collavre/comments/command_processor.rb +37 -0
- data/app/services/collavre/comments/mcp_command.rb +109 -0
- data/app/services/collavre/comments/mcp_command_builder.rb +32 -0
- data/app/services/collavre/creatives/filter_pipeline.rb +196 -0
- data/app/services/collavre/creatives/filters/assignee_filter.rb +30 -0
- data/app/services/collavre/creatives/filters/base_filter.rb +24 -0
- data/app/services/collavre/creatives/filters/comment_filter.rb +21 -0
- data/app/services/collavre/creatives/filters/date_filter.rb +58 -0
- data/app/services/collavre/creatives/filters/progress_filter.rb +25 -0
- data/app/services/collavre/creatives/filters/search_filter.rb +28 -0
- data/app/services/collavre/creatives/filters/tag_filter.rb +16 -0
- data/app/services/collavre/creatives/importer.rb +47 -0
- data/app/services/collavre/creatives/index_query.rb +191 -0
- data/app/services/collavre/creatives/path_exporter.rb +131 -0
- data/app/services/collavre/creatives/permission_cache_builder.rb +194 -0
- data/app/services/collavre/creatives/permission_checker.rb +42 -0
- data/app/services/collavre/creatives/plan_tagger.rb +53 -0
- data/app/services/collavre/creatives/progress_service.rb +89 -0
- data/app/services/collavre/creatives/reorderer.rb +187 -0
- data/app/services/collavre/creatives/tree_builder.rb +231 -0
- data/app/services/collavre/creatives/tree_formatter.rb +36 -0
- data/app/services/collavre/gemini_parent_recommender.rb +77 -0
- data/app/services/collavre/github/client.rb +112 -0
- data/app/services/collavre/github/pull_request_analyzer.rb +280 -0
- data/app/services/collavre/github/pull_request_processor.rb +181 -0
- data/app/services/collavre/github/webhook_provisioner.rb +130 -0
- data/app/services/collavre/google_calendar_service.rb +149 -0
- data/app/services/collavre/link_preview_fetcher.rb +230 -0
- data/app/services/collavre/markdown_importer.rb +202 -0
- data/app/services/collavre/mcp_service.rb +217 -0
- data/app/services/collavre/notion_client.rb +231 -0
- data/app/services/collavre/notion_creative_exporter.rb +296 -0
- data/app/services/collavre/notion_service.rb +249 -0
- data/app/services/collavre/ppt_importer.rb +76 -0
- data/app/services/collavre/ruby_llm_interaction_logger.rb +34 -0
- data/app/services/collavre/system_events/context_builder.rb +41 -0
- data/app/services/collavre/system_events/dispatcher.rb +19 -0
- data/app/services/collavre/system_events/router.rb +72 -0
- data/app/services/collavre/tools/creative_retrieval_service.rb +138 -0
- data/app/views/admin/shared/_tabs.html.erb +4 -0
- data/app/views/collavre/comments/_activity_log_details.html.erb +23 -0
- data/app/views/collavre/comments/_comment.html.erb +147 -0
- data/app/views/collavre/comments/_comments_popup.html.erb +46 -0
- data/app/views/collavre/comments/_list.html.erb +10 -0
- data/app/views/collavre/comments/_presence_avatars.html.erb +8 -0
- data/app/views/collavre/comments/_reaction_picker.html.erb +15 -0
- data/app/views/collavre/comments/_read_receipts.html.erb +19 -0
- data/app/views/collavre/creatives/_add_button.html.erb +20 -0
- data/app/views/collavre/creatives/_delete_button.html.erb +12 -0
- data/app/views/collavre/creatives/_github_integration_modal.html.erb +77 -0
- data/app/views/collavre/creatives/_import_upload_zone.html.erb +10 -0
- data/app/views/collavre/creatives/_inline_edit_form.html.erb +89 -0
- data/app/views/collavre/creatives/_mobile_actions_menu.html.erb +24 -0
- data/app/views/collavre/creatives/_notion_integration_modal.html.erb +90 -0
- data/app/views/collavre/creatives/_set_plan_modal.html.erb +25 -0
- data/app/views/collavre/creatives/_share_button.html.erb +55 -0
- data/app/views/collavre/creatives/edit.html.erb +4 -0
- data/app/views/collavre/creatives/index.html.erb +192 -0
- data/app/views/collavre/creatives/new.html.erb +4 -0
- data/app/views/collavre/creatives/show.html.erb +29 -0
- data/app/views/collavre/creatives/slide_view.html.erb +20 -0
- data/app/views/collavre/email_verification_mailer/verify.html.erb +4 -0
- data/app/views/collavre/email_verification_mailer/verify.text.erb +2 -0
- data/app/views/collavre/emails/index.html.erb +19 -0
- data/app/views/collavre/emails/show.html.erb +5 -0
- data/app/views/collavre/inbox_items/_item.html.erb +18 -0
- data/app/views/collavre/inbox_items/_items.html.erb +3 -0
- data/app/views/collavre/inbox_items/_list.html.erb +7 -0
- data/app/views/collavre/inbox_items/index.html.erb +1 -0
- data/app/views/collavre/inbox_mailer/daily_summary.html.erb +11 -0
- data/app/views/collavre/inbox_mailer/daily_summary.text.erb +8 -0
- data/app/views/collavre/invitation_mailer/invite.html.erb +5 -0
- data/app/views/collavre/invitation_mailer/invite.text.erb +3 -0
- data/app/views/collavre/invites/show.html.erb +8 -0
- data/app/views/collavre/passwords/edit.html.erb +12 -0
- data/app/views/collavre/passwords/new.html.erb +17 -0
- data/app/views/collavre/passwords_mailer/reset.html.erb +4 -0
- data/app/views/collavre/passwords_mailer/reset.text.erb +2 -0
- data/app/views/collavre/sessions/new.html.erb +27 -0
- data/app/views/collavre/sessions/providers/_google.html.erb +7 -0
- data/app/views/collavre/sessions/providers/_passkey.html.erb +3 -0
- data/app/views/collavre/shared/_link_creative_modal.html.erb +6 -0
- data/app/views/collavre/shared/_navigation.html.erb +37 -0
- data/app/views/collavre/shared/navigation/_help_button.html.erb +1 -0
- data/app/views/collavre/shared/navigation/_inbox_button.html.erb +5 -0
- data/app/views/collavre/shared/navigation/_mobile_inbox_button.html.erb +3 -0
- data/app/views/collavre/shared/navigation/_mobile_plans_button.html.erb +1 -0
- data/app/views/collavre/shared/navigation/_panels.html.erb +18 -0
- data/app/views/collavre/shared/navigation/_plans_button.html.erb +3 -0
- data/app/views/collavre/shared/navigation/_search_form.html.erb +6 -0
- data/app/views/collavre/user_themes/index.html.erb +75 -0
- data/app/views/collavre/users/_app_header.html.erb +5 -0
- data/app/views/collavre/users/_contact_management.html.erb +77 -0
- data/app/views/collavre/users/_id_pwd_fields.html.erb +27 -0
- data/app/views/collavre/users/edit_ai.html.erb +79 -0
- data/app/views/collavre/users/edit_password.html.erb +27 -0
- data/app/views/collavre/users/index.html.erb +80 -0
- data/app/views/collavre/users/new.html.erb +33 -0
- data/app/views/collavre/users/new_ai.html.erb +87 -0
- data/app/views/collavre/users/passkeys.html.erb +43 -0
- data/app/views/collavre/users/show.html.erb +143 -0
- data/app/views/inbox/badge_component/_count.html.erb +2 -0
- data/app/views/layouts/collavre/slide.html.erb +29 -0
- data/config/locales/comments.en.yml +114 -0
- data/config/locales/comments.ko.yml +110 -0
- data/config/locales/contacts.en.yml +16 -0
- data/config/locales/contacts.ko.yml +16 -0
- data/config/locales/creatives.en.yml +183 -0
- data/config/locales/creatives.ko.yml +164 -0
- data/config/locales/invites.en.yml +19 -0
- data/config/locales/invites.ko.yml +17 -0
- data/config/locales/notifications.en.yml +8 -0
- data/config/locales/notifications.ko.yml +8 -0
- data/config/locales/plans.en.yml +12 -0
- data/config/locales/plans.ko.yml +19 -0
- data/config/locales/themes.en.yml +29 -0
- data/config/locales/themes.ko.yml +27 -0
- data/config/locales/users.en.yml +151 -0
- data/config/locales/users.ko.yml +146 -0
- data/config/routes.rb +92 -0
- data/db/migrate/20241201000000_create_notion_integrations.rb +29 -0
- data/db/migrate/20250128110017_create_creatives.rb +11 -0
- data/db/migrate/20250128120122_create_users.rb +11 -0
- data/db/migrate/20250128120123_create_sessions.rb +11 -0
- data/db/migrate/20250128123633_create_subscribers.rb +10 -0
- data/db/migrate/20250312000000_create_notion_block_links.rb +16 -0
- data/db/migrate/20250312010000_allow_multiple_notion_blocks_per_creative.rb +5 -0
- data/db/migrate/20250522115048_remove_name_from_creatives.rb +5 -0
- data/db/migrate/20250522190651_add_parent_id_to_creatives.rb +5 -0
- data/db/migrate/20250523133100_rename_inventory_count_to_progress.rb +13 -0
- data/db/migrate/20250523133101_add_sequence_to_creatives.rb +5 -0
- data/db/migrate/20250525205100_add_user_id_to_creatives.rb +10 -0
- data/db/migrate/20250527014217_create_creative_shares.rb +12 -0
- data/db/migrate/20250528142349_add_origin_id_to_creatives.rb +5 -0
- data/db/migrate/20250530060200_create_tags.rb +9 -0
- data/db/migrate/20250531105150_add_value_to_tags.rb +5 -0
- data/db/migrate/20250531140142_create_labels.rb +12 -0
- data/db/migrate/20250531140145_change_tag_to_label_reference.rb +6 -0
- data/db/migrate/20250601000000_create_comments.rb +10 -0
- data/db/migrate/20250601061830_drop_plans_and_variations.rb +6 -0
- data/db/migrate/20250604122600_add_owner_to_labels.rb +5 -0
- data/db/migrate/20250606000000_rename_email_address_to_email_in_users.rb +7 -0
- data/db/migrate/20250606150329_create_creative_hierarchies.rb +16 -0
- data/db/migrate/20250610142000_create_creative_expanded_states.rb +11 -0
- data/db/migrate/20250611030138_create_invitations.rb +15 -0
- data/db/migrate/20250611105524_create_inbox_items.rb +14 -0
- data/db/migrate/20250612150000_update_permissions.rb +43 -0
- data/db/migrate/20250612232913_add_email_verified_at_to_users.rb +5 -0
- data/db/migrate/20250616065905_add_avatar_to_users.rb +5 -0
- data/db/migrate/20250617092111_add_display_level_to_users.rb +5 -0
- data/db/migrate/20250620004558_create_emails.rb +13 -0
- data/db/migrate/20250621000000_create_comment_read_pointers.rb +11 -0
- data/db/migrate/20250622000000_add_completion_mark_to_users.rb +5 -0
- data/db/migrate/20250623000000_add_theme_to_users.rb +5 -0
- data/db/migrate/20250624000000_add_name_to_users.rb +18 -0
- data/db/migrate/20250714190000_create_devices.rb +17 -0
- data/db/migrate/20250715120000_add_notifications_enabled_to_users.rb +5 -0
- data/db/migrate/20250823000000_add_no_access_permission.rb +25 -0
- data/db/migrate/20250826000000_add_calendar_id_to_users.rb +5 -0
- data/db/migrate/20250827000000_add_google_oauth_tokens_to_users.rb +8 -0
- data/db/migrate/20250827061238_add_timezone_to_users.rb +5 -0
- data/db/migrate/20250828000000_create_calendar_events.rb +16 -0
- data/db/migrate/20250828060000_add_creative_to_calendar_events.rb +5 -0
- data/db/migrate/20250830141052_add_locale_to_users.rb +5 -0
- data/db/migrate/20250830141101_add_message_key_to_inbox_items.rb +6 -0
- data/db/migrate/20250902025423_remove_description_and_featured_image_from_creatives.rb +6 -0
- data/db/migrate/20250910000000_add_private_to_comments.rb +5 -0
- data/db/migrate/20250910105640_migrate_write_permissions_to_admin.rb +17 -0
- data/db/migrate/20250911084338_backfill_creative_in_comment_inbox_items.rb +27 -0
- data/db/migrate/20250923002959_deduplicate_device_fcm_tokens.rb +25 -0
- data/db/migrate/20250924000000_add_system_admin_to_users.rb +6 -0
- data/db/migrate/20250925000000_create_github_integrations.rb +26 -0
- data/db/migrate/20250927000000_add_webhook_secret_to_github_repository_links.rb +29 -0
- data/db/migrate/20250928000000_add_action_and_approver_to_comments.rb +6 -0
- data/db/migrate/20250928010000_add_action_execution_tracking_to_comments.rb +6 -0
- data/db/migrate/20250928105957_add_github_gemini_prompt_to_creatives.rb +5 -0
- data/db/migrate/20250929000000_add_comment_and_creative_refs_to_inbox_items.rb +6 -0
- data/db/migrate/20251001000001_create_contacts.rb +71 -0
- data/db/migrate/20251002000000_add_shared_by_to_creative_shares.rb +14 -0
- data/db/migrate/20251124120902_add_ai_fields_to_users.rb +7 -0
- data/db/migrate/20251124122218_add_created_by_id_to_users.rb +5 -0
- data/db/migrate/20251124124521_add_llm_api_key_to_users.rb +5 -0
- data/db/migrate/20251124130000_add_searchable_to_users.rb +6 -0
- data/db/migrate/20251125072705_migrate_linked_creative_children_to_origin.rb +28 -0
- data/db/migrate/20251126040752_add_description_to_creatives.rb +75 -0
- data/db/migrate/20251127000000_move_comments_from_linked_creatives_to_origins.rb +18 -0
- data/db/migrate/20251201073823_add_tools_to_users.rb +5 -0
- data/db/migrate/20251202062715_create_mcp_tools.rb +16 -0
- data/db/migrate/20251204125754_create_tasks_and_task_actions.rb +23 -0
- data/db/migrate/20251204125756_add_routing_expression_to_users.rb +5 -0
- data/db/migrate/20251204161133_set_default_routing_expression_for_ai_agents.rb +13 -0
- data/db/migrate/20251211033025_nullify_self_referencing_origins.rb +9 -0
- data/db/migrate/20251211080040_create_topics_and_add_topic_to_comments.rb +15 -0
- data/db/migrate/20251215143500_create_activity_logs.rb +16 -0
- data/db/migrate/20251222125727_create_webauthn_credentials.rb +14 -0
- data/db/migrate/20251222125839_add_webauthn_id_to_users.rb +5 -0
- data/db/migrate/20251223022315_create_comment_reactions.rb +13 -0
- data/db/migrate/20251223072625_create_user_themes.rb +11 -0
- data/db/migrate/20251230074456_add_creative_id_to_labels.rb +5 -0
- data/db/migrate/20251230113607_refactor_labels.rb +38 -0
- data/db/migrate/20251231010012_backfill_tags_for_labels.rb +15 -0
- data/db/migrate/20251231013234_drop_subscribers.rb +9 -0
- data/db/migrate/20260106090544_allow_null_user_id_in_creative_shares.rb +6 -0
- data/db/migrate/20260106160643_create_system_settings.rb +11 -0
- data/db/migrate/20260116000000_create_creative_shares_cache.rb +15 -0
- data/db/migrate/20260116000001_populate_creative_shares_cache.rb +23 -0
- data/db/migrate/20260119022933_make_source_share_id_nullable_in_creative_shares_caches.rb +7 -0
- data/db/migrate/20260119023446_populate_owner_cache_entries.rb +25 -0
- data/db/migrate/20260119100000_add_account_lockout_to_users.rb +6 -0
- data/db/migrate/20260119110000_add_last_active_at_to_sessions.rb +6 -0
- data/db/migrate/20260120045354_encrypt_oauth_tokens.rb +60 -0
- data/db/migrate/20260120162259_remove_fk_from_creative_shares_caches.rb +7 -0
- data/db/migrate/20260120163856_remove_timestamps_from_creative_shares_caches.rb +6 -0
- data/lib/collavre/configuration.rb +14 -0
- data/lib/collavre/engine.rb +77 -0
- data/lib/collavre/user_extensions.rb +29 -0
- data/lib/collavre/version.rb +3 -0
- data/lib/collavre.rb +26 -0
- data/lib/generators/collavre/install/install_generator.rb +105 -0
- data/lib/generators/collavre/install/templates/build.cjs.tt +100 -0
- data/lib/tasks/collavre_assets.rake +15 -0
- metadata +591 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Collavre
|
|
2
|
+
module SystemEvents
|
|
3
|
+
class Dispatcher
|
|
4
|
+
def self.dispatch(event_name, context)
|
|
5
|
+
new.dispatch(event_name, context)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def dispatch(event_name, context)
|
|
9
|
+
# Build context once to ensure consistency between Router and Job
|
|
10
|
+
enriched_context = Collavre::SystemEvents::ContextBuilder.new(context).build
|
|
11
|
+
agents = Router.new.route(event_name, enriched_context)
|
|
12
|
+
|
|
13
|
+
agents.each do |agent|
|
|
14
|
+
AiAgentJob.perform_later(agent.id, event_name, enriched_context)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Collavre
|
|
2
|
+
module SystemEvents
|
|
3
|
+
class Router
|
|
4
|
+
def route(event_name, context)
|
|
5
|
+
# Build the context for Liquid
|
|
6
|
+
liquid_context = Collavre::SystemEvents::ContextBuilder.new(context).build
|
|
7
|
+
liquid_context["event_name"] = event_name
|
|
8
|
+
|
|
9
|
+
# Find all AI agents
|
|
10
|
+
agents = User.where.not(llm_vendor: nil)
|
|
11
|
+
|
|
12
|
+
matched_agents = []
|
|
13
|
+
|
|
14
|
+
agents.each do |agent|
|
|
15
|
+
next if agent.routing_expression.blank?
|
|
16
|
+
|
|
17
|
+
# Permission Check
|
|
18
|
+
# If agent is not searchable, it must have feedback permission on the creative
|
|
19
|
+
unless agent.searchable?
|
|
20
|
+
creative_id = context.dig("creative", "id") || context.dig(:creative, :id)
|
|
21
|
+
if creative_id
|
|
22
|
+
creative = Creative.find_by(id: creative_id)
|
|
23
|
+
if creative
|
|
24
|
+
# Check for feedback permission (which implies read access)
|
|
25
|
+
# has_permission? checks for the specific permission or higher
|
|
26
|
+
unless creative.has_permission?(agent, :feedback)
|
|
27
|
+
# Rails.logger.info "Agent #{agent.id} skipped: No feedback permission on Creative #{creative.id}"
|
|
28
|
+
next
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
# If creative ID is present but not found, skip for safety
|
|
32
|
+
next
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
# If no creative context, we might skip or allow depending on policy.
|
|
36
|
+
# Assuming 'chat.creative' implies creative context is required for this check.
|
|
37
|
+
# If it's a global event without creative, maybe searchable check isn't needed?
|
|
38
|
+
# But the user said "must have feedback permission on the chat.creative".
|
|
39
|
+
# If there is no creative, we can't check permission, so we should probably skip to be safe
|
|
40
|
+
# unless it's a purely global event. But for now, let's skip.
|
|
41
|
+
next
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
# Add 'agent' to context so they can refer to themselves
|
|
47
|
+
agent_context = liquid_context.merge("agent" => agent.as_json(only: [ :id, :name, :email ]))
|
|
48
|
+
|
|
49
|
+
# Parse and evaluate the routing expression
|
|
50
|
+
# We wrap the expression in an if block to evaluate truthiness
|
|
51
|
+
expression = agent.routing_expression.strip
|
|
52
|
+
unless expression.start_with?("{%")
|
|
53
|
+
expression = "{% if #{expression} %}true{% endif %}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
template = Liquid::Template.parse(expression)
|
|
57
|
+
result = template.render(agent_context)
|
|
58
|
+
|
|
59
|
+
# Check if the result evaluates to "true" string or boolean true
|
|
60
|
+
if result.strip == "true"
|
|
61
|
+
matched_agents << agent
|
|
62
|
+
end
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
Rails.logger.error("Routing error for agent #{agent.id}: #{e.message}")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
matched_agents
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Collavre
|
|
2
|
+
require "sorbet-runtime"
|
|
3
|
+
require "rails_mcp_engine"
|
|
4
|
+
module Tools
|
|
5
|
+
class CreativeRetrievalService
|
|
6
|
+
extend T::Sig
|
|
7
|
+
extend ToolMeta
|
|
8
|
+
|
|
9
|
+
tool_name "creative_retrieval_service"
|
|
10
|
+
tool_description "Retrieve creatives by ID or query text. without query or ID, it will return root creatives. Returns a list of matching creatives with their details, supporting both hierarchical tree and flat list formats.\n\nA Creative is a content block that functions like a task, organized in a tree structure similar to a to-do list. You can navigate the tree at any level as a structured document, with progress automatically calculated to show what’s been completed.\n\ne.g.\n- When user say creative or Test creative, it means \"Test\" creative and it's children as a writing page.\n- Summary of Test creative? - you need to search \"Test\" creatives with level 3 or more and find the title is \"Test\" or similar and make summary of that."
|
|
11
|
+
|
|
12
|
+
tool_param :id, description: "The ID of the creative to retrieve."
|
|
13
|
+
tool_param :query, description: "Text to search for in creative descriptions."
|
|
14
|
+
tool_param :level, description: "Creative tree depth to return (default: 3).", required: false
|
|
15
|
+
tool_param :simple, description: "If true, returns a simplified flat list. If false (default), returns a tree structure with HTML.", required: false
|
|
16
|
+
|
|
17
|
+
sig { params(id: T.nilable(Integer), query: T.nilable(String), level: T.nilable(Integer), simple: T.nilable(T::Boolean)).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
18
|
+
def call(id: nil, query: nil, level: 3, simple: false)
|
|
19
|
+
level ||= 3
|
|
20
|
+
simple ||= false
|
|
21
|
+
|
|
22
|
+
# Ensure fresh permission cache for this tool execution
|
|
23
|
+
Current.creative_share_cache = nil if Current.respond_to?(:creative_share_cache=)
|
|
24
|
+
|
|
25
|
+
# Mock session and request setup
|
|
26
|
+
setup_mock_environment
|
|
27
|
+
|
|
28
|
+
controller = Collavre::CreativesController.new
|
|
29
|
+
setup_controller(controller)
|
|
30
|
+
|
|
31
|
+
if id.present?
|
|
32
|
+
# Get the creative details (show)
|
|
33
|
+
show_result = dispatch_request(controller, :show, id: id, format: :json)
|
|
34
|
+
return show_result if show_result.is_a?(Array) && show_result.first[:error]
|
|
35
|
+
|
|
36
|
+
# Get the children (index with id acts as parent filter)
|
|
37
|
+
index_result = dispatch_request(controller, :index, id: id, search: query, simple: simple, level: level, format: :json)
|
|
38
|
+
return index_result if index_result.is_a?(Array) && index_result.first[:error]
|
|
39
|
+
|
|
40
|
+
# Combine results
|
|
41
|
+
# show_result is expected to be a hash of the creative
|
|
42
|
+
# index_result is expected to be a list of children or simple list
|
|
43
|
+
|
|
44
|
+
# Parse show result
|
|
45
|
+
creative_details = JSON.parse(show_result[:body], symbolize_names: true)
|
|
46
|
+
|
|
47
|
+
# Parse index result
|
|
48
|
+
children_data = JSON.parse(index_result[:body], symbolize_names: true)
|
|
49
|
+
filtered_children = filter_result(children_data)
|
|
50
|
+
|
|
51
|
+
# Merge
|
|
52
|
+
# We construct a tree node for the parent, with the children attached
|
|
53
|
+
parent_node = filter_tree([ creative_details ]).first
|
|
54
|
+
parent_node[:children] = filtered_children
|
|
55
|
+
|
|
56
|
+
[ parent_node ]
|
|
57
|
+
else
|
|
58
|
+
# Normal index call
|
|
59
|
+
result = dispatch_request(controller, :index, search: query, simple: simple, level: level, format: :json)
|
|
60
|
+
|
|
61
|
+
if result[:status] == 200
|
|
62
|
+
parsed = JSON.parse(result[:body], symbolize_names: true)
|
|
63
|
+
filter_result(parsed)
|
|
64
|
+
else
|
|
65
|
+
[ { error: "Controller returned status #{result[:status]}", body: result[:body] } ]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def setup_mock_environment
|
|
73
|
+
raise "Current.user is required" unless Current.user
|
|
74
|
+
unless Current.session
|
|
75
|
+
require "ostruct"
|
|
76
|
+
Current.session = OpenStruct.new(user: Current.user, persisted?: false)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def setup_controller(controller)
|
|
81
|
+
# Stub cookies
|
|
82
|
+
controller.define_singleton_method(:cookies) do
|
|
83
|
+
@mock_cookies ||= begin
|
|
84
|
+
jar = OpenStruct.new
|
|
85
|
+
def jar.signed; self; end
|
|
86
|
+
def jar.encrypted; self; end
|
|
87
|
+
def jar.[](key); nil; end
|
|
88
|
+
def jar.delete(key); nil; end
|
|
89
|
+
jar
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def dispatch_request(controller, action, params)
|
|
95
|
+
env = Rack::MockRequest.env_for(
|
|
96
|
+
"/creatives",
|
|
97
|
+
method: "GET",
|
|
98
|
+
params: params.compact,
|
|
99
|
+
"HTTP_X_ORIGIN_SECRET" => ENV["ORIGIN_SHARED_SECRET"] # Internal call
|
|
100
|
+
)
|
|
101
|
+
controller.request = ActionDispatch::Request.new(env)
|
|
102
|
+
controller.response = ActionDispatch::Response.new
|
|
103
|
+
controller.process(action)
|
|
104
|
+
|
|
105
|
+
{ status: controller.response.status, body: controller.response.body }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def filter_result(result)
|
|
109
|
+
if result.is_a?(Array)
|
|
110
|
+
# Simple mode
|
|
111
|
+
result.map { |item| item.slice(:id, :description, :progress) }
|
|
112
|
+
elsif result.is_a?(Hash) && result[:creatives].is_a?(Array)
|
|
113
|
+
# Normal mode (Tree)
|
|
114
|
+
filter_tree(result[:creatives])
|
|
115
|
+
else
|
|
116
|
+
[]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def filter_tree(nodes)
|
|
121
|
+
nodes.map do |node|
|
|
122
|
+
description = if node.dig(:templates, :description_html)
|
|
123
|
+
Rails::Html::FullSanitizer.new.sanitize(node.dig(:templates, :description_html))
|
|
124
|
+
else
|
|
125
|
+
node[:description]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
id: node[:id],
|
|
130
|
+
description: description&.strip,
|
|
131
|
+
progress: node.dig(:inline_editor_payload, :progress) || node[:progress],
|
|
132
|
+
children: node.dig(:children_container, :nodes) ? filter_tree(node.dig(:children_container, :nodes)) : []
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<div class="tab-list">
|
|
2
|
+
<%= link_to t('admin.tabs.system'), main_app.admin_path, class: "tab-button #{'active' if controller_name == 'settings'}" %>
|
|
3
|
+
<%= link_to t('admin.tabs.users'), collavre.users_path, class: "tab-button #{'active' if controller_name == 'users'}" %>
|
|
4
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<%= turbo_frame_tag "activity_log_details_#{comment.id}" do %>
|
|
2
|
+
<% if activity_logs.any? %>
|
|
3
|
+
<div class="activity-log-list">
|
|
4
|
+
<% activity_logs.each do |log| %>
|
|
5
|
+
<div class="activity-log-item">
|
|
6
|
+
<details>
|
|
7
|
+
<summary class="activity-log-summary">
|
|
8
|
+
<span class="activity-name"><%= log.activity %></span>
|
|
9
|
+
<span class="activity-time"><%= time_ago_in_words(log.created_at) %> ago</span>
|
|
10
|
+
</summary>
|
|
11
|
+
<div class="activity-log-body">
|
|
12
|
+
<pre class="activity-log-yaml"><code><%= log.log.to_yaml.sub(/^---\n/, '') %></code></pre>
|
|
13
|
+
</div>
|
|
14
|
+
</details>
|
|
15
|
+
</div>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
<% else %>
|
|
19
|
+
<div class="activity-log-empty">
|
|
20
|
+
No activity logs available.
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<% comment_topic = comment.topic %>
|
|
2
|
+
<% current_topic_id = local_assigns[:current_topic_id] %>
|
|
3
|
+
<div class="comment-item" id="<%= dom_id(comment) %>" data-controller="comment" data-user-id="<%= comment.user&.id %>" data-comment-id="<%= comment.id %>" data-topic-id="<%= comment_topic&.id %>" data-creative-id="<%= comment.creative_id %>">
|
|
4
|
+
<div class="comment-select">
|
|
5
|
+
<input type="checkbox"
|
|
6
|
+
class="comment-select-checkbox"
|
|
7
|
+
value="<%= comment.id %>"
|
|
8
|
+
aria-label="<%= t('collavre.comments.select_label') %>" />
|
|
9
|
+
</div>
|
|
10
|
+
<div>
|
|
11
|
+
<%= render AvatarComponent.new(user: comment.user, size: 20, classes: 'avatar comment-avatar') %>
|
|
12
|
+
<% system_prefix = "#{t('collavre.comments.system_user')}:" %>
|
|
13
|
+
<% display_name =
|
|
14
|
+
if comment.user.present?
|
|
15
|
+
comment.user.display_name
|
|
16
|
+
elsif comment.content.to_s.strip.start_with?(system_prefix)
|
|
17
|
+
t('collavre.comments.system_user')
|
|
18
|
+
else
|
|
19
|
+
t('collavre.comments.gemini')
|
|
20
|
+
end %>
|
|
21
|
+
<% timestamp = comment.created_at.in_time_zone %>
|
|
22
|
+
<strong><%= display_name %></strong>
|
|
23
|
+
<span>
|
|
24
|
+
· <%= content_tag(
|
|
25
|
+
:time,
|
|
26
|
+
t('datetime.ago', time: time_ago_in_words(timestamp)),
|
|
27
|
+
title: l(timestamp, format: :chat_timestamp),
|
|
28
|
+
datetime: timestamp.iso8601
|
|
29
|
+
) %>
|
|
30
|
+
</span>
|
|
31
|
+
<span id="read_receipts_comment_<%= comment.id %>" class="read-receipt-wrapper">
|
|
32
|
+
<% users_read = defined?(read_by_users) ? read_by_users : (defined?(read_receipts) ? read_receipts[comment.id] : nil) %>
|
|
33
|
+
<% present_user_ids = defined?(present_user_ids) ? present_user_ids : nil %>
|
|
34
|
+
<%= render "collavre/comments/read_receipts", read_by_users: users_read, present_user_ids: present_user_ids unless comment.private? %>
|
|
35
|
+
</span>
|
|
36
|
+
<% if comment.private? %>
|
|
37
|
+
<span class="private-label">[<%= t('collavre.comments.private') %>]</span>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% if comment.action_executed_at.present? %>
|
|
40
|
+
<span class="comment-status-label approved-label">[<%= t('collavre.comments.approved_label') %>]</span>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% can_convert_comment = comment.user == Current.user || comment.creative.has_permission?(Current.user, :admin) %>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="comment-action-container">
|
|
45
|
+
<button class="add-reaction-btn" type="button" data-action="click->comment#triggerReactionPicker" title="<%= t('collavre.comments.add_reaction') %>">
|
|
46
|
+
<span class="grayscale-emoji">☺</span>
|
|
47
|
+
</button>
|
|
48
|
+
|
|
49
|
+
<button class="<%= ['convert-comment-btn', ('comment-owner-only' unless can_convert_comment)].compact.join(' ') %>" data-comment-target="ownerButton" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.convert_to_creative') %>">
|
|
50
|
+
<%= t('collavre.comments.convert_button') %>
|
|
51
|
+
</button>
|
|
52
|
+
<% can_approve = comment.approval_status(Current.user) == :ok %>
|
|
53
|
+
<% if can_approve %>
|
|
54
|
+
<button class="approve-comment-btn" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.approve_button') %>">
|
|
55
|
+
<%= t('collavre.comments.approve_button') %>
|
|
56
|
+
</button>
|
|
57
|
+
<% end %>
|
|
58
|
+
<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') %>">
|
|
59
|
+
<%= t('collavre.comments.edit_button') %>
|
|
60
|
+
</button>
|
|
61
|
+
<button class="copy-comment-link-btn" data-comment-id="<%= comment.id %>" data-comment-url="<%= collavre.creative_comment_url(comment.creative, comment, Rails.application.config.action_mailer.default_url_options) %>" title="<%= t('collavre.comments.copy_link_button') %>">
|
|
62
|
+
<%= t('collavre.comments.copy_link_button') %>
|
|
63
|
+
</button>
|
|
64
|
+
<% can_delete = comment.user == Current.user || comment.creative.user == Current.user || comment.creative.has_permission?(Current.user, :admin) %>
|
|
65
|
+
<% if can_delete %>
|
|
66
|
+
<button class="delete-comment-btn" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.delete') %>">
|
|
67
|
+
<%= t('collavre.comments.delete_button') %>
|
|
68
|
+
</button>
|
|
69
|
+
<% end %>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="comment-content"><%= comment.content %></div>
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
<% if comment.images.attached? %>
|
|
75
|
+
<div class="comment-attachments">
|
|
76
|
+
<% comment.images.each do |image| %>
|
|
77
|
+
<% preview_image = image.variable? ? image.variant(resize_to_limit: [ 800, 800 ]) : image %>
|
|
78
|
+
<% preview_image_path =
|
|
79
|
+
if preview_image.respond_to?(:processed)
|
|
80
|
+
main_app.rails_representation_path(preview_image, only_path: true)
|
|
81
|
+
else
|
|
82
|
+
main_app.rails_blob_path(preview_image, only_path: true)
|
|
83
|
+
end %>
|
|
84
|
+
<%= link_to main_app.rails_blob_path(image, only_path: true), target: '_blank', rel: 'noopener', class: 'comment-attachment-link' do %>
|
|
85
|
+
<%= image_tag preview_image_path, class: 'comment-attachment-image', alt: '', loading: 'lazy' %>
|
|
86
|
+
<% end %>
|
|
87
|
+
<% end %>
|
|
88
|
+
</div>
|
|
89
|
+
<% end %>
|
|
90
|
+
<% reaction_emojis = [ "👍", "🎉", "❤️", "😂", "😮", "😢", "😡" ] %>
|
|
91
|
+
<% reactions = comment.comment_reactions.to_a %>
|
|
92
|
+
<% reaction_groups = reactions.group_by(&:emoji) %>
|
|
93
|
+
<% current_user_id = Current.user&.id %>
|
|
94
|
+
<% reacted_emojis = reactions.filter { |reaction| reaction.user_id == current_user_id }.map(&:emoji).to_set %>
|
|
95
|
+
<% if reaction_groups.any? %>
|
|
96
|
+
<div class="comment-reactions">
|
|
97
|
+
<div class="comment-reaction-list">
|
|
98
|
+
<% reaction_groups.sort_by { |emoji, _| emoji }.each do |emoji, grouped| %>
|
|
99
|
+
<% count = grouped.size %>
|
|
100
|
+
<% count_class = count > 1 ? "comment-reaction-count is-visible" : "comment-reaction-count" %>
|
|
101
|
+
<button class="comment-reaction <%= 'reacted' if reacted_emojis.include?(emoji) %>" type="button" data-action="click->comment#toggleReaction" data-emoji="<%= emoji %>" data-reacted="<%= reacted_emojis.include?(emoji) %>">
|
|
102
|
+
<span class="comment-reaction-emoji"><%= emoji %></span>
|
|
103
|
+
<span class="<%= count_class %>"><%= count %></span>
|
|
104
|
+
</button>
|
|
105
|
+
<% end %>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
<% end %>
|
|
109
|
+
|
|
110
|
+
<% if comment_topic.present? && current_topic_id.blank? %>
|
|
111
|
+
<div class="comment-topic-link">
|
|
112
|
+
<a href="#" class="comment-topic-switch" data-topic-id="<%= comment_topic.id %>">#<%= comment_topic.name %></a>
|
|
113
|
+
</div>
|
|
114
|
+
<% end %>
|
|
115
|
+
<% if comment.action.present? %>
|
|
116
|
+
<div class="comment-action-block" data-comment-id="<%= comment.id %>">
|
|
117
|
+
<details class="comment-action-details">
|
|
118
|
+
<summary class="comment-action-summary"><%= t("collavre.comments.action_summary") %></summary>
|
|
119
|
+
<div class="comment-action-body">
|
|
120
|
+
<pre class="comment-action-json" data-comment-action-json><%= formatted_comment_action(comment) %></pre>
|
|
121
|
+
<% if can_approve && comment.action_executed_at.blank? %>
|
|
122
|
+
<button class="edit-comment-action-btn" type="button" data-comment-id="<%= comment.id %>"><%= t("collavre.comments.edit_action_button") %></button>
|
|
123
|
+
<form class="comment-action-edit-form" data-comment-id="<%= comment.id %>" style="display:none;">
|
|
124
|
+
<textarea class="comment-action-edit-textarea" name="comment[action]" rows="8"><%= formatted_comment_action(comment) %></textarea>
|
|
125
|
+
<div class="comment-action-edit-buttons">
|
|
126
|
+
<button class="cancel-comment-action-edit-btn" type="button"><%= t("app.cancel") %></button>
|
|
127
|
+
<button class="save-comment-action-btn" type="submit"><%= t("app.save") %></button>
|
|
128
|
+
</div>
|
|
129
|
+
</form>
|
|
130
|
+
<% end %>
|
|
131
|
+
</div>
|
|
132
|
+
</details>
|
|
133
|
+
</div>
|
|
134
|
+
<% end %>
|
|
135
|
+
<% if comment.activity_logs.exists? %>
|
|
136
|
+
<div class="comment-activity-log-block">
|
|
137
|
+
<details>
|
|
138
|
+
<summary><%= t("collavre.comments.activity_logs_summary") %></summary>
|
|
139
|
+
<%= turbo_frame_tag "activity_log_details_#{comment.id}", src: collavre.creative_comment_activity_log_path(comment.creative, comment), loading: "lazy" do %>
|
|
140
|
+
Loading...
|
|
141
|
+
<% end %>
|
|
142
|
+
</details>
|
|
143
|
+
</div>
|
|
144
|
+
<% end %>
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
</div>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
<div id="comments-popup" data-controller="comments--popup comments--list comments--form comments--presence comments--mention-menu comments--topics" class="popup-box"
|
|
3
|
+
data-loading-text="<%= t('app.loading') %>"
|
|
4
|
+
data-delete-confirm-text="<%= t("collavre.comments.delete_confirm") %>"
|
|
5
|
+
data-update-comment-text="<%= t('collavre.comments.update_comment') %>"
|
|
6
|
+
data-convert-confirm-text="<%= t('collavre.comments.convert_confirm') %>"
|
|
7
|
+
data-copy-link-success-text="<%= t('collavre.comments.copy_link_success') %>"
|
|
8
|
+
data-copy-link-error-text="<%= t('collavre.comments.copy_link_error') %>"
|
|
9
|
+
data-approve-success-text="<%= t('collavre.comments.approve_success') %>"
|
|
10
|
+
data-approve-error-text="<%= t('collavre.comments.approve_error') %>"
|
|
11
|
+
data-action-update-success-text="<%= t('collavre.comments.action_update_success') %>"
|
|
12
|
+
data-action-update-error-text="<%= t('collavre.comments.action_update_error') %>"
|
|
13
|
+
data-search-empty-text="<%= t('collavre.comments.search_empty_prompt') %>"
|
|
14
|
+
data-speech-unavailable-text="<%= t('collavre.comments.speech_unavailable') %>"
|
|
15
|
+
data-voice-start-text="<%= t('collavre.comments.voice_button') %>"
|
|
16
|
+
data-voice-stop-text="<%= t('collavre.comments.voice_stop') %>"
|
|
17
|
+
data-move-no-selection-text="<%= t('collavre.comments.move_no_selection') %>"
|
|
18
|
+
data-move-error-text="<%= t('collavre.comments.move_error') %>">
|
|
19
|
+
<div class="resize-handle resize-handle-left" data-comments--popup-target="leftHandle"></div>
|
|
20
|
+
<div class="resize-handle resize-handle-right" data-comments--popup-target="rightHandle"></div>
|
|
21
|
+
<button id="close-comments-btn" data-comments--popup-target="closeButton" class="popup-close-btn">×</button>
|
|
22
|
+
<h3 id="comments-popup-title" data-comments--popup-target="title"><%= t('collavre.comments.comments') %></h3>
|
|
23
|
+
<div id="comment-participants" data-comments--presence-target="participants" data-comments--mention-menu-target="participants"></div>
|
|
24
|
+
<div id="comment-topics" data-comments--topics-target="list" class="comment-topics-list"
|
|
25
|
+
data-confirm-delete-text="<%= t('collavre.topics.delete_confirm') %>"
|
|
26
|
+
data-new-topic-placeholder="<%= t('collavre.topics.new_placeholder') %>"></div>
|
|
27
|
+
<%= render UserMentionMenuComponent.new(menu_id: 'mention-menu') %>
|
|
28
|
+
<div id="comments-list" data-comments--popup-target="list" data-comments--list-target="list"><%= t('app.loading') %></div>
|
|
29
|
+
<div id="typing-indicator" data-comments--presence-target="typingIndicator"></div>
|
|
30
|
+
<form id="new-comment-form" data-comments--popup-target="form" data-comments--form-target="form" style="display:none;">
|
|
31
|
+
<textarea class="shared-input-surface" name="comment[content]" data-comments--form-target="textarea" data-comments--presence-target="textarea" data-comments--mention-menu-target="textarea" rows="2" enterkeyhint="send"></textarea>
|
|
32
|
+
<div class="comment-bottom">
|
|
33
|
+
<input type="file" id="comment-images" name="comment[images][]" accept="image/*" multiple data-comments--form-target="imageInput" style="display:none;" />
|
|
34
|
+
<label style="height: 22px"><input type="checkbox" id="comment-private" data-comments--form-target="privateCheckbox" data-comments--presence-target="privateCheckbox" name="comment[private]" /> <%= t('collavre.comments.private') %></label>
|
|
35
|
+
<div class="comment-actions">
|
|
36
|
+
<button class="creative-action-btn" id="attach-image-btn" data-comments--form-target="imageButton" type="button"><%= t('collavre.comments.image_button') %></button>
|
|
37
|
+
<button class="creative-action-btn" id="voice-comments-btn" data-comments--form-target="voiceButton" type="button" data-voice-state="idle"><%= t('collavre.comments.voice_button') %></button>
|
|
38
|
+
<button class="creative-action-btn" id="search-comments-btn" data-comments--form-target="searchButton" type="button"><%= t('collavre.comments.search_button') %></button>
|
|
39
|
+
<button class="creative-action-btn" id="move-comments-btn" data-comments--form-target="moveButton" type="button" disabled><%= t('collavre.comments.move_button') %></button>
|
|
40
|
+
<button class="creative-action-btn" id="cancel-edit-btn" data-comments--form-target="cancel" type="button" style="display:none;"><%= t('app.cancel') %></button>
|
|
41
|
+
<button class="creative-action-btn" type="submit" data-comments--form-target="submit"><%= svg_tag 'send.svg', class: 'send-icon' %></button>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="comment-attachment-list" data-comments--form-target="attachmentList" aria-live="polite"></div>
|
|
44
|
+
</div>
|
|
45
|
+
</form>
|
|
46
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%= turbo_stream_from [creative, :comments] %>
|
|
2
|
+
<% if comments.any? %>
|
|
3
|
+
<% current_topic_id = local_assigns[:current_topic_id] %>
|
|
4
|
+
<% comments.each do |comment| %>
|
|
5
|
+
<%= render partial: 'collavre/comments/comment', locals: { comment: comment, read_by_users: read_receipts&.[](comment.id), present_user_ids: present_user_ids, current_topic_id: current_topic_id } %>
|
|
6
|
+
<% end %>
|
|
7
|
+
<% else %>
|
|
8
|
+
<div id="no-comments"><%= t('collavre.comments.no_comments') %></div>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<div id="comment-participants">
|
|
2
|
+
<% accessible_users = [creative.user].compact + creative.all_shared_users(:feedback).map(&:user) %>
|
|
3
|
+
<% accessible_users.uniq.each do |u| %>
|
|
4
|
+
<% classes = 'avatar comment-presence-avatar' %>
|
|
5
|
+
<% classes += ' inactive' unless present_ids.include?(u.id) %>
|
|
6
|
+
<%= render AvatarComponent.new(user: u, size: 20, classes: classes, data: { email: u.email }) %>
|
|
7
|
+
<% end %>
|
|
8
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<div id="global-reaction-picker"
|
|
2
|
+
class="comment-reaction-picker"
|
|
3
|
+
hidden
|
|
4
|
+
data-controller="reaction-picker"
|
|
5
|
+
data-action="click@window->reaction-picker#handleClickOutside"
|
|
6
|
+
style="position: fixed; z-index: 9999;">
|
|
7
|
+
<% ["👍", "🎉", "❤️", "😂", "😮", "😢", "😡", "✅", "👌", "👀"].each do |emoji| %>
|
|
8
|
+
<button class="comment-reaction-picker-emoji"
|
|
9
|
+
type="button"
|
|
10
|
+
data-action="click->reaction-picker#select"
|
|
11
|
+
data-emoji="<%= emoji %>">
|
|
12
|
+
<%= emoji %>
|
|
13
|
+
</button>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<% if defined?(read_by_users) && read_by_users.present? %>
|
|
2
|
+
<div class="read-receipt-container">
|
|
3
|
+
<div class="read-receipt-avatars">
|
|
4
|
+
<% read_by_users.each do |user| %>
|
|
5
|
+
<% present_ids = defined?(present_user_ids) ? present_user_ids : nil %>
|
|
6
|
+
<% classes = ['avatar', 'comment-presence-avatar'] %>
|
|
7
|
+
<% classes << 'inactive' if present_ids && !present_ids.include?(user.id) %>
|
|
8
|
+
<div class="read-receipt-avatar" title="<%= t('collavre.comments.read_by', name: user.display_name) %>">
|
|
9
|
+
<%= render AvatarComponent.new(
|
|
10
|
+
user: user,
|
|
11
|
+
size: 16,
|
|
12
|
+
classes: classes.join(' '),
|
|
13
|
+
data: { user_id: user.id }
|
|
14
|
+
) %>
|
|
15
|
+
</div>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<% if @parent_creative %>
|
|
2
|
+
<%= button_tag(
|
|
3
|
+
type: 'button',
|
|
4
|
+
class: 'popup-menu-item add-creative-btn',
|
|
5
|
+
data: { parent_id: @parent_creative.id },
|
|
6
|
+
aria: { label: t('collavre.creatives.index.new_creative') },
|
|
7
|
+
title: t('collavre.creatives.index.new_creative_shortcut')
|
|
8
|
+
) do %>
|
|
9
|
+
<span aria-hidden="true"><%= svg_tag 'add.svg', class: 'icon-add', width: 16, height: 16 %></span>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% else %>
|
|
12
|
+
<%= button_tag(
|
|
13
|
+
type: 'button',
|
|
14
|
+
class: 'popup-menu-item new-root-creative-btn',
|
|
15
|
+
aria: { label: t('collavre.creatives.index.new_creative') },
|
|
16
|
+
title: t('collavre.creatives.index.new_creative_shortcut')
|
|
17
|
+
) do %>
|
|
18
|
+
<span aria-hidden="true"><%= svg_tag 'add.svg', class: 'icon-add', width: 16, height: 16 %></span>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<%= render PopupMenuComponent.new(
|
|
2
|
+
button_content: t('collavre.creatives.index.delete'),
|
|
3
|
+
button_classes: ["danger-link", local_assigns[:extra_button_classes]].compact.join(" "),
|
|
4
|
+
menu_id: 'delete-options-popup-' + [local_assigns[:extra_button_classes]].compact.join('-')) do %>
|
|
5
|
+
<%= button_to t('collavre.creatives.index.delete_only_this'), @parent_creative, method: :delete,
|
|
6
|
+
form: { data: { turbo_confirm: t('collavre.creatives.index.are_you_sure_delete_only_this') } },
|
|
7
|
+
params: {}, class: 'danger-link popup-menu-item' %>
|
|
8
|
+
<%= button_to t('collavre.creatives.index.delete_with_children'), @parent_creative, method: :delete,
|
|
9
|
+
params: { delete_with_children: true },
|
|
10
|
+
form: { data: { turbo_confirm: t('collavre.creatives.index.are_you_sure_delete_with_children') } },
|
|
11
|
+
class: 'danger-link' %>
|
|
12
|
+
<% end %>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<div id="github-integration-modal"
|
|
2
|
+
data-success-message="<%= t('collavre.creatives.index.github_integration_saved', default: 'Github integration saved successfully.') %>"
|
|
3
|
+
data-login-required="<%= t('collavre.creatives.index.github_integration_login_required', default: 'Sign in with your Github account to start the integration.') %>"
|
|
4
|
+
data-no-creative="<%= t('collavre.creatives.index.github_integration_missing_creative', default: 'No Creative selected for integration.') %>"
|
|
5
|
+
data-webhook-url-label="<%= t('collavre.creatives.index.github_integration_webhook_url_label', default: 'Webhook URL') %>"
|
|
6
|
+
data-webhook-secret-label="<%= t('collavre.creatives.index.github_integration_webhook_secret_label', default: 'Webhook secret') %>"
|
|
7
|
+
data-existing-message="<%= t('collavre.creatives.index.github_integration_existing_message', default: 'You\'re already connected to the repositories below.') %>"
|
|
8
|
+
data-delete-confirm="<%= t('collavre.creatives.index.github_integration_delete_confirm', default: 'Do you want to remove the Github integration?') %>"
|
|
9
|
+
data-delete-success="<%= t('collavre.creatives.index.github_integration_delete_success', default: 'Github integration removed successfully.') %>"
|
|
10
|
+
data-delete-error="<%= t('collavre.creatives.index.github_integration_delete_error', default: 'Failed to remove the Github integration.') %>"
|
|
11
|
+
data-delete-button-label="<%= t('collavre.creatives.index.github_integration_delete_button', default: 'Remove integration') %>"
|
|
12
|
+
data-delete-select-warning="<%= t('collavre.creatives.index.github_integration_delete_select_warning', default: '삭제할 Repository를 선택하세요.') %>"
|
|
13
|
+
style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;align-items:center;justify-content:center;">
|
|
14
|
+
<div class="popup-box" style="min-width:360px;max-width:90vw;">
|
|
15
|
+
<button type="button" id="close-github-modal" class="popup-close-btn">×</button>
|
|
16
|
+
<h2><%= t('collavre.creatives.index.github_integration_title', default: 'Configure Github integration') %></h2>
|
|
17
|
+
<p id="github-integration-status" class="github-modal-status"></p>
|
|
18
|
+
|
|
19
|
+
<div class="github-wizard-step" id="github-step-connect">
|
|
20
|
+
<p id="github-connect-message" class="github-modal-subtext"><%= t('collavre.creatives.index.github_integration_connect', default: 'Sign in with your Github account to start linking.') %></p>
|
|
21
|
+
<form id="github-login-form" action="/auth/github" method="post" target="github-auth-window" style="display:none;">
|
|
22
|
+
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
|
|
23
|
+
</form>
|
|
24
|
+
<button type="button" id="github-login-btn" class="btn btn-primary" data-window-width="620" data-window-height="720">
|
|
25
|
+
<%= t('collavre.creatives.index.github_login_button', default: 'Sign in with Github') %>
|
|
26
|
+
</button>
|
|
27
|
+
<div id="github-existing-connections" style="display:none;margin-top:1.25em;">
|
|
28
|
+
<p style="margin-bottom:0.5em;"><%= t('collavre.creatives.index.github_integration_existing_intro', default: '이미 연동된 Repository 목록입니다:') %></p>
|
|
29
|
+
<ul id="github-existing-repo-list" style="padding-left:1.2em;margin-bottom:0.75em;color:var(--color-text);"></ul>
|
|
30
|
+
<button type="button" id="github-delete-btn" class="btn btn-danger" style="display:none;">
|
|
31
|
+
<%= t('collavre.creatives.index.github_integration_delete_button', default: '연동 삭제') %>
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="github-wizard-step" id="github-step-organization" style="display:none;">
|
|
37
|
+
<p class="github-modal-subtext"><%= t('collavre.creatives.index.github_integration_choose_org', default: 'Select the organization that owns the repositories.') %></p>
|
|
38
|
+
<div id="github-organization-list" class="github-list github-modal-list-box" style="max-height:200px;overflow:auto;"></div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="github-wizard-step" id="github-step-repositories" style="display:none;">
|
|
42
|
+
<p class="github-modal-subtext"><%= t('collavre.creatives.index.github_integration_choose_repo', default: 'Select repositories to link. You can choose multiple.') %></p>
|
|
43
|
+
<div id="github-repository-list" class="github-list github-modal-list-box" style="max-height:240px;overflow:auto;"></div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="github-wizard-step" id="github-step-summary" style="display:none;">
|
|
47
|
+
<p class="github-modal-subtext"><%= t('collavre.creatives.index.github_integration_summary', default: 'The following repositories will be linked to this Creative:') %></p>
|
|
48
|
+
<p id="github-webhook-instructions" class="github-modal-subtext" style="display:none;margin-bottom:0.5em;"><%= t('collavre.creatives.index.github_integration_webhook_instructions', default: 'Configure each repository with the webhook details below.') %></p>
|
|
49
|
+
<ul id="github-selected-repos" style="padding-left:1.2em;color:var(--color-text);"></ul>
|
|
50
|
+
<p id="github-summary-empty" class="github-modal-empty" style="display:none;"><%= t('collavre.creatives.index.github_integration_summary_empty', default: 'No repositories selected.') %></p>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="github-wizard-step" id="github-step-prompt" style="display:none;">
|
|
54
|
+
<p class="github-modal-subtext"><%= t('collavre.creatives.index.github_integration_prompt_title', default: 'Review the Gemini analysis prompt and edit it if needed.') %></p>
|
|
55
|
+
<textarea id="github-gemini-prompt" style="width:100%;min-height:220px;padding:0.75em;border:1px solid var(--color-border);border-radius:4px;font-family:monospace;font-size:0.95em;"></textarea>
|
|
56
|
+
<p class="github-modal-subtext" style="margin-top:0.75em;font-size:0.9em;">
|
|
57
|
+
<%= t('collavre.creatives.index.github_integration_prompt_help', default: 'Use the placeholders below to inject runtime details as needed:') %>
|
|
58
|
+
<code>#{pr_title}</code>,
|
|
59
|
+
<code>#{pr_body}</code>,
|
|
60
|
+
<code>#{commit_messages}</code>,
|
|
61
|
+
<code>#{diff}</code>,
|
|
62
|
+
<code>#{creative_tree}</code>,
|
|
63
|
+
<code>#{language_instructions}</code>
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div id="github-wizard-error" style="display:none;margin:0.5em 0;color:#c0392b;font-weight:bold;"></div>
|
|
68
|
+
|
|
69
|
+
<div class="github-wizard-footer" style="display:flex;justify-content:space-between;gap:0.5em;margin-top:1.5em;">
|
|
70
|
+
<button type="button" id="github-prev-btn" class="btn btn-secondary" style="display:none;"><%= t('app.previous', default: 'Previous') %></button>
|
|
71
|
+
<div style="margin-left:auto;display:flex;gap:0.5em;">
|
|
72
|
+
<button type="button" id="github-next-btn" class="btn btn-primary" style="display:none;"><%= t('app.next', default: 'Next') %></button>
|
|
73
|
+
<button type="button" id="github-finish-btn" class="btn btn-primary" style="display:none;"><%= t('app.finish', default: 'Finish') %></button>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|