Skip to content

Instantly share code, notes, and snippets.

@sharapeco
Last active April 10, 2017 05:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sharapeco/aca28dad498188d818b8f421f751ffc8 to your computer and use it in GitHub Desktop.
Save sharapeco/aca28dad498188d818b8f421f751ffc8 to your computer and use it in GitHub Desktop.
wysiwygetize (by Spharian) readable code
jQuery.fn.wysxiwygetize = function(aOptions) {
var selection;
var $self = this,
$lastFocusedTextarea,
$lastFocusedEditor,
isFocused = false,
scrollTop,
fullScreenEditorId,
tabIndex = 0,
hasTabInput = false,
isMouseUp = true,
isSourceMode = false,
$document = $(document),
$window = $(window),
$btnsList = $(".buttons-list");
var $fullscreenContainer = $("#fullscreen-container");
var copyStyleList = ["border-top-width", "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", "box-shadow", "color", "font-family", "font-size", "background-color"],
editorStyles = $self.css(copyStyleList),
textareaStyles = $(".styled-output").eq(0).prev().focus().css(copyStyleList);
function getSelection() {
if (window.getSelection) {
return window.getSelection();
} else if (document.selection.createRange) {
return document.selection.createRange();
}
return null;
}
function updateSelectionHandler() {
if (!isFocused) {
return;
}
setTimeout(function() {
var rect;
selection = getSelection();
if (!selection) {
return;
}
$btnsList.show();
if (selection.getRangeAt) {
rect = selection.getRangeAt(0).getClientRects()[0];
} else {
rect = selection.getClientRects()[0];
}
if (rect) {
$btnsList.css({
top: rect.bottom + 10,
left: rect.left
})
}
if (!selection.getRangeAt(0).collapsed || selection.text) {
$btnsList.show();
} else {
$btnsList.hide();
}
})
}
function resizeHandler() {
$(".styled-output").each(function() {
var $editor = $(this),
$textarea = $editor.prev();
$editor.css({
width: $textarea.outerWidth(),
height: $textarea.outerHeight(),
top: 0,
left: 0
})
})
}
function exitFullscreen() {
var $wrapper = $("[data-wysigetize-item=" + fullScreenEditorId + "]"),
val = $fullscreenContainer.find("textarea").val(),
html = $fullscreenContainer.find(".styled-output").html();
$wrapper.find("textarea").val(val);
$wrapper.find(".styled-output").html(html);
isSourceMode ? $(".viewsource-btn").addClass(options.viewsource.activeClass) : $(".viewsource-btn").removeClass(options.viewsource.activeClass);
$fullscreenContainer.stop(false, true).fadeOut(function() {
$fullscreenContainer.empty();
$(".wysigetize-textarea-wrapper").removeClass("fullscreen");
$("body, html").animate({
scrollTop: scrollTop
}, 200)
});
$("html, body").removeClass("no-wy-scroll")
}
function toggleBlockElement(tagName) {
selection.anchorNode.parentNode.localName === tagName ? document.execCommand("formatBlock", false, "<p>") : document.execCommand("formatBlock", false, "<" + tagName + ">");
$btnsList.hide();
}
var options = $.extend({
buttons: {
bold: "B",
italic: "I",
underline: "U",
link: "Link",
h1: "H1",
h2: "H2",
h3: "H3"
},
viewsource: {
display: true,
label: "View source",
"class": "viewsource-style",
activeClass: "active-btn"
},
fullscreen: {
display: true,
label: "Fullscreen",
exitLabel: "Quit fullscreen",
"class": "fullscreen-style"
},
style: "dark"
}, aOptions),
$body = $("body"),
$btnsList = $('<ul class="buttons-list"></ul>'),
$btns = $(".buttons-list button"),
editorCount = 0;
var toolbarBG, toolbarHoverBG, toolbarFG;
switch (options.style) {
case "dark":
toolbarBG = "#383838";
toolbarHoverBG = "#272626";
toolbarFG = "#fff";
break;
case "light":
toolbarBG = "#F7F7F7";
toolbarHoverBG = "#E1E1E1";
toolbarFG = "#252525";
}
$("html").addClass("wysiwygetize");
$body.append($btnsList);
$body.append('<div id="fullscreen-container"></div>');
$btnsList.css({
background: toolbarHoverBG,
borderRadius: "3px",
overflow: "hidden",
margin: 0,
padding: 0,
listStyle: "none",
display: "none",
position: "fixed",
zIndex: 20,
border: "1px solid #272626"
});
var btnsCount = 0;
$.each(options.buttons, function(btnValue, btnLabel) {
btnsCount++;
var n = 1;
btnsCount === 1 && (n = 0);
var $btn = $('<button data-tag="' + btnValue + '">' + btnLabel + "</button>");
$btn.css({
background: toolbarBG,
border: "none",
color: toolbarFG,
cursor: "pointer",
fontFamily: "Arial, sans-serif",
fontSize: "17px",
margin: 0,
outline: "none",
display: "block",
padding: "13px 20px"
});
switch (btnValue) {
case "bold":
$btn.css("font-weight", "bold");
break;
case "italic":
$btn.css("font-style", "italic");
break;
case "underline":
$btn.css("text-decoration", "underline")
}
$btnsList.append('<li style="float: left; margin: 0 0 0 ' + n + 'px">' + $btn[0].outerHTML + "</li>")
});
$btns.hover(function() {
$(this).css("background", toolbarHoverBG);
}, function() {
$btns.css("background", toolbarBG);
});
$self.each(function() {
var $textarea = $(this);
tabIndex++;
$textarea.css({
outline: "none",
resize: "none"
});
editorCount++;
$textarea.wrap('<div class="wysigetize-textarea-wrapper" data-wysigetize-item="' + editorCount + '">');
options.viewsource.display && options.fullscreen.display ? $textarea.after('<ul class="wysigetize-controls" style="top: -' + $textarea.css("margin-bottom") + '"><li class="fullscreen-btn ' + options.fullscreen.class + '">' + options.fullscreen.label + '</li><li class="viewsource-btn ' + options.viewsource.class + '">' + options.viewsource.label + "</li></ul>") : options.viewsource.display ? $textarea.after('<ul class="wysigetize-controls" style="top: -' + $textarea.css("margin-bottom") + '"><li class="viewsource ' + options.viewsource.class + '">' + options.viewsource.label + "</li></ul>") : options.fullscreen.display && $textarea.after('<ul class="wysigetize-controls" style="top: -' + $textarea.css("margin-bottom") + '"><li class="fullscreen ' + options.fullscreen.class + '">' + options.fullscreen.label + "</li></ul>");
$textarea.after($('<div class="styled-output" contenteditable="true" tabindex="' + tabIndex + '">').css({
backgroundColor: $textarea.css("background-color"),
borderRadius: $textarea.css("border-radius"),
position: "absolute",
top: 0,
left: 0,
border: $textarea.css("border"),
width: $textarea.outerWidth(),
height: $textarea.outerHeight(),
boxShadow: $textarea.css("box-shadow"),
marginTop: $textarea.css("margin-top"),
marginLeft: $textarea.css("margin-left"),
padding: $textarea.css("padding-top"),
fontFamily: $textarea.css("font-family"),
fontSize: $textarea.css("font-size"),
lineHeight: $textarea.css("line-height"),
outline: "none",
overflowX: "hidden",
borderColor: $textarea.css("border-top-color"),
borderWidth: $textarea.css("border-top-width"),
border: $textarea.css("border-top-width") + " solid " + $textarea.css("border-top-color"),
wordWrap: "break-word"
}))
});
var $textareaWrapper = $(".wysigetize-textarea-wrapper");
$textareaWrapper.width($textareaWrapper.find("textarea").outerWidth());
// もとの textarea に内容を反映する
$document.on("keyup", ".styled-output", function() {
var $editor = $(this);
$editor.prev().val($editor.html());
});
// Tab フォーカスしたときに内容を選択状態にする
$document.on("focus", ".styled-output", function() {
isFocused = true;
var $editor = $(this), selection, range;
if (hasTabInput) {
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
} else if (window.getSelection) {
selection = window.getSelection(),
range = document.createRange();
range.selectNodeContents($editor[0]);
selection.removeAllRanges();
selection.addRange(range);
}
hasTabInput = false;
}
});
$document.on("blur", ".styled-output", function() {
var $editor = $(this);
isFocused = false;
$lastFocusedEditor = $editor.first();
$lastFocusedTextarea = $editor.first().prev();
if (isMouseUp) {
$editor.css(editorStyles);
$btnsList.hide();
}
});
$(".styled-output").each(function() {
var $editor = $(this);
$editor.html($editor.prev().text())
});
$(".styled-output").eq(0).prev().blur();
$(".styled-output").focus(function() {
$(this).css(textareaStyles)
});
$document.mouseup(function() {
updateSelectionHandler()
});
$document.keyup(function() {
updateSelectionHandler();
});
$document.click(function() {
setTimeout(function() {
resizeHandler();
});
});
$document.keydown(function(e) {
if (e.which === 27) { // ESC
exitFullscreen();
}
});
$window.keydown(function(e) {
if (e.which === 9) { // TAB
hasTabInput = true;
}
});
$window.resize(function() {
resizeHandler()
});
$window.scroll(function() {
if (selection && selection.rangeCount !== 0) {
var rect = selection.getRangeAt(0).getClientRects()[0];
if (rect) {
var left = rect.left,
bottom = rect.bottom;
$btnsList.css({
top: bottom + 10,
left: left
})
}
}
});
$(".styled-output").scroll(function() {
$btnsList.hide()
});
$document.on("mousedown", ".buttons-list button[data-tag]", function() {
isMouseUp = false;
});
$document.on("mouseup", ".buttons-list button[data-tag]", function() {
isMouseUp = true;
});
$document.on("click", ".buttons-list button[data-tag]", function() {
$($lastFocusedEditor).focus();
switch ($(this).data("tag")) {
case "bold":
document.execCommand("bold", false, null);
break;
case "italic":
document.execCommand("italic", false, null);
break;
case "underline":
document.execCommand("underline", false, null);
break;
case "link":
var linkURL = prompt("Specify a link");
linkURL && document.execCommand("createLink", false, linkURL);
break;
case "h1":
toggleBlockElement("h1");
break;
case "h2":
toggleBlockElement("h2");
break;
case "h3":
toggleBlockElement("h3")
}
var html = $($lastFocusedEditor).html();
$($lastFocusedTextarea).val(html);
});
$document.on("click", ".viewsource-btn", function() {
var $btn = $(this),
$editor = $btn.parents(".wysigetize-textarea-wrapper").find(".styled-output");
$btn.toggleClass(options.viewsource.activeClass);
$editor.toggle();
isSourceMode = !isSourceMode;
$(".styled-output").each(function() {
var $editor = $(this);
$editor.html($editor.prev().val())
})
});
$document.on("click", ".fullscreen-btn", function() {
var $clonedWrapper, $origWrapper, scrollTop, html;
if ($fullscreenContainer.children().length === 0) {
scrollTop = $document.scrollTop();
$origWrapper = $(this).parents(".wysigetize-textarea-wrapper");
html = $origWrapper.find("textarea").val();
$clonedWrapper = $origWrapper.clone();
fullScreenEditorId = $clonedWrapper.data("wysigetize-item");
$fullscreenContainer.append($clonedWrapper);
$fullscreenContainer.fadeIn(function() {
$("html, body").addClass("no-wy-scroll")
});
$clonedWrapper.addClass("fullscreen").removeAttr("data-wysigetize-item");
$clonedWrapper.find(".fullscreen-btn").text(options.fullscreen.exitLabel);
$clonedWrapper.find("textarea").val(html);
} else {
exitFullscreen();
}
})
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment