diff --git a/src/editor.js b/src/editor.js new file mode 100644 index 0000000..752963e --- /dev/null +++ b/src/editor.js @@ -0,0 +1,139 @@ +import Prism from "prismjs"; +import "prismjs/plugins/custom-class/prism-custom-class"; +Prism.plugins.customClass.map({ number: "prism-number", tag: "prism-tag" }); + +// codeInput +// by WebCoder49 +// Based on a CSS-Tricks Post +// Needs Prism.js + +var codeInput = { + update: function(text, code_input) { + code_input.setAttribute("value", text); + let result_element = code_input.getElementsByClassName("code-input_highlighting-content")[0]; + // Handle final newlines (see article) + if(text[text.length-1] == "\n") { + text += " "; + } + // Update code + result_element.innerHTML = text.replace(new RegExp("&", "g"), "&").replace(new RegExp("<", "g"), "<"); /* Global RegExp */ + // Syntax Highlight + Prism.highlightElement(result_element); + }, + + sync_scroll: function(element, code_input) { + /* Scroll result to scroll coords of event - sync with textarea */ + let result_element = code_input.getElementsByClassName("code-input_highlighting")[0]; + // Get and set x and y + result_element.scrollTop = element.scrollTop; + result_element.scrollLeft = element.scrollLeft; + }, + + check_tab: function(element, event) { + let code = element.value; + if(event.key == "Tab") { + /* Tab key pressed */ + event.preventDefault(); // stop normal + let before_tab = code.slice(0, element.selectionStart); // text before tab + let after_tab = code.slice(element.selectionEnd, element.value.length); // text after tab + let cursor_pos = element.selectionEnd + 1; // where cursor moves after tab - moving forward by 1 char to after tab + element.value = before_tab + "\t" + after_tab; // add tab char + // move cursor + element.selectionStart = cursor_pos; + element.selectionEnd = cursor_pos; + } + } +} +window.codeInput = codeInput + +class CodeInput extends HTMLElement { // Create code input element + constructor() { + super(); // Element + } + connectedCallback() { + // Added to document + + /* Defaults */ + let lang = this.getAttribute("lang") || "HTML"; + let placeholder = this.getAttribute("placeholder") || "Enter " + this.lang + " Source Code"; + let value = this.getAttribute("value") || this.innerHTML || ""; + + this.innerHTML = ""; // Clear Content + + /* Create Textarea */ + let textarea = document.createElement("textarea"); + textarea.placeholder = placeholder; + textarea.value = value; + textarea.className = "code-input_editing"; + textarea.setAttribute("spellcheck", "false"); + + if(this.getAttribute("name")) { + textarea.setAttribute("name", this.getAttribute("name")); // for use in forms + this.removeAttribute("name"); + } + + textarea.setAttribute("oninput", "codeInput.update(this.value, this.parentElement); codeInput.sync_scroll(this, this.parentElement);"); + textarea.setAttribute("onscroll", "codeInput.sync_scroll(this, this.parentElement);"); + textarea.setAttribute("onkeydown", "codeInput.check_tab(this, event);"); + + this.append(textarea); + + /* Create pre code */ + let code = document.createElement("code"); + code.className = "code-input_highlighting-content language-" + lang; // Language for Prism.js + code.innerText = value; + + let pre = document.createElement("pre"); + pre.className = "code-input_highlighting"; + pre.setAttribute("aria-hidden", "true"); // Hide for screen readers + pre.append(code); + this.append(pre); + + /* Add code from value attribute - useful for loading from backend */ + let text = this.value; + let result_element = code + + // Handle final newlines (see article) + // if(text[text.length-1] == "\n") { + // text += " "; + // } + + // Update code + result_element.innerHTML = text.replace(new RegExp("&", "g"), "&").replace(new RegExp("<", "g"), "<"); /* Global RegExp */ + + // Syntax Highlight + Prism.highlightElement(result_element); + } + static get observedAttributes() { + return ["value", "placeholder"]; // Attributes to monitor + } + attributeChangedCallback(name, oldValue, newValue) { + + let textarea = this.getElementsByClassName("code-input_editing")[0]; + let result_element = this.getElementsByClassName("code-input_highlighting-content")[0]; + + switch(name) { + + case "value": + + // Handle final newlines (see article) + // if(text[text.length-1] == "\n") { + // text += " "; + // } + + // Update code + result_element.innerHTML = text.replace(new RegExp("&", "g"), "&").replace(new RegExp("<", "g"), "<"); /* Global RegExp */ + + // Syntax Highlight + Prism.highlightElement(result_element); + + break; + + case "placeholder": + textarea.placeholder = oldValue; + break; + } + } +} + +customElements.define("code-input", CodeInput); // Set tag \ No newline at end of file diff --git a/src/scss/editor.scss b/src/scss/editor.scss new file mode 100644 index 0000000..3fd23f1 --- /dev/null +++ b/src/scss/editor.scss @@ -0,0 +1,93 @@ +textarea { + border: none; + overflow: auto; + outline: none; + + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + + resize: none; /*remove the resize handle on the bottom right*/ +} +.content .tag, .content .number { + display: inline; + padding: inherit; + font-size: inherit; + line-height: inherit; + text-align: inherit; + vertical-align: inherit; + border-radius: inherit; + font-weight: inherit; + white-space: inherit; + background: inherit; + margin: inherit; +} + + + + +/* Code-Input Compability */ +/* By WebCoder49 */ +/* First Published on CSS-Tricks.com */ + +code-input { + /* Allow other elems to be inside */ + position: relative; + top: 0; + left: 0; + display: block; +} + +.code-input_editing, .code-input_highlighting { + /* Both elements need the same text and space styling so they are directly on top of each other */ + margin: 10px!important; + padding: 10px!important; + border: 0; + width: calc(100% - 32px)!important; + height: calc(100% - 32px)!important; +} +.code-input_editing, .code-input_highlighting, .code-input_highlighting * { + /* Also add text styles to highlighing tokens */ + font-size: 15pt!important; + font-family: monospace; + line-height: 20pt!important; + tab-size: 2!important; +} + + +.code-input_editing, .code-input_highlighting { + /* In the same place */ + position: absolute; + top: 0; + left: 0; +} + + +/* Move the textarea in front of the result */ + +.code-input_editing { + z-index: 1; +} +.code-input_highlighting { + z-index: 0; +} + + +/* Make textarea almost completely transparent */ + +.code-input_editing { + color: transparent; + background: transparent; + caret-color: white; /* Or choose your favourite color */ +} + +/* Can be scrolled */ +.code-input_editing, .code-input_highlighting { + overflow: auto; + white-space: nowrap; /* Allows textarea to scroll horizontally */ +} + +/* No resize on textarea */ +.code-input_editing { + resize: none; +} \ No newline at end of file diff --git a/src/scss/prism.scss b/src/scss/prism.scss new file mode 100644 index 0000000..bff28f7 --- /dev/null +++ b/src/scss/prism.scss @@ -0,0 +1,134 @@ + +/* PrismJS 1.23.0 +https://prismjs.com/download.html#themes=prism-funky&languages=markup */ +/** + * prism.js Funky theme + * Based on “Polyfilling the gaps” talk slides http://lea.verou.me/polyfilling-the-gaps/ + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: .4em .8em; + margin: .5em 0; + overflow: auto; + /* background: url('data:image/svg+xml;charset=utf-8,%0D%0A%0D%0A%0D%0A<%2Fsvg>'); + background-size: 1em 1em; - WebCoder49*/ + background: black; /* - WebCoder49 */ +} + +code[class*="language-"] { + background: black; + color: white; + box-shadow: -.3em 0 0 .3em black, .3em 0 0 .3em black; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .2em; + border-radius: .3em; + box-shadow: none; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #aaa; +} + +.token.punctuation { + color: #999; +} + +.token.namespace { + opacity: .7; +} + +.token.property, +.token.prism-tag, +.token.boolean, +.token.prism-number, +.token.constant, +.token.symbol { + color: #0cf; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin { + color: yellow; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.token.variable, +.token.inserted { + color: yellowgreen; +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: deeppink; +} + +.token.regex, +.token.important { + color: orange; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.token.deleted { + color: red; +} + +/* Plugin styles: Diff Highlight */ +pre.diff-highlight.diff-highlight > code .token.deleted:not(.prefix), +pre > code.diff-highlight.diff-highlight .token.deleted:not(.prefix) { + background-color: rgba(255, 0, 0, .3); + display: inline; +} + +pre.diff-highlight.diff-highlight > code .token.inserted:not(.prefix), +pre > code.diff-highlight.diff-highlight .token.inserted:not(.prefix) { + background-color: rgba(0, 255, 128, .3); + display: inline; +} diff --git a/src/tmp.css b/src/tmp.css index 9a611dc..47a9d8a 100644 --- a/src/tmp.css +++ b/src/tmp.css @@ -3,15 +3,15 @@ /* First Published on CSS-Tricks.com */ textarea { - border: none; - overflow: auto; - outline: none; + border: none; + overflow: auto; + outline: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; - resize: none; /*remove the resize handle on the bottom right*/ + resize: none; /*remove the resize handle on the bottom right*/ } .content .tag, .content .number { display: inline;