Skip to content

Commit 2e57910

Browse files
georgedautovdimodi
andauthored
docs(Editor): add editor mentions kb (#3375)
* docs(Editor): add editor mentions kb Co-authored-by: Dimo Dimov <961014+dimodi@users.noreply.github.com> * Integrate suggestions from code review * Integrate improvements from code review * Update knowledge-base/editor-mentions.md Co-authored-by: Dimo Dimov <961014+dimodi@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Dimo Dimov <961014+dimodi@users.noreply.github.com> --------- Co-authored-by: Dimo Dimov <961014+dimodi@users.noreply.github.com>
1 parent 8f879a5 commit 2e57910

File tree

1 file changed

+277
-0
lines changed

1 file changed

+277
-0
lines changed

knowledge-base/editor-mentions.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
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. Setup the Javascript project by running the following command in the root folder of your project:
41+
````SH.skip-repl
42+
npm init -y
43+
````
44+
> This command creates a `package.json` file with the project's configuration. The `-y` flag accepts all defaults for simplicity. In a real world application, consider running `npm init` without the flag to configure settings interactively.
45+
2. Install a JavaScript bundler. In this example we will use [webpack](https://webpack.js.org), so run:
46+
````SH.skip-repl
47+
npm install webpack webpack-cli --save-dev
48+
````
49+
3. Configure the build script in the `scripts` section of `package.json` (in this example all of our Javascript files will go into `wwwroot/js`):
50+
````JSON.skip-repl
51+
"scripts": {
52+
"build": "webpack ./wwwroot/js/index.js --output-path ./wwwroot/js --output-filename index.bundle.js"
53+
},
54+
````
55+
5. Update the module type in `package.json`:
56+
````JSON.skip-repl
57+
"type": module"
58+
````
59+
This enables ES6 `import`/`export` syntax instead of the CommonJS require statements which will be useful later on.
60+
6. Install [proseMirror-mentions](https://github.com/joelewis/prosemirror-mentions) by running:
61+
````SH.skip-repl
62+
npm install prosemirror-mentions
63+
````
64+
7. Create a file named `index.js` in your project's `wwwroot/js` directory and paste the contents from the respective code tab below.
65+
8. Build the JavaScript bundle by running:
66+
````SH.skip-repl
67+
npm run build
68+
````
69+
This creates the `index.bundle.js` file in your `wwwroot/js` directory.
70+
71+
### Include the JavaScript Bundle
72+
73+
After building the JavaScript bundle, you need to include it in your Blazor application:
74+
75+
**Global Level (App.razor):**
76+
<div class="skip-repl"></div>
77+
78+
````razor App.razor
79+
<!DOCTYPE html>
80+
<html>
81+
<head>
82+
<!-- other head content -->
83+
</head>
84+
<body>
85+
<!-- body content -->
86+
87+
<script src="js/index.bundle.js"></script>
88+
</body>
89+
</html>
90+
````
91+
92+
### Integrate the Mentions Plugin
93+
94+
The following code demonstrates how to integrate the `proseMirror-mentions` plugin in the Editor.
95+
96+
<div class="skip-repl"></div>
97+
98+
````razor Component.razor
99+
@using Microsoft.Extensions.Logging.Abstractions
100+
101+
@implements IDisposable
102+
103+
@inject IJSRuntime JSRuntime
104+
@inject IServiceProvider ServiceProvider
105+
106+
<TelerikEditor Plugins="pluginsProvider"
107+
Schema="schemaProvider"
108+
EditMode="EditorEditMode.Div">
109+
</TelerikEditor>
110+
111+
@code {
112+
// Replace Component with your actual component type
113+
private DotNetObjectReference<Component>? dotNetRef;
114+
private List<Mention> Mentions { get; set; } = new List<Mention>()
115+
{
116+
new()
117+
{
118+
Id = "board",
119+
Name = "Jane Simons",
120+
Email = "jane.simons@company.com",
121+
},
122+
new()
123+
{
124+
Id = "engineering",
125+
Name = "Peter Parker",
126+
Email = "peter.parker@company.com"
127+
},
128+
new()
129+
{
130+
Id = "generalManager",
131+
Name = "Liam Turner",
132+
Email = "liam.turner@company.com"
133+
}
134+
};
135+
136+
protected override async Task OnAfterRenderAsync(bool firstRender)
137+
{
138+
if (firstRender)
139+
{
140+
dotNetRef = DotNetObjectReference.Create(this);
141+
await JSRuntime.InvokeVoidAsync("initializeMentions", dotNetRef);
142+
}
143+
}
144+
145+
[JSInvokable]
146+
public async Task<Mention[]> GetMentionSuggestionsAsync(string text)
147+
{
148+
return Mentions.Where(mention => mention.Name.ToLower().Contains(text)).ToArray();
149+
}
150+
151+
[JSInvokable]
152+
public async Task<string> GetMentionSuggestionsHTML(List<Mention> mentions)
153+
{
154+
using var htmlRenderer = new HtmlRenderer(ServiceProvider, NullLoggerFactory.Instance);
155+
var html = await htmlRenderer.Dispatcher.InvokeAsync(async () =>
156+
{
157+
var dictionary = new Dictionary<string, object?>
158+
{
159+
{ "Items", mentions }
160+
};
161+
var parameters = ParameterView.FromDictionary(dictionary);
162+
var output = await htmlRenderer.RenderComponentAsync<MentionSuggestionList>(parameters);
163+
return output.ToHtmlString();
164+
});
165+
166+
return html;
167+
}
168+
169+
public void Dispose()
170+
{
171+
dotNetRef?.Dispose();
172+
}
173+
}
174+
````
175+
````razor MentionSuggestionList.razor
176+
@*
177+
IMPORTANT: outer div's "suggestion-item-list" class is mandatory. The plugin uses this class for querying.
178+
IMPORTANT: inner div's "suggestion-item" class is mandatory too for the same reasons.
179+
*@
180+
181+
<div class="suggestion-item-list k-popup k-list-container" style="max-height: 200px;">
182+
<div class="k-list">
183+
<div class="k-list-content">
184+
<ul class="k-list-ul">
185+
@if (Items == null || Items.Count() == 0)
186+
{
187+
<li class="suggestion-item k-list-item">
188+
No Suggestions
189+
</li>
190+
}
191+
else
192+
{
193+
@foreach (Mention item in Items)
194+
{
195+
<li class="suggestion-item k-list-item">
196+
@item.Name
197+
<br />
198+
@item.Email
199+
</li>
200+
}
201+
}
202+
</ul>
203+
</div>
204+
</div>
205+
</div>
206+
207+
@code {
208+
[Parameter]
209+
public IEnumerable<Mention>? Items { get; set; }
210+
}
211+
````
212+
````cs Mention.cs
213+
public class Mention
214+
{
215+
public string Id { get; set; } = string.Empty;
216+
public string Name { get; set; } = string.Empty;
217+
public string Email { get; set; } = string.Empty;
218+
}
219+
````
220+
````js wwwroot/js/index.js
221+
import { addMentionNodes, addTagNodes, getMentionsPlugin } from 'prosemirror-mentions';
222+
223+
let _dotnetRef;
224+
window.initializeMentions = (dotnetRef) => {
225+
_dotnetRef = dotnetRef;
226+
}
227+
228+
let mentionSuggestionsHTML = null;
229+
230+
var mentionPlugin = getMentionsPlugin({
231+
getSuggestions: (type, text, done) => {
232+
setTimeout(async () => {
233+
if (type === 'mention') {
234+
try {
235+
const suggestions = await _dotnetRef.invokeMethodAsync('GetMentionSuggestionsAsync', text);
236+
mentionSuggestionsHTML = await _dotnetRef.invokeMethodAsync('GetMentionSuggestionsHTML', suggestions);
237+
done(suggestions);
238+
} catch (error) {
239+
console.error('Error getting suggestions:', error);
240+
done([]);
241+
}
242+
}
243+
}, 0);
244+
},
245+
getSuggestionsHTML: (items, type) => {
246+
if (type === 'mention') {
247+
return mentionSuggestionsHTML;
248+
}
249+
}
250+
});
251+
252+
window.pluginsProvider = (args) => {
253+
const schema = args.getSchema();
254+
255+
return [mentionPlugin, ...args.getPlugins(schema)];
256+
}
257+
258+
window.schemaProvider = (args) => {
259+
const schema = args.getSchema();
260+
const Schema = args.ProseMirror.Schema;
261+
const nodes = addMentionNodes(schema.spec.nodes);
262+
const mentionsSchema = new Schema({
263+
nodes: nodes,
264+
marks: schema.spec.marks
265+
});
266+
267+
return mentionsSchema;
268+
}
269+
270+
````
271+
272+
## See Also
273+
274+
* [Editor Schema](slug:editor-prosemirror-schema-overview)
275+
* [Editor Plugins](slug:editor-prosemirror-plugins)
276+
* [ProseMirror Documentation](https://prosemirror.net/docs/ref)
277+
* [proseMirror-mentions](https://github.com/joelewis/prosemirror-mentions)

0 commit comments

Comments
 (0)