diff --git a/docs/components/codemirror.vue b/docs/components/codemirror.vue index 6b18341b8be..4a5b0f73458 100755 --- a/docs/components/codemirror.vue +++ b/docs/components/codemirror.vue @@ -44,6 +44,10 @@ export default { type: String, default: 'indent' }, + tabSize: { + type: [Number, String], + default: 2 + }, lineWrapping: { type: Boolean, default: true @@ -74,6 +78,7 @@ export default { mode: this.mode, theme: this.theme, tabMode: this.tabMode, + tabSize: parseInt(this.tabSize, 10) || 2, lineWrapping: this.lineWrapping, lineNumbers: this.lineNumbers, autoCloseTags: true, diff --git a/docs/pages/play.vue b/docs/pages/play.vue index a988f1d8550..896495cd7c4 100755 --- a/docs/pages/play.vue +++ b/docs/pages/play.vue @@ -2,45 +2,34 @@
-
- Here you can interactively play and test components with a fresh vue instance. -
- TIP: - You can clone docs repo, to hack and develop components. - changes will be reflected and hot-reloaded instantly. -
- Please refer to - Docs - for more info about available tags and usage. +
+

+ Here you can interactively play and test components with a fresh vue instance. + Please refer to the Docs + section for more info about available tags and usage. +

+

+ TIP: + You can clone docs repo, to hack and develop components. + changes will be reflected and hot-reloaded instantly. +

-
+
- - - - - - Export to JSFiddle + target="_blank"> + + + + + + + Export to JSFiddle + Reset to default
@@ -114,37 +103,38 @@ {{ vertical ? 'Horizontal' : 'Vertical' }}
-
-
-
+
-
-
-
- Console - - Clear - -
-
-
- {{ message[0] }} - {{ message[1] }} -
-
-
+
+
+ Console + + Clear +
+ +
  • + {{ + message[0] === 'danger' ? 'error' : 'log' + }} + {{ message[1] }} +
  • +
    +
     
    @@ -154,7 +144,19 @@ @@ -163,25 +165,43 @@ import Vue from 'vue' import debounce from 'lodash/debounce' const defaultJS = `{ - data: { - name: 'Zeus' - }, + data: { + name: 'Bootstrap-Vue', + show: true + }, + watch: { + show(newVal, oldVal) { + console.log( + 'Alert is ' + (this.show ? 'visible' : 'hidden') + ) + } + } }` -const defaultHTML = ` Hello {{ name }}! ` + +const defaultHTML = `
    + + {{ show ? 'Hide' : 'Show' }} Alert + + + Hello {{ name }}! + +
    ` + +// Maximum age of localstorage before we revert back to defaults +// 7 days +const maxRetention = 7 * 24 * 60 * 60 * 1000 + +const removeNode = node => node && node.parentNode && node.parentNode.removeChild(node) export default { data () { return { html: '', js: '', - vm: null, messages: [], - originalLog: null, - originalWarn: null, - originalError: null, + logIdx: 0, vertical: false, - full: false, - lazy_run_: null + full: false } }, head () { @@ -198,120 +218,188 @@ export default { '//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js' ] }, + isDefault () { + return this.js.trim() === defaultJS.trim() && + this.html.trim() === defaultHTML.trim() + }, + isOk () { + let o + const js = this.js.trim() || '{}' + try { + /* eslint-disable no-eval */ + eval(`o = ${js}`) + /* eslint-enable no-eval */ + } catch (err) { + return false + } + if (!this.html && !o.template && typeof o.render !== 'function') { + return false + } + return true + }, js_fiddle () { - const js = `new Vue({el:'#app',\r\n${this.js.trim()}})`.trim() - return `window.onload = function() {${js}}` + let js = this.js.trim() || '{}' + const comma = js === '{}' ? '' : ',' + js = js.replace(/^\{/, `{\r\n el: '#app'${comma}\r\n`) + js = `new Vue(${js})` + return `window.onload = function() {\r\n${js}\r\n}` }, html_fiddle () { - return `
    \r\n${this.html}\r\n
    `.trim() - }, - lazy_run () { - if (!this.lazy_run_) { - this.lazy_run_ = debounce(this.run.bind(this), 500) - } - return this.lazy_run_ + return `
    \r\n${this.html.trim()}\r\n
    ` } }, watch: { html () { - this.lazy_run() + this.run() }, js () { - this.lazy_run() + this.run() } }, - mounted () { - this.load() - this.run() + created () { + const self = this + // Non reactive property to store the playground vm + this.playVM = null - if (typeof window !== 'undefined') { - this.originalLog = console.log - this.originalWarn = console.warn - this.originalError = console.error - const self = this + if (this.$isServer) { + this.run = () => {} + return + } - console.warn = function () { - self.log('warning', arguments) - } + // Create our debounced runner + this.run = debounce(this._run, 500) - console.log = function () { - self.log('info', arguments) - } + // Disable global error handler as it screws up out log capture + this.oldErrorHandler = Vue.config.errorHandler + Vue.config.errorHandler = null - console.error = function () { - self.log('danger', arguments) + // Override console.log + if (typeof window !== 'undefined' && console) { + const that = console + this.originalLog = console.log + console.log = function () { + self.log.call(self, 'info', ...arguments) + self.originalLog.apply(that, arguments) } } }, + mounted () { + // load our content into the editors after dom updated + this.$nextTick(this.load) + }, beforeDestroy () { - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' && this.originalLog) { console.log = this.originalLog - console.warn = this.originalWarn - console.error = this.originalError } - this.destroyVM() + if (this.oldErrorHandler) { + Vue.config.errorHandler = this.oldErrorHandler + } + if (!this.$isServer) { + this.destroyVM() + } }, methods: { - log (tag, args) { + log (tag, ...args) { // We have to ignore props mutation warning due to vue bug when we have two instances if (String(args[0]).indexOf('Avoid mutating a prop directly') !== -1) { return } - - const argsArr = [tag] - for (let i = 0; i < args.length; i++) { - argsArr.push(args[i]) + const msg = args.map(String).join(' ') + if (this.messages.length && msg.indexOf('Error in render') !== -1 && msg === this.messages[0][1]) { + // prevent duplicate render errors + return } - - this.originalLog.apply(console, argsArr) - if (this.messages.length > 10) { this.messages.splice(10) } - this.messages.unshift([argsArr.shift(), argsArr.map(String).join(' ')]) + this.messages.unshift([tag, msg, this.logIdx++]) }, destroyVM () { - if (this.vm) { + if (this.playVM) { + let parent try { - this.vm.$destroy() + parent = this.playVM.$parent + this.playVM.$destroy() + removeNode(this.playVM.$el) + this.playVM.$el.innerHTML = '' + } catch (err) { + } + try { + parent.$destroy() } catch (err) { } - this.vm = null } + this.playVM = null + this.$refs.result.innerHTML = '' }, - run () { - // Commit latest changes - this.commit() + createVM () { + let options + const js = this.js.trim() || '{}' + const html = this.html.trim() + const self = this - // Destroy old VM if exists - this.destroyVM() + const errHandler = (err, vm, info) => { + self.log('danger', `Error in ${info}: ${err.message}`) + self.destroyVM() + return false + } - // Set HTML - this.$refs.result.innerHTML = `
    ` + // Test JavaScript + try { + /* eslint-disable no-eval */ + eval(`options = ${js}`) + /* eslint-enable no-eval */ + } catch (err) { + errHandler(err, null, 'javascript') + this.playVM = null + return + } - // Clear messages - this.clear() + if (!html && !options.template && typeof options.render !== 'function') { + this.log('danger', 'No template or render function provided') + return + } + + if (!options.render) { + options.template = `
    ${options.template || html}
    ` + } else { + delete options.template + } + + let holder = document.createElement('div') + this.$refs.result.appendChild(holder) - // Try Create new VM try { - let options - try { - /* eslint-disable no-eval */ - let js = this.js.trim() - if (js.indexOf('{') !== 0) { - js = `{${js}}` - } - eval(`options= ${js}`) - } catch (err) { - throw new Error(`Compiling JS: ${err}`) - } - options.router = this.$router - options.el = '#result' - options.template = `
    ${this.html}
    ` - this.vm = new Vue(options) + const fakeParent = new Vue({ + template: '
    ', + errorCaptured: errHandler + }) + this.playVM = new Vue(Object.assign({}, options, { + el: holder, + // we set a fake parent so we can capture errors + parent: fakeParent, + // router needed for tooltips and popovers so they hide when route changes + router: this.$router + })) } catch (err) { - console.error(err) + holder = null + this.destroyVM() + errhandler(err, null, 'vm create') + return + } + + this.save() + }, + _run () { + if (this.$isServer) { + return } + // Destroy old VM if exists + this.destroyVM() + // clear the log + this.clear() + // create and render the instance + this.createVM() }, toggleVertical () { this.vertical = !this.vertical @@ -322,19 +410,43 @@ export default { clear () { this.messages.splice(0) }, + reset() { + // Needed to trick codemirror component to reload contents + this.js = this.html = '' + this.$nextTick(() => { + this.js = defaultJS.trim() + this.html = defaultHTML.trim() + this.save() + }) + }, load () { - if (typeof window === 'undefined' || !window.localStorage) { + const ls = window && window.localStorage + if (!ls) { + this.js = defaultJS.trim() + this.html = defaultHTML.trim() return } - this.js = window.localStorage.getItem('playground_js') || defaultJS.trim() - this.html = window.localStorage.getItem('playground_html') || defaultHTML.trim() + const ts = parseInt(ls.getItem('playground_ts'), 10) || 0 + if (Date.now() - ts > maxRetention) { + // clear local storage if it is old + ls.removeItem('playground_js') + ls.removeItem('playground_html') + ls.removeItem('playground_ts') + } + this.js = ls.getItem('playground_js') || defaultJS.trim() + this.html = ls.getItem('playground_html') || defaultHTML.trim() }, - commit () { + save () { if (typeof window === 'undefined' || !window.localStorage) { return } - window.localStorage.setItem('playground_js', this.js) - window.localStorage.setItem('playground_html', this.html) + try { + window.localStorage.setItem('playground_js', this.js) + window.localStorage.setItem('playground_html', this.html) + window.localStorage.setItem('playground_ts', String(Date.now())) + } catch (err) { + // silently ignore errors on safari iOS private mode + } } } }