diff --git a/frontend/src/app/shared/components/editor/components/ckeditor/ckeditor.types.ts b/frontend/src/app/shared/components/editor/components/ckeditor/ckeditor.types.ts
index 35dd84b2a9b..3b578071b5c 100644
--- a/frontend/src/app/shared/components/editor/components/ckeditor/ckeditor.types.ts
+++ b/frontend/src/app/shared/components/editor/components/ckeditor/ckeditor.types.ts
@@ -20,6 +20,10 @@ export interface CKEditorDomEventData {
keyCode:number;
}
+// Opaque handle for a parsed CKEditor model fragment (DocumentFragment).
+// We never inspect its internals, only pass it from data.parse to model.insertContent.
+export type CKEditorModelFragment = unknown;
+
export interface ICKEditorInstance {
id:string;
@@ -39,9 +43,13 @@ export interface ICKEditorInstance {
listenTo(node:unknown, key:string, callback:(evt:CKEditorEvent, data:CKEditorDomEventData) => unknown, options:CKEditorListenOptions):void;
+ data:{
+ parse(data:string):CKEditorModelFragment;
+ };
model:{
on(ev:string, callback:() => unknown):void
fire(ev:string, data:unknown):void
+ insertContent(content:CKEditorModelFragment):void
document:{
on(ev:string, callback:() => unknown):void
};
diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/quote-comment.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/quote-comment.controller.ts
index 40d836b354c..25a26ff7916 100644
--- a/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/quote-comment.controller.ts
+++ b/frontend/src/stimulus/controllers/dynamic/work-packages/activities-tab/quote-comment.controller.ts
@@ -65,19 +65,19 @@ export default class QuoteCommentController extends Controller {
.join('');
// if we ever change CKEditor or how @mentions work this will break
- return `@${userName} ${textWrote}:\n\n${quoted}`;
+ return `@${userName} ${textWrote}:\n\n${quoted}\n\n`;
}
private insertQuoteOnExistingEditor(quotedText:string) {
- if (this.ckEditorInstance) {
- const editorData = this.ckEditorInstance.getData({ trim: false });
+ const editor = this.ckEditorInstance;
+ if (!editor) return;
- if (editorData.endsWith('
') || editorData.endsWith('\n')) {
- this.ckEditorInstance.setData(`${editorData}${quotedText}`);
- } else {
- this.ckEditorInstance.setData(`${editorData}\n\n${quotedText}`);
- }
- }
+ // insert at the current cursor position (preserved by CKEditor across blur/focus),
+ // then place focus so the user can type immediately after the blockquote
+ const fragment = editor.data.parse(quotedText);
+ editor.model.insertContent(fragment);
+
+ this.workPackagesActivitiesTabEditorOutlet.focusEditor();
}
private setCommentRestriction(isInternal:boolean) {
diff --git a/spec/features/activities/work_package/activities_spec.rb b/spec/features/activities/work_package/activities_spec.rb
index a51a855b57b..eaf918ae0fd 100644
--- a/spec/features/activities/work_package/activities_spec.rb
+++ b/spec/features/activities/work_package/activities_spec.rb
@@ -919,6 +919,9 @@ RSpec.describe "Work package activity", :js, :with_cuprite, with_ee: %i[internal
# expect the quoted comment to be shown
activity_tab.ckeditor.expect_include_value("@A Member wrote:\nFirst comment by member")
+
+ # expect the editor to be focused so the user can type immediately
+ activity_tab.expect_focus_on_editor
end
end
@@ -937,8 +940,11 @@ RSpec.describe "Work package activity", :js, :with_cuprite, with_ee: %i[internal
# quote other user's comment
activity_tab.quote_comment(first_comment_by_member)
- # expect the original comment and quote are shown
+ # expect the original comment and quote are shown (quote appended after existing content)
activity_tab.ckeditor.expect_include_value("Partial message:\n@A Member wrote:\nFirst comment by member")
+
+ # expect the editor to be focused so the user can type immediately
+ activity_tab.expect_focus_on_editor
end
end
end