Skip to content

Commit 53951cc

Browse files
georgedautovdimodi
andcommitted
docs(Editor): add editor mentions kb
Co-authored-by: Dimo Dimov <961014+dimodi@users.noreply.github.com>
1 parent f1b8fec commit 53951cc

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed

knowledge-base/editor-mentions.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
---
2+
title: Mentions in Editor
3+
description: Learn how to add support for mentions in the Telerik Editor component for Blazor.
4+
type: how-to
5+
page_title: Mentions in Editor
6+
slug: editor-kb-mentions
7+
position:
8+
tags: telerik, blazor, editor, mentions
9+
ticketid: 1545505
10+
res_type: kb
11+
---
12+
13+
## Environment
14+
15+
<table>
16+
<tbody>
17+
<tr>
18+
<td>Product</td>
19+
<td>Editor for Blazor</td>
20+
</tr>
21+
</tbody>
22+
</table>
23+
24+
## Description
25+
26+
How to enable or implement support for `@mentions` in the TelerikEditor for Blazor, similar to GitHub, Facebook, etc.?
27+
28+
## Solution
29+
30+
You can use the [proseMirror-mentions](https://github.com/joelewis/prosemirror-mentions) plugin to provide a `@mentions` and `#hashtags` functionality for the Telerik Blazor Editor component. To implement the feature, customize the built-in [ProseMirror schema](slug:editor-prosemirror-schema-overview) and [integrate a ProseMirror plugin](slug:editor-prosemirror-plugins).
31+
32+
For dynamic positioning of the mentions list, set the [`EditMode`](slug:Telerik.Blazor.Components.TelerikEditor#telerik_blazor_components_telerikeditor_editmode) property of the Editor to `EditorEditorMode.Div`. This ensures that the mentions dropdown position is correct relative to the Editor content.
33+
34+
````RAZOR.skip-repl
35+
<TelerikEditor EditMode="EditorEditMode.Div" />
36+
````
37+
38+
### Setting up WebPack and Installing proseMirror-mentions
39+
40+
1. Create a directory at the root of your project which will contain your JavaScript module (e.g. `npmjs`). All of the next steps will be done inside that directory
41+
2. Setup the Javascript project by running:
42+
````SH.skip-repl
43+
npm init -y
44+
````
45+
> The `-y` flag accepts all defaults for simplicity. In a real world application, consider running `npm init` without the flag to configure settings interactively.
46+
3. Install a JavaScript bundler. In this example we will use [webpack](https://webpack.js.org), so run:
47+
````SH.skip-repl
48+
npm install webpack webpack-cli --save-dev
49+
````
50+
4. Configure the build script in `package.json`. Add this to the `scripts` section:
51+
````JSON.skip-repl
52+
"scripts": {
53+
"build": "webpack ./src/index.js --output-path ../wwwroot/js --output-filename index.bundle.js"
54+
},
55+
````
56+
> In this example all of our Javascript files will go into `npmjs/src`
57+
5. Update the module type in `package.json`:
58+
````JSON.skip-repl
59+
"type": module"
60+
````
61+
This enables ES6 `import`/`export` syntax instead of the CommonJS require statements which will be useful later on.
62+
6. Install [proseMirror-mentions](https://github.com/joelewis/prosemirror-mentions) by running:
63+
````SH.skip-repl
64+
npm install prosemirror-mentions
65+
````
66+
67+
### Integrate the Mentions Plugin
68+
69+
The following code demonstrates how to integrate the `proseMirror-mentions` plugin in the Editor.
70+
71+
<div class="skip-repl"></div>
72+
73+
````razor Component.razor
74+
@using Microsoft.Extensions.Logging.Abstractions
75+
76+
@implements IDisposable
77+
78+
@inject IJSRuntime JSRuntime
79+
@inject IServiceProvider ServiceProvider
80+
81+
<TelerikEditor Plugins="pluginsProvider"
82+
Schema="schemaProvider"
83+
EditMode="EditorEditMode.Div">
84+
</TelerikEditor>
85+
86+
@code {
87+
// Replace Component with your actual component type
88+
private DotNetObjectReference<Component>? dotNetRef;
89+
private List<Mention> Mentions { get; set; } = new List<Mention>()
90+
{
91+
new()
92+
{
93+
Id = "board",
94+
Name = "Jane Simons",
95+
Email = "jane.simons@company.com",
96+
},
97+
new()
98+
{
99+
Id = "engineering",
100+
Name = "Peter Parker",
101+
Email = "peter.parker@company.com"
102+
},
103+
new()
104+
{
105+
Id = "generalManager",
106+
Name = "Liam Turner",
107+
Email = "liam.turner@company.com"
108+
}
109+
};
110+
111+
protected override async Task OnAfterRenderAsync(bool firstRender)
112+
{
113+
if (firstRender)
114+
{
115+
dotNetRef = DotNetObjectReference.Create(this);
116+
await JSRuntime.InvokeVoidAsync("initializeMentions", dotNetRef);
117+
}
118+
}
119+
120+
[JSInvokable]
121+
public async Task<Mention[]> GetMentionSuggestionsAsync(string text)
122+
{
123+
return Mentions.Where(mention => mention.Name.ToLower().Contains(text)).ToArray();
124+
}
125+
126+
[JSInvokable]
127+
public async Task<string> GetMentionSuggestionsHTML(List<Mention> mentions)
128+
{
129+
using var htmlRenderer = new HtmlRenderer(ServiceProvider, NullLoggerFactory.Instance);
130+
var html = await htmlRenderer.Dispatcher.InvokeAsync(async () =>
131+
{
132+
var dictionary = new Dictionary<string, object?>
133+
{
134+
{ "Items", mentions }
135+
};
136+
var parameters = ParameterView.FromDictionary(dictionary);
137+
var output = await htmlRenderer.RenderComponentAsync<MentionSuggestionList>(parameters);
138+
return output.ToHtmlString();
139+
});
140+
141+
return html;
142+
}
143+
144+
public void Dispose()
145+
{
146+
dotNetRef?.Dispose();
147+
}
148+
}
149+
````
150+
````razor MentionSuggestionList.razor
151+
@*
152+
IMPORTANT: outer div's "suggestion-item-list" class is mandatory. The plugin uses this class for querying.
153+
IMPORTANT: inner div's "suggestion-item" class is mandatory too for the same reasons
154+
*@
155+
156+
<div class="suggestion-item-list">
157+
@if (Items == null || Items.Count == 0)
158+
{
159+
<div class="suggestion-item">
160+
No suggestions
161+
</div>
162+
} else
163+
{
164+
@foreach (Mention item in Items) {
165+
<div class="suggestion-item">
166+
<div class="suggestion-item-content">
167+
<div class="suggestion-text">
168+
<div class="suggestion-name">
169+
@item.Name
170+
</div>
171+
<div class="suggestion-title">
172+
@item.Email
173+
</div>
174+
</div>
175+
</div>
176+
</div>
177+
}
178+
}
179+
</div>
180+
181+
@code {
182+
[Parameter]
183+
public IEnumerable<Mention> Items { get; set; }
184+
}
185+
````
186+
````cs Mention.cs
187+
public class Mention
188+
{
189+
public string Id { get; set; } = string.Empty;
190+
public string Name { get; set; } = string.Empty;
191+
public string Email { get; set; } = string.Empty;
192+
}
193+
````
194+
````js index.js
195+
import { addMentionNodes, addTagNodes, getMentionsPlugin } from 'prosemirror-mentions';
196+
197+
let _dotnetRef;
198+
window.initializeMentions = (dotnetRef) => {
199+
_dotnetRef = dotnetRef;
200+
}
201+
202+
let mentionSuggestionsHTML = null;
203+
204+
var mentionPlugin = getMentionsPlugin({
205+
getSuggestions: (type, text, done) => {
206+
setTimeout(async () => {
207+
if (type === 'mention') {
208+
try {
209+
const suggestions = await _dotnetRef.invokeMethodAsync('GetMentionSuggestionsAsync', text);
210+
mentionSuggestionsHTML = await _dotnetRef.invokeMethodAsync('GetMentionSuggestionsHTML', suggestions);
211+
done(suggestions);
212+
} catch (error) {
213+
console.error('Error getting suggestions:', error);
214+
done([]);
215+
}
216+
}
217+
}, 0);
218+
},
219+
getSuggestionsHTML: (items, type) => {
220+
if (type === 'mention') {
221+
return mentionSuggestionsHTML;
222+
}
223+
}
224+
});
225+
226+
window.pluginsProvider = (args) => {
227+
const schema = args.getSchema();
228+
229+
return [mentionPlugin, ...args.getPlugins(schema)];
230+
}
231+
232+
window.schemaProvider = (args) => {
233+
const schema = args.getSchema();
234+
const Schema = args.ProseMirror.Schema;
235+
const nodes = addMentionNodes(schema.spec.nodes);
236+
const mentionsSchema = new Schema({
237+
nodes: nodes,
238+
marks: schema.spec.marks
239+
});
240+
241+
return mentionsSchema;
242+
}
243+
244+
````
245+
246+
## See Also
247+
248+
* [Editor Schema](slug:editor-prosemirror-schema-overview)
249+
* [Editor Plugins](slug:editor-prosemirror-plugins)
250+
* [ProseMirror Documentation](https://prosemirror.net/docs/ref)
251+
* [proseMirror-mentions](https://github.com/joelewis/prosemirror-mentions)

0 commit comments

Comments
 (0)