Very simple and lightweight BBCode editor. No external libraries needed.
Last active
November 10, 2016 16:17
-
-
Save zanlib0/ef258277b3fc6cd30519eec4d8b7c4f2 to your computer and use it in GitHub Desktop.
Clockwork - BBCode editor
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
| .wrapper | |
| //.popup | |
| .title | |
| h3 Popup title. | |
| span X | |
| .content | |
| form | |
| input(type="submit") | |
| .toolbar | |
| .textarea | |
| textarea#text-field |
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
| 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']) |
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
| *, *: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