In this tutorial, we will walk you through integrating the Summernote rich-text editor with Bootstrap 5. Summernote is a simple yet powerful WYSIWYG editor that makes content creation easy with its clean interface and feature-packed toolbar. By the end of this guide, you'll have a fully functional text editor in your Bootstrap 5 project.
Step 1: Setting up the HTML structure
Create a basic HTML file and include the necessary Bootstrap 5 and jQuery CDN links, along with Summernote's CSS and JS files.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap 5 demo</title>
<script type="text/javascript" src="//code.jquery.com/jquery-3.6.0.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link href="https://testing.amankhalsa.in/summernote_bs5/summernote-bs5.css" rel="stylesheet">
</head>
<body>
<testarea id="summernote">Hello Summernote bootstrap 5</testarea>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous">
</script>
<script src="https://testing.amankhalsa.in/summernote_bs5/summernote-bs5.js"></script>
<script>
$(document).ready(function() {
$('#summernote').summernote();
});
</script>
</body>
</html>
Step 2: Explaining the Code
jQuery: Summernote requires jQuery, so we use the jQuery 3.6.0 CDN.
Bootstrap 5: Added Bootstrap's CSS and JS to style the page and enable responsive design.
Summernote CSS & JS: These are necessary for the editor to work.
Initializing Summernote: The $('#summernote').summernote() method activates the editor, and you can customize it using various options like height, placeholder, and tabsize.
Step 3: Customizing Summernote
You can further customize Summernote by adding options like:
$('#summernote').summernote({
placeholder: 'Type your text here...',
height: 400,
toolbar: [
['style', ['bold', 'italic', 'underline', 'clear']],
['font', ['strikethrough', 'superscript', 'subscript']],
['fontsize', ['fontsize']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']]
]
});
Integrating Summernote with Bootstrap 5 is quick and easy, providing a powerful content editor for your web applications. Whether you’re building a blog, a CMS, or any content-heavy platform, Summernote gives you the flexibility to enhance user experience.
In laravel livewire how to Integrate sumernote:
Can use this js code
<script>
document.addEventListener('livewire:init', () => {
$('#content').summernote({
height: 300,
dialogsInBody: true,
toolbar: [
['style', ['style']],
['fontsize', ['fontsize']],
['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript',
'subscript', 'clear'
]],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['height', ['height']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen', 'codeview']],
['fontname', ['fontname']],
['misc', ['help']],
],
callbacks: {
onChange: function(contents) {
@this.set('content', contents); // Update Livewire property
}
}
});
// when edit record it will work to set data
Livewire.hook('component.init', ({
component,
cleanup
}) => {
Livewire.on('setSummernoteContent', (content) => {
if (event.detail && event.detail.content) {
const content = event.detail.content;
$('#content').summernote('code',
content); // Load new content into the editor
} else {
console.log("No content found in the event.");
}
});
})
Livewire.on('resetSummernoteContent', () => {
$('#content').summernote('reset');
$('#content').summernote('code', ''); // Clear the editor
});
});
</script>
Blade file Textarea
<div class="col-md-12">
<div class="form-group">
<label for="content">Description</label>
<div wire:ignore>
<textarea wire:model="content" id="content" class="form-control js-summernote" placeholder="Enter Blog Description"></textarea>
</div>
</div> @error('content') <span class="text-danger">{{ $message }}</span> @enderror
</div>
In livewire component can pass data to dispatch like this or get in js for set to editor :
$this->dispatch('setSummernoteContent', content: $this->content);
When you want to use editor in model can use like this
$('#create_Blog_model').on('shown.bs.modal', function() {
$('#description').summernote({
height: 300,
dialogsInBody: true,
toolbar: [
['style', ['style']],
['fontsize', ['fontsize']],
['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear' ]],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['height', ['height']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen', 'codeview']],
['fontname', ['fontname']],
['misc', ['help']],
],
callbacks: {
onChange: function(contents) {
@this.set('content', contents); // Update Livewire property
}
}
});
});
// when model will close can reset data of editor
jQuery('#create_Blog_model').on('hidden.bs.modal', function(e) { // do something...
console.log('Model hidden');
@this.resetModalData()
});
Code Highlighting:
Uses Highlight.js (hljs.highlightAll()) to apply syntax highlighting for code blocks.
Users can paste code in various languages, with an interactive popup prompting for the language and code snippet.
Custom Paste Options:
Overrides the paste event to give users a choice between pasting plain text or pasting as a code block.
Displays an elegant popup for paste options and automatically removes it when the user clicks outside.
Keyboard Shortcuts:
Supports keyboard shortcuts for code block manipulation:
Enter to insert new lines within code blocks.
Shift + Enter to exit code blocks.
Why Use This Setup?
Seamless Laravel Livewire Integration: Ensures real-time updates without page reloads.
Developer-Friendly: Supports embedding code snippets directly within the editor.
User-Centric: Provides intuitive formatting tools for both non-technical users and developers.
Highly Customizable: Adjust toolbars, themes, and callbacks to match your project’s needs.
< script >
document.addEventListener('livewire:init', () => {
hljs.highlightAll();
$('#content').summernote({
height: 300,
dialogsInBody: true,
focus: true,
codemirror: {
mode: 'text/html',
htmlMode: true,
lineNumbers: true,
lineWrapping: true,
tabSize: 2,
theme: 'monokai',
tabMode: 'indent'
},
toolbar: [
['style', ['style']],
['fontsize', ['fontsize']],
['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript',
'subscript', 'clear'
]],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['height', ['height']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen', 'codeview']],
['fontname', ['fontname']],
['misc', ['help']],
['insert', ['code']]
],
callbacks: {
onChange: function(contents) {
@this.set('content', contents);
highlightCodeBlocks();
},
onInit: function() {
document.querySelectorAll('a[data-value="pre"]').forEach((element) => {
element.addEventListener('click', function() {
const selectedLang = prompt(
'Enter the language (e.g., html, javascript, php, python):',
'html');
const codeSnippet = prompt('Enter your code:',
'Your code here...');
if (selectedLang && codeSnippet) {
const codeBlock =
`<pre><code class="language-${selectedLang}">${escapeHtml(codeSnippet)}</code></pre>`;
$('#content').summernote('editor.pasteHTML',
codeBlock);
highlightCodeBlocks();
}
});
});
},
onPaste: function(e) {
e.preventDefault();
let text = (e.originalEvent || e).clipboardData.getData('text/plain');
// Remove any existing paste popup
const existingPopup = document.getElementById('pasteOptionsPopup');
if (existingPopup) {
existingPopup.remove();
}
// Create a small popup within the Summernote editor
const editor = document.querySelector('.note-editable');
const pasteOptions = document.createElement('div');
pasteOptions.id = 'pasteOptionsPopup';
pasteOptions.style.cssText = `
position: absolute; top: 10px; left: 10px; background: #fff;
border: 1px solid #ccc; padding: 10px; z-index: 1000;
box-shadow: 0 2px 10px rgba(0,0,0,0.2); border-radius: 5px;
`;
pasteOptions.innerHTML = `
<p style="margin: 0 0 10px;">Paste as:</p>
<button id="pasteAsText" style="margin-right: 10px; padding: 5px 10px;">Text</button>
<button id="pasteAsCode" style="padding: 5px 10px;">Code</button>
`;
editor.appendChild(pasteOptions);
// Add event listeners for buttons
document.getElementById('pasteAsText').addEventListener('click', () => {
$('#content').summernote('pasteHTML', escapeHtml(text).replace(
/\n/g, '<br>'));
pasteOptions.remove();
});
document.getElementById('pasteAsCode').addEventListener('click', () => {
$('#content').summernote('pasteHTML',
`<pre><code>${escapeHtml(text)}</code></pre>`);
highlightCodeBlocks();
pasteOptions.remove();
});
// Auto-close popup if user clicks outside
document.addEventListener('click', function closePopup(event) {
if (!pasteOptions.contains(event.target)) {
pasteOptions.remove();
document.removeEventListener('click', closePopup);
}
}, {
once: true
});
},
onKeydown: function(e) {
const isInCodeBlock = isInsideCodeBlock();
if (isInCodeBlock && e.key === 'Enter') {
e.preventDefault();
document.execCommand('insertHTML', false, '\n');
}
// Exit code block with Shift + Enter
if (isInCodeBlock && e.shiftKey && e.key === 'Enter') {
e.preventDefault();
$('#content').summernote('pasteHTML', '<p><br></p>');
}
}
},
// Disable auto-paragraph and empty tag insertions
});
function highlightCodeBlocks() {
$('#content').siblings('.note-editor').find('pre code').each(function() {
hljs.highlightElement(this);
});
}
function escapeHtml(text) {
return text.replace(/[&<>"']/g, function(tag) {
const chars = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return chars[tag] || tag;
});
}
function isInsideCodeBlock() {
const selection = window.getSelection();
if (!selection.rangeCount) return false;
let node = selection.getRangeAt(0).startContainer;
while (node) {
if (node.nodeName === 'CODE' || node.nodeName === 'PRE') {
return true;
}
node = node.parentNode;
}
return false;
}
Livewire.hook('component.init', ({
component,
cleanup
}) => {
Livewire.on('setSummernoteContent', (content) => {
if (event.detail && event.detail.content) {
const content = event.detail.content;
$('#content').summernote('code',
content); // Load new content into the editor
highlightCodeBlocks();
} else {
console.log('No content found in the event.');
}
});
});
Livewire.on('resetSummernoteContent', () => {
$('#content').summernote('reset');
$('#content').summernote('code', '');
});
}); <
/script>
Can use this at top
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<div class="bg-image" style="background-image: url({{ asset('assets/media/various/promo-code.png') }});">
This setup is perfect for creating content management systems (CMS), blogs, or any web application requiring an interactive and responsive text editor.
For more customization options, visit Summernote's official GitHub repository.
Happy coding!