This is an automated email from the ASF dual-hosted git repository.

tbonelee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new e1712ecee5 [ZEPPELIN-6429] Focus paragraph editor on clone/insert in 
New UI
e1712ecee5 is described below

commit e1712ecee5f170ec375b3b4945e061bd2ca73a0d
Author: YONGJAE LEE (이용재) <[email protected]>
AuthorDate: Tue Jun 9 00:27:12 2026 +0900

    [ZEPPELIN-6429] Focus paragraph editor on clone/insert in New UI
    
    ### What is this PR for?
    After cloning or inserting a paragraph in the New UI, the cursor stays on 
the wrapper element instead of the editor, so you have to click before typing. 
This focuses the new paragraph's editor one tick after `PARAGRAPH_ADDED`, gated 
to clone/insert initiated by this client so auto-append on run and other 
clients' inserts don't steal focus. It also skips dirty-marking on programmatic 
editor `setValue` (`isFlush`) so the cloned content isn't discarded. (The clone 
*content* loss itself i [...]
    
    ### What type of PR is it?
    Bug Fix
    
    ### Todos
    
    ### What is the Jira issue?
    ZEPPELIN-6429
    
    ### How should this be tested?
    
    ### Screenshots (if appropriate)
    
https://github.com/user-attachments/assets/2dca9137-3eb3-49c3-bff8-da3429613025
    
    ### Questions:
    * Does the license files need to update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    
    Closes #5267 from voidmatcha/fix/clone-paragraph-cursor-focus-master.
    
    Signed-off-by: ChanHo Lee <[email protected]>
---
 .../org/apache/zeppelin/socket/NotebookServer.java | 13 +++----
 .../src/interfaces/message-notebook.interface.ts   |  1 +
 .../pages/workspace/notebook/notebook.component.ts | 11 ++++++
 .../paragraph/code-editor/code-editor.component.ts | 11 ++++--
 .../src/app/services/message.service.ts            | 40 ++++++++++++++++++++--
 5 files changed, 64 insertions(+), 12 deletions(-)

diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
index 20343d8a0a..090272ce5d 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
@@ -692,16 +692,17 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
     inlineBroadcastParagraphs(userParagraphMap, msgId);
   }
 
-  private void inlineBroadcastNewParagraph(Note note, Paragraph para) {
+  private void inlineBroadcastNewParagraph(Note note, Paragraph para, String 
msgId) {
     LOGGER.info("Broadcasting paragraph on run call instead of note.");
     int paraIndex = note.getParagraphs().indexOf(para);
 
-    Message message = new Message(OP.PARAGRAPH_ADDED).put("paragraph", 
para).put("index", paraIndex);
+    Message message =
+        new Message(OP.PARAGRAPH_ADDED).withMsgId(msgId).put("paragraph", 
para).put("index", paraIndex);
     connectionManager.broadcast(note.getId(), message);
   }
 
-  private void broadcastNewParagraph(Note note, Paragraph para) {
-    inlineBroadcastNewParagraph(note, para);
+  private void broadcastNewParagraph(Note note, Paragraph para, String msgId) {
+    inlineBroadcastNewParagraph(note, para, msgId);
   }
 
   private void inlineBroadcastNoteList() {
@@ -1451,7 +1452,7 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
           @Override
           public void onSuccess(Paragraph p, ServiceContext context) throws 
IOException {
             super.onSuccess(p, context);
-            broadcastNewParagraph(p.getNote(), p);
+            broadcastNewParagraph(p.getNote(), p, fromMessage.msgId);
           }
         });
 
@@ -1555,7 +1556,7 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
                 StringUtils.isEmpty(p.getScriptText())) &&
                   isTheLastParagraph) {
                 Paragraph newPara = 
p.getNote().addNewParagraph(p.getAuthenticationInfo());
-                broadcastNewParagraph(p.getNote(), newPara);
+                broadcastNewParagraph(p.getNote(), newPara, fromMessage.msgId);
               }
             }
           });
diff --git 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
index 986aed0b91..665e8dfd71 100644
--- 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
+++ 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
@@ -167,6 +167,7 @@ export interface ImportNoteReceived {
 
 export interface ParagraphAdded {
   index: number;
+  msgId?: string;
   paragraph: ParagraphItem;
 }
 
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
index 6905a5fc4e..1cb8d2b288 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
@@ -157,6 +157,17 @@ export class NotebookComponent extends 
MessageListenersManager implements OnInit
 
     definedNote.paragraphs[paragraphIndex].focus = true;
     this.cdr.markForCheck();
+
+    // Focus the editor only for a clone/insert initiated by this client (not 
auto-append on run or remote inserts).
+    // Defer a tick so the new paragraph's editor child exists, since `focus = 
true` alone misses it.
+    if (this.messageService.consumeLocalAddFocusMsgId(data.msgId)) {
+      const addedId = data.paragraph.id;
+      setTimeout(() => {
+        const added = this.listOfNotebookParagraphComponent?.find(e => 
e.paragraph.id === addedId);
+        added?.focusEditor();
+        added?.notebookParagraphCodeEditorComponent?.setRestorePosition();
+      });
+    }
   }
 
   @MessageListener(OP.SAVE_NOTE_FORMS)
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts
 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts
index c212de77cf..093a34e11c 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts
@@ -94,19 +94,24 @@ export class NotebookParagraphCodeEditorComponent
           this.position = e.position;
         });
       }),
-      editor.onDidChangeModelContent(() => {
+      editor.onDidChangeModelContent(e => {
         this.ngZone.run(() => {
           const model = editor.getModel();
           if (!model) {
             throw new Error('Model content changed but model not found.');
           }
           this.text = model.getValue();
-          this.textChanged.emit(this.text);
-          this.setParagraphMode(true);
           this.autoAdjustEditorHeight();
           setTimeout(() => {
             this.autoAdjustEditorHeight();
           });
+          this.setParagraphMode(true);
+          // A flush is a programmatic setValue (editor init, remote content 
update, patch), not a user edit.
+          // Such changes must not mark the paragraph dirty.
+          if (e.isFlush) {
+            return;
+          }
+          this.textChanged.emit(this.text);
         });
       })
     );
diff --git a/zeppelin-web-angular/src/app/services/message.service.ts 
b/zeppelin-web-angular/src/app/services/message.service.ts
index 8949e5e1c7..9b86a7d42d 100644
--- a/zeppelin-web-angular/src/app/services/message.service.ts
+++ b/zeppelin-web-angular/src/app/services/message.service.ts
@@ -12,6 +12,7 @@
 
 import { Inject, Injectable, OnDestroy, Optional } from '@angular/core';
 import { Observable } from 'rxjs';
+import { take } from 'rxjs/operators';
 
 import { MessageInterceptor, MESSAGE_INTERCEPTOR } from '@zeppelin/interfaces';
 import {
@@ -22,6 +23,7 @@ import {
   MessageSendDataTypeMap,
   Note,
   NoteConfig,
+  OP,
   ParagraphConfig,
   ParagraphParams,
   PersonalizedMode,
@@ -38,6 +40,8 @@ import { TicketService } from './ticket.service';
   providedIn: 'root'
 })
 export class MessageService extends Message implements OnDestroy {
+  private readonly localAddFocusMsgIds = new Set<string>();
+
   constructor(
     private baseUrlService: BaseUrlService,
     private ticketService: TicketService,
@@ -47,7 +51,11 @@ export class MessageService extends Message implements 
OnDestroy {
   }
 
   interceptReceived(data: WebSocketMessage<MessageReceiveDataTypeMap>): 
WebSocketMessage<MessageReceiveDataTypeMap> {
-    return this.messageInterceptor ? this.messageInterceptor.received(data) : 
super.interceptReceived(data);
+    const received = this.messageInterceptor ? 
this.messageInterceptor.received(data) : super.interceptReceived(data);
+    if (received.op === OP.PARAGRAPH_ADDED && received.data && received.msgId) 
{
+      (received.data as MessageReceiveDataTypeMap[OP.PARAGRAPH_ADDED]).msgId = 
received.msgId;
+    }
+    return received;
   }
 
   bootstrap(): void {
@@ -78,6 +86,30 @@ export class MessageService extends Message implements 
OnDestroy {
     return super.receive<K>(op);
   }
 
+  consumeLocalAddFocusMsgId(msgId: string | undefined): boolean {
+    if (!msgId) {
+      return false;
+    }
+    return this.localAddFocusMsgIds.delete(msgId);
+  }
+
+  private captureLocalAddFocusMsgId(sendMessage: () => void): void {
+    const subscription = super
+      .sent()
+      .pipe(take(1))
+      .subscribe(message => {
+        if (message.msgId) {
+          this.localAddFocusMsgIds.add(message.msgId);
+        }
+      });
+    try {
+      sendMessage();
+    } catch (error) {
+      subscription.unsubscribe();
+      throw error;
+    }
+  }
+
   opened(): Observable<Event> {
     return super.opened();
   }
@@ -167,7 +199,7 @@ export class MessageService extends Message implements 
OnDestroy {
   }
 
   insertParagraph(newIndex: number): void {
-    super.insertParagraph(newIndex);
+    this.captureLocalAddFocusMsgId(() => super.insertParagraph(newIndex));
   }
 
   copyParagraph(
@@ -177,7 +209,9 @@ export class MessageService extends Message implements 
OnDestroy {
     paragraphConfig: ParagraphConfig,
     paragraphParams: ParagraphParams
   ): void {
-    super.copyParagraph(newIndex, paragraphTitle, paragraphData, 
paragraphConfig, paragraphParams);
+    this.captureLocalAddFocusMsgId(() =>
+      super.copyParagraph(newIndex, paragraphTitle, paragraphData, 
paragraphConfig, paragraphParams)
+    );
   }
 
   angularObjectUpdate(

Reply via email to