Skip to content

feat(Vditor): redesign javascript interop#931

Merged
ArgoZhang merged 8 commits intomasterfrom
dev-vidtor
Feb 19, 2026
Merged

feat(Vditor): redesign javascript interop#931
ArgoZhang merged 8 commits intomasterfrom
dev-vidtor

Conversation

@ArgoZhang
Copy link
Member

@ArgoZhang ArgoZhang commented Feb 19, 2026

Link issues

fixes #930

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Refactor the Vditor component’s JavaScript interop to use a generic execution helper instead of storing JS object references, while enriching Vditor-related types with bilingual XML documentation.

Enhancements:

  • Simplify Vditor JS interop by routing editor operations through a new execute function rather than maintaining IJSObjectReference state.
  • Update Vditor component methods (reset, insert, get value/HTML/selection, focus/blur, enable/disable) to use the new JS execution pattern.
  • Add bilingual (Chinese/English) XML documentation to Vditor, VditorOptions, VditorMode, and VditorIconStyle types for clearer API usage.
  • Adjust Vditor JavaScript init/reset logic to stop returning the Vditor instance and introduce a generic execute helper for calling editor methods from .NET.

Copilot AI review requested due to automatic review settings February 19, 2026 01:31
@bb-auto bb-auto bot added the enhancement New feature or request label Feb 19, 2026
@bb-auto bb-auto bot added this to the v9.2.0 milestone Feb 19, 2026
@sourcery-ai
Copy link

sourcery-ai bot commented Feb 19, 2026

Reviewer's Guide

Refactors the Vditor component’s JavaScript interop to remove direct IJSObjectReference holding, route all editor operations through generic JS helpers, and enhances documentation/comments with bilingual XML docs across Vditor types.

Sequence diagram for Vditor method execution via generic JS execute interop

sequenceDiagram
    participant Vditor as Vditor_component
    participant JSRuntime as IJSRuntime
    participant Module as Vditor_razor_js
    participant Editor as Vditor_instance

    Vditor->>JSRuntime: InvokeVoidAsync execute(Id, setValue, [Value, true])
    JSRuntime->>Module: execute(id, method, args)
    Module->>Module: const md = Data.get(id)
    Module->>Editor: cb.call(vditor, ...args)
    Editor-->>Module: return from setValue
    Module-->>JSRuntime: ret
    JSRuntime-->>Vditor: Task completed
Loading

Updated class diagram for Vditor component and related types

classDiagram
    class Vditor {
        +VditorOptions Options
        +Func~Task~ OnRenderedAsync
        +Func~string, Task~ OnInputAsync
        +Func~string, Task~ OnFocusAsync
        +Func~string, Task~ OnBlurAsync
        +Func~string, Task~ OnSelectAsync
        +Func~string, Task~ OnEscapeAsync
        +Func~string, Task~ OnCtrlEnterAsync
        -string _lastValue
        +Task OnAfterRenderAsync(bool firstRender)
        +Task InvokeInitAsync()
        +Task Reset(string value, VditorOptions options)
        +Task InsertValueAsync(string value, bool render)
        +Task~string?~ GetValueAsync()
        +Task~string?~ GetHtmlAsync()
        +Task~string?~ GetSelectionAsync()
        +Task EnableAsync()
        +Task DisableAsync()
        +Task FocusAsync()
        +Task BlurAsync()
        +Task TriggerRenderedAsync()
        +Task TriggerInputAsync(string value)
        +Task TriggerFocusAsync(string value)
        +Task TriggerBlurAsync(string value)
        +Task TriggerSelectAsync(string value)
        +Task TriggerEscapeAsync(string value)
        +Task TriggerCtrlEnterAsync(string value)
    }

    class VditorOptions {
        +VditorOptions()
        +VditorMode Mode
        +string Language
        +VditorIconStyle IconStyle
        +bool Debug
        +string Placeholder
        +string Width
        +string Height
        +string MinHeight
        +string CDN
        +string Tab
        +int UndoDelay
        +bool TypeWriterMode
    }

    class VditorMode {
        <<enumeration>>
        WYSIWYG
        IR
        SV
    }

    class VditorIconStyle {
        <<enumeration>>
        Ant
        Material
    }

    Vditor --> VditorOptions : uses
    VditorOptions --> VditorMode : uses
    VditorOptions --> VditorIconStyle : uses
Loading

File-Level Changes

Change Details Files
Refactor Vditor component JS interop to use generic execute/reset helpers instead of storing a JS object reference.
  • Remove private IJSObjectReference field and related lifetime management from the Vditor component.
  • Change OnAfterRenderAsync and value change handling to call a generic JS execute helper to set the editor value.
  • Update InvokeInitAsync to call a JS init method without returning a JS object reference.
  • Update Reset to call a void JS reset helper rather than capturing the returned editor instance.
  • Rewrite editor operation methods (insert, get value/html/selection, enable/disable, focus/blur) to call a generic JS execute function via InvokeAsync/InvokeVoidAsync.
src/components/BootstrapBlazor.Vditor/Vditor.razor.cs
src/components/BootstrapBlazor.Vditor/Vditor.razor.js
Introduce a generic execute function on the JavaScript side to dispatch calls to the underlying Vditor instance.
  • Stop returning the Vditor instance from init and reset and instead keep it only in the internal Data map.
  • Add an execute(id, method, args) function that looks up the Vditor instance by id and dynamically invokes the requested method, returning its result if any.
src/components/BootstrapBlazor.Vditor/Vditor.razor.js
Improve and standardize bilingual XML documentation for Vditor-related C# types.
  • Convert summary comments in Vditor component to bilingual Chinese/English XML with clearer wording and corrected typos.
  • Update VditorOptions property summaries to bilingual XML docs describing defaults and behavior.
  • Update VditorMode and VditorIconStyle enum and member comments to bilingual XML documentation.
src/components/BootstrapBlazor.Vditor/Vditor.razor.cs
src/components/BootstrapBlazor.Vditor/VditorOptions.cs
src/components/BootstrapBlazor.Vditor/VditorMode.cs
src/components/BootstrapBlazor.Vditor/VditorIconStyle.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#930 Redesign the Vditor component's JavaScript interop in C# to stop holding a persistent IJSObjectReference and instead use id-based calls (e.g., generic execute/reset), while preserving the public API surface (methods like Reset, InsertValueAsync, GetValueAsync, etc.).
#930 Implement the corresponding changes in the Vditor JavaScript module to support the new interop pattern, including updating init/reset and adding a generic execute function that routes method calls to the underlying Vditor instance.
#930 Improve or at least maintain the documentation/comments around Vditor, reflecting the updated interop design and providing clear bilingual (zh/en) XML documentation for the component and its options.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ArgoZhang ArgoZhang merged commit 013e063 into master Feb 19, 2026
2 checks passed
@ArgoZhang ArgoZhang deleted the dev-vidtor branch February 19, 2026 01:33
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In Vditor.razor.js, execute assumes Data.get(id) always returns an object; add a null/undefined check before destructuring to avoid runtime errors when the component has not been initialized or has been disposed.
  • The Reset method in Vditor.razor.cs accepts an options parameter but still passes the Options property to the JS reset call; consider using the method parameter so callers can actually override the options per reset.
  • The console.log call inside execute in Vditor.razor.js looks like debug output; consider removing or guarding it behind a debug flag to avoid noisy logs in production.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `Vditor.razor.js`, `execute` assumes `Data.get(id)` always returns an object; add a null/undefined check before destructuring to avoid runtime errors when the component has not been initialized or has been disposed.
- The `Reset` method in `Vditor.razor.cs` accepts an `options` parameter but still passes the `Options` property to the JS `reset` call; consider using the method parameter so callers can actually override the options per reset.
- The `console.log` call inside `execute` in `Vditor.razor.js` looks like debug output; consider removing or guarding it behind a debug flag to avoid noisy logs in production.

## Individual Comments

### Comment 1
<location> `src/components/BootstrapBlazor.Vditor/Vditor.razor.js:47-50` </location>
<code_context>
-    return md.vditor;
+}
+
+export function execute(id, method, args) {
+    console.log(method, args);
+    const md = Data.get(id);
+    const { vditor } = md;
+    let ret = '';
+    if (vditor) {
</code_context>

<issue_to_address>
**issue:** `execute` assumes the id exists in `Data`, which can throw if called before init/reset or after dispose.

If `Data.get(id)` returns `undefined` (e.g. when `execute` is called before `init` completes or after disposal), `const { vditor } = md;` will throw. Please guard against `md` being null/undefined before destructuring (e.g. `if (!md) return;`) so calls in these states become safe no-ops as they were previously on the C# side.
</issue_to_address>

### Comment 2
<location> `src/components/BootstrapBlazor.Vditor/Vditor.razor.js:48` </location>
<code_context>
+}
+
+export function execute(id, method, args) {
+    console.log(method, args);
+    const md = Data.get(id);
+    const { vditor } = md;
</code_context>

<issue_to_address>
**suggestion (performance):** Unconditional console logging in `execute` may be noisy in production.

`console.log(method, args);` will log every editor interaction and clutter production logs, with minor performance overhead. Consider removing it or guarding it with a debug flag or environment check.

Suggested implementation:

```javascript
export function execute(id, method, args) {
    if (window && window.VDITOR_DEBUG) {
        console.log(method, args);
    }

```

To make use of this guard, set `window.VDITOR_DEBUG = true` in your development environment (for example, in a dev-only script block) when you actually want to see these logs. In production, simply do not set this flag (or set it to `false`) and the logs will be suppressed.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +47 to +50
export function execute(id, method, args) {
console.log(method, args);
const md = Data.get(id);
const { vditor } = md;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: execute assumes the id exists in Data, which can throw if called before init/reset or after dispose.

If Data.get(id) returns undefined (e.g. when execute is called before init completes or after disposal), const { vditor } = md; will throw. Please guard against md being null/undefined before destructuring (e.g. if (!md) return;) so calls in these states become safe no-ops as they were previously on the C# side.

}

export function execute(id, method, args) {
console.log(method, args);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Unconditional console logging in execute may be noisy in production.

console.log(method, args); will log every editor interaction and clutter production logs, with minor performance overhead. Consider removing it or guarding it with a debug flag or environment check.

Suggested implementation:

export function execute(id, method, args) {
    if (window && window.VDITOR_DEBUG) {
        console.log(method, args);
    }

To make use of this guard, set window.VDITOR_DEBUG = true in your development environment (for example, in a dev-only script block) when you actually want to see these logs. In production, simply do not set this flag (or set it to false) and the logs will be suppressed.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR redesigns the JavaScript interop architecture for the Vditor component by introducing a generic execute method pattern that simplifies method calls between C# and JavaScript. The refactoring eliminates the need to store IJSObjectReference in C#, instead managing all object references exclusively in JavaScript through a Data store.

Changes:

  • Introduced a new execute(id, method, args) function in JavaScript that dynamically invokes methods on the stored Vditor instance
  • Removed IJSObjectReference storage in C# code and simplified all public API methods to use the execute pattern
  • Updated all XML documentation to use bilingual format with <para lang="zh"> and <para lang="en"> tags
  • Fixed several Chinese documentation typos ("即使" → "即时", "案件" → "按键")
  • Removed unnecessary DisposeAsync override since the base class handles disposal automatically
  • Bumped package version from 10.0.2 to 10.0.3

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
Vditor.razor.js Added execute method for dynamic method invocation; removed return statements from init and reset; added dispose function
Vditor.razor.cs Refactored all public methods to use execute pattern; removed _vditor field and DisposeAsync override; updated documentation
VditorOptions.cs Updated to bilingual XML documentation format
VditorMode.cs Updated to bilingual XML documentation format; fixed typo in Chinese
VditorIconStyle.cs Updated to bilingual XML documentation format
BootstrapBlazor.Vditor.csproj Version bump to 10.0.3
Comments suppressed due to low confidence (1)

src/components/BootstrapBlazor.Vditor/Vditor.razor.js:63

  • The dispose function has inadequate null safety. If Data.get(id) returns null or undefined, destructuring will throw an error. Add a null check: const md = Data.get(id); if (!md) return;
export function dispose(id) {
    const md = Data.get(id);
    const { vditor } = md;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const { vditor } = md;
let ret = '';
if (vditor) {
var cb = vditor[method];
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use 'let' instead of 'var' for modern JavaScript. The 'var' keyword has function scoping which can lead to unexpected behavior, while 'let' provides block scoping.

Suggested change
var cb = vditor[method];
let cb = vditor[method];

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +106
protected override Task InvokeInitAsync() => InvokeAsync<IJSObjectReference>("init", Id, Interop, new
{
_vditor = await InvokeAsync<IJSObjectReference>("init", Id, Interop, new
{
Options,
Value
});
}
Options,
Value
});
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The InvokeInitAsync method is calling InvokeAsync with a generic type parameter IJSObjectReference, but the JavaScript init function no longer returns the vditor object (the return statement was removed at line 18 of the diff). This will cause the C# code to receive an undefined value. Since the returned value is no longer used, this should be changed to InvokeVoidAsync instead of InvokeAsync.

Copilot uses AI. Check for mistakes.

/// <summary>
/// 返回选中的字符串
/// <para lang="zh">获取 返回选中的字符串</para>
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Chinese documentation has redundant wording: "获取 返回选中的字符串" contains both "获取" (get) and "返回" (return). This should be simplified to either "获取选中的字符串" or "返回选中的字符串" for clarity and consistency.

Suggested change
/// <para lang="zh">获取 返回选中的字符串</para>
/// <para lang="zh">获取选中的字符串</para>

Copilot uses AI. Check for mistakes.
console.log(method, args);
const md = Data.get(id);
const { vditor } = md;
let ret = '';
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initializing ret to an empty string is problematic. The vditor methods being called can return various types (string, undefined, void, etc.). For example, getValue returns a string, but methods like setValue, focus, or enable return void/undefined. Initializing to empty string will cause methods that return void to incorrectly return an empty string. Initialize to undefined instead: let ret;

Suggested change
let ret = '';
let ret;

Copilot uses AI. Check for mistakes.
let ret = '';
if (vditor) {
var cb = vditor[method];
if (cb) {
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check 'if (cb)' is too permissive. It should verify that cb is actually a function to prevent runtime errors if vditor[method] exists but is not callable. Change to: if (typeof cb === 'function')

Suggested change
if (cb) {
if (typeof cb === 'function') {

Copilot uses AI. Check for mistakes.
}

export function execute(id, method, args) {
console.log(method, args);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This debug console.log statement should be removed before merging to production. Debug logging should not be left in production code.

Suggested change
console.log(method, args);

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +51
const md = Data.get(id);
const { vditor } = md;
let ret = '';
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The execute function has inadequate null safety. If Data.get(id) returns null or undefined (e.g., if dispose was called or init failed), accessing md.vditor will throw an error. Add a null check for md before destructuring: if (!md) return ret;

Suggested change
const md = Data.get(id);
const { vditor } = md;
let ret = '';
let ret = '';
const md = Data.get(id);
if (!md) {
return ret;
}
const { vditor } = md;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(Vditor): redesign javascript interop

2 participants