collavre 0.24.0 → 0.24.1

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.
@@ -7,7 +7,7 @@ const ICON_ARCHIVE = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none
7
7
  const ICON_RESTORE = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6.69 3L3 13"/></svg>`
8
8
 
9
9
  export default class extends Controller {
10
- static targets = ["list"]
10
+ static targets = ["list", "creationContainer"]
11
11
 
12
12
  connect() {
13
13
  this.topics = []
@@ -189,15 +189,37 @@ export default class extends Controller {
189
189
  }
190
190
  }
191
191
 
192
- // Add create button container (write permission is sufficient for topic creation)
193
- if (canCreateTopic) {
194
- html += `<span class="topic-creation-container" data-comments--topics-target="creationContainer"
195
- data-action="dragover->comments--topics#handleAddButtonDragOver dragleave->comments--topics#handleDragLeave drop->comments--topics#handleAddButtonDrop">
196
- <button class="add-topic-btn" data-action="click->comments--topics#showInput">+</button>
197
- </span>`
192
+ this.listTarget.innerHTML = html
193
+
194
+ // The create button lives outside the scrolling strip so it stays reachable
195
+ // without horizontal scrolling, no matter how many topics there are.
196
+ this.renderCreationContainer(canCreateTopic)
197
+ }
198
+
199
+ // Write permission is sufficient for topic creation.
200
+ renderCreationContainer(canCreateTopic) {
201
+ if (!this.hasCreationContainerTarget) return
202
+ const container = this.creationContainerTarget
203
+
204
+ container.hidden = !canCreateTopic
205
+ if (!canCreateTopic) {
206
+ container.innerHTML = ''
207
+ return
198
208
  }
199
209
 
200
- this.listTarget.innerHTML = html
210
+ // renderTopics re-runs on every topic broadcast; don't wipe a name being typed.
211
+ // `creating` marks an already-submitted name, whose input must give way to the button.
212
+ // A draft only survives re-renders of the creative it was typed for: chat-nav
213
+ // switches creatives without blurring, and posting it there is the wrong creative.
214
+ const draftIsCurrent = String(this._draftCreativeId) === String(this.creativeId)
215
+ if (!this.creating && draftIsCurrent && container.querySelector('.topic-input')) return
216
+
217
+ this.renderAddButton()
218
+ }
219
+
220
+ renderAddButton() {
221
+ this.creationContainerTarget.innerHTML =
222
+ `<button class="add-topic-btn" data-action="click->comments--topics#showInput">+</button>`
201
223
  }
202
224
 
203
225
  handleDragOver(event) {
@@ -404,7 +426,7 @@ export default class extends Controller {
404
426
  }
405
427
  this.loadTopics()
406
428
  } else {
407
- alertDialog("Failed to delete topic")
429
+ alertDialog(this._i18n("delete_error"))
408
430
  }
409
431
  } catch (e) {
410
432
  console.error("Error deleting topic", e)
@@ -431,7 +453,7 @@ export default class extends Controller {
431
453
  }
432
454
  this.loadTopics()
433
455
  } else {
434
- alertDialog("Failed to archive topic")
456
+ alertDialog(this._i18n("archive_error"))
435
457
  }
436
458
  } catch (e) {
437
459
  console.error("Error archiving topic", e)
@@ -454,7 +476,7 @@ export default class extends Controller {
454
476
  if (response.ok) {
455
477
  this.loadTopics()
456
478
  } else {
457
- alertDialog("Failed to restore topic")
479
+ alertDialog(this._i18n("restore_error"))
458
480
  }
459
481
  } catch (e) {
460
482
  console.error("Error restoring topic", e)
@@ -517,9 +539,10 @@ export default class extends Controller {
517
539
 
518
540
  showInput(event) {
519
541
  event.preventDefault()
520
- const container = this.element.querySelector('[data-comments--topics-target="creationContainer"]')
521
- if (!container) return
542
+ if (!this.hasCreationContainerTarget) return
543
+ const container = this.creationContainerTarget
522
544
 
545
+ this._draftCreativeId = this.creativeId
523
546
  const placeholder = this.listTarget.dataset.newTopicPlaceholder || "New Topic"
524
547
  container.innerHTML = `<input type="text" class="topic-input" placeholder="${placeholder}"
525
548
  data-action="keydown->comments--topics#handleInputKey blur->comments--topics#resetInput"
@@ -532,9 +555,8 @@ export default class extends Controller {
532
555
  resetInput() {
533
556
  // Small delay to allow enter key to process first if that was the cause
534
557
  setTimeout(() => {
535
- const container = this.element.querySelector('[data-comments--topics-target="creationContainer"]')
536
- if (container && !this.creating) {
537
- container.innerHTML = `<button class="add-topic-btn" data-action="click->comments--topics#showInput">+</button>`
558
+ if (this.hasCreationContainerTarget && !this.creating) {
559
+ this.renderAddButton()
538
560
  }
539
561
  }, 200)
540
562
  }
@@ -661,7 +683,7 @@ export default class extends Controller {
661
683
  this.renderTopics(this.topics, this.canManageTopics, this.canCreateTopic)
662
684
  this.restoreSelection()
663
685
  } else {
664
- alertDialog("Failed to update topic")
686
+ alertDialog(this._i18n("update_error"))
665
687
  this.loadTopics() // Reload to restore state
666
688
  }
667
689
  } catch (e) {
@@ -752,7 +774,7 @@ export default class extends Controller {
752
774
  // Dispatch change event manually since we skipped the click handler
753
775
  this.dispatch("change", { detail: { topicId: topic.id, mainTopicId: this.mainTopicId } })
754
776
  } else {
755
- alertDialog("Failed to create topic")
777
+ alertDialog(this._i18n("create_error"))
756
778
  }
757
779
  } catch (e) {
758
780
  console.error("Error creating topic", e)
@@ -1000,6 +1022,21 @@ export default class extends Controller {
1000
1022
  }
1001
1023
  }
1002
1024
 
1025
+ // Localized strings are handed down from the ERB partial as data
1026
+ // attributes; the English literals are last-resort fallbacks for when the
1027
+ // controller is mounted without them.
1028
+ _i18n(key) {
1029
+ const translations = {
1030
+ set_agent_error: this.element.dataset.topicSetAgentError || 'Unable to assign the agent to this topic.',
1031
+ create_error: this.element.dataset.topicCreateError || 'Unable to create the topic.',
1032
+ update_error: this.element.dataset.topicUpdateError || 'Unable to update the topic.',
1033
+ delete_error: this.element.dataset.topicDeleteError || 'Unable to delete the topic.',
1034
+ archive_error: this.element.dataset.topicArchiveError || 'Unable to archive the topic.',
1035
+ restore_error: this.element.dataset.topicRestoreError || 'Unable to restore the topic.'
1036
+ }
1037
+ return translations[key] || key
1038
+ }
1039
+
1003
1040
  async setTopicPrimaryAgent(topicId, agent) {
1004
1041
  if (!this.creativeId) return
1005
1042
 
@@ -1013,13 +1050,23 @@ export default class extends Controller {
1013
1050
  body: JSON.stringify({ agent_id: agent.id })
1014
1051
  })
1015
1052
 
1053
+ const data = await response.json().catch(() => ({}))
1054
+
1016
1055
  if (!response.ok) {
1017
- const data = await response.json()
1018
- console.error('Failed to set primary agent:', data.error)
1056
+ alertDialog(data.error || this._i18n("set_agent_error"))
1057
+ return
1019
1058
  }
1020
- // Topic update comes via WebSocket broadcast
1059
+
1060
+ // Render the avatar from the response rather than waiting for the
1061
+ // WebSocket broadcast. A dropped broadcast (e.g. the topics channel
1062
+ // subscription was refused) would otherwise leave the avatar
1063
+ // invisible until the next page load. The broadcast still runs and
1064
+ // propagates the change to other connected users; re-applying it
1065
+ // here is a no-op merge.
1066
+ if (data.topic) this.updateTopicInList(data.topic)
1021
1067
  } catch (e) {
1022
1068
  console.error('Error setting primary agent', e)
1069
+ alertDialog(this._i18n("set_agent_error"))
1023
1070
  }
1024
1071
  }
1025
1072
 
@@ -59,6 +59,12 @@
59
59
  data-review-type-review-label="<%= t('collavre.comments.review_type_review') %>"
60
60
  data-review-type-question-label="<%= t('collavre.comments.review_type_question') %>"
61
61
  data-remove-from-history-label="<%= t('collavre.comments.remove_from_history') %>"
62
+ data-topic-set-agent-error="<%= t('collavre.comments.topic_set_agent_error') %>"
63
+ data-topic-create-error="<%= t('collavre.comments.topic_create_error') %>"
64
+ data-topic-update-error="<%= t('collavre.comments.topic_update_error') %>"
65
+ data-topic-delete-error="<%= t('collavre.comments.topic_delete_error') %>"
66
+ data-topic-archive-error="<%= t('collavre.comments.topic_archive_error') %>"
67
+ data-topic-restore-error="<%= t('collavre.comments.topic_restore_error') %>"
62
68
  data-inbox-reply-button="<%= t('collavre.comments.inbox_reply_button') %>"
63
69
  data-table-download-csv-text="<%= t('collavre.comments.table_download.csv') %>"
64
70
  data-table-download-excel-text="<%= t('collavre.comments.table_download.excel') %>"
@@ -129,6 +135,8 @@
129
135
  <div id="comment-topics" data-comments--topics-target="list" class="comment-topics-list"
130
136
  data-confirm-delete-text="<%= t('collavre.topics.delete_confirm') %>"
131
137
  data-new-topic-placeholder="<%= t('collavre.topics.new_placeholder') %>"></div>
138
+ <span class="topic-creation-container" data-comments--topics-target="creationContainer" hidden
139
+ data-action="dragover->comments--topics#handleAddButtonDragOver dragleave->comments--topics#handleDragLeave drop->comments--topics#handleAddButtonDrop"></span>
132
140
  <button type="button" class="topic-list-btn"
133
141
  data-action="click->comments--topics#openTopicListPopup"
134
142
  title="<%= t('collavre.comments.topic_list') %>"
@@ -107,6 +107,12 @@ en:
107
107
  navigate_forward: Next chat
108
108
  recent_chats: Recent chats
109
109
  remove_from_history: Remove from history
110
+ topic_set_agent_error: Unable to assign the agent to this topic.
111
+ topic_create_error: Unable to create the topic.
112
+ topic_update_error: Unable to update the topic.
113
+ topic_delete_error: Unable to delete the topic.
114
+ topic_archive_error: Unable to archive the topic.
115
+ topic_restore_error: Unable to restore the topic.
110
116
  inbox_reply_button: Reply
111
117
  replace_button: Replace
112
118
  move_no_selection: Select at least one message to move.
@@ -104,6 +104,12 @@ ko:
104
104
  navigate_forward: 다음 채팅
105
105
  recent_chats: 최근 채팅
106
106
  remove_from_history: 히스토리에서 제거
107
+ topic_set_agent_error: 이 토픽에 에이전트를 지정할 수 없습니다.
108
+ topic_create_error: 토픽을 생성할 수 없습니다.
109
+ topic_update_error: 토픽을 수정할 수 없습니다.
110
+ topic_delete_error: 토픽을 삭제할 수 없습니다.
111
+ topic_archive_error: 토픽을 보관할 수 없습니다.
112
+ topic_restore_error: 토픽을 복원할 수 없습니다.
107
113
  inbox_reply_button: 답글
108
114
  replace_button: 바꾸기
109
115
  move_no_selection: 이동할 메시지를 선택해주세요.
@@ -1,3 +1,3 @@
1
1
  module Collavre
2
- VERSION = "0.24.0"
2
+ VERSION = "0.24.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: collavre
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.24.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Collavre
@@ -306,12 +306,17 @@ files:
306
306
  - app/javascript/controllers/comment_version_controller.js
307
307
  - app/javascript/controllers/comments/__tests__/form_controller_double_submit.test.js
308
308
  - app/javascript/controllers/comments/__tests__/form_controller_review.test.js
309
+ - app/javascript/controllers/comments/__tests__/list_controller_prev_message.test.js
309
310
  - app/javascript/controllers/comments/__tests__/list_controller_selection.test.js
310
311
  - app/javascript/controllers/comments/__tests__/popup_controller.test.js
311
312
  - app/javascript/controllers/comments/__tests__/presence_controller.test.js
313
+ - app/javascript/controllers/comments/__tests__/prev_message_navigator.test.js
312
314
  - app/javascript/controllers/comments/__tests__/review_quotes_store.test.js
315
+ - app/javascript/controllers/comments/__tests__/topics_controller_action_errors.test.js
316
+ - app/javascript/controllers/comments/__tests__/topics_controller_creation_container.test.js
313
317
  - app/javascript/controllers/comments/__tests__/topics_controller_delete.test.js
314
318
  - app/javascript/controllers/comments/__tests__/topics_controller_override.test.js
319
+ - app/javascript/controllers/comments/__tests__/topics_controller_primary_agent.test.js
315
320
  - app/javascript/controllers/comments/__tests__/topics_controller_topic_list.test.js
316
321
  - app/javascript/controllers/comments/contexts_controller.js
317
322
  - app/javascript/controllers/comments/drop_trigger_controller.js
@@ -320,6 +325,7 @@ files:
320
325
  - app/javascript/controllers/comments/mention_menu_controller.js
321
326
  - app/javascript/controllers/comments/popup_controller.js
322
327
  - app/javascript/controllers/comments/presence_controller.js
328
+ - app/javascript/controllers/comments/prev_message_navigator.js
323
329
  - app/javascript/controllers/comments/review_quotes_store.js
324
330
  - app/javascript/controllers/comments/topics_controller.js
325
331
  - app/javascript/controllers/common_popup_controller.js