An embeddable code editor for the browser 🍯
A Pen by Anton Medvedev on CodePen.
An embeddable code editor for the browser 🍯
A Pen by Anton Medvedev on CodePen.
<main class="view"> | |
<div class="logo"></div> | |
<h1 class="title"> | |
CodeJar Code Editor | |
</h1> | |
<div class="window"> | |
<div class="window-header"> | |
<div class="action-buttons"></div> | |
<div class="controls"> | |
<div>language: <a class="switch-language" href="">js</a></div> | |
<div>style: <a class="switch-style" href="">dracula</a></div> | |
</div> | |
</div> | |
<div class="window-body"> | |
<div class="editor language-js" data-gramm="false"></div> | |
</div> | |
</div> | |
<div class="features"> | |
<h2>Features</h2> | |
<ul> | |
<li>Lightweight (2 kB only) | |
<li>Preserves indentation on a new line | |
<li>Adds closing brackets, quotes | |
<li>Indents line with the Tab key | |
<li>Supports undo/redo | |
</ul> | |
</div> | |
</main> |
import { CodeJar } from "https://medv.io/codejar/codejar.js"; | |
const highlight = (editor) => { | |
// highlight.js does not trims old tags, | |
// let's do it by this hack. | |
editor.textContent = editor.textContent; | |
hljs.highlightBlock(editor); | |
}; | |
const editor = document.querySelector(".editor"); | |
const jar = new CodeJar(editor, highlight); | |
let currentStyle = 0; | |
const styles = [ | |
"dracula", | |
"github", | |
"solarized-dark", | |
"solarized-light", | |
"railscasts", | |
"monokai-sublime", | |
"mono-blue", | |
"tomorrow", | |
"color-brewer", | |
"zenburn", | |
"agate", | |
"androidstudio", | |
"atom-one-light", | |
"rainbow", | |
"vs", | |
"atom-one-dark" | |
].map((name) => { | |
const link = document.createElement("link"); | |
link.href = `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/styles/${name}.min.css`; | |
link.rel = "stylesheet"; | |
link.disabled = "true"; | |
document.head.appendChild(link); | |
return link; | |
}); | |
styles[currentStyle].removeAttribute("disabled"); | |
const switchStyleButton = document.querySelector(".switch-style"); | |
switchStyleButton.addEventListener("click", (event) => { | |
event.preventDefault(); | |
styles[currentStyle].setAttribute("disabled", "true"); | |
currentStyle = (currentStyle + 1) % styles.length; | |
styles[currentStyle].removeAttribute("disabled"); | |
let [, name] = styles[currentStyle].href.match( | |
/highlight.js.+?\/styles\/(.+?)\.min\.css$/ | |
); | |
switchStyleButton.textContent = name; | |
}); | |
let currentLanguage = 0; | |
const languages = [ | |
function () { | |
editor.className = "editor language-js"; | |
jar.updateCode(`import {CodeJar} from '@medv/codejar'; | |
import Prism from 'prismjs'; | |
const editor = document.querySelector('#editor'); | |
const jar = new CodeJar(editor, Prism.highlightElement, {tab: '\\t'}); | |
// Update code | |
jar.updateCode('let foo = bar'); | |
// Get code | |
let code = jar.toString(); | |
// Listen to updates | |
jar.onUpdate(code => { | |
console.log(code); | |
}); | |
`); | |
jar.updateOptions({ tab: " " }); | |
}, | |
function () { | |
editor.className = "editor language-md"; | |
jar.updateCode(`# CodeJar | |
An embeddable code editor for the browser 🍯 | |
## Features | |
* Lightweight (**2 kB** only) | |
* Preserves indentation on a new line | |
* Adds closing brackets, quotes | |
* Indents line with the \`Tab\` key | |
* Supports *undo*/*redo* | |
## Getting Started | |
\`\`\`bash | |
npm i @medv/codejar | |
\`\`\``); | |
jar.updateOptions({ tab: " " }); | |
}, | |
function () { | |
editor.className = "editor language-go"; | |
jar.updateCode(`package main | |
import ( | |
\t"fmt" | |
\t"github.com/antonmedv/expr" | |
) | |
func main() { | |
\tfmt.Println("Hello, CodeJar") | |
\toutput, err := expr.Eval("1+2") | |
\tif err != nil { | |
\t\tpanic(err) | |
\t} | |
} | |
`); | |
jar.updateOptions({ tab: "\t" }); | |
}, | |
function () { | |
editor.className = "editor language-ts"; | |
jar.updateCode(`interface Person { | |
firstName: string; | |
lastName: string; | |
} | |
function greeter(person: Person) { | |
return "Hello, " + person.firstName + " " + person.lastName; | |
} | |
let user = { | |
firstName: "Jane", | |
lastName: "User" | |
}; | |
document.body.textContent = greeter(user);`); | |
jar.updateOptions({ tab: " " }); | |
}, | |
function () { | |
editor.className = "editor language-rust"; | |
jar.updateCode(`#[derive(Debug)] | |
struct Rectangle { | |
width: u32, | |
height: u32, | |
} | |
impl Rectangle { | |
fn area(&self) -> u32 { | |
self.width * self.height | |
} | |
} | |
fn main() { | |
let rect1 = Rectangle { width: 30, height: 50 }; | |
println!( | |
"The area of the rectangle is {} square pixels.", | |
rect1.area() | |
); | |
}`); | |
jar.updateOptions({ tab: " " }); | |
}, | |
function () { | |
editor.className = "editor language-html"; | |
jar.updateCode(`<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>CodeJar</title> | |
<meta name="author" content="Anton Medvedev"> | |
<meta name="description" content="Micro Code Editor"> | |
</head> | |
<body> | |
<h1>CodeJar — Micro Code Editor</h1> | |
</body> | |
</html>`); | |
jar.updateOptions({ tab: " " }); | |
}, | |
function () { | |
editor.className = "editor language-kotlin"; | |
jar.updateCode(`suspend fun main() = coroutineScope { | |
for (i in 0 until 10) { | |
launch { | |
delay(1000L - i * 10) | |
print("❤️$i ") | |
} | |
} | |
} | |
val positiveNumbers = list.filter { it > 0 } | |
fun calculateTotal(obj: Any) { | |
if (obj is Invoice) | |
obj.calculateTotal() | |
}`); | |
jar.updateOptions({ tab: " " }); | |
} | |
]; | |
languages[currentLanguage](); | |
const switchLanguageButton = document.querySelector(".switch-language"); | |
switchLanguageButton.addEventListener("click", (event) => { | |
event.preventDefault(); | |
currentLanguage = (currentLanguage + 1) % languages.length; | |
languages[currentLanguage](); | |
const [, name] = editor.className.match(/language-(\w+)/); | |
switchLanguageButton.textContent = name; | |
}); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script> |
@import url("https://fonts.googleapis.com/css2?family=Lato:wght@300&family=PT+Mono&display=swap"); | |
@import url("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/styles/dracula.min.css"); | |
body { | |
background-image: linear-gradient( | |
109.6deg, | |
rgba(253, 199, 141, 1) 11.3%, | |
rgba(249, 143, 253, 1) 100.2% | |
); | |
font-family: Lato, sans-serif; | |
font-weight: 300; | |
font-size: 15px; | |
margin: 0; | |
} | |
*, | |
*:before, | |
*:after { | |
box-sizing: border-box; | |
} | |
*:focus { | |
outline: none; | |
} | |
a, | |
a:visited, | |
a:active { | |
color: black; | |
} | |
main { | |
min-height: 100vh; | |
display: flex; | |
align-items: center; | |
flex-direction: column; | |
} | |
.logo { | |
display: block; | |
margin: 30px auto 10px; | |
width: calc(145px / 2); | |
height: calc(185px / 2); | |
background: url(https://medv.io/codejar/codejar.svg) no-repeat; | |
background-size: contain; | |
} | |
.title { | |
color: #fff; | |
text-align: center; | |
font-weight: 300; | |
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); | |
font-size: 34px; | |
margin-top: 20px; | |
} | |
.window { | |
width: 547px; | |
border-radius: 6px; | |
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.1); | |
overflow: hidden; | |
margin-bottom: 20px; | |
} | |
.window .window-header { | |
height: 25px; | |
background: Gainsboro; | |
position: relative; | |
} | |
.window .window-header .action-buttons { | |
position: absolute; | |
top: 50%; | |
left: 10px; | |
margin-top: -5px; | |
width: 10px; | |
height: 10px; | |
background: Crimson; | |
border-radius: 50%; | |
box-shadow: 15px 0 0 Orange, 30px 0 0 LimeGreen; | |
} | |
.editor { | |
border-bottom-left-radius: 6px; | |
border-bottom-right-radius: 6px; | |
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), | |
0 3px 1px -2px rgba(0, 0, 0, 0.2); | |
font-family: "PT Mono", monospace; | |
font-size: 14px; | |
font-weight: 400; | |
min-height: 340px; | |
letter-spacing: normal; | |
line-height: 20px; | |
padding: 10px; | |
resize: none !important; | |
tab-size: 4; | |
} | |
.editor.hljs { | |
padding: 10px; | |
} | |
.controls { | |
font-size: 14px; | |
position: absolute; | |
top: 50%; | |
right: 10px; | |
margin-top: -10px; | |
display: flex; | |
} | |
.controls > div:first-child > a { | |
display: inline-block; | |
width: 40px; | |
} | |
.features { | |
width: 547px; | |
font-size: 16px; | |
margin-bottom: 30px; | |
} |