collavre 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/collavre/actiontext.css +73 -71
- data/app/assets/stylesheets/collavre/activity_logs.css +18 -45
- data/app/assets/stylesheets/collavre/comments_popup.css +197 -35
- data/app/assets/stylesheets/collavre/creatives.css +101 -51
- data/app/assets/stylesheets/collavre/dark_mode.css +221 -88
- data/app/assets/stylesheets/collavre/design_tokens.css +334 -0
- data/app/assets/stylesheets/collavre/mention_menu.css +13 -9
- data/app/assets/stylesheets/collavre/popup.css +57 -27
- data/app/assets/stylesheets/collavre/slide_view.css +6 -6
- data/app/assets/stylesheets/collavre/user_menu.css +4 -5
- data/app/components/collavre/plans_timeline_component.html.erb +2 -2
- data/app/controllers/collavre/admin/orchestration_controller.rb +9 -2
- data/app/controllers/collavre/admin/settings_controller.rb +199 -0
- data/app/controllers/collavre/comments/reactions_controller.rb +1 -9
- data/app/controllers/collavre/comments_controller.rb +39 -162
- data/app/controllers/collavre/creatives_controller.rb +18 -58
- data/app/controllers/collavre/users_controller.rb +31 -3
- data/app/helpers/collavre/application_helper.rb +97 -0
- data/app/helpers/collavre/creatives_helper.rb +10 -202
- data/app/javascript/collavre.js +0 -1
- data/app/javascript/components/creative_tree_row.js +3 -2
- data/app/javascript/controllers/comment_controller.js +309 -4
- data/app/javascript/controllers/comments/form_controller.js +52 -0
- data/app/javascript/controllers/comments/presence_controller.js +13 -0
- data/app/javascript/controllers/creatives/tree_controller.js +2 -1
- data/app/javascript/controllers/link_creative_controller.js +29 -3
- data/app/javascript/lib/__tests__/html_code_block_wrapper.test.js +201 -0
- data/app/javascript/lib/html_code_block_wrapper.js +168 -0
- data/app/javascript/lib/utils/markdown.js +2 -1
- data/app/javascript/modules/creative_row_editor.js +5 -1
- data/app/javascript/utils/emoji_parser.js +21 -0
- data/app/jobs/collavre/ai_agent_job.rb +6 -2
- data/app/jobs/collavre/cron_action_job.rb +18 -6
- data/app/jobs/collavre/cron_scheduler_job.rb +112 -0
- data/app/models/collavre/comment/approvable.rb +50 -0
- data/app/models/collavre/comment/broadcastable.rb +119 -0
- data/app/models/collavre/comment/notifiable.rb +111 -0
- data/app/models/collavre/comment.rb +13 -258
- data/app/models/collavre/comment_reaction.rb +15 -0
- data/app/models/collavre/creative/describable.rb +86 -0
- data/app/models/collavre/creative/linkable.rb +77 -0
- data/app/models/collavre/creative/permissible.rb +103 -0
- data/app/models/collavre/creative.rb +3 -289
- data/app/models/collavre/orchestrator_policy.rb +1 -1
- data/app/models/collavre/system_setting.rb +27 -1
- data/app/models/collavre/user.rb +42 -0
- data/app/models/collavre/user_theme.rb +10 -0
- data/app/services/collavre/ai_agent/approval_handler.rb +110 -0
- data/app/services/collavre/ai_agent/message_builder.rb +129 -0
- data/app/services/collavre/ai_agent/review_handler.rb +70 -0
- data/app/services/collavre/ai_agent_service.rb +93 -150
- data/app/services/collavre/ai_client.rb +23 -4
- data/app/services/collavre/auto_theme_generator.rb +168 -50
- data/app/services/collavre/command_menu_service.rb +70 -0
- data/app/services/collavre/comment_move_service.rb +94 -0
- data/app/services/collavre/comments/action_executor.rb +10 -0
- data/app/services/collavre/comments/mcp_command.rb +1 -2
- data/app/services/collavre/creatives/create_service.rb +86 -0
- data/app/services/collavre/creatives/destroy_service.rb +41 -0
- data/app/services/collavre/creatives/index_query.rb +3 -0
- data/app/services/collavre/markdown_converter.rb +240 -0
- data/app/services/collavre/mention_parser.rb +63 -0
- data/app/services/collavre/orchestration/agent_context_builder.rb +24 -8
- data/app/services/collavre/orchestration/agent_orchestrator.rb +59 -10
- data/app/services/collavre/orchestration/loop_breaker.rb +12 -7
- data/app/services/collavre/orchestration/policy_resolver.rb +16 -2
- data/app/services/collavre/orchestration/scheduler.rb +4 -3
- data/app/services/collavre/orchestration/stuck_detector.rb +1 -1
- data/app/services/collavre/system_events/context_builder.rb +1 -6
- data/app/services/collavre/tools/creative_batch_service.rb +107 -0
- data/app/services/collavre/tools/creative_update_service.rb +17 -12
- data/app/services/collavre/tools/cron_create_service.rb +17 -5
- data/app/views/admin/shared/_tabs.html.erb +2 -1
- data/app/views/collavre/admin/orchestration/show.html.erb +11 -0
- data/app/views/collavre/admin/settings/_system_tab.html.erb +138 -0
- data/app/views/collavre/admin/settings/_uiux_tab.html.erb +44 -0
- data/app/views/collavre/admin/settings/index.html.erb +11 -0
- data/app/views/collavre/admin/settings/uiux.html.erb +11 -0
- data/app/views/collavre/comments/_comment.html.erb +15 -5
- data/app/views/collavre/comments/_comments_popup.html.erb +9 -2
- data/app/views/collavre/creatives/_mobile_actions_menu.html.erb +0 -3
- data/app/views/collavre/creatives/_share_button.html.erb +0 -52
- data/app/views/collavre/creatives/_share_modal.html.erb +52 -0
- data/app/views/collavre/creatives/index.html.erb +5 -8
- data/app/views/collavre/shared/navigation/_panels.html.erb +2 -2
- data/app/views/collavre/user_themes/index.html.erb +7 -9
- data/app/views/collavre/users/_contact_management.html.erb +2 -1
- data/app/views/collavre/users/edit_ai.html.erb +7 -0
- data/app/views/collavre/users/index.html.erb +16 -1
- data/app/views/collavre/users/new_ai.html.erb +18 -8
- data/app/views/collavre/users/passkeys.html.erb +1 -1
- data/app/views/collavre/users/show.html.erb +1 -1
- data/app/views/layouts/collavre/slide.html.erb +8 -1
- data/config/locales/admin.en.yml +88 -0
- data/config/locales/admin.ko.yml +88 -0
- data/config/locales/ai_agent.en.yml +5 -1
- data/config/locales/ai_agent.ko.yml +5 -1
- data/config/locales/comments.en.yml +5 -1
- data/config/locales/comments.ko.yml +5 -1
- data/config/locales/orchestration.en.yml +8 -0
- data/config/locales/orchestration.ko.yml +8 -0
- data/config/locales/users.en.yml +12 -0
- data/config/locales/users.ko.yml +12 -0
- data/config/routes.rb +7 -1
- data/db/migrate/20260212011655_add_quoted_comment_to_comments.rb +7 -0
- data/db/migrate/20260213044247_add_agent_conf_to_users.rb +5 -0
- data/lib/collavre/engine.rb +25 -0
- data/lib/collavre/version.rb +1 -1
- metadata +32 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Collavre
|
|
2
|
+
require "sorbet-runtime"
|
|
3
|
+
require "rails_mcp_engine"
|
|
4
|
+
|
|
5
|
+
module Tools
|
|
6
|
+
class CreativeBatchService
|
|
7
|
+
extend T::Sig
|
|
8
|
+
extend ToolMeta
|
|
9
|
+
|
|
10
|
+
tool_name "creative_batch_service"
|
|
11
|
+
tool_description "Execute multiple Creative operations (create, update, delete) in a single batch call. " \
|
|
12
|
+
"All operations run inside a transaction — if any operation fails, the entire batch is rolled back.\n\n" \
|
|
13
|
+
"This tool requires approval before execution.\n\n" \
|
|
14
|
+
"Each operation is a hash with an 'action' key ('create', 'update', or 'delete') plus action-specific fields."
|
|
15
|
+
|
|
16
|
+
def self.requires_approval?
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
tool_param :operations, description: "Array of operation objects. Each object must have an 'action' key.\n\n" \
|
|
21
|
+
"For 'create': { action: 'create', parent_id: <int>, description: <string>, progress: <float>, after_id: <int>, before_id: <int> }\n" \
|
|
22
|
+
"For 'update': { action: 'update', id: <int>, description: <string>, progress: 1.0, parent_id: <int> } — progress only accepts 1.0 (complete) and only on leaf Creatives\n" \
|
|
23
|
+
"For 'delete': { action: 'delete', id: <int> }\n\n" \
|
|
24
|
+
"Fields other than 'action' and 'id'/'parent_id' are optional.", required: true
|
|
25
|
+
|
|
26
|
+
class BatchRollbackError < StandardError
|
|
27
|
+
attr_reader :results
|
|
28
|
+
|
|
29
|
+
def initialize(results)
|
|
30
|
+
@results = results
|
|
31
|
+
super("Batch operation failed")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sig { params(operations: T::Array[T::Hash[String, T.untyped]]).returns(T::Hash[Symbol, T.untyped]) }
|
|
36
|
+
def call(operations:)
|
|
37
|
+
raise "Current.user is required" unless Current.user
|
|
38
|
+
|
|
39
|
+
results = []
|
|
40
|
+
|
|
41
|
+
ApplicationRecord.transaction do
|
|
42
|
+
operations.each_with_index do |op, idx|
|
|
43
|
+
op = op.transform_keys(&:to_s)
|
|
44
|
+
action = op["action"]
|
|
45
|
+
|
|
46
|
+
result = case action
|
|
47
|
+
when "create" then execute_create(op)
|
|
48
|
+
when "update" then execute_update(op)
|
|
49
|
+
when "delete" then execute_delete(op)
|
|
50
|
+
else
|
|
51
|
+
{ error: "Unknown action '#{action}'" }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
result[:index] = idx
|
|
55
|
+
result[:action] = action
|
|
56
|
+
results << result
|
|
57
|
+
|
|
58
|
+
raise BatchRollbackError, results if result[:error]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
{ success: true, results: results }
|
|
63
|
+
rescue BatchRollbackError => e
|
|
64
|
+
failed = e.results.find { |r| r[:error] }
|
|
65
|
+
{ success: false, error: "Operation #{failed[:index]} (#{failed[:action]}) failed: #{failed[:error]}", results: e.results }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def execute_create(op)
|
|
71
|
+
service = CreativeCreateService.new
|
|
72
|
+
service.call(
|
|
73
|
+
description: op["description"] || "",
|
|
74
|
+
parent_id: op["parent_id"]&.to_i,
|
|
75
|
+
progress: op["progress"]&.to_f,
|
|
76
|
+
after_id: op["after_id"]&.to_i,
|
|
77
|
+
before_id: op["before_id"]&.to_i
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def execute_update(op)
|
|
82
|
+
id = op["id"]&.to_i
|
|
83
|
+
return { error: "id is required for update" } unless id&.positive?
|
|
84
|
+
|
|
85
|
+
service = CreativeUpdateService.new
|
|
86
|
+
service.call(
|
|
87
|
+
id: id,
|
|
88
|
+
description: op["description"],
|
|
89
|
+
progress: op.key?("progress") ? op["progress"]&.to_f : nil,
|
|
90
|
+
parent_id: op.key?("parent_id") ? op["parent_id"]&.to_i : nil
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def execute_delete(op)
|
|
95
|
+
id = op["id"]&.to_i
|
|
96
|
+
return { error: "id is required for delete" } unless id&.positive?
|
|
97
|
+
|
|
98
|
+
creative = Creative.find_by(id: id)
|
|
99
|
+
return { error: "Creative not found", id: id } unless creative
|
|
100
|
+
return { error: "No write permission on this Creative", id: id } unless creative.has_permission?(Current.user, :write)
|
|
101
|
+
|
|
102
|
+
creative.destroy!
|
|
103
|
+
{ success: true, id: id, deleted: true }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -7,11 +7,11 @@ module Tools
|
|
|
7
7
|
extend ToolMeta
|
|
8
8
|
|
|
9
9
|
tool_name "creative_update_service"
|
|
10
|
-
tool_description "Update an existing Creative's content, progress, or parent. Use this to:\n- Modify the description/title of a Creative\n-
|
|
10
|
+
tool_description "Update an existing Creative's content, progress, or parent. Use this to:\n- Modify the description/title of a Creative\n- Mark a leaf Creative as complete (progress = 1.0)\n- Move a Creative to a different parent\n\nProgress constraints:\n- Only 1.0 (100%) is allowed — partial progress updates are not supported\n- Only leaf Creatives (with no children) can have their progress updated\n- Parent Creative progress is automatically calculated from children\n\nUse creative_retrieval_service to find the correct Creative before updating."
|
|
11
11
|
|
|
12
12
|
tool_param :id, description: "The ID of the Creative to update.", required: true
|
|
13
13
|
tool_param :description, description: "New content/title for the Creative. Accepts HTML format. If omitted, description remains unchanged.", required: false
|
|
14
|
-
tool_param :progress, description: "
|
|
14
|
+
tool_param :progress, description: "Set to 1.0 to mark a leaf Creative as complete. Only 1.0 is allowed; partial progress and updates on parent Creatives are rejected.", required: false
|
|
15
15
|
tool_param :parent_id, description: "New parent Creative ID to move this Creative under. Use null/0 to make it a root Creative.", required: false
|
|
16
16
|
|
|
17
17
|
sig { params(id: Integer, description: T.nilable(String), progress: T.nilable(Float), parent_id: T.nilable(Integer)).returns(T::Hash[Symbol, T.untyped]) }
|
|
@@ -29,7 +29,6 @@ module Tools
|
|
|
29
29
|
|
|
30
30
|
# Get the effective origin for updating content
|
|
31
31
|
base = creative.effective_origin
|
|
32
|
-
previous_progress = base.progress
|
|
33
32
|
|
|
34
33
|
updates = {}
|
|
35
34
|
parent_updates = {}
|
|
@@ -41,7 +40,19 @@ module Tools
|
|
|
41
40
|
|
|
42
41
|
# Handle progress update
|
|
43
42
|
if progress.present?
|
|
44
|
-
|
|
43
|
+
progress_value = progress.to_f.clamp(0.0, 1.0)
|
|
44
|
+
|
|
45
|
+
# Only 100% (1.0) progress updates are allowed via tools
|
|
46
|
+
unless progress_value == 1.0
|
|
47
|
+
return { error: "Only progress of 1.0 (100%) is allowed. Partial progress updates are not supported.", id: id }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Only leaf creatives (no children) can have progress updated directly
|
|
51
|
+
if base.children.exists?
|
|
52
|
+
return { error: "Cannot update progress on a parent Creative. Only leaf Creatives (with no children) can be marked complete.", id: id }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
updates[:progress] = progress_value
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
# Handle parent change (on the creative itself, not base)
|
|
@@ -77,14 +88,8 @@ module Tools
|
|
|
77
88
|
if updates.present?
|
|
78
89
|
success &&= base.update(updates)
|
|
79
90
|
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
if success && requested_progress.present? && requested_progress >= 1 && previous_progress.to_f < 1
|
|
83
|
-
if base.children.exists?
|
|
84
|
-
base.self_and_descendants.where(origin_id: nil)
|
|
85
|
-
.update_all(progress: 1.0, updated_at: Time.current)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
91
|
+
# Note: progress updates are only allowed on leaf Creatives (validated above).
|
|
92
|
+
# Parent progress is automatically calculated from children.
|
|
88
93
|
end
|
|
89
94
|
|
|
90
95
|
if success
|
|
@@ -10,7 +10,7 @@ module Tools
|
|
|
10
10
|
tool_description "Create a new recurring scheduled job. The job will periodically post a message to a creative's topic, triggering agent orchestration. Schedule uses cron syntax (e.g., '*/5 * * * *' for every 5 minutes, '0 9 * * *' for daily at 9am)."
|
|
11
11
|
|
|
12
12
|
tool_param :creative_id, description: "The creative ID to post recurring messages to.", required: true
|
|
13
|
-
tool_param :
|
|
13
|
+
tool_param :topic_name, description: "The topic name within the creative to post to. Use 'Main' for the main topic (topic_id = nil).", required: true
|
|
14
14
|
tool_param :schedule, description: "Cron schedule expression (e.g., '0 9 * * *' for daily at 9am, '*/30 * * * *' for every 30 minutes).", required: true
|
|
15
15
|
tool_param :message, description: "The message content to post on each execution. This triggers the agent orchestration pipeline.", required: true
|
|
16
16
|
tool_param :description, description: "Human-readable description of what this cron job does.", required: false
|
|
@@ -18,13 +18,13 @@ module Tools
|
|
|
18
18
|
sig do
|
|
19
19
|
params(
|
|
20
20
|
creative_id: Integer,
|
|
21
|
-
|
|
21
|
+
topic_name: String,
|
|
22
22
|
schedule: String,
|
|
23
23
|
message: String,
|
|
24
24
|
description: T.nilable(String)
|
|
25
25
|
).returns(T::Hash[Symbol, T.untyped])
|
|
26
26
|
end
|
|
27
|
-
def call(creative_id:,
|
|
27
|
+
def call(creative_id:, topic_name:, schedule:, message:, description: nil)
|
|
28
28
|
raise "Current.user is required" unless Current.user
|
|
29
29
|
|
|
30
30
|
creative = Creative.find_by(id: creative_id)
|
|
@@ -33,8 +33,8 @@ module Tools
|
|
|
33
33
|
return { error: "No write permission on this Creative", id: creative_id }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
return
|
|
36
|
+
topic_id = resolve_topic_id(creative, topic_name)
|
|
37
|
+
return topic_id if topic_id.is_a?(Hash) && topic_id[:error]
|
|
38
38
|
|
|
39
39
|
parsed = Fugit.parse(schedule)
|
|
40
40
|
unless parsed.is_a?(Fugit::Cron)
|
|
@@ -65,9 +65,21 @@ module Tools
|
|
|
65
65
|
schedule: task.schedule,
|
|
66
66
|
description: task.description,
|
|
67
67
|
creative_id: creative_id,
|
|
68
|
+
topic_name: topic_name,
|
|
68
69
|
next_run: task.next_time&.iso8601
|
|
69
70
|
}
|
|
70
71
|
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def resolve_topic_id(creative, topic_name)
|
|
76
|
+
return nil if topic_name.casecmp("main").zero?
|
|
77
|
+
|
|
78
|
+
topic = Topic.find_by(name: topic_name, creative_id: creative.effective_origin.id)
|
|
79
|
+
return { error: "Topic '#{topic_name}' not found for this creative" } unless topic
|
|
80
|
+
|
|
81
|
+
topic.id
|
|
82
|
+
end
|
|
71
83
|
end
|
|
72
84
|
end
|
|
73
85
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<div class="tab-list">
|
|
2
|
-
<%= link_to t('admin.tabs.system'),
|
|
2
|
+
<%= link_to t('admin.tabs.system'), collavre.admin_settings_path, class: "tab-button #{'active' if controller_name == 'settings' && action_name == 'index'}" %>
|
|
3
|
+
<%= link_to t('admin.tabs.uiux'), collavre.admin_uiux_path, class: "tab-button #{'active' if controller_name == 'settings' && action_name == 'uiux'}" %>
|
|
3
4
|
<%= link_to t('admin.tabs.users'), collavre.users_path, class: "tab-button #{'active' if controller_name == 'users'}" %>
|
|
4
5
|
<%= link_to t('admin.tabs.orchestration'), collavre.admin_orchestration_path, class: "tab-button #{'active' if controller_name == 'orchestration'}" %>
|
|
5
6
|
</div>
|
|
@@ -49,6 +49,17 @@
|
|
|
49
49
|
<li><code>topic_max_concurrent_jobs</code> — <%= t('admin.orchestration.opt_topic_max_concurrent') %></li>
|
|
50
50
|
</ul>
|
|
51
51
|
|
|
52
|
+
<p style="margin-bottom: 0.75em;"><strong><%= t('admin.orchestration.collaboration_options') %></strong></p>
|
|
53
|
+
<ul style="margin-left: 1.5em; margin-bottom: 1em;">
|
|
54
|
+
<li><code>a2a_focus_instruction</code> — <%= t('admin.orchestration.opt_a2a_focus') %></li>
|
|
55
|
+
<li><code>a2a_completion_instruction</code> — <%= t('admin.orchestration.opt_a2a_completion') %></li>
|
|
56
|
+
<li><code>a2a_followup_instruction</code> — <%= t('admin.orchestration.opt_a2a_followup') %></li>
|
|
57
|
+
<li><code>mention_rule</code> — <%= t('admin.orchestration.opt_mention_rule') %></li>
|
|
58
|
+
<li><code>confidence_rule</code> — <%= t('admin.orchestration.opt_confidence_rule') %></li>
|
|
59
|
+
<li><code>escalation_rule</code> — <%= t('admin.orchestration.opt_escalation_rule') %></li>
|
|
60
|
+
<li><code>review_rule</code> — <%= t('admin.orchestration.opt_review_rule') %></li>
|
|
61
|
+
</ul>
|
|
62
|
+
|
|
52
63
|
<p style="margin-bottom: 0.75em;"><strong><%= t('admin.orchestration.scope_types') %></strong></p>
|
|
53
64
|
<ul style="margin-left: 1.5em;">
|
|
54
65
|
<li><code>Creative</code> — <%= t('admin.orchestration.scope_creative') %></li>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<section class="tab-panel active">
|
|
2
|
+
<%= tag.div(flash[:alert], class: "flash-alert") if flash[:alert] %>
|
|
3
|
+
<%= form_with url: collavre.admin_settings_path, method: :patch, local: true, html: { class: 'profile-form' } do |f| %>
|
|
4
|
+
<%# system tab %>
|
|
5
|
+
<div>
|
|
6
|
+
<%= f.label :help_link, t('admin.settings.help_link') %>
|
|
7
|
+
<%= f.text_field :help_link, value: @help_link, placeholder: "https://..." %>
|
|
8
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
9
|
+
<%= t('admin.settings.help_link_hint') %>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div style="margin-top: 1.5em;">
|
|
14
|
+
<label class="checkbox-label" style="display: flex; align-items: center; gap: 0.5em;">
|
|
15
|
+
<%= f.check_box :mcp_tool_approval, checked: @mcp_tool_approval %>
|
|
16
|
+
<span><%= t('admin.settings.mcp_tool_approval') %></span>
|
|
17
|
+
</label>
|
|
18
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em; margin-left: 1.8em;">
|
|
19
|
+
<%= t('admin.settings.mcp_tool_approval_hint') %>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div style="margin-top: 1.5em;">
|
|
24
|
+
<label class="checkbox-label" style="display: flex; align-items: center; gap: 0.5em;">
|
|
25
|
+
<%= f.check_box :creatives_login_required, checked: @creatives_login_required %>
|
|
26
|
+
<span><%= t('admin.settings.creatives_login_required') %></span>
|
|
27
|
+
</label>
|
|
28
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em; margin-left: 1.8em;">
|
|
29
|
+
<%= t('admin.settings.creatives_login_required_hint') %>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div style="margin-top: 1.5em;">
|
|
34
|
+
<%= f.label :home_page_path, t('admin.settings.home_page_path') %>
|
|
35
|
+
<%= f.text_field :home_page_path, value: @home_page_path, placeholder: "/" %>
|
|
36
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
37
|
+
<%= t('admin.settings.home_page_path_hint') %>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div style="margin-top: 2em; border-top: 1px solid var(--color-border); padding-top: 1.5em;">
|
|
42
|
+
<h3 style="font-size: 1.1em; margin-bottom: 1em;"><%= t('admin.settings.account_lockout') %></h3>
|
|
43
|
+
<div style="display: flex; gap: 2em; flex-wrap: wrap;">
|
|
44
|
+
<div style="flex: 1; min-width: 200px;">
|
|
45
|
+
<%= f.label :max_login_attempts, t('admin.settings.max_login_attempts') %>
|
|
46
|
+
<%= f.number_field :max_login_attempts, value: @max_login_attempts, min: 1, max: 100, style: "width: 100%;" %>
|
|
47
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
48
|
+
<%= t('admin.settings.max_login_attempts_hint') %>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div style="flex: 1; min-width: 200px;">
|
|
52
|
+
<%= f.label :lockout_duration_minutes, t('admin.settings.lockout_duration_minutes') %>
|
|
53
|
+
<%= f.number_field :lockout_duration_minutes, value: @lockout_duration_minutes, min: 1, max: 1440, style: "width: 100%;" %>
|
|
54
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
55
|
+
<%= t('admin.settings.lockout_duration_minutes_hint') %>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div style="margin-top: 2em; border-top: 1px solid var(--color-border); padding-top: 1.5em;">
|
|
62
|
+
<h3 style="font-size: 1.1em; margin-bottom: 1em;"><%= t('admin.settings.password_policy') %></h3>
|
|
63
|
+
<div style="max-width: 300px;">
|
|
64
|
+
<%= f.label :password_min_length, t('admin.settings.password_min_length') %>
|
|
65
|
+
<%= f.number_field :password_min_length, value: @password_min_length, min: SystemSetting::DEFAULT_PASSWORD_MIN_LENGTH, max: 72, style: "width: 100%;" %>
|
|
66
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
67
|
+
<%= t('admin.settings.password_min_length_hint') %>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div style="margin-top: 2em; border-top: 1px solid var(--color-border); padding-top: 1.5em;">
|
|
73
|
+
<h3 style="font-size: 1.1em; margin-bottom: 1em;"><%= t('admin.settings.session_timeout') %></h3>
|
|
74
|
+
<div style="max-width: 300px;">
|
|
75
|
+
<%= f.label :session_timeout_minutes, t('admin.settings.session_timeout_minutes') %>
|
|
76
|
+
<%= f.number_field :session_timeout_minutes, value: @session_timeout_minutes, min: 0, max: 10080, style: "width: 100%;" %>
|
|
77
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
78
|
+
<%= t('admin.settings.session_timeout_minutes_hint') %>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div style="margin-top: 2em; border-top: 1px solid var(--color-border); padding-top: 1.5em;">
|
|
84
|
+
<h3 style="font-size: 1.1em; margin-bottom: 1em;"><%= t('admin.settings.rate_limiting') %></h3>
|
|
85
|
+
|
|
86
|
+
<div style="margin-bottom: 1.5em;">
|
|
87
|
+
<h4 style="font-size: 1em; margin-bottom: 0.75em; color: var(--color-text-muted);"><%= t('admin.settings.password_reset_rate') %></h4>
|
|
88
|
+
<div style="display: flex; gap: 2em; flex-wrap: wrap;">
|
|
89
|
+
<div style="flex: 1; min-width: 200px;">
|
|
90
|
+
<%= f.label :password_reset_rate_limit, t('admin.settings.rate_limit_requests') %>
|
|
91
|
+
<%= f.number_field :password_reset_rate_limit, value: @password_reset_rate_limit, min: 1, max: 1000, style: "width: 100%;" %>
|
|
92
|
+
</div>
|
|
93
|
+
<div style="flex: 1; min-width: 200px;">
|
|
94
|
+
<%= f.label :password_reset_rate_period_minutes, t('admin.settings.rate_limit_period_minutes') %>
|
|
95
|
+
<%= f.number_field :password_reset_rate_period_minutes, value: @password_reset_rate_period_minutes, min: 1, max: 1440, style: "width: 100%;" %>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
99
|
+
<%= t('admin.settings.password_reset_rate_hint') %>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div>
|
|
104
|
+
<h4 style="font-size: 1em; margin-bottom: 0.75em; color: var(--color-text-muted);"><%= t('admin.settings.api_rate') %></h4>
|
|
105
|
+
<div style="display: flex; gap: 2em; flex-wrap: wrap;">
|
|
106
|
+
<div style="flex: 1; min-width: 200px;">
|
|
107
|
+
<%= f.label :api_rate_limit, t('admin.settings.rate_limit_requests') %>
|
|
108
|
+
<%= f.number_field :api_rate_limit, value: @api_rate_limit, min: 1, max: 10000, style: "width: 100%;" %>
|
|
109
|
+
</div>
|
|
110
|
+
<div style="flex: 1; min-width: 200px;">
|
|
111
|
+
<%= f.label :api_rate_period_minutes, t('admin.settings.rate_limit_period_minutes') %>
|
|
112
|
+
<%= f.number_field :api_rate_period_minutes, value: @api_rate_period_minutes, min: 1, max: 60, style: "width: 100%;" %>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
116
|
+
<%= t('admin.settings.api_rate_hint') %>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div style="margin-top: 2em; border-top: 1px solid var(--color-border); padding-top: 1.5em;">
|
|
122
|
+
<h3 style="font-size: 1.1em; margin-bottom: 1em;"><%= t('admin.settings.auth_providers') %></h3>
|
|
123
|
+
<div style="display: flex; flex-direction: column; gap: 0.8em;">
|
|
124
|
+
<% Rails.application.config.auth_providers.sort_by { |p| p[:priority] }.each do |provider| %>
|
|
125
|
+
<label class="checkbox-label" style="display: flex; align-items: center; gap: 0.5em;">
|
|
126
|
+
<%= check_box_tag "auth_providers[]", provider[:key], @enabled_auth_providers.include?(provider[:key].to_s), id: "auth_provider_#{provider[:key]}" %>
|
|
127
|
+
<span><%= t(provider[:name], default: provider[:name].to_s.humanize) %></span>
|
|
128
|
+
</label>
|
|
129
|
+
<% end %>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.5em;">
|
|
132
|
+
<%= t('admin.settings.auth_providers_hint', default: "Select allowed authentication methods. Warning: Disabling all may lock you out.") %>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<%= f.submit t('admin.settings.save') %>
|
|
137
|
+
<% end %>
|
|
138
|
+
</section>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<section class="tab-panel active">
|
|
2
|
+
<%= tag.div(flash[:alert], class: "flash-alert") if flash[:alert] %>
|
|
3
|
+
<%= form_with url: collavre.admin_uiux_path, method: :patch, local: true, html: { class: 'profile-form' } do |f| %>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<h3 style="font-size: 1.1em; margin-bottom: 1em;"><%= t('admin.settings.default_themes') %></h3>
|
|
7
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-bottom: 1.5em;">
|
|
8
|
+
<%= t('admin.settings.default_themes_hint') %>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div style="display: flex; gap: 2em; flex-wrap: wrap;">
|
|
12
|
+
<div style="flex: 1; min-width: 250px;">
|
|
13
|
+
<%= label_tag :default_light_theme_id, t('admin.settings.default_light_theme') %>
|
|
14
|
+
<%= select_tag :default_light_theme_id,
|
|
15
|
+
options_for_select(
|
|
16
|
+
[[t('admin.settings.theme_system_default'), '']] +
|
|
17
|
+
@available_themes.map { |t| [t.name, t.id] },
|
|
18
|
+
@default_light_theme_id.to_s
|
|
19
|
+
),
|
|
20
|
+
style: "width: 100%;" %>
|
|
21
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
22
|
+
<%= t('admin.settings.default_light_theme_hint') %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div style="flex: 1; min-width: 250px;">
|
|
27
|
+
<%= label_tag :default_dark_theme_id, t('admin.settings.default_dark_theme') %>
|
|
28
|
+
<%= select_tag :default_dark_theme_id,
|
|
29
|
+
options_for_select(
|
|
30
|
+
[[t('admin.settings.theme_system_default'), '']] +
|
|
31
|
+
@available_themes.map { |t| [t.name, t.id] },
|
|
32
|
+
@default_dark_theme_id.to_s
|
|
33
|
+
),
|
|
34
|
+
style: "width: 100%;" %>
|
|
35
|
+
<div class="help-text" style="font-size: 0.85em; color: var(--color-text-muted); margin-top: 0.25em;">
|
|
36
|
+
<%= t('admin.settings.default_dark_theme_hint') %>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<%= f.submit t('admin.settings.save') %>
|
|
43
|
+
<% end %>
|
|
44
|
+
</section>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<% content_for :head do %>
|
|
2
|
+
<%= stylesheet_link_tag 'collavre/comments_popup', media: 'all' %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<h1 class="no-top-margin"><%= t('admin.title') %></h1>
|
|
6
|
+
|
|
7
|
+
<%= render "admin/shared/tabs" %>
|
|
8
|
+
|
|
9
|
+
<div class="tab-panels">
|
|
10
|
+
<%= render "collavre/admin/settings/system_tab" %>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<% content_for :head do %>
|
|
2
|
+
<%= stylesheet_link_tag 'collavre/comments_popup', media: 'all' %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<h1 class="no-top-margin"><%= t('admin.title') %></h1>
|
|
6
|
+
|
|
7
|
+
<%= render "admin/shared/tabs" %>
|
|
8
|
+
|
|
9
|
+
<div class="tab-panels">
|
|
10
|
+
<%= render "collavre/admin/settings/uiux_tab" %>
|
|
11
|
+
</div>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<% current_topic_id = local_assigns[:current_topic_id] %>
|
|
3
3
|
<% has_pending_action = comment.action.present? && comment.action_executed_at.blank? %>
|
|
4
4
|
<% approver_id = comment.approver_id %>
|
|
5
|
-
<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 %>" data-creative-owner-id="<%= comment.creative&.user_id %>" data-has-pending-action="<%= has_pending_action %>" data-approver-id="<%= approver_id %>">
|
|
5
|
+
<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 %>" data-creative-owner-id="<%= comment.creative&.user_id %>" data-has-pending-action="<%= has_pending_action %>" data-approver-id="<%= approver_id %>" data-ai-user="<%= comment.user&.ai_user? %>" data-streaming="<%= local_assigns.fetch(:streaming, false) %>">
|
|
6
6
|
<div class="comment-select">
|
|
7
7
|
<input type="checkbox"
|
|
8
8
|
class="comment-select-checkbox"
|
|
@@ -11,14 +11,11 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
<div>
|
|
13
13
|
<%= render Collavre::AvatarComponent.new(user: comment.user, size: 20, classes: 'avatar comment-avatar') %>
|
|
14
|
-
<% system_prefix = "#{t('collavre.comments.system_user')}:" %>
|
|
15
14
|
<% display_name =
|
|
16
15
|
if comment.user.present?
|
|
17
16
|
comment.user.display_name
|
|
18
|
-
elsif comment.content.to_s.strip.start_with?(system_prefix)
|
|
19
|
-
t('collavre.comments.system_user')
|
|
20
17
|
else
|
|
21
|
-
t('collavre.comments.
|
|
18
|
+
t('collavre.comments.system_user')
|
|
22
19
|
end %>
|
|
23
20
|
<% timestamp = comment.created_at.in_time_zone %>
|
|
24
21
|
<strong><%= display_name %></strong>
|
|
@@ -59,6 +56,14 @@
|
|
|
59
56
|
<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') %>">
|
|
60
57
|
<%= t('collavre.comments.edit_button') %>
|
|
61
58
|
</button>
|
|
59
|
+
<% if comment.user&.ai_user? %>
|
|
60
|
+
<button class="review-comment-btn" data-comment-target="reviewButton" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.review_button') %>">
|
|
61
|
+
<%= t('collavre.comments.review_button') %>
|
|
62
|
+
</button>
|
|
63
|
+
<button class="replace-comment-btn" data-comment-target="replaceButton" data-comment-id="<%= comment.id %>" title="<%= t('collavre.comments.replace_button') %>" disabled>
|
|
64
|
+
<%= t('collavre.comments.replace_button') %>
|
|
65
|
+
</button>
|
|
66
|
+
<% end %>
|
|
62
67
|
<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') %>">
|
|
63
68
|
<%= t('collavre.comments.copy_link_button') %>
|
|
64
69
|
</button>
|
|
@@ -66,6 +71,11 @@
|
|
|
66
71
|
<%= t('collavre.comments.delete_button') %>
|
|
67
72
|
</button>
|
|
68
73
|
</div>
|
|
74
|
+
<% if comment.quoted_text.present? %>
|
|
75
|
+
<div class="comment-quoted-block">
|
|
76
|
+
<blockquote class="comment-quoted-text"><%= comment.quoted_text %></blockquote>
|
|
77
|
+
</div>
|
|
78
|
+
<% end %>
|
|
69
79
|
<div class="comment-content"><%= comment.content %></div>
|
|
70
80
|
|
|
71
81
|
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
data-move-error-text="<%= t('collavre.comments.move_error') %>"
|
|
32
32
|
data-hint-drag-topic-text="<%= t('collavre.comments.hint_drag_topic') %>"
|
|
33
33
|
data-hint-move-button-text="<%= t('collavre.comments.hint_move_button') %>"
|
|
34
|
-
data-add-participant-text="<%= t('collavre.comments.add_participant') %>"
|
|
34
|
+
data-add-participant-text="<%= t('collavre.comments.add_participant') %>"
|
|
35
|
+
data-review-button-text="<%= t('collavre.comments.review_button') %>">
|
|
35
36
|
<div class="resize-handle resize-handle-left" data-comments--popup-target="leftHandle"></div>
|
|
36
37
|
<div class="resize-handle resize-handle-right" data-comments--popup-target="rightHandle"></div>
|
|
37
38
|
<div class="comments-popup-header">
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
<%= svg_tag "exit-fullscreen.svg", class: "comments-popup-action-icon" %>
|
|
51
52
|
</span>
|
|
52
53
|
</button>
|
|
53
|
-
<button id="close-comments-btn" data-comments--popup-target="closeButton" class="
|
|
54
|
+
<button id="close-comments-btn" data-comments--popup-target="closeButton" class="popup-close-btn" type="button">×</button>
|
|
54
55
|
</div>
|
|
55
56
|
</div>
|
|
56
57
|
<div id="comment-participants" data-comments--presence-target="participants" data-comments--mention-menu-target="participants"></div>
|
|
@@ -62,6 +63,12 @@
|
|
|
62
63
|
<div id="comments-list" data-comments--popup-target="list" data-comments--list-target="list"><%= t('app.loading') %></div>
|
|
63
64
|
<div id="typing-indicator" data-comments--presence-target="typingIndicator"></div>
|
|
64
65
|
<form id="new-comment-form" data-comments--popup-target="form" data-comments--form-target="form" style="display:none;">
|
|
66
|
+
<input type="hidden" name="comment[quoted_comment_id]" data-comments--form-target="quotedCommentId" value="" />
|
|
67
|
+
<input type="hidden" name="comment[quoted_text]" data-comments--form-target="quotedText" value="" />
|
|
68
|
+
<div class="comment-quote-indicator" data-comments--form-target="quoteIndicator" style="display:none;">
|
|
69
|
+
<span class="comment-quote-indicator-text" data-comments--form-target="quoteIndicatorText"></span>
|
|
70
|
+
<button type="button" class="comment-quote-cancel" data-action="click->comments--form#cancelQuote" title="<%= t('app.cancel') %>">×</button>
|
|
71
|
+
</div>
|
|
65
72
|
<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>
|
|
66
73
|
<div class="comment-bottom">
|
|
67
74
|
<input type="file" id="comment-images" name="comment[images][]" accept="image/*" multiple data-comments--form-target="imageInput" style="display:none;" />
|
|
@@ -10,9 +10,6 @@
|
|
|
10
10
|
<button type="button" class="popup-menu-item" data-controller="click-target" data-click-target-id-value="share-creative-btn" data-action="click->click-target#trigger"><%= t('collavre.creatives.index.share') %></button>
|
|
11
11
|
<% end %>
|
|
12
12
|
<% if can_manage_integrations %>
|
|
13
|
-
<%# Legacy hardcoded integrations %>
|
|
14
|
-
<button type="button" class="popup-menu-item" data-controller="click-target" data-click-target-id-value="github-integration-btn" data-action="click->click-target#trigger"><%= t('collavre.creatives.index.integrations', default: '연동') %> - Github</button>
|
|
15
|
-
<%# Dynamically registered integrations %>
|
|
16
13
|
<% Collavre::IntegrationRegistry.each do |integration| %>
|
|
17
14
|
<% if integration.enabled_for?(current_creative) %>
|
|
18
15
|
<button type="button"
|
|
@@ -1,55 +1,3 @@
|
|
|
1
1
|
<button id="share-creative-btn" class="btn btn-primary desktop-only">
|
|
2
2
|
<span aria-hidden="true"><%= svg_tag 'share.svg', class: 'icon-up', width: 22, height: 20 %></span>
|
|
3
3
|
</button>
|
|
4
|
-
<!-- Share Creative Modal -->
|
|
5
|
-
<div id="share-creative-modal" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:10000;align-items:center;justify-content:center;" data-creative-id="<%= (@parent_creative || @creative).id %>">
|
|
6
|
-
<div class="popup-box" style="min-width:320px;max-width:90vw;">
|
|
7
|
-
<button id="close-share-modal" class="popup-close-btn">×</button>
|
|
8
|
-
<h2><%= t('collavre.creatives.index.share_creative') %></h2>
|
|
9
|
-
<form id="share-creative-form" action="<%= collavre.creative_creative_shares_path(@parent_creative || @creative) %>" method="post" data-controller="share-invite">
|
|
10
|
-
<%= csrf_meta_tags %>
|
|
11
|
-
<div style="margin-bottom:1em; position:relative;"
|
|
12
|
-
data-controller="share-user-search"
|
|
13
|
-
data-share-user-search-creative-id-value="<%= (@parent_creative || @creative).id %>"
|
|
14
|
-
data-share-user-search-scope-value="contacts">
|
|
15
|
-
<label for="share-user-email"><%= t('collavre.creatives.index.user_email') %></label>
|
|
16
|
-
<input type="email" name="user_email" id="share-user-email" style="width:100%;"
|
|
17
|
-
data-share-invite-target="email"
|
|
18
|
-
data-share-user-search-target="input"
|
|
19
|
-
data-action="blur->share-invite#check input->share-user-search#input focus->share-user-search#focus keydown->share-user-search#handleKey"
|
|
20
|
-
autocomplete="off" />
|
|
21
|
-
<%= render Collavre::UserMentionMenuComponent.new(menu_id: 'share-user-suggestions') %>
|
|
22
|
-
</div>
|
|
23
|
-
<div style="margin-bottom:1em;">
|
|
24
|
-
<select name="permission" id="share-permission" style="width:100%;">
|
|
25
|
-
<option value="no_access"><%= t('collavre.creatives.index.permission_no_access') %></option>
|
|
26
|
-
<option value="read"><%= t('collavre.creatives.index.permission_read') %></option>
|
|
27
|
-
<option value="feedback"><%= t('collavre.creatives.index.permission_feedback') %></option>
|
|
28
|
-
<option value="write"><%= t('collavre.creatives.index.permission_write') %></option>
|
|
29
|
-
<option value="admin"><%= t('collavre.creatives.index.permission_admin') %></option>
|
|
30
|
-
</select>
|
|
31
|
-
</div>
|
|
32
|
-
<button type="submit" class="btn btn-primary" data-share-invite-target="submit" data-share="<%= t('collavre.creatives.index.share') %>" data-invite="<%= t('collavre.creatives.index.invite') %>"><%= t('collavre.creatives.index.share') %></button>
|
|
33
|
-
<button type="button" id="creative-invite-link" class="btn btn-secondary" data-creative-id="<%= (@parent_creative || @creative).id %>" data-no-access-message="<%= t('collavre.creatives.index.invite_link_no_access') %>" data-copied-template="<%= t('collavre.creatives.index.invite_link_copied_permission', permission: '__PERMISSION__') %>"><%= t('collavre.creatives.index.invite_link') %></button>
|
|
34
|
-
</form>
|
|
35
|
-
<% if @shared_list.any? %>
|
|
36
|
-
<div style="margin-top:1em;">
|
|
37
|
-
<strong><%= t('collavre.creatives.index.shared_with') %>:</strong>
|
|
38
|
-
<ul class="share-grid">
|
|
39
|
-
<% @shared_list.each do |share| %>
|
|
40
|
-
<li>
|
|
41
|
-
<span>
|
|
42
|
-
<%= render Collavre::AvatarComponent.new(user: share.user, size: 20, classes: 'avatar share-avatar') %>
|
|
43
|
-
</span>
|
|
44
|
-
<span><%= share.user&.display_name || (share.user_id.nil? ? t('collavre.creatives.index.public_share') : t('collavre.creatives.index.unknown_user')) %></span>
|
|
45
|
-
<span><%= t("collavre.creatives.index.permission_#{share.permission}") %></span>
|
|
46
|
-
<span>
|
|
47
|
-
<%= button_to '×', collavre.creative_creative_share_path(@parent_creative || @creative, share), method: :delete, form: { data: { turbo_confirm: t('collavre.creatives.index.are_you_sure_delete_share') } }, class: 'delete-share-btn', style: 'padding:0 0.5em;' %>
|
|
48
|
-
</span>
|
|
49
|
-
</li>
|
|
50
|
-
<% end %>
|
|
51
|
-
</ul>
|
|
52
|
-
</div>
|
|
53
|
-
<% end %>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|