I am using a tool called logseq to manage my notes. It is a fantastic tool, and I would like to be able to extend it. I am not an expert web developer, but I am a .NET developer. I would like to be able to write plugins for logseq using .NET and C#. I have found a way to do it using WebAssembly. In this post, I am going to show you how to do it.

Prerequisites

Logseq plugin skeleton

There are multiple samples available on the logseq-plugin-samples github repository. I chose the first one Slash command Sample to conduct my experiment of creating a first plugin.

We need to take over the script included in the index.html file, representing the logseq API.

1
<script src="https://cdn.jsdelivr.net/npm/@logseq/libs@0.0.1-alpha.34/dist/lsplugin.user.min.js"></script>

Then JavaScript code lets you interact with the logseq application with a nice and simple API. Here, we register a slash command called 💥 Big Bang which will display a message in the logseq application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function main () {
logseq.Editor.registerSlashCommand(
'💥 Big Bang',
async () => {
const { content, uuid } = await logseq.Editor.getCurrentBlock()

logseq.App.showMsg(`
[:div.p-2
[:h1 "#${uuid}"]
[:h2.text-xl "${content}"]]
`)
}
)
}

// bootstrap
logseq.ready(main).catch(console.error)

Finally, the definition of the plugin used to register it in the logseq application.

package.json
1
2
3
4
5
6
7
8
9
10
11
{
"name": "logseq-slash-dotnetwasm",
"version": "0.0.1",
"description": ".NET WASM slash command.",
"main": "index.html",
"author": "Laurent Kempé",
"license": "MIT",
"logseq": {
"id": "logseq-slash-dotnetwasm"
}
}

.NET 7 WebAssembly new capabilities

If you are from the .NET ecosystem, you are probably familiar with Blazor. For the other, it is a framework to be able to write .NET code in the browser. It is using WebAssembly to be able to do it. It is not the only way anymore to use .NET in the browser. There are new capabilities to be able to use .NET 7 independently of Blazor from any JavaScript in a browser app or a Node.js based console app.

You need to install .NET 7 RC1 SDK and run the following commands

1
2
dotnet workload install wasm-tools
dotnet workload install wasm-experimental

Then create a new WebAssembly browser app project using the following command

1
dotnet new wasmbrowser

creating the following code bringing interoperation possibilities between the JavaScript and the .NET code

Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.Runtime.InteropServices.JavaScript;

Console.WriteLine("Hello, Browser!");

public partial class MyClass
{
[JSExport]
internal static string Greeting()
{
var text = $"Hello, World! Greetings from {GetHRef()}";
Console.WriteLine(text);
return text;
}

[JSImport("window.location.href", "main.js")]
internal static partial string GetHRef();
}

You can use the JSExport attribute to export a C# method to JavaScript. Similarly, you can also use the JSImport attribute to import a method from JavaScript and be able to call it from C#.

It also creates a bit of JavaScript main.js with the interesting part being the part that is calling the Greeting method from the C# code, itself calling the GetHRef() C# method delegated to the JavaScript window.location.href.

main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

setModuleImports("main.js", {
window: {
location: {
href: () => globalThis.window.location.href
}
}
});

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const text = exports.MyClass.Greeting();
console.log(text);

document.getElementById("out").innerHTML = `${text}`;

and to tight everything together an index.html file is also created

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>

<head>
<title>wasmbrowser</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="modulepreload" href="./main.js" />
<link rel="modulepreload" href="./dotnet.js" />
</head>

<body>
<span id="out"></span>
<script type='module' src="./main.js"></script>
</body>

</html>

Integrating logseq JavaScript plugin with .NET WASM

As this is an experiment, I am doing minimum changes to integrate both worlds. Starting with main.js and integrating the logseq API, we register the slash command which is calling the Greeting method from the C# code.

main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Called by C# WASM Guest() method
setModuleImports("main.js", {
guest: () => "logseq"
});

function main () {
logseq.Editor.registerSlashCommand(
'🫥 .NET WASM',
async () => {
// Calling into .NET WASM
const text = exports.MyClass.Greeting()

logseq.App.showMsg(`
[:div.p-2
[:h2.text-xl "${text}"]]
`)
}
)
}

const config = getConfig()
const exports = await getAssemblyExports(config.mainAssemblyName)

logseq.ready(main).catch(console.error)

The Greeting C# method calls the “Guest” C# method which is delegating to the JavaScript guest method.

Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
public partial class MyClass
{
[JSExport]
internal static string Greeting()
{
var text = $"Hello {Guest()}! Greetings from .NET C# Wasm";
Console.WriteLine(text);
return text;
}

[JSImport("guest", "main.js")]
internal static partial string Guest();
}

Building the plugin

To build the plugin, we need to run the following command

1
dotnet build -c Release

which will create the bin/Release/net7.0/browser-wasm/AppBundle folder which we will import in logseq using the Plugins / Load unpacked plugin menu.

logseq .NET WASM plugin

It is working 🎉

logseq .NET WASM plugin demo

Conclusion

We have seen that with minimum knowledge of web development, the help of WebAssembly, and new .NET 7 capabilities, we can write plugins for logseq using .NET and C#, thanks to WASM/WebAssembly. Nevertheless, this is going much further than that. We can now write .NET code in the browser, and we can use it in any JavaScript based application including Node.js based console app. So, what works for logseq could be used for any other applications which are based on JavaScript. It is a new world of possibilities.

Code

References