Last active
May 19, 2018 15:17
-
-
Save mblarsen/f628fc3c196b5f58d326242061922446 to your computer and use it in GitHub Desktop.
WordPress style shortcodes in Vue
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
/* 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