Skip to content

Instantly share code, notes, and snippets.

@zanlib0
Last active November 10, 2016 16:17
Show Gist options
  • Select an option

  • Save zanlib0/ef258277b3fc6cd30519eec4d8b7c4f2 to your computer and use it in GitHub Desktop.

Select an option

Save zanlib0/ef258277b3fc6cd30519eec4d8b7c4f2 to your computer and use it in GitHub Desktop.
Clockwork - BBCode editor

Clockwork - BBCode editor

Very simple and lightweight BBCode editor. No external libraries needed.

A Pen by MrHudson on CodePen.

License.

.wrapper
//.popup
.title
h3 Popup title.
span X
.content
form
input(type="submit")
.toolbar
.textarea
textarea#text-field
let textarea = document.querySelector("#text-field")
class ToolbarElement {
constructor(title, description) {
this.title = title //what shows in the toolbar
this.description = description //tooltip
addElementToToolbar(this)
}
}
class GenericStaticElement extends ToolbarElement {
constructor(title, description, tag) {
super(title, description)
this.tag = tag //[tag][/tag]
}
//Adds a tag in the text.
activate(input) {
let selected = getSelectedText(input)
let wrapped = `[${this.tag}]`
input.value = selected.before + wrapped + selected.centre + selected.after
}
}
class GenericElement extends GenericStaticElement {
constructor(title, description, tag) {
super(title, description, tag)
}
//Wraps the input in the chosen tags.
activate(input) {
let selected = getSelectedText(input)
let wrapped = `[${this.tag}]${selected.centre}[/${this.tag}]`
input.value = selected.before + wrapped + selected.after
}
}
//Top-level class for popups.
class PopupElement {
constructor(title, description, tag) {
this.title = title
this.description = description
this.tag = tag
addElementToToolbar(this)
}
activate(input) {
//Generate all the popup elements.
let popup = document.createElement("div")
popup.classList.add("popup")
document.querySelector(".wrapper").appendChild(popup)
let popupTitleContainer = document.createElement("div")
popupTitleContainer.classList.add("title")
popup.appendChild(popupTitleContainer)
let popupTitle = document.createElement("h3")
popupTitle.textContent = this.description
popupTitleContainer.appendChild(popupTitle)
let popupClose = document.createElement("span")
popupClose.textContent = "X"
popupTitleContainer.appendChild(popupClose)
popupClose.addEventListener("click", this.close)
let popupContent = document.createElement("div")
popupContent.classList.add("content")
popup.appendChild(popupContent)
let popupForm = document.createElement("form")
popupContent.appendChild(popupForm)
let popupSubmitButton = document.createElement("input")
popupSubmitButton.setAttribute("type", "submit")
popupForm.appendChild(popupSubmitButton)
//Show the popup.
popup.style.display = "flex"
//Return object.
let obj = {
popup: popup,
popupTitleContainer: popupTitleContainer,
popupTitle: popupTitle,
popupClose: popupClose,
popupContent: popupContent,
popupForm: popupForm,
popupSubmitButton: popupSubmitButton
}
return obj
}
close() {
//Close the popup.
let wrapper = document.querySelector(".wrapper")
let popup = document.querySelector(".popup")
wrapper.removeChild(popup)
}
}
//Element with a single label and text input.
class PopupInputElement extends PopupElement {
constructor(title, description, tag, label) {
super(title, description, tag)
this.label = label
}
activate(input) {
let obj = super.activate()
let label = document.createElement("p")
label.textContent = this.label
obj.popupForm.appendChild(label)
obj.popupForm.insertBefore(label, obj.popupForm.childNodes[0])
let textInput = document.createElement("input")
textInput.setAttribute("type", "text")
obj.popupForm.appendChild(textInput)
obj.popupForm.insertBefore(textInput, obj.popupForm.childNodes[1])
textInput.focus()
let form = obj.popupForm
form.addEventListener("submit", (event) => {
if (event.preventDefault) event.preventDefault()
let attribute = textInput.value
let selected = getSelectedText(textarea)
let output
if (attribute.trim() != "") { //if the input box is not empty
output = `${selected.before}[${this.tag}=${attribute}]${selected.centre}[/${this.tag}]${selected.after}`
} else {
output = `${selected.before}[${this.tag}]${selected.centre}[/${this.tag}]${selected.after}`
}
textarea.value = output
super.close()
})
}
}
//Element with a single label and colour input.
class PopupColourElement extends PopupElement {
constructor(title, description, tag, label) {
super(title, description, tag)
this.label = label
}
activate(input) {
let obj = super.activate()
let label = document.createElement("p")
label.textContent = this.label
obj.popupForm.appendChild(label)
obj.popupForm.insertBefore(label, obj.popupForm.childNodes[0])
let colorInput = document.createElement("input")
colorInput.setAttribute("type", "color")
obj.popupForm.appendChild(colorInput)
obj.popupForm.insertBefore(colorInput, obj.popupForm.childNodes[1])
colorInput.focus()
obj.popupForm.addEventListener("submit", (event) => {
if (event.preventDefault) event.preventDefault()
let attribute = colorInput.value
let selected = getSelectedText(textarea)
let output
if (attribute.trim() != "") { //if the input box is not empty
output = `${selected.before}[${this.tag}=${attribute}]${selected.centre}[/${this.tag}]${selected.after}`
} else {
output = `${selected.before}[${this.tag}]${selected.centre}[/${this.tag}]${selected.after}`
}
textarea.value = output
super.close()
})
}
}
class PopupComboElement extends PopupElement {
constructor(title, description, tag, label, options) {
super(title, description, tag)
this.label = label
this.options = options
}
activate(input) {
let obj = super.activate()
let label = document.createElement("p")
label.textContent = this.label
obj.popupForm.appendChild(label)
obj.popupForm.insertBefore(label, obj.popupForm.childNodes[0])
let select = document.createElement("select")
obj.popupForm.appendChild(select)
obj.popupForm.insertBefore(select, obj.popupForm.childNodes[1])
for(let element of this.options) {
console.log(element)
let option = document.createElement("option")
option.textContent = element
option.setAttribute("type", element)
select.appendChild(option)
}
obj.popupForm.addEventListener("submit", (event) => {
if(event.preventDefault) event.preventDefault()
let attribute = select.options[select.selectedIndex].value
let selected = getSelectedText(textarea)
let output = `${selected.before}[${this.tag}=${attribute}]${selected.centre}[/${this.tag}]`
textarea.value = output
super.close()
})
}
}
//Append element to the toolbar
function addElementToToolbar(element) {
let para = document.createElement("div")
para.classList.add("toolbar-button")
para.textContent = element.title
document.querySelector(".toolbar").appendChild(para)
para.addEventListener("click", () => {
element.activate(textarea)
})
}
function addSeparator() {
let para = document.createElement("div")
para.classList.add("toolbar-separator")
para.textContent = " | "
document.querySelector(".toolbar").appendChild(para)
}
//Get selected text and return everything before it, inside, and after.
function getSelectedText(input) {
let start = input.selectionStart
let end = input.selectionEnd
let object = {
before: input.value.substring(0, start),
centre: input.value.substring(start, end),
after: input.value.substring(end)
}
return object
}
//Toolbar items
const bold = new GenericElement("b", "Bold", "b")
const italic = new GenericElement("i", "Italic", "i")
const underline = new GenericElement("u", "Underline", "u")
const strikethrough = new GenericElement("s", "Strikethrough", "s")
addSeparator()
const indent = new GenericElement("indent", "Absolute indentation", "indent")
const code = new GenericElement("code", "Code block", "code")
addSeparator()
const list = new GenericElement("list", "Unordered list", "list")
const orderedList = new GenericElement("list=1", "Ordered list", "list=1")
const bulletPoint = new GenericStaticElement("*", "List point", "*")
addSeparator()
const hr = new GenericStaticElement("hr", "Horizontal rule", "hr")
addSeparator()
const url = new PopupInputElement("url", "Hyperlink", "url", "Insert URL:")
const quote = new PopupInputElement("quote", "Quote", "quote", "Insert the author's name:")
const piAmount = new PopupInputElement("pi", "Percentage indent", "pi", "Indentation percent:")
addSeparator()
const color = new PopupColourElement("color", "Colour", "color", "Choose a colour:")
addSeparator()
const align = new PopupComboElement("align", "Text align", "align", "Align direction:", ['left', 'center', 'right', 'justify'])
*, *:before, *:after
margin 0
padding 0
box-sizing border-box
.wrapper
height 100%
width 100%
.popup
position absolute
display none
flex-direction column
background #ddd
padding 16px
border 1px rgba(0,0,0,.5) solid
top: 50%
left: 50%
transform: translate(-50%, -50%)
font-family: sans-serif
font-size: .8em
.title
display flex
justify-content space-between
width 100%
padding 4px 0
span
user-select none
cursor pointer
align-self flex-end
.content
padding 4px 0
width 200px
form
display flex
flex-direction column
input
width 200px
input[type="text"]
display block
padding 4px
input[type="submit"]
padding 4px
margin 4px auto 0 auto
.toolbar
width 100%
display flex
flex-wrap wrap
justify-content space-around
padding 0 8px
.toolbar-button
height 32px
min-width 32px
padding 0 8px
display flex
justify-content center
align-items center
font-family monospace
user-select none
cursor pointer
.toolbar-separator
height 32px
min-width 16px
padding 0 8px
display flex
justify-content center
align-items center
font-family monospace
user-select none
cursor default
color: #aaa
.textarea
height 360px
width 100%
padding 0 8px
textarea
height 100%
width 100%
padding 8px
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment