Created
March 8, 2020 21:12
-
-
Save KevinBatdorf/5742e4df2682f00f69fc2d1e542ad3cd to your computer and use it in GitHub Desktop.
Adds a live preview window for support on WP.org
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name WP Support Live Preview | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description try to take over the world! | |
// @author You | |
// @match https://wordpress.org/support/* | |
// @grant none | |
// ==/UserScript== | |
(function($) { | |
'use strict'; | |
// TODO: Handle @username | |
// TODO: extract all styles to here instead of relying on BBPress | |
document.head.insertAdjacentHTML("beforeend", ` | |
<style> | |
.wp-editor-container { | |
border: 0 !important; | |
} | |
#qt_bbp_reply_content_toolbar, | |
#qt_bbp_topic_content_toolbar { | |
border-left: 1px solid #ccc !important; | |
border-right: 1px solid #ccc !important; | |
} | |
#bbp-live-preview ul li { | |
list-style-type: square !important; | |
margin-left: 10px !important; | |
} | |
#bbp-live-preview code { | |
font-size: 14.4px; | |
} | |
</style> | |
`) | |
const setActiveState = function(element) { | |
element.style.color = 'black' | |
} | |
const setInactiveState = function(element) { | |
element.style.color = '#999999' | |
} | |
const toHtmlEntities = function(string, whiteList = []) { | |
let tagOpen = false | |
return string.replace(/[\u00A0-\u9999<>\&]/gim, function(match, offset, string) { | |
const itemFound = whiteList.some(item => { | |
if (tagOpen) { | |
//console.log('tag closed') | |
tagOpen = false | |
return true | |
} | |
if (string.substring(offset + 1, offset + 1 + item.length) == item) { | |
//console.log('tag opened', string.substring(offset + 1, offset + 1 + item.length), item) | |
tagOpen = true | |
return true | |
} | |
if (string.substring(offset + 1, offset + 2 + item.length) == '/' + item) { | |
//console.log('tag opened', string.substring(offset + 1, offset + 2 + item.length), item) | |
tagOpen = true | |
return true | |
} | |
if (string.substring(offset - item.length, offset) == item) return true | |
return false | |
}) | |
return itemFound ? match : '&#' + match.charCodeAt(0) + ';'; | |
}) | |
} | |
const formatUrlAndEmail = function(string) { | |
// https://stackoverflow.com/questions/22539616/how-to-wrap-urls-with-anchor-tag-and-ignore-urls-that-already-have-anchor-tags-i | |
const url = /(http|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/gi; | |
const mailto = /(mailto:[a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/gi; | |
return $('<div>').html(string).contents().each(function () { | |
console.log($(this)) | |
if (this.nodeType === 3) { // if node is a textNode | |
$(this).replaceWith(function () { | |
return this.nodeValue.replace(url, function (m) { | |
return '<a href="' + m + '" rel="nofollow">' + m + '</a>'; | |
}).replace(mailto, function(m) { | |
return '<a href="' + m + '" rel="nofollow" target="_blank">' + m + '</a>'; | |
}); | |
}) | |
} | |
}).end().html(); | |
} | |
const Previewer = function(wpEditor) { | |
this.domElement = document.createElement('div') | |
this.domElement.id = 'bbp-live-preview' | |
// Add styles | |
this.domElement.style.border = '1px solid #e5e5e5' | |
//this.domElement.style.borderTop = '0' | |
this.domElement.style.background = 'white' | |
this.domElement.style.padding = '0 8px' | |
this.domElement.style.margin = '0' | |
this.domElement.style.minHeight = '267px' | |
this.domElement.style.fontSize = '0.8rem' | |
this.domElement.style.display = 'none' | |
this.domElement.style.lineHeight = '21.6px' | |
// Add class to inherit some styling | |
this.domElement.classList.add('bbp-replies') | |
this.domElement.classList.add('bbp-reply-content') | |
// Add container below textarea | |
wpEditor.parentNode.appendChild(this.domElement) | |
// Set initial value | |
this.value = 'Nothing to preview' | |
this.domElement.innerHTML = this.value | |
// Create buttons | |
const buttons = document.createElement('div') | |
buttons.style.background = '#f0f0f0' | |
buttons.style.padding = '0.5rem 0 0' | |
buttons.style.border = '1px solid #ccc' | |
// Write button | |
const writeButton = document.createElement('button') | |
writeButton.innerText = "Write" | |
writeButton.style.background = '#f0f0f0' | |
writeButton.style.border = '0' | |
writeButton.style.padding = '0' | |
writeButton.style.margin = '0 1rem 5px' | |
setActiveState(writeButton) | |
buttons.appendChild(writeButton) | |
const previewButton = document.createElement('button') | |
previewButton.innerText = 'Preview' | |
previewButton.style.background = '#f0f0f0' | |
previewButton.style.border = '0' | |
previewButton.style.padding = '0' | |
previewButton.style.margin = '0 0 5px' | |
setInactiveState(previewButton) | |
buttons.appendChild(previewButton) | |
// Add buttons | |
wpEditor.parentNode.parentNode.insertBefore(buttons, wpEditor.parentNode) | |
wpEditor.parentNode.style.position = 'relative' | |
// Add button events | |
writeButton.addEventListener('click', event => { | |
event.preventDefault() | |
setInactiveState(previewButton) | |
setActiveState(writeButton) | |
this.domElement.style.display = 'none' | |
wpEditor.style.display = 'block' | |
document.querySelector('.quicktags-toolbar').style.display = 'block' | |
}) | |
previewButton.addEventListener('click', event => { | |
event.preventDefault() | |
Preview.update(wpEditor.value) | |
setInactiveState(writeButton) | |
setActiveState(previewButton) | |
this.domElement.style.display = 'block' | |
wpEditor.style.display = 'none' | |
document.querySelector('.quicktags-toolbar').style.display = 'none' | |
}) | |
} | |
Previewer.prototype.update = function(markup) { | |
if (markup === this.value) return | |
this.value = markup | |
// Formatting | |
let formatted = formatUrlAndEmail(this.value) | |
// For code and pre, get all the matches first. This preserved whitespace | |
let matches = formatted.matchAll(/[\r\n]\`\s*([^\`]*)\s*\`/g) | |
for (const match of matches) { | |
formatted = formatted.replace(match[0], '<pre><code>{{MATCH}}</code></pre>') | |
formatted = formatted.replace('{{MATCH}}', match[1]) | |
} | |
matches = formatted.matchAll(/\`\s*([^\`]*)\s*\`/g); | |
for (const match of matches) { | |
formatted = formatted.replace(match[0], '<code>{{MATCH}}</code>') | |
formatted = formatted.replace('{{MATCH}}', match[1]) | |
} | |
formatted = toHtmlEntities(formatted, ['pre', 'ul', 'li']) | |
formatted = formatted.replace(/(?:\r\n|\r|\n)/g, '<br>') | |
this.domElement.innerHTML = this.value.length ? formatted : 'Nothing to preview' | |
// Wrap all unwrapped tags with a paragraph tag (uses jQuery) | |
$(this.domElement).contents().filter(function() { | |
if (!JSON.stringify(this).length) return false | |
return 3 === this.nodeType | |
}).wrap('<p style="margin:1em 0"></p>') | |
$(this.domElement).children('a').wrap('<p style="margin:1em 0"></p>') | |
$(this.domElement).find('> *').not('pre').find('br').remove() | |
$(this.domElement).find('> br').remove() | |
// Encode everything then put back | |
this.domElement.innerHTML = toHtmlEntities(this.domElement.innerHTML, ['gt', 'lt']) | |
this.domElement.innerHTML = this.domElement.innerText | |
} | |
const wpEditor = document.querySelector('.bbp-the-content') | |
const Preview = new Previewer(wpEditor) | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some notes.