Skip to content

Instantly share code, notes, and snippets.

@nazarbek7
Created April 6, 2020 09:19
Show Gist options
  • Save nazarbek7/e0e08acd134859b05bbe9f44c335afde to your computer and use it in GitHub Desktop.
Save nazarbek7/e0e08acd134859b05bbe9f44c335afde to your computer and use it in GitHub Desktop.
Simple WYSIWYG (contenteditable) + paste from MS Word
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<body>
<div class="container">
<h2>Simple WYSIWYG</h2>
<div class="editor-toolbar">
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="headingMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Heading
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="headingMenu">
<li><a href="javascript:void(0)" onclick="runCommand(this, 'formatBlock', 'h1')" class="h1">Heading 1</a></li>
<li><a href="javascript:void(0)" onclick="runCommand(this, 'formatBlock', 'h2')" class="h2">Heading 2</a></li>
<li><a href="javascript:void(0)" onclick="runCommand(this, 'formatBlock', 'h3')" class="h3">Heading 3</a></li>
<li><a href="javascript:void(0)" onclick="runCommand(this, 'formatBlock', 'h4')" class="h4">Heading 4</a></li>
</ul>
</div>
<a href="javascript:void(0)" role="button" class="toolbar-btn unselectable" onclick="runCommand(this, 'bold', null)" unselectable="on"><i class="fa fa-bold"></i></a>
<a href="javascript:void(0)" role="button" class="toolbar-btn" onclick="runCommand(this, 'italic', null)"><i class="fa fa-italic"></i></a>
<a href="javascript:void(0)" role="button" onclick="runCommand(this, 'underline', null)"><i class="fa fa-underline"></i></a>
<a href="javascript:void(0)" role="button" onclick="runCommand(this, 'indent', null)"><i class="fa fa-indent"></i></a>
<a href="javascript:void(0)" role="button" onclick="runCommand(this, 'insertUnorderedList', null)"><i class="fa fa-list-ul"></i></a>
<a href="javascript:void(0)" role="button" onclick="runCommand(this, 'insertOrderedList', null)"><i class="fa fa-list-ol"></i></a>
<a href="javascript:void(0)" role="button" onclick="runCommand(this, 'createLink', null)"><i class="fa fa-link"></i></a>
<a href="javascript:void(0)" role="button" onclick="runCommand(this, 'unlink', null)"><i class="fa fa-unlink"></i></a>
<a href="javascript:void(0)" role="button" onclick="runCommand(this, 'redo', null)"><i class="fa fa-repeat"></i></a>
<a href="javascript:void(0)" role="button" onclick="runCommand(this, 'undo', null)"><i class="fa fa-undo"></i></a>
</div>
<div id="editor" contenteditable="true"></div>
<textarea name="messageText" id="messageText" class="hidden"></textarea>
<div class="row">
<div class="col-sm-2">
<button id="submit" class="btn btn-default btn-block" type="submit">Submit</button>
</div>
</div>
</div>
</body>
// Move caret back to
function placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
// Clean HTML tags using sanitize-html
function cleanHtml() {
let value = $("#editor").html();
let clean = sanitizeHtml(value, {
allowedTags: ['div', 'blockquote', 'b', 'strong', 'i', 'em', 'ul', 'ol', 'li'],
allowedAttributes: {
'blockquote': ['style']
}
});
let cleanValue = clean.trim();
setContent();
}
// Paste from MS Word *CREDIT: https://gist.github.com/sbrin/6801034
(function($) {
$.fn.msword_html_filter = function(options) {
let settings = $.extend( {}, options);
function word_filter(editor){
let content = editor.html();
// Word comments like conditional comments etc
content = content.replace(/<!--[\s\S]+?-->/gi, '');
// Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
// MS Office namespaced tags, and a few other tags
content = content.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '');
// Convert <s> into <strike> for line-though
content = content.replace(/<(\/?)s>/gi, "<$1strike>");
// Replace nbsp entites to char since it's easier to handle
//content = content.replace(/&nbsp;/gi, "\u00a0");
content = content.replace(/&nbsp;/gi, ' ');
// Convert <span style="mso-spacerun:yes">___</span> to string of alternating
// breaking/non-breaking spaces of same length
content = content.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, function(str, spaces) {
return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : '';
});
editor.html(content);
// Parse out list indent level for lists
$('p', editor).each(function(){
let str = $(this).attr('style');
let matches = /mso-list:\w+ \w+([0-9]+)/.exec(str);
if (matches) {
$(this).data('_listLevel', parseInt(matches[1], 10));
}
});
// Parse Lists
let last_level=0;
let pnt = null;
$('p', editor).each(function(){
let cur_level = $(this).data('_listLevel');
if(cur_level != undefined){
let txt = $(this).text();
let list_tag = '<ul></ul>';
if (/^\s*\w+\./.test(txt)) {
let matches = /([0-9])\./.exec(txt);
if (matches) {
let start = parseInt(matches[1], 10);
list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>';
}else{
list_tag = '<ol></ol>';
}
}
if(cur_level>last_level){
if(last_level==0){
$(this).before(list_tag);
pnt = $(this).prev();
}else{
pnt = $(list_tag).appendTo(pnt);
}
}
if(cur_level<last_level){
for(let i=0; i<last_level-cur_level; i++){
pnt = pnt.parent();
}
}
$('span:first', this).remove();
pnt.append('<li>' + $(this).html().replace(/\d+\./g, '') + '</li>')
$('b:empty').remove();
$(this).remove();
last_level = cur_level;
}else{
last_level = 0;
}
})
$('[style]', editor).removeAttr('style');
$('[align]', editor).removeAttr('align');
$('span', editor).replaceWith(function() {return $(this).contents();});
$('span:empty', editor).remove();
$("[class^='Mso']", editor).removeAttr('class');
$('p:empty', editor).remove();
}
return this.each(function() {
let self = this;
$(self).on('keyup paste', function(){
setTimeout(function() {
let content = $(self).html();
(/class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i.test( content )) ? word_filter( $(self) ) : cleanHtml();
}, 400);
});
});
};
})( jQuery )
$(function(){
$('#editor').msword_html_filter();
})
function setContent() {
let value = $(this).html();
let el = $("#editor").get(0);
placeCaretAtEnd(el);
}
//### EVENTS/ACTIONS ###//
//execCommand(aCommandName, aShowDefaultUI, aValueArgument)
function runCommand(el, commandName, arg) {
if (commandName === "createLink") {
let argument = prompt("Insert link:");
document.execCommand(commandName, false, argument);
}else {
document.execCommand(commandName, false, arg);
}
$("#editor").focus();
return false;
}
// Capture wysiwyg val and assign to textarea val
// $("#editor").keyup(function() {
// let value = $(this).html();
// $("#messageText").val(value);
// });
// Show submitted data
$('#submit').click(function(e) {
e.preventDefault();
let content = $("#editor").html().trim();
alert("VALUE SUBMITTED: \n" + content);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/index.min.js"></script>
@grey: #e4e4e4;
@white: #fff;
@border: #cecece;
body {
font-family:"Open Sans", sans-serif;
background: @grey;
margin: 20px 0;
.h1 {
font-size: 2em;
}
.h2 {
font-size: 1.5em;
}
.h3 {
font-size: 1.17em;
}
.h4 {
font-size: 1em;
}
}
.editor-toolbar {
background: @white;
border: 1px solid @border;
margin: 0;
padding: 0;
a {
display: inline-block;
padding: 8px 12px;
color: #000;
&:hover {
background: @grey;
}
}
.active {
background: @grey;
}
.dropdown {
display: inline-block;
.btn-default {
border: none;
&:focus, &:hover {
background: none;
}
}
a {
margin: 0;
}
}
}
#editor {
resize: vertical;
overflow: auto;
line-height: 1.5;
background: @white;
border: 1px solid @border;
border-top: none;
min-height: 150px;
box-shadow: none;
padding: 8px 16px;
margin: 0 0 10px 0;
font-size: 14px;
&:focus{
outline: none;
}
}
// IE fix for toolbar buttons
unselectable {
-webkit-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment