Building a Grammar Checker Plugin for TinyMCE Editor: A React and Python Journey

Senura Vihan Jayadeva
7 min readJul 14, 2023

Hello guys today I’m going to show you how to implement a simple project to check grammar and spelling mistakes with the help of language_tool_python which is a Python wrapper for LanguageTool. LanguageTool is open-source grammar tool, also known as the spellchecker for OpenOffice. This library allows you to detect grammar errors and spelling mistakes.

In this project backend is built using the Django framework, while the frontend uses React.

First you will need to create a Django application with a REST API endpoint that receives text input and returns the corrected text.

Here’s a step-by-step guide to implementing this functionality:

Set up a Django project and create a new Django app within it. You can use the following commands:

$ django-admin startproject grammar_checker
$ cd grammar_checker
$ python manage.py startapp api

Install the language-tool-python library using pip:

$ pip install language-tool-python

Create a Django REST API view to handle the grammar correction. In the api/views.py file, add the following code:

import language_tool_python
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

class GrammarCorrectionView(APIView):
def post(self, request):
text = request.data.get('text')

# Perform grammar correction
language_tool = language_tool_python.LanguageTool('en-US', config={'maxSpellingSuggestions': 1})
#text = 'A sentence with a error in the Hitchhiker’s Guide tot he Galaxy'
matches = language_tool.check(text)
correctText = language_tool.correct(text)

for mistakes in matches:
print(mistakes)

# Return the corrected text in the response
response_data = {
'text': text,
'corrected_text': correctText,
'matches': matches
}
return Response(response_data, status=status.HTTP_200_OK)

Configure the URL routing. In the grammar_checker/urls.py file, add the following code:

from django.urls import path
from api.views import GrammarCorrectionView

urlpatterns = [
path('api/grammar-correction/', GrammarCorrectionView.as_view(), name='grammar_correction'),
]

Okay all set for the backend api. You can run the Django development server using below command

$ python manage.py runserver

For now we can test the grammar correction functionality using Postman or any other API testing tool. Send a POST request to http://localhost:8000/api/grammar-correction/ with the JSON payload:

{
"text": "Your input text goes here."
}

Now you should receive a response with the corrected text and other details.

Right next we can create a React app using the below commad

npx create-react-app tinymce-editor-integration

Since we are using TinyMCE Editor you have to install the tinymce-react package and save it to your package.json with --save.

npm install --save @tinymce/tinymce-react

Now using a text editor ( VS Code), open ./src/App.js and replace the contents with:

 import React, { useRef } from 'react';
import { Editor } from '@tinymce/tinymce-react';

export default function App() {
const editorRef = useRef(null);

return (
<>
<Editor
onInit={(evt, editor) => editorRef.current = editor}
initialValue="<p>This is the initial content of the editor.</p>"
init={{
height: 500,
menubar: true,
plugins: [
'image'
],
toolbar: 'image',
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
}}
/>
</>
);
}

Now, I need to develop a plugin for the text editor. The plugin should allow users to select content within the editor and then click on an icon located in the menu bar or toolbar. This action will trigger a call to our backend API, which will check for grammar and spelling mistakes. Additionally, I would like to create a user-friendly interface that displays a modal showing the identified errors and providing suggestions. When the user clicks on the ‘Correct’ or ‘Apply Suggestions’ button, the incorrect selected content should be replaced with the corrected version.

Okay I will do some changes to Editor in App.js.

      <Editor
apiKey="your-api-key"
onInit={handleEditorInit}
initialValue="<p>This is the initial content of the editor.</p>"
init={{
height: 500,
menubar: true,
plugins: ["image"],
toolbar: "image | GrammarChecker",
setup: (editor) => {
editor.ui.registry.addButton("GrammarChecker", {
text: "Grammar Checker",
icon: "highlight-bg-color",
tooltip:
"Highlight a prompt and click this button to query ChatGPT",
enabled: true,
onAction: async () => {
const selection = editor.selection.getContent();

if (selection !== "") {
const data = {
text: selection,
};
const response = await fetch(
"http://127.0.0.1:8000/api/grammar-correction/",
{
method: "POST", // *GET, POST, PUT, DELETE, etc.
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
}
);
response.json().then((response) => {
let correctedText = response.corrected_text;
let matches = response.matches;
let grammarMistakes = [];
for (let match of matches) {
let mistakes = [];

let incorrectText = match[4];
// Calculate the start and end positions for the span element
let start = match[3];
let end = match[3] + match[6];

// Rearrange the incorrectText with the span element
let rearrangedText =
incorrectText.substring(0, start) +
`<span class="incorrecttext">` +
incorrectText.substring(start, end) +
"</span>" +
incorrectText.substring(end);

mistakes.push(rearrangedText);
mistakes.push(`<b>Error : </b> ` + match[8]);
mistakes.push(`<b>${match[1]}</b> `);
mistakes.push(
`<b>Suggestions : </b> <span class="suggestions">` +
match[2].slice(0, 2) +
"</span>"
);

grammarMistakes.push(mistakes);
}

openPopupGrammarChecker(
grammarMistakes,
editor,
correctedText,
matches.length
);
});
} else {
alert("Please select a sentence");
}
},
});
},
}}
/>

In TinyMCE, the setup function is a configuration option that allows you to customize and extend the editor's behavior during initialization. It provides a way to interact with the editor instance and modify its settings, toolbar, buttons, and more.

The setup function takes a callback function as its argument, which is executed when the editor is being set up. Inside this callback function, you can access the editor instance and use various methods and properties to customize its functionality.

So as you can see in the above code it defines a custom button called “Grammar Checker” which will call our API to check the grammar mistakes. Here I can get the selected content using editor.selection.getContent().

After some implementation I call openPopupGrammarChecker method. This function is responsible for displaying a popup with the grammar mistakes and additional information.

I created a file called grammarchecker under Utils folder and implemented the openPopupGrammarChecker method as below.

import "./grammerchecker.css";

export function openPopupGrammarChecker(
grammarMistakes,
editor,
correctedText,
count
) {
// Create the popup element
let popup = document.createElement("div");
popup.id = "popup-grammar-checker-panel";
popup.className = "popup-grammar-checker";

// Create the header for count
let countText = document.createElement("h3");
countText.className = "popup-grammar-checker-header";
if (count > 0) {
countText.innerHTML = `${count} mistakes found`;
} else {
countText.innerHTML = `No mistakes found`;
}
// Append the paragraphs to the popup element
popup.appendChild(countText);

grammarMistakes.forEach((mistakes) => {
let divGrid = document.createElement("div");
divGrid.className = "div-grid";
mistakes.forEach((mistake) => {
// Create the paragraphs
let paragraph = document.createElement("p");
paragraph.innerHTML = mistake;
// Append the paragraphs to the popup element
divGrid.appendChild(paragraph);
});

popup.appendChild(divGrid);
});

if (count > 0) {
// Create the correction grammar button element
let correctionButton = document.createElement("button");
correctionButton.className = "grammar-correction-button";
correctionButton.innerHTML = "Apply Suggestions";
correctionButton.onclick = function () {
editor.selection.setContent(correctedText);
popup.remove();
return false;
};

// Append the re correct button element to the popup
popup.appendChild(correctionButton);
}

// Create the close button element
let closeButton = document.createElement("button");
closeButton.className = "grammar-correction-close-button";
closeButton.innerHTML = "Close";
closeButton.onclick = function () {
popup.remove();
return false;
};
// Append the close button element to the popup
popup.appendChild(closeButton);
// Append the popup element to the body
document.body.appendChild(popup);

// Close the popup when clicked outside
document.addEventListener("click", function (event) {
if (!popup.contains(event.target) && popup.parentNode) {
popup.parentNode.removeChild(popup);
}
});
}

Here is the css file for Modal which shows grammar errors.

.incorrecttext {
background-color: #ee8888; /* Replace #ff0000 with your desired background color */
padding: 4px;
border-radius: 5%;
}

.suggestions {
background-color: #93e483; /* Replace #ff0000 with your desired background color */
padding: 4px;
border-radius: 5%;
}

.popup-grammar-checker {
position: absolute;
background-color: #d6d5d5;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 700px;
height: auto;
max-height: 80vh;
border: 1px solid #d6d5d5;
border-radius: 3%;
padding: 20px;
z-index: 9999;
overflow: auto;
}

.div-grid {
background-color: #ffffff;
padding: 10px;
margin: 10px;
}

.popup-grammar-checker-header {
margin: 10px;
}

.grammar-correction-button {
margin: 10px;
padding: 10px;
width: 180px;
background-color: rgb(29, 189, 1);
color: #fff;
font-size: medium;
font-weight: 500;
border-radius: 5%;
border: none;
}

.grammar-correction-close-button {
margin: 10px;
padding: 10px;
width: 100px;
background-color: rgb(201, 64, 30);
color: #fff;
font-size: medium;
font-weight: 500;
border-radius: 5%;
border: none;
}

Mmm all good. You can start the react application using npm start command.

Give some sentence with grammar or spelling mistakes. Then click on Grammar Check button and see the results.

Github Link : https://github.com/senuravihanjayadeva/Grammar-Checker-React-Django

Hope you learnt something today. Thank you.

--

--

Senura Vihan Jayadeva

Software Engineering undergraduate of Sri Lanka Institute of Information Technology | Physical Science Undergraduate of University of Sri Jayewardenepura