Skip to content

Instantly share code, notes, and snippets.

@mblarsen
Last active May 19, 2018 15:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mblarsen/f628fc3c196b5f58d326242061922446 to your computer and use it in GitHub Desktop.
Save mblarsen/f628fc3c196b5f58d326242061922446 to your computer and use it in GitHub Desktop.
WordPress style shortcodes in Vue
/* Based on ideas described here: https://medium.com/@mblarsen/wordpress-style-shortcodes-using-vue-js-d2acd20f403f */
<script>
import 'babel-polyfill'
import { default as Tokenizer } from 'shortcode-tokenizer'
import Vue from 'vue'
import Row from 'components/ui/Row'
import Column from 'components/ui/Column'
import ProductList from 'components/content/ProductList'
import ProductCard from 'components/content/ProductCard'
const hyphenate = Vue.util.hyphenate
/* map from code to component */
const codeMap = {
'row': Row,
'column': Column,
'col': Column,
'product-list': ProductList,
'product-card': ProductCard
}
const allComponents = Object.values(codeMap)
.reduce(function (all, c) {
all[hyphenate(c.name)] = c
return all
}, {})
function renderToken(token) {
return wrapKeepAlive(
renderText(
renderSelfClosing(
renderOpen(token)
)
)
).output
}
function wrapKeepAlive(token) {
if (typeof token.params['keep-alive'] !== 'undefined') {
token.output = `<keep-alive>${token.output}</keep-alive>`
}
return token
}
function renderParams(token) {
if (Object.keys(token.params)) {
return ' ' + Object.entries(token.params)
.map(pair => {
if (pair[0] === 'keep-alive') {
return null
}
let value = pair[1]
let key = ':' + pair[0]
if (typeof pair[1] === 'string') {
value = `"${value}"`
key = pair[0]
}
return `${key}=${value}`
})
.join(' ')
}
return ''
}
function renderText(token) {
if (token.type === Tokenizer.TEXT || token.type === Tokenizer.ERROR) {
token.output = token.body
}
return token
}
function renderOpen(token) {
if (token.type === Tokenizer.OPEN) {
let name = getComponentName(token)
let params = renderParams(token)
let children = token.children
.map(renderToken)
.join('')
token.output = `<${name}${params}>${children}</${name}>`
}
return token
}
function ensureOneRoot(source, content) {
return source.length > 1 ? `<div>${content}</div>` : content
}
function renderSelfClosing(token) {
if (token.type === Tokenizer.SELF_CLOSING) {
let name = getComponentName(token)
let params = renderParams(token)
token.output = `<${name}${params}></${name}>`
}
return token
}
function getComponentName(token) {
if (typeof codeMap[token.name] === 'undefined') {
throw new Error(`Unknown code: ${token.name}`)
}
return hyphenate(codeMap[token.name].name)
}
export default {
props: {
content: {
type: String,
required: true
},
strict: {
type: Boolean,
default: true
}
},
methods: {
renderContent() {
try {
let ast = this.tokenizer
.input(this.content)
.ast()
let content = ast
.map(renderToken)
.join('')
return ensureOneRoot(ast, content)
} catch (err) {
// TODO use error component
console.error(err)
return `<div class="error">${err.message}</div>`
}
}
},
created() {
this.tokenizer = new Tokenizer()
this.tokenizer.strict = this.strict
},
render(h) {
return h(Vue.component('code-wrapper', {
template: this.renderContent(),
components: allComponents
}))
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment