Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/BootstrapBlazor.CodeEditor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!package-lock.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>10.0.1</Version>
</PropertyGroup>

<PropertyGroup>
<PackageTags>Bootstrap Blazor WebAssembly wasm UI Components</PackageTags>
<Description>Bootstrap UI components extensions of monaco-editor</Description>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

Expand All @@ -7,52 +7,62 @@
namespace BootstrapBlazor.Components;

/// <summary>
///
/// <para lang="zh">代码编辑器组件</para>
/// <para lang="en">Code editor component</para>
/// </summary>
public partial class CodeEditor
{
private const string MONACO_VS_PATH = "/_content/BootstrapBlazor.CodeEditor/monaco-editor/min/vs";
private const string MonacoStylePath = "_content/BootstrapBlazor.CodeEditor/monaco-editor/monaco.css";

private const string CodeEditorStylePath = "_content/BootstrapBlazor.CodeEditor/code-editor.bundle.css";

/// <summary>
/// Language used by the editor: csharp, javascript, ...
/// <para lang="zh">获得/设置 编辑器语言</para>
/// <para lang="en">Gets or sets the editor language</para>
/// </summary>
[Parameter]
[NotNull]
public string? Language { get; set; }

/// <summary>
/// Theme of the editor.
/// <para lang="zh">获得/设置 编辑器主题</para>
/// <para lang="en">Gets or sets the editor theme</para>
/// </summary>
[Parameter]
[NotNull]
public string? Theme { get; set; }

/// <summary>
/// Gets or sets the value of the input. This should be used with two-way binding.
/// <para lang="zh">获得/设置 输入的值。应与双向绑定一起使用。</para>
/// <para lang="en">Gets or sets the value of the input. This should be used with two-way binding.</para>
/// </summary>
[Parameter]
public string? Value { get; set; }

/// <summary>
/// Gets or sets a callback that updates the bound value.
/// <para lang="zh">获得/设置 更新绑定值的回调。</para>
/// <para lang="en">Gets or sets a callback that updates the bound value.</para>
/// </summary>
[Parameter]
public EventCallback<string?> ValueChanged { get; set; }

/// <summary>
/// Gets or sets a callback that updates the bound value.
/// <para lang="zh">获得/设置 更新绑定值的回调。</para>
/// <para lang="en">Gets or sets a callback that updates the bound value.</para>
/// </summary>
[Parameter]
public Func<string?, Task>? OnValueChanged { get; set; }

/// <summary>
/// 获得/设置 是否显示行号 默认 false
/// <para lang="zh">获得/设置 是否显示行号 默认 false</para>
/// <para lang="en">Gets or sets whether to show line numbers. Default is false.</para>
/// </summary>
[Parameter]
public bool ShowLineNo { get; set; }

/// <summary>
/// 获得/设置 是否显示只读 默认 false
/// <para lang="zh">获得/设置 是否显示只读 默认 false</para>
/// <para lang="en">Gets or sets whether to show read-only. Default is false.</para>
/// </summary>
[Parameter]
public bool IsReadonly { get; set; }
Expand Down Expand Up @@ -98,30 +108,46 @@ protected override async Task InvokeInitAsync()
Value,
Language,
Theme,
Path = MONACO_VS_PATH,
LineNumbers = ShowLineNo,
ReadOnly = IsReadonly,
StyleSheets = new List<string>()
{
#if NET9_0_OR_GREATER
Assets[MonacoStylePath],
Assets[CodeEditorStylePath]
#else
MonacoStylePath,
CodeEditorStylePath
#endif
}
};
await InvokeVoidAsync("init", Id, Interop, options);
}

/// <summary>
///
/// <para lang="zh">使代码编辑器获得焦点。</para>
/// <para lang="en">Sets focus to the code editor.</para>
/// </summary>
/// <returns></returns>
public async Task Focus() => await InvokeVoidAsync("focus");

/// <summary>
///
/// <para lang="zh">重新计算代码编辑器的布局。</para>
/// <para lang="en">Recalculates the layout of the code editor.</para>
/// </summary>
/// <returns></returns>
public async Task Resize() => await InvokeVoidAsync("resize");

/// <summary>
///
/// <para lang="zh">在当前光标位置插入文本,替换当前选定内容。</para>
/// <para lang="en">Inserts text at the current cursor position, replacing the current selection.</para>
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <param name="data"></param>
public async Task InsertTextAsync(string data) => await InvokeVoidAsync("insertText", Id, data);

/// <summary>
/// <para lang="zh">更新编辑器值并通知值变更回调。</para>
/// <para lang="en">Updates the editor value and notifies the value change callbacks.</para>
/// </summary>
/// <param name="value">The updated editor value.</param>
[JSInvokable]
public async Task UpdateValueAsync(string value)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { addLink, addScript } from '../../../BootstrapBlazor/modules/utility.js'
import { addLink } from '../../../BootstrapBlazor/modules/utility.js'
import Data from '../../../BootstrapBlazor/modules/data.js'
import EventHandler from "../../../BootstrapBlazor/modules/event-handler.js"

let monacoLoader;

const loadMonaco = () => {
monacoLoader ??= import('../../monaco-editor/monaco.js');
return monacoLoader;
}

export async function init(id, interop, options) {
const editor = {};
Data.set(id, editor);

await addLink('_content/BootstrapBlazor.CodeEditor/code-editor.bundle.css');
await addScript('_content/BootstrapBlazor.CodeEditor/monaco-editor/min/vs/loader.js');
const [module] = await Promise.all([
loadMonaco(),
...options.styleSheets.map(styleSheet => addLink(styleSheet))
]);
editor.monaco = module.monaco;

const init = container => {

// Hide the Progress Ring
monaco.editor.onDidCreateEditor((e) => {
const progress = container.querySelector(".spinner");
if (progress && progress.style) {
progress.style.display = "none";
}
});

// Create the Monaco Editor
const body = container.querySelector(".code-editor-body");
editor.editor = monaco.editor.create(body, {
editor.editor = editor.monaco.editor.create(body, {
ariaLabel: "online code editor",
value: options.value,
language: options.language,
Expand All @@ -30,62 +30,65 @@ export async function init(id, interop, options) {
readOnly: options.readOnly,
});

// Catch when the editor lost the focus (didType to immediate)
const progress = container.querySelector(".spinner");
if (progress) {
progress.style.display = "none";
}

editor.editor.onDidBlurEditorText((e) => {
const code = editor.editor.getValue();
interop.invokeMethodAsync("UpdateValueAsync", code);
});

monaco.editor.setModelLanguage(monaco.editor.getModels()[0], options.language)

editor.editor.layout();

EventHandler.on(window, "resize", () => {
editor.editor.layout();
});
}

const protocol = window.location.protocol;
const host = window.location.hostname;
const port = window.location.port;
let fullDomain = "";

if (port === "80" && protocol === "http:") {
fullDomain = `${protocol}//${host}`;
} else if (port === "443" && protocol === "https:") {
fullDomain = `${protocol}//${host}`;
} else {
fullDomain = `${protocol}//${host}:${port}`;
}

// require is provided by loader.min.js.
require.config({
paths: {'vs': `${fullDomain}${options.path}`}
});
editor.handler = setInterval(() => {
const container = document.getElementById(id);
if (container?.offsetHeight > 0) {
clearInterval(editor.handler);
init(container);
editor.handler = null;
delete editor.handler;
}
}, 50);
}

require(["vs/editor/editor.main"], () => {
editor.handler = setInterval(() => {
var container = document.getElementById(id);
if (container.offsetHeight > 0) {
clearInterval(editor.handler);
init(container);
editor.handler = null;
delete editor.handler;
}
}, 50);
});
export function insertText(id, insertData) {
const wrapper = Data.get(id);
if (!wrapper) return;

const editor = wrapper.editor;
const selection = editor.getSelection();
editor.executeEdits('insert-custom-text', [
{
range: selection,
text: insertData,
forceMoveMarkers: true
}
]);
editor.focus();
}

// Update the editor options
export function monacoSetOptions(id, options) {
var editor = Data.get(id);
if (editor) {
editor.editor.setValue(options.value);
editor.editor.updateOptions({
const wrapper = Data.get(id);
if (wrapper?.editor) {
const value = options.value ?? '';
if (wrapper.editor.getValue() !== value) {
wrapper.editor.setValue(value);
}
wrapper.editor.updateOptions({
language: options.language,
theme: options.theme
});
monaco.editor.setModelLanguage(monaco.editor.getModels()[0], options.language)
const model = wrapper.editor.getModel();
if (model) {
wrapper.monaco.editor.setModelLanguage(model, options.language);
}
}
}

Expand Down
Loading
Loading