Skip to content

Commit e5a8cdf

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 e5a8cdf

File tree

1 file changed

+258
-0
lines changed

1 file changed

+258
-0
lines changed

knowledge-base/editor-mentions.md

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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 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+
### Install WebPack and Install proseMirror-mentions
39+
40+
1. Create a directory at the root of your project which will contain your JavaScript module (e.g. `npmjs`)
41+
2. In that directory, run
42+
````SH.skip-repl
43+
npm init
44+
````
45+
3. Install a JavaScript bundler. In this example we will use [webpack](https://webpack.js.org), so run
46+
> `npm install webpack webpack-cli --save-dev`
47+
4. Add an `npm build` script that will use webpack to bundle our Javascript file. Modify the `scripts` section of the `package.json` file to add the following build script (note: in this example all of our Javascript files will go into `NpmJs/src`)
48+
````json.skip-repl
49+
"scripts": {
50+
"build": "webpack ./src/index.js --output-path ../wwwroot/js --output-filename index.bundle.js"
51+
},
52+
````
53+
5. Install [proseMirror-mentions](https://github.com/joelewis/prosemirror-mentions) by running
54+
> `npm install prosemirror-mentions`
55+
56+
### Integrate the Mentions Plugin
57+
58+
The following code demonstrates how to integrate the `proseMirror-mentions` plugin in the Editor.
59+
60+
<div class="skip-repl"></div>
61+
62+
````razor Component
63+
@using Microsoft.Extensions.Logging.Abstractions
64+
65+
@implements IDisposable
66+
67+
<TelerikEditor Plugins="pluginsProvider"
68+
Schema="schemaProvider"
69+
EditMode="EditorEditMode.Div">
70+
</TelerikEditor>
71+
72+
@code {
73+
private DotNetObjectReference<Editor>? _dotNetRef;
74+
private List<Mention> Mentions { get; set; } = new List<Mention>()
75+
{
76+
new()
77+
{
78+
Id = "board",
79+
Name = "Jane Simons",
80+
Email = "jane.simons@company.com",
81+
},
82+
new()
83+
{
84+
Id = "engineering",
85+
Name = "Peter Parker",
86+
Email = "peter.parker@company.com"
87+
},
88+
new()
89+
{
90+
Id = "generalManager",
91+
Name = "Liam Turner",
92+
Email = "liam.turner@company.com"
93+
}
94+
};
95+
96+
[Inject]
97+
private IJSRuntime JSRuntime { get; set; }
98+
99+
[Inject]
100+
private IServiceProvider ServiceProvider { get; set; }
101+
102+
protected override async Task OnAfterRenderAsync(bool firstRender)
103+
{
104+
if (firstRender)
105+
{
106+
_dotNetRef = DotNetObjectReference.Create(this);
107+
await JSRuntime.InvokeVoidAsync("initializeMentions", _dotNetRef);
108+
}
109+
}
110+
111+
[JSInvokable]
112+
public async Task<Mention[]> GetMentionSuggestionsAsync(string text)
113+
{
114+
return Mentions.Where(mention => mention.Name.ToLower().Contains(text)).ToArray();
115+
}
116+
117+
[JSInvokable]
118+
public async Task<string> GetMentionSuggestionsHTML(List<Mention> mentions)
119+
{
120+
using var htmlRenderer = new HtmlRenderer(ServiceProvider, NullLoggerFactory.Instance);
121+
var html = await htmlRenderer.Dispatcher.InvokeAsync(async () =>
122+
{
123+
var dictionary = new Dictionary<string, object?>
124+
{
125+
{ "Items", mentions }
126+
};
127+
var parameters = ParameterView.FromDictionary(dictionary);
128+
var output = await htmlRenderer.RenderComponentAsync<MentionSuggestionList>(parameters);
129+
return output.ToHtmlString();
130+
});
131+
132+
return html;
133+
}
134+
135+
public void Dispose()
136+
{
137+
_dotNetRef?.Dispose();
138+
}
139+
}
140+
````
141+
````razor MentionSuggestionList
142+
<div class="suggestion-item-list">
143+
@if (Items == null || Items.Count == 0)
144+
{
145+
<div class="suggestion-item">
146+
No suggestions
147+
</div>
148+
} else
149+
{
150+
@foreach (Mention item in Items) {
151+
<div class="suggestion-item">
152+
<div class="suggestion-item-content">
153+
<div class="suggestion-text">
154+
<div class="suggestion-name">
155+
@item.Name
156+
</div>
157+
<div class="suggestion-title">
158+
@item.Email
159+
</div>
160+
</div>
161+
</div>
162+
</div>
163+
}
164+
}
165+
</div>
166+
167+
@code {
168+
[Parameter]
169+
public IEnumerable<Mention> Items { get; set; }
170+
}
171+
````
172+
````cs Mention.cs
173+
public class Mention
174+
{
175+
public string Id { get; set; } = string.Empty;
176+
public string Name { get; set; } = string.Empty;
177+
public string Email { get; set; } = string.Empty;
178+
}
179+
````
180+
````js Javascript
181+
import { addMentionNodes, addTagNodes, getMentionsPlugin } from 'prosemirror-mentions';
182+
183+
let _dotnetRef;
184+
window.initializeMentions = (dotnetRef) => {
185+
_dotnetRef = dotnetRef;
186+
}
187+
188+
/**
189+
* IMPORTANT: outer div's "suggestion-item-list" class is mandatory. The plugin uses this class for querying.
190+
* IMPORTANT: inner div's "suggestion-item" class is mandatory too for the same reasons
191+
*/
192+
var getMentionSuggestionsHTML = items => '<div class="suggestion-item-list">' +
193+
items.map(i => '<div class="suggestion-item">' + i.name + '</div>').join('') +
194+
'</div>';
195+
196+
/**
197+
* IMPORTANT: outer div's "suggestion-item-list" class is mandatory. The plugin uses this class for querying.
198+
* IMPORTANT: inner div's "suggestion-item" class is mandatory too for the same reasons
199+
*/
200+
var getTagSuggestionsHTML = items => '<div class="suggestion-item-list">' +
201+
items.map(i => '<div class="suggestion-item">' + i.tag + '</div>').join('') +
202+
'</div>';
203+
204+
let mentionSuggestionsHTML = null;
205+
206+
var mentionPlugin = getMentionsPlugin({
207+
getSuggestions: (type, text, done) => {
208+
setTimeout(async () => {
209+
if (type === 'mention') {
210+
try {
211+
const suggestions = await _dotnetRef.invokeMethodAsync('GetMentionSuggestionsAsync', text);
212+
mentionSuggestionsHTML = await _dotnetRef.invokeMethodAsync('GetMentionSuggestionsHTML', suggestions);
213+
done(suggestions);
214+
} catch (error) {
215+
console.error('Error getting suggestions:', error);
216+
done([]);
217+
}
218+
} else {
219+
// pass dummy tag suggestions
220+
done([{ tag: 'WikiLeaks' }, { tag: 'NetNeutrality' }])
221+
}
222+
}, 0);
223+
},
224+
getSuggestionsHTML: (items, type) => {
225+
if (type === 'mention') {
226+
return mentionSuggestionsHTML;
227+
} else if (type === 'tag') {
228+
return getTagSuggestionsHTML(items)
229+
}
230+
}
231+
});
232+
233+
window.pluginsProvider = (args) => {
234+
const schema = args.getSchema();
235+
236+
return [mentionPlugin, ...args.getPlugins(schema)];
237+
}
238+
239+
window.schemaProvider = (args) => {
240+
const schema = args.getSchema();
241+
const Schema = args.ProseMirror.Schema;
242+
const nodes = addTagNodes(addMentionNodes(schema.spec.nodes));
243+
const mentionsSchema = new Schema({
244+
nodes: nodes,
245+
marks: schema.spec.marks
246+
});
247+
248+
return mentionsSchema;
249+
}
250+
251+
````
252+
253+
## See Also
254+
255+
* [Editor Schema](slug:editor-prosemirror-schema-overview)
256+
* [Editor Plugins](slug:editor-prosemirror-plugins)
257+
* [ProseMirror Documentation](https://prosemirror.net/docs/ref)
258+
* [proseMirror-mentions](https://github.com/joelewis/prosemirror-mentions)

0 commit comments

Comments
 (0)