您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View html immediately
// ==UserScript== // @name CF-Html-Viewer // @version 0.3.2 // @description View html immediately // @author Iverycool // @namespace Cyberforum // @match https://www.cyberforum.ru/* // @grant none // ==/UserScript== (function() { const $ = (sec, node = document) => node.querySelector(sec); const $$ = (sec, node = document) => node.querySelectorAll(sec); const size = 1.4; // const isFF = ~navigator.userAgent.search(/firefox/i); const styles = { but: 'color: rgb(96, 96, 96); font-weight: normal; margin-left: 5px;', rightBut: 'color: rgb(96, 96, 96); font-weight: normal; float: right;', frame: 'display: block; width: 100%;', textarea: getStyleString({ 'position': 'absolute', 'margin': 0, 'padding': 0, 'border': 'transparent', 'background': 'transparent', 'color': 'transparent', 'font-family': 'Consolas,Monaco,\'Andale Mono\',\'Ubuntu Mono\',monospace', 'font-size': '1em', 'line-height': size + ' !important', 'white-space': 'pre', 'caret-color': 'black', 'outline': 'none', 'resize': 'none' }) } const sels = { codeFrame: 'div.codeframe', codePre: 'td.de1 > pre.de1', lnPre: 'td.ln > pre.de1' } document.addEventListener('DOMContentLoaded', () => { // debugger; init().then(() => $$('#posts div[id^="post_message_"]').forEach(register)); }); function init() { const prismStyle = createElem('link', document.head); prismStyle.rel = 'stylesheet'; prismStyle.href = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/themes/prism.min.css'; const myStyle = createElem('style', document.head); myStyle.innerHTML = 'pre.de1, pre.de1 * { line-height: ' + size + ' !important }' + ['html5', 'phphtml', 'css', 'javascript'].map(name => `table.${name} > tbody {background: #f5f2f0 !important}` ).join(''); const prismScript = createElem('script', document.head); prismScript.type = 'text/javascript'; prismScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/prism.min.js'; return new Promise(res => prismScript.onload = res); } function register(post) { const viewer = new Viewer(); const htmlNodes = $$('table.html5, table.phphtml', post); const cssNodes = $$('table.css', post); const jsNodes = $$('table.javascript', post); htmlNodes.forEach(htmlNode => { makeViewerLink(viewer, htmlNode); makeEditableLink(viewer, htmlNode, 'html'); stylize(htmlNode, 'html'); }); cssNodes.forEach(cssNode => { makeUseLink(viewer, cssNode, 'style'); makeEditableLink(viewer, cssNode, 'css'); stylize(cssNode, 'css'); }); jsNodes.forEach(jsNode => { createLink({ parentNode: $('.head', jsNode), style: styles.but, text: 'Выполнить', onClick: () => eval($(sels.codePre, jsNode).innerText) }); makeUseLink(viewer, jsNode, 'script'); makeEditableLink(viewer, jsNode, 'js'); stylize(jsNode, 'js'); }); } class Viewer { style = []; script = []; mounted = false; create(parent, htmlCode, onClose = () => {}) { this.clean(); this.mounted = true; this.html = htmlCode.replace(/ /g, ''); this.container = createElem('tfoot', parent); createLink({ parentNode: this.container, style: styles.but, text: 'Открыть в новом окне', onClick: () => this.openInNewWin() }); createLink({ parentNode: this.container, style: styles.but, text: 'Скачать страницу', onClick: () => this.download() }) createLink({ parentNode: this.container, style: styles.rightBut, text: 'Закрыть', onClick: () => (this.clean(), onClose()) }); this.iframe = createElem('iframe', this.container); this.iframe.style = styles.frame; this.update(); } clean() { if (this.container) this.container.remove(); this.iframe = null; this.doc = null; this.html = ''; this.mounted = false; } add(id, code, type) { if (!this.mounted) return false; const ind = this[type].findIndex(el => el[0] == id); if (~ind) { this[type].splice(ind, 1, [id, code.replace(/ /g, '')]) } else { this[type].push([id, code.replace(/ /g, '')]); } return this.update(); } has(id, type) { return !!(~this[type].findIndex(el => el[0] == id)); } remove(id, type) { const ind = this[type].findIndex(el => el[0] == id); const res = (~ind) ? !!this[type].splice(ind, 1) : false; this.update(); return res; } update(code) { if (!this.mounted) return; if (code) this.html = code; const str = this._compile(); this.iframe.src = 'about:blank'; return new Promise(res => { this.iframe.onload = () => { this.doc = this.iframe.contentWindow.document; this.doc.write(str); this.doc.close(); this.updateFrameHeight(); res(); } }); } updateFrameHeight() { const coords = getCoords(this.doc.body.lastElementChild, this.iframe.contentWindow); const contentHeight = coords.top + coords.height; const frameHeight = (contentHeight > 500) ? '500px' : contentHeight + 16 + 'px'; this.iframe.style.height = frameHeight; } openInNewWin() { const str = this._compile(); const win = window.open('about:blank'); win.onload = () => { win.document.write(str); win.document.close(); } } download() { const str = this._compile(); const id = this.container.parentElement.querySelector(sels.codeFrame).id; const a = createElem('a'); a.download = `doc-${id}.html`; a.href = 'data:text/html;charset=UTF-8,' + str; a.click(); a.remove(); } _compile() { const wrap = (arr, type) => arr.map(([,code]) => `<${type}>${code}</${type}>`).join(''); return wrap(this.style, 'style') + wrap(this.script, 'script') + this.html; } } function stylize(node, type) { const pre = $(sels.codePre, node); const code = Prism.highlight(pre.innerText, Prism.languages[type]); pre.classList.add(`language-${type}`); pre.innerHTML = `<code class="language-${type}">${code}</code>`; updateCodeFrameHeight($(sels.codeFrame, node)); } function updateCodeFrameHeight(div) { let conHeight = getCoords(div.firstChild).height; if (conHeight < 25) conHeight = div.style.height; div.style.height = ((conHeight > 570) ? 590 : conHeight + 20) + 'px'; } function makeViewerLink(viewer, htmlNode) { const makeObj = { text: 'Показать viewer', onClick: function() { viewer.create(htmlNode, $(sels.codePre, htmlNode).innerText, () => { this.update(makeObj); }); this.update(unMakeObj); } } const unMakeObj = { text: 'Обновить viewer', onClick: () => viewer.update($(sels.codePre, htmlNode).innerText) } createLink({ parentNode: $('.head', htmlNode), style: styles.but, ...makeObj }); } function makeUseLink(viewer, codeNode, type) { const id = $(sels.codeFrame, codeNode).id; const useObj = { text: 'Использовать в viewer', onClick: function() { if (viewer.add(id, $(sels.codePre, codeNode).innerText, type)) { this.update(unUseObj); } } } const unUseObj = { text: 'Отвязать от viewer', onClick: function() { viewer.remove(id, type); this.update(useObj); } } createLink({ parentNode: $('.head', codeNode), style: styles.but, ...useObj }); } function makeEditableLink(viewer, node, type) { const id = $(sels.codeFrame, node).id; let textarea, autoReloadingLink; const makeObj = { text: 'Редактировать', onClick: function() { textarea = makeInput($(sels.codePre, node), type); this.update(unMakeObj); autoReloadingLink = makeAutoReloadingLink(); } } const unMakeObj = { text: 'Отменить редактирование', onClick: function() { textarea.remove(); autoReloadingLink.remove(); this.update(makeObj); } } createLink({ parentNode: $('.head', node), style: styles.but, ...makeObj }); function makeAutoReloadingLink() { const makeObj = { style: styles.but + 'color: rgb(130, 130, 130);', text: 'AutoReloading - off', onClick: function() { textarea.onInput = function(text) { if (type === 'html') { return viewer.update(text); } else { return viewer.add(id, text, type === 'js' ? 'script' : 'style'); } } this.update(unMakeObj); } }; const unMakeObj = { style: styles.but + 'color: green', text: 'AutoReloading - on', onClick: function() { textarea.onInput = null; this.update(makeObj); } }; return createLink({ parentNode: $('.head', node), ...makeObj }); } } function makeInput(pre, type, onInput = () => {}) { const div = pre.closest(sels.codeFrame); const lnPre = div.querySelector(sels.lnPre); const textarea = createElem('textarea', pre.parentElement); const divScroll = { left: div.scrollLeft, top: div.scrollTop } div.scrollTo(0, 0); const preCoords = getCoords(pre); const divCoords = getCoords(div); const paddingLeft = preCoords.left - divCoords.left; const paddingTop = preCoords.top - divCoords.top; textarea.style = styles.textarea + getStyleString({ 'padding-left': paddingLeft + 'px', 'padding-top': paddingTop + 'px' }); updateTextarea(); textarea.spellcheck = false; textarea.value = pre.innerText; textarea.scrollTo(divScroll.left, divScroll.top); div.scrollTo(divScroll.left, divScroll.top); textarea.oninput = async () => { const text = textarea.value; const textLen = text.split('\n').length; const code = Prism.highlight(text, Prism.languages[type]); pre.innerHTML = `<code class="language-${type}">${code}</code>`; lnPre.innerText = new Array(textLen).fill(1).map((_, i) => i + 1).join('\n'); await (textarea.onInput || onInput)(text); updateCodeFrameHeight(div); updateTextarea(); } textarea.onscroll = () => div.scrollTo(textarea.scrollLeft, textarea.scrollTop); function updateTextarea() { const tStyle = textarea.style; const divCoords = getCoords(div); tStyle.width = divCoords.width - paddingLeft + 'px'; tStyle.height = divCoords.height - paddingTop + 'px'; tStyle.left = divCoords.left + 'px'; tStyle.top = divCoords.top + 'px'; } return textarea; } // Utils function createElem(name, parentNode = document.documentElement) { const elem = document.createElement(name); parentNode.appendChild(elem); return elem; } function createLink({parentNode = document.documentElement, style, text, onClick = () => {}}) { const a = createElem('a', parentNode); a.style = style; a.href = '#'; a.innerText = text; a.onclick = function(ev) { ev.preventDefault(); onClick.call(a, ev); } a.update = function({style: newStyle, text: newText, onClick: newOnClick}) { a.style = newStyle || style; a.innerText = newText || text; a.onclick = function(ev) { ev.preventDefault(); (newOnClick || onClick).call(a, ev); } } return a; } function getStyleString(obj) { return Object.entries(obj).reduce((str, [key, value]) => str + `${key}:${value};`, ''); } function getCoords(elem, win = window) { const box = elem.getBoundingClientRect(); return { top: box.top + win.pageYOffset, left: box.left + win.pageXOffset, width: box.width, height: box.height } } })();