collavre 0.22.0 → 0.23.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/README.md +1 -0
- data/app/assets/stylesheets/collavre/actiontext.css +251 -90
- data/app/assets/stylesheets/collavre/code_highlight.css +7 -201
- data/app/assets/stylesheets/collavre/comments_popup.css +118 -61
- data/app/assets/stylesheets/collavre/creatives.css +11 -2
- data/app/assets/stylesheets/collavre/modal_dialog.css +32 -0
- data/app/assets/stylesheets/collavre/tables.css +91 -0
- data/app/channels/collavre/inbox_badge_channel.rb +30 -0
- data/app/controllers/collavre/api/v1/mobile/agent_events_controller.rb +224 -0
- data/app/controllers/collavre/api/v1/mobile/base_controller.rb +95 -0
- data/app/controllers/collavre/api/v1/mobile/devices_controller.rb +31 -0
- data/app/controllers/collavre/api/v1/mobile/voice_commands_controller.rb +25 -0
- data/app/controllers/collavre/creatives_controller.rb +16 -5
- data/app/controllers/collavre/tasks_controller.rb +13 -4
- data/app/controllers/collavre/topics_controller.rb +49 -1
- data/app/controllers/collavre/typo_corrections_controller.rb +39 -0
- data/app/controllers/concerns/collavre/users_controller/profile_and_settings.rb +16 -1
- data/app/controllers/concerns/collavre/users_controller/registration.rb +41 -1
- data/app/helpers/collavre/application_helper.rb +1 -0
- data/app/javascript/collavre.js +2 -0
- data/app/javascript/components/ImageResizer.jsx +9 -3
- data/app/javascript/components/InlineLexicalEditor.jsx +155 -38
- data/app/javascript/components/creative_tree_row.js +20 -3
- data/app/javascript/components/plugins/list_tab_indent_plugin.jsx +16 -0
- data/app/javascript/components/plugins/table_hover_actions_plugin.jsx +405 -0
- data/app/javascript/controllers/__tests__/inbox_badge_controller.test.js +73 -0
- data/app/javascript/controllers/comment_controller.js +5 -4
- data/app/javascript/controllers/comment_version_controller.js +2 -1
- data/app/javascript/controllers/comments/__tests__/form_controller_double_submit.test.js +159 -0
- data/app/javascript/controllers/comments/__tests__/presence_controller.test.js +3 -2
- data/app/javascript/controllers/comments/__tests__/topics_controller_delete.test.js +94 -0
- data/app/javascript/controllers/comments/form_controller.js +21 -5
- data/app/javascript/controllers/comments/list_controller.js +18 -17
- data/app/javascript/controllers/comments/presence_controller.js +2 -1
- data/app/javascript/controllers/comments/topics_controller.js +14 -8
- data/app/javascript/controllers/creatives/__tests__/tree_controller.test.js +150 -0
- data/app/javascript/controllers/creatives/import_controller.js +2 -1
- data/app/javascript/controllers/creatives/select_mode_controller.js +2 -1
- data/app/javascript/controllers/creatives/tree_controller.js +142 -1
- data/app/javascript/controllers/image_lightbox_controller.js +2 -1
- data/app/javascript/controllers/inbox_badge_controller.js +33 -0
- data/app/javascript/controllers/index.js +4 -1
- data/app/javascript/controllers/share_modal_controller.js +4 -3
- data/app/javascript/controllers/topic_search_controller.js +2 -1
- data/app/javascript/creatives/drag_drop/event_handlers.js +14 -5
- data/app/javascript/creatives/topic_move_members_popup.js +156 -0
- data/app/javascript/creatives/tree_renderer.js +11 -0
- data/app/javascript/lib/__tests__/turbo_confirm.test.js +81 -0
- data/app/javascript/lib/__tests__/typo_correction.test.js +192 -0
- data/app/javascript/lib/api/__tests__/api_error.test.js +96 -0
- data/app/javascript/lib/api/__tests__/queue_manager.test.js +88 -1
- data/app/javascript/lib/api/api_error.js +108 -0
- data/app/javascript/lib/api/queue_manager.js +38 -4
- data/app/javascript/lib/common_popup.js +18 -5
- data/app/javascript/lib/editor/__tests__/code_edit_view_token_parity.test.js +121 -0
- data/app/javascript/lib/editor/__tests__/code_language_roundtrip.test.js +152 -0
- data/app/javascript/lib/editor/__tests__/code_languages.test.js +93 -0
- data/app/javascript/lib/editor/code_languages.js +173 -0
- data/app/javascript/lib/editor/code_token_theme.js +41 -0
- data/app/javascript/lib/lexical/__tests__/image_focus.test.js +139 -0
- data/app/javascript/lib/lexical/__tests__/list_tab_indent.test.js +633 -0
- data/app/javascript/lib/lexical/__tests__/markdown_serialize.test.js +627 -0
- data/app/javascript/lib/lexical/__tests__/minimize_html.test.js +20 -1
- data/app/javascript/lib/lexical/__tests__/selection_boundary.test.js +88 -0
- data/app/javascript/lib/lexical/__tests__/table_transformer.test.js +163 -0
- data/app/javascript/lib/lexical/__tests__/trailing_paragraph.test.js +104 -0
- data/app/javascript/lib/lexical/list_tab_indent.js +210 -0
- data/app/javascript/lib/lexical/markdown_serialize.js +320 -0
- data/app/javascript/lib/lexical/selection_boundary.js +58 -0
- data/app/javascript/lib/lexical/table_transformer.js +182 -0
- data/app/javascript/lib/lexical/trailing_paragraph.js +29 -0
- data/app/javascript/lib/turbo_confirm.js +46 -0
- data/app/javascript/lib/typo_correction.js +146 -0
- data/app/javascript/lib/utils/__tests__/confirm_dialog.test.js +88 -0
- data/app/javascript/lib/utils/__tests__/dialog.test.js +92 -0
- data/app/javascript/lib/utils/__tests__/markdown.test.js +153 -0
- data/app/javascript/lib/utils/__tests__/sanitize_description.test.js +68 -0
- data/app/javascript/lib/utils/__tests__/table_download.test.js +93 -0
- data/app/javascript/lib/utils/confirm_dialog.js +10 -0
- data/app/javascript/lib/utils/dialog.js +300 -0
- data/app/javascript/lib/utils/markdown.js +154 -67
- data/app/javascript/lib/utils/sanitize_description.js +31 -0
- data/app/javascript/lib/utils/table_download.js +15 -0
- data/app/javascript/modules/__tests__/typo_corrector.test.js +365 -0
- data/app/javascript/modules/creative_row_editor.js +110 -70
- data/app/javascript/modules/export_to_markdown.js +2 -1
- data/app/javascript/modules/lexical_inline_editor.jsx +2 -1
- data/app/javascript/modules/slide_view.js +11 -2
- data/app/javascript/modules/typo_corrector.js +534 -0
- data/app/jobs/collavre/ai_agent_job.rb +7 -4
- data/app/jobs/collavre/compress_job.rb +6 -2
- data/app/models/collavre/comment/broadcastable.rb +46 -7
- data/app/models/collavre/comment/notifiable.rb +14 -4
- data/app/models/collavre/comment.rb +79 -31
- data/app/models/collavre/creative/describable.rb +89 -10
- data/app/models/collavre/task.rb +15 -0
- data/app/models/collavre/user.rb +57 -1
- data/app/services/collavre/ai_client.rb +28 -10
- data/app/services/collavre/auto_theme_generator.rb +1 -1
- data/app/services/collavre/creatives/index_query.rb +85 -16
- data/app/services/collavre/creatives/tree_builder.rb +2 -1
- data/app/services/collavre/gemini_parent_recommender.rb +1 -1
- data/app/services/collavre/inbox_reply_service.rb +5 -0
- data/app/services/collavre/markdown_converter.rb +13 -3
- data/app/services/collavre/mobile/event_summarizer.rb +40 -0
- data/app/services/collavre/orchestration/agent_orchestrator.rb +33 -7
- data/app/services/collavre/orchestration/arbiter.rb +16 -0
- data/app/services/collavre/orchestration/matcher.rb +79 -4
- data/app/services/collavre/orchestration/policy_resolver.rb +4 -3
- data/app/services/collavre/orchestration/stuck_detector.rb +141 -34
- data/app/services/collavre/tools/creative_batch_service.rb +3 -2
- data/app/services/collavre/tools/creative_create_service.rb +8 -8
- data/app/services/collavre/tools/creative_update_service.rb +23 -8
- data/app/services/collavre/typo_corrector.rb +188 -0
- data/app/views/collavre/comments/_comment.html.erb +5 -0
- data/app/views/collavre/comments/_comments_popup.html.erb +14 -1
- data/app/views/collavre/creatives/_inline_edit_form.html.erb +1 -0
- data/app/views/collavre/creatives/_topic_move_members_modal.html.erb +42 -0
- data/app/views/collavre/creatives/index.html.erb +14 -1
- data/app/views/collavre/creatives/slide_view.html.erb +1 -1
- data/app/views/collavre/users/show.html.erb +3 -0
- data/app/views/collavre/users/typo_correction.html.erb +50 -0
- data/app/views/layouts/collavre/slide.html.erb +1 -0
- data/config/locales/comments.en.yml +15 -0
- data/config/locales/comments.ko.yml +15 -0
- data/config/locales/integrations.en.yml +1 -1
- data/config/locales/integrations.ko.yml +1 -1
- data/config/locales/mobile.en.yml +16 -0
- data/config/locales/mobile.ko.yml +16 -0
- data/config/locales/orchestration.en.yml +1 -0
- data/config/locales/orchestration.ko.yml +1 -0
- data/config/locales/users.en.yml +15 -0
- data/config/locales/users.ko.yml +15 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20260612000000_add_topic_concurrency_defer_to_comments.rb +38 -0
- data/db/migrate/20260617090000_add_typo_correction_settings_to_users.rb +18 -0
- data/db/seeds.rb +51 -0
- data/lib/collavre/version.rb +1 -1
- data/lib/generators/collavre/install/install_generator.rb +1 -0
- metadata +55 -2
- data/app/services/collavre/tools/description_normalizable.rb +0 -16
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*
|
|
4
|
+
* Regression for "an external image steals editing focus": clicking into the
|
|
5
|
+
* text to edit while an image is present used to wipe the caret. ImageResizer
|
|
6
|
+
* registered a CLICK_COMMAND handler that called the node-selection hook's
|
|
7
|
+
* setSelected(false) on any click outside the image. That hook, when the live
|
|
8
|
+
* selection is NOT already a NodeSelection (i.e. it is the RangeSelection caret
|
|
9
|
+
* the click just placed), synthesizes a fresh empty NodeSelection and replaces
|
|
10
|
+
* the caret with it — so the editor lost its caret and became uneditable the
|
|
11
|
+
* instant you clicked to edit. The fix uses clearSelection(), which only acts on
|
|
12
|
+
* an existing NodeSelection and leaves a RangeSelection (the caret) intact.
|
|
13
|
+
*
|
|
14
|
+
* ImageResizer is a .jsx React component that can't be imported under this
|
|
15
|
+
* native-ESM jest config, so this test reconstructs the two deselect paths and
|
|
16
|
+
* the exact CLICK_COMMAND handler logic on a headless editor.
|
|
17
|
+
*/
|
|
18
|
+
import {
|
|
19
|
+
createEditor,
|
|
20
|
+
$getRoot,
|
|
21
|
+
$getSelection,
|
|
22
|
+
$isRangeSelection,
|
|
23
|
+
$isNodeSelection,
|
|
24
|
+
$createNodeSelection,
|
|
25
|
+
$setSelection,
|
|
26
|
+
$createParagraphNode,
|
|
27
|
+
$createTextNode,
|
|
28
|
+
$applyNodeReplacement,
|
|
29
|
+
DecoratorNode,
|
|
30
|
+
CLICK_COMMAND,
|
|
31
|
+
COMMAND_PRIORITY_LOW,
|
|
32
|
+
} from "lexical"
|
|
33
|
+
|
|
34
|
+
class TestImageNode extends DecoratorNode {
|
|
35
|
+
__src
|
|
36
|
+
static getType() { return "image" }
|
|
37
|
+
static clone(n) { return new TestImageNode(n.__src, n.__key) }
|
|
38
|
+
static importJSON() { return $createImage("") }
|
|
39
|
+
constructor(src = "", key) { super(key); this.__src = src }
|
|
40
|
+
createDOM() { return document.createElement("span") }
|
|
41
|
+
updateDOM() { return false }
|
|
42
|
+
decorate() { return null }
|
|
43
|
+
}
|
|
44
|
+
function $createImage(src) { return $applyNodeReplacement(new TestImageNode(src)) }
|
|
45
|
+
|
|
46
|
+
// Build: [image][paragraph "hello"] with the caret placed inside the text,
|
|
47
|
+
// i.e. the state right after a user clicks into the paragraph to edit.
|
|
48
|
+
function setup() {
|
|
49
|
+
const editor = createEditor({ namespace: "t", nodes: [TestImageNode], onError: (e) => { throw e } })
|
|
50
|
+
// jsdom: createEditor needs a root element to dispatch DOM-bound commands.
|
|
51
|
+
const el = document.createElement("div")
|
|
52
|
+
el.contentEditable = "true"
|
|
53
|
+
document.body.appendChild(el)
|
|
54
|
+
editor.setRootElement(el)
|
|
55
|
+
let imageKey
|
|
56
|
+
editor.update(() => {
|
|
57
|
+
const root = $getRoot()
|
|
58
|
+
root.clear()
|
|
59
|
+
root.append($createImage("https://lipcoding.kr/x.png"))
|
|
60
|
+
const p = $createParagraphNode()
|
|
61
|
+
p.append($createTextNode("hello"))
|
|
62
|
+
root.append(p)
|
|
63
|
+
imageKey = root.getFirstChild().getKey()
|
|
64
|
+
}, { discrete: true })
|
|
65
|
+
editor.update(() => {
|
|
66
|
+
$getRoot().getLastChild().getFirstChild().select(2, 2)
|
|
67
|
+
}, { discrete: true })
|
|
68
|
+
return { editor, imageKey }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function selKind(editor) {
|
|
72
|
+
let kind = "none"
|
|
73
|
+
editor.getEditorState().read(() => {
|
|
74
|
+
const s = $getSelection()
|
|
75
|
+
if ($isRangeSelection(s)) kind = "range"
|
|
76
|
+
else if ($isNodeSelection(s)) kind = "node"
|
|
77
|
+
})
|
|
78
|
+
return kind
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Mirror of the hook's setSelected(false): the OLD, destructive deselect path.
|
|
82
|
+
function setSelectedFalse(editor, key) {
|
|
83
|
+
editor.update(() => {
|
|
84
|
+
let selection = $getSelection()
|
|
85
|
+
if (!$isNodeSelection(selection)) {
|
|
86
|
+
selection = $createNodeSelection()
|
|
87
|
+
$setSelection(selection)
|
|
88
|
+
}
|
|
89
|
+
if ($isNodeSelection(selection)) selection.delete(key)
|
|
90
|
+
}, { discrete: true })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Mirror of the hook's clearSelection(): the safe deselect path the fix uses.
|
|
94
|
+
function clearSelection(editor) {
|
|
95
|
+
editor.update(() => {
|
|
96
|
+
const selection = $getSelection()
|
|
97
|
+
if ($isNodeSelection(selection)) selection.clear()
|
|
98
|
+
}, { discrete: true })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe("image editing focus", () => {
|
|
102
|
+
test("the old setSelected(false) deselect destroys the caret (documents the bug)", () => {
|
|
103
|
+
const { editor, imageKey } = setup()
|
|
104
|
+
expect(selKind(editor)).toBe("range")
|
|
105
|
+
setSelectedFalse(editor, imageKey)
|
|
106
|
+
expect(selKind(editor)).not.toBe("range") // caret lost — the reported bug
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test("clearSelection() leaves the caret intact", () => {
|
|
110
|
+
const { editor } = setup()
|
|
111
|
+
expect(selKind(editor)).toBe("range")
|
|
112
|
+
clearSelection(editor)
|
|
113
|
+
expect(selKind(editor)).toBe("range")
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("CLICK_COMMAND outside the image keeps the caret (fixed handler)", () => {
|
|
117
|
+
const { editor } = setup()
|
|
118
|
+
|
|
119
|
+
// Stand-in for ImageResizer's container ref; the click target is text
|
|
120
|
+
// outside it, exactly like clicking into the paragraph to edit.
|
|
121
|
+
const container = document.createElement("span")
|
|
122
|
+
const outsideTarget = document.createElement("span")
|
|
123
|
+
|
|
124
|
+
editor.registerCommand(
|
|
125
|
+
CLICK_COMMAND,
|
|
126
|
+
(event) => {
|
|
127
|
+
if (container && !container.contains(event.target)) {
|
|
128
|
+
clearSelection(editor) // fixed path (was setSelected(false))
|
|
129
|
+
}
|
|
130
|
+
return false
|
|
131
|
+
},
|
|
132
|
+
COMMAND_PRIORITY_LOW
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
expect(selKind(editor)).toBe("range")
|
|
136
|
+
editor.dispatchCommand(CLICK_COMMAND, { target: outsideTarget })
|
|
137
|
+
expect(selKind(editor)).toBe("range") // caret survives the click
|
|
138
|
+
})
|
|
139
|
+
})
|