From 44191756956df29458ed3c8a0a8ad3eb6c07406a Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sun, 22 Apr 2018 02:58:20 +0300 Subject: [PATCH 1/4] Text knob: add escapeHTML option; use it by default in Vue, Angular, and Polymer --- addons/knobs/package.json | 1 + addons/knobs/src/KnobManager.js | 8 ++++++-- addons/knobs/src/angular/index.js | 2 +- addons/knobs/src/base.js | 8 ++++++-- addons/knobs/src/polymer/index.js | 2 +- addons/knobs/src/vue/index.js | 2 +- examples/angular-cli/src/stories/addon-knobs.stories.ts | 5 ++++- .../src/stories/addon-knobs.stories.js | 5 ++++- .../official-storybook/stories/addon-knobs.stories.js | 3 ++- examples/polymer-cli/src/stories/addon-knobs.stories.js | 3 ++- .../vue-kitchen-sink/src/stories/addon-knobs.stories.js | 9 ++++++++- yarn.lock | 2 +- 12 files changed, 37 insertions(+), 13 deletions(-) diff --git a/addons/knobs/package.json b/addons/knobs/package.json index a5bf96f9ac9b..ab03b5031482 100644 --- a/addons/knobs/package.json +++ b/addons/knobs/package.json @@ -17,6 +17,7 @@ "@storybook/components": "4.0.0-alpha.3", "babel-runtime": "^6.26.0", "deep-equal": "^1.0.1", + "escape-html": "^1.0.3", "global": "^4.3.2", "insert-css": "^2.0.0", "lodash.debounce": "^4.0.8", diff --git a/addons/knobs/src/KnobManager.js b/addons/knobs/src/KnobManager.js index a844e099f259..ae66631e0ad0 100644 --- a/addons/knobs/src/KnobManager.js +++ b/addons/knobs/src/KnobManager.js @@ -1,10 +1,14 @@ /* eslint no-underscore-dangle: 0 */ import deepEqual from 'deep-equal'; +import escape from 'escape-html'; + import KnobStore from './KnobStore'; // This is used by _mayCallChannel to determine how long to wait to before triggering a panel update const PANEL_UPDATE_INTERVAL = 400; +const getKnobValue = ({ value, options = {} }) => (options.escapeHTML ? escape(value) : value); + export default class KnobManager { constructor() { this.knobStore = new KnobStore(); @@ -23,7 +27,7 @@ export default class KnobManager { // But, if the user changes the code for the defaultValue we should set // that value instead. if (existingKnob && deepEqual(options.value, existingKnob.defaultValue)) { - return existingKnob.value; + return getKnobValue(existingKnob); } const defaultValue = options.value; @@ -34,7 +38,7 @@ export default class KnobManager { }; knobStore.set(name, knobInfo); - return knobStore.get(name).value; + return getKnobValue(knobStore.get(name)); } _mayCallChannel() { diff --git a/addons/knobs/src/angular/index.js b/addons/knobs/src/angular/index.js index 753ed8ec964a..2a354c8161c1 100644 --- a/addons/knobs/src/angular/index.js +++ b/addons/knobs/src/angular/index.js @@ -4,7 +4,7 @@ import { prepareComponent } from './helpers'; import { knob, - text, + escapedText as text, boolean, number, color, diff --git a/addons/knobs/src/base.js b/addons/knobs/src/base.js index ffe86e593c2c..07ec59e8fc19 100644 --- a/addons/knobs/src/base.js +++ b/addons/knobs/src/base.js @@ -7,8 +7,12 @@ export function knob(name, options) { return manager.knob(name, options); } -export function text(name, value, groupId) { - return manager.knob(name, { type: 'text', value, groupId }); +export function text(name, value, options = {}, groupId) { + return manager.knob(name, { type: 'text', value, options, groupId }); +} + +export function escapedText(name, value, options = {}, groupId) { + return text(name, value, { escapeHTML: true, ...options }, groupId); } export function boolean(name, value, groupId) { diff --git a/addons/knobs/src/polymer/index.js b/addons/knobs/src/polymer/index.js index 9454c665e0b8..fb19f65f636f 100644 --- a/addons/knobs/src/polymer/index.js +++ b/addons/knobs/src/polymer/index.js @@ -4,7 +4,7 @@ import './WrapStory.html'; import { knob, - text, + escapedText as text, boolean, number, color, diff --git a/addons/knobs/src/vue/index.js b/addons/knobs/src/vue/index.js index cb37c127708b..d80e9f3a3d92 100644 --- a/addons/knobs/src/vue/index.js +++ b/addons/knobs/src/vue/index.js @@ -2,7 +2,7 @@ import addons from '@storybook/addons'; import { knob, - text, + escapedText as text, boolean, number, color, diff --git a/examples/angular-cli/src/stories/addon-knobs.stories.ts b/examples/angular-cli/src/stories/addon-knobs.stories.ts index f4a1cd90b38c..ff4a661486f4 100644 --- a/examples/angular-cli/src/stories/addon-knobs.stories.ts +++ b/examples/angular-cli/src/stories/addon-knobs.stories.ts @@ -79,4 +79,7 @@ storiesOf('Addon|Knobs', module) nice, }, }; - }); + }) + .add('XSS safety', () => ({ + template: text('Rendered string', ''), + })); diff --git a/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js index c4246eee5005..d518f313e546 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js @@ -68,4 +68,7 @@ storiesOf('Addons|Knobs', module) ), }; - }); + }) + .add('XSS safety', () => ({ + view: () => text('Rendered string', ''), + })); diff --git a/examples/official-storybook/stories/addon-knobs.stories.js b/examples/official-storybook/stories/addon-knobs.stories.js index c88ae1b69cc5..f1c4f6fbe1fa 100644 --- a/examples/official-storybook/stories/addon-knobs.stories.js +++ b/examples/official-storybook/stories/addon-knobs.stories.js @@ -189,7 +189,8 @@ storiesOf('Addons|Knobs.withKnobs', module)

Hit the knob load button and it should trigger an async load after a short delay

- )); + )) + .add('XSS safety', () => text('Rendered string', '')); storiesOf('Addons|Knobs.withKnobsOptions', module) .addDecorator( diff --git a/examples/polymer-cli/src/stories/addon-knobs.stories.js b/examples/polymer-cli/src/stories/addon-knobs.stories.js index 2c9a94e8b7cf..b1bfd8dff47a 100644 --- a/examples/polymer-cli/src/stories/addon-knobs.stories.js +++ b/examples/polymer-cli/src/stories/addon-knobs.stories.js @@ -58,4 +58,5 @@ storiesOf('Addon|Knobs', module)

${nice ? 'Nice to meet you!' : 'Leave me alone!'}

`; - }); + }) + .add('XSS safety', () => text('Rendered string', '')); diff --git a/examples/vue-kitchen-sink/src/stories/addon-knobs.stories.js b/examples/vue-kitchen-sink/src/stories/addon-knobs.stories.js index a63f1646792f..26015c311b76 100644 --- a/examples/vue-kitchen-sink/src/stories/addon-knobs.stories.js +++ b/examples/vue-kitchen-sink/src/stories/addon-knobs.stories.js @@ -66,4 +66,11 @@ storiesOf('Addon|Knobs', module) `, }; - }); + }) + .add('XSS safety', () => ({ + template: ` +
+ ${text('Rendered string', '')} +
+ `, + })); diff --git a/yarn.lock b/yarn.lock index efa3b7c6bf4d..95017687023c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5357,7 +5357,7 @@ escape-html@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.2.tgz#d77d32fa98e38c2f41ae85e9278e0e0e6ba1022c" -escape-html@~1.0.3: +escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" From 05b92049045370f7688cdb389021ae499941d1f9 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sun, 22 Apr 2018 03:06:17 +0300 Subject: [PATCH 2/4] Add docs --- addons/knobs/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/addons/knobs/README.md b/addons/knobs/README.md index d494539ac987..05717cf2b2d6 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -161,6 +161,14 @@ const groupId = 'GROUP-ID1'; const value = text(label, defaultValue, groupId); ``` + +Text knob accepts `escapeHTML` option: +```js +text(label, defaultValue, {escapeHTML: true}, groupId) +``` +This option is true by default in storybook for Vue, Angular, and Polymer, because those frameworks allow rendering plain HTML. +You can still set it to false, but it's strongly unrecommendend in cases when you host your storybook on some route of your main site or web app. + ### boolean Allows you to get a boolean value from the user. From 602aae1262e5dda0d0c735fd788f562e6cc49d9a Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sun, 22 Apr 2018 03:18:36 +0300 Subject: [PATCH 3/4] Update storyshots --- addons/knobs/src/react/WrapStory.js | 2 +- .../__snapshots__/addon-knobs.stories.storyshot | 12 ++++++++++++ .../__snapshots__/addon-knobs.stories.storyshot | 6 ++++++ .../stories/addon-knobs.stories.js | 4 +++- .../__snapshots__/addon-knobs.stories.storyshot | 8 ++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/addons/knobs/src/react/WrapStory.js b/addons/knobs/src/react/WrapStory.js index 405d1305ccc4..148ba186cd1f 100644 --- a/addons/knobs/src/react/WrapStory.js +++ b/addons/knobs/src/react/WrapStory.js @@ -101,7 +101,7 @@ WrapStory.propTypes = { subscribe: PropTypes.func, unsubscribe: PropTypes.func, }).isRequired, - initialContent: PropTypes.object, // eslint-disable-line react/forbid-prop-types, react/no-unused-prop-types + initialContent: PropTypes.node, // eslint-disable-line react/no-unused-prop-types }; polyfill(WrapStory); diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot index c33cc5239ccc..7b714f4052b2 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot @@ -120,3 +120,15 @@ exports[`Storyshots Addon|Knobs Simple 1`] = ` `; + +exports[`Storyshots Addon|Knobs XSS safety 1`] = ` + + + <img src=x onerror="alert('XSS Attack')" > + + +`; diff --git a/examples/official-storybook/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/official-storybook/stories/__snapshots__/addon-knobs.stories.storyshot index 31a1b0fc13e4..1facf226a0c3 100644 --- a/examples/official-storybook/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/official-storybook/stories/__snapshots__/addon-knobs.stories.storyshot @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Storyshots Addons|Knobs.withKnobs XSS safety 1`] = ` +
+ <img src=x onerror="alert('XSS Attack')" > +
+`; + exports[`Storyshots Addons|Knobs.withKnobs dynamic knobs 1`] = `
diff --git a/examples/official-storybook/stories/addon-knobs.stories.js b/examples/official-storybook/stories/addon-knobs.stories.js index f1c4f6fbe1fa..3406b6407205 100644 --- a/examples/official-storybook/stories/addon-knobs.stories.js +++ b/examples/official-storybook/stories/addon-knobs.stories.js @@ -190,7 +190,9 @@ storiesOf('Addons|Knobs.withKnobs', module)
)) - .add('XSS safety', () => text('Rendered string', '')); + .add('XSS safety', () => ( +
{text('Rendered string', '')}
+ )); storiesOf('Addons|Knobs.withKnobsOptions', module) .addDecorator( diff --git a/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-knobs.stories.storyshot index f2a13b0fcadf..f54e6f19d0e5 100644 --- a/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-knobs.stories.storyshot @@ -43,3 +43,11 @@ exports[`Storyshots Addon|Knobs Simple 1`] = ` I am John Doe and I'm 44 years old.
`; + +exports[`Storyshots Addon|Knobs XSS safety 1`] = ` +
+ + <img src=x onerror="alert('XSS Attack')" > + +
+`; From dcad904581ec81a1e6354440b392a655abace664 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sun, 22 Apr 2018 16:05:42 +0300 Subject: [PATCH 4/4] Make escapeHTML a global option & apply it to all values --- addons/knobs/README.md | 10 ++----- addons/knobs/src/KnobManager.js | 31 ++++++++++++++++++-- addons/knobs/src/angular/index.js | 23 ++------------- addons/knobs/src/base.js | 32 +++++++++++++++++---- addons/knobs/src/components/types/Button.js | 4 +-- addons/knobs/src/mithril/index.js | 20 ++----------- addons/knobs/src/polymer/index.js | 21 ++------------ addons/knobs/src/react/index.js | 20 ++----------- addons/knobs/src/vue/index.js | 23 ++------------- 9 files changed, 72 insertions(+), 112 deletions(-) diff --git a/addons/knobs/README.md b/addons/knobs/README.md index 05717cf2b2d6..ad0c01c6f983 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -162,13 +162,6 @@ const groupId = 'GROUP-ID1'; const value = text(label, defaultValue, groupId); ``` -Text knob accepts `escapeHTML` option: -```js -text(label, defaultValue, {escapeHTML: true}, groupId) -``` -This option is true by default in storybook for Vue, Angular, and Polymer, because those frameworks allow rendering plain HTML. -You can still set it to false, but it's strongly unrecommendend in cases when you host your storybook on some route of your main site or web app. - ### boolean Allows you to get a boolean value from the user. @@ -393,6 +386,9 @@ const stories = storiesOf('Storybook Knobs', module); stories.addDecorator(withKnobsOptions({ debounce: { wait: number, leading: boolean}, // Same as lodash debounce. timestamps: true // Doesn't emit events while user is typing. + escapeHTML: true // Escapes strings to be safe for inserting as innerHTML. This option is true by default in storybook for Vue, Angular, and Polymer, because those frameworks allow rendering plain HTML. + // You can still set it to false, but it's strongly unrecommendend in cases when you host your storybook on some route of your main site or web app. + })); ``` diff --git a/addons/knobs/src/KnobManager.js b/addons/knobs/src/KnobManager.js index ae66631e0ad0..9ae2ca937a76 100644 --- a/addons/knobs/src/KnobManager.js +++ b/addons/knobs/src/KnobManager.js @@ -7,17 +7,42 @@ import KnobStore from './KnobStore'; // This is used by _mayCallChannel to determine how long to wait to before triggering a panel update const PANEL_UPDATE_INTERVAL = 400; -const getKnobValue = ({ value, options = {} }) => (options.escapeHTML ? escape(value) : value); +const escapeStrings = obj => { + if (typeof obj === 'string') { + return escape(obj); + } + if (obj == null || typeof obj !== 'object') { + return obj; + } + if (Array.isArray(obj)) { + const newArray = obj.map(escapeStrings); + const didChange = newArray.some((newValue, key) => newValue !== obj[key]); + return didChange ? newArray : obj; + } + return Object.entries(obj).reduce((acc, [key, oldValue]) => { + const newValue = escapeStrings(oldValue); + return newValue === oldValue ? acc : { ...acc, [key]: newValue }; + }, obj); +}; export default class KnobManager { constructor() { this.knobStore = new KnobStore(); + this.options = {}; } setChannel(channel) { this.channel = channel; } + setOptions(options) { + this.options = options; + } + + getKnobValue({ value }) { + return this.options.escapeHTML ? escapeStrings(value) : value; + } + knob(name, options) { this._mayCallChannel(); @@ -27,7 +52,7 @@ export default class KnobManager { // But, if the user changes the code for the defaultValue we should set // that value instead. if (existingKnob && deepEqual(options.value, existingKnob.defaultValue)) { - return getKnobValue(existingKnob); + return this.getKnobValue(existingKnob); } const defaultValue = options.value; @@ -38,7 +63,7 @@ export default class KnobManager { }; knobStore.set(name, knobInfo); - return getKnobValue(knobStore.get(name)); + return this.getKnobValue(knobStore.get(name)); } _mayCallChannel() { diff --git a/addons/knobs/src/angular/index.js b/addons/knobs/src/angular/index.js index 2a354c8161c1..688740ad83eb 100644 --- a/addons/knobs/src/angular/index.js +++ b/addons/knobs/src/angular/index.js @@ -1,10 +1,8 @@ -import addons from '@storybook/addons'; - import { prepareComponent } from './helpers'; import { knob, - escapedText as text, + text, boolean, number, color, @@ -15,7 +13,7 @@ import { selectV2, button, files, - manager, + makeDecorators, } from '../base'; export { knob, text, boolean, number, color, object, array, date, select, selectV2, button, files }; @@ -23,19 +21,4 @@ export { knob, text, boolean, number, color, object, array, date, select, select export const angularHandler = (channel, knobStore) => getStory => context => prepareComponent({ getStory, context, channel, knobStore }); -function wrapperKnobs(options) { - const channel = addons.getChannel(); - manager.setChannel(channel); - - if (options) channel.emit('addon:knobs:setOptions', options); - - return angularHandler(channel, manager.knobStore); -} - -export function withKnobs(storyFn, context) { - return wrapperKnobs()(storyFn)(context); -} - -export function withKnobsOptions(options = {}) { - return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); -} +export const { withKnobs, withKnobsOptions } = makeDecorators(angularHandler, { escapeHTML: true }); diff --git a/addons/knobs/src/base.js b/addons/knobs/src/base.js index 07ec59e8fc19..1343fc15b10f 100644 --- a/addons/knobs/src/base.js +++ b/addons/knobs/src/base.js @@ -1,4 +1,6 @@ import deprecate from 'util-deprecate'; +import addons from '@storybook/addons'; + import KnobManager from './KnobManager'; export const manager = new KnobManager(); @@ -7,12 +9,8 @@ export function knob(name, options) { return manager.knob(name, options); } -export function text(name, value, options = {}, groupId) { - return manager.knob(name, { type: 'text', value, options, groupId }); -} - -export function escapedText(name, value, options = {}, groupId) { - return text(name, value, { escapeHTML: true, ...options }, groupId); +export function text(name, value, groupId) { + return manager.knob(name, { type: 'text', value, groupId }); } export function boolean(name, value, groupId) { @@ -77,3 +75,25 @@ export function button(name, callback, groupId) { export function files(name, accept, value = []) { return manager.knob(name, { type: 'files', accept, value }); } + +export function makeDecorators(handler, defaultOptions = {}) { + function wrapperKnobs(options) { + const allOptions = { ...defaultOptions, ...options }; + + manager.setOptions(allOptions); + const channel = addons.getChannel(); + manager.setChannel(channel); + channel.emit('addon:knobs:setOptions', allOptions); + + return handler(channel, manager.knobStore); + } + + return { + withKnobs(storyFn, context) { + return wrapperKnobs()(storyFn)(context); + }, + withKnobsOptions(options = {}) { + return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); + }, + }; +} diff --git a/addons/knobs/src/components/types/Button.js b/addons/knobs/src/components/types/Button.js index 8335cfbab77c..7438ff9753dd 100644 --- a/addons/knobs/src/components/types/Button.js +++ b/addons/knobs/src/components/types/Button.js @@ -35,7 +35,7 @@ ButtonType.propTypes = { onClick: PropTypes.func.isRequired, }; -ButtonType.serialize = value => value; -ButtonType.deserialize = value => value; +ButtonType.serialize = () => undefined; +ButtonType.deserialize = () => undefined; export default ButtonType; diff --git a/addons/knobs/src/mithril/index.js b/addons/knobs/src/mithril/index.js index f2dbdd29c2ae..216049d1694c 100644 --- a/addons/knobs/src/mithril/index.js +++ b/addons/knobs/src/mithril/index.js @@ -2,7 +2,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies import m from 'mithril'; -import addons from '@storybook/addons'; import WrapStory from './WrapStory'; @@ -18,7 +17,7 @@ import { select, selectV2, button, - manager, + makeDecorators, } from '../base'; export { knob, text, boolean, number, color, object, array, date, select, selectV2, button }; @@ -31,19 +30,4 @@ export const mithrilHandler = (channel, knobStore) => getStory => context => { }; }; -function wrapperKnobs(options) { - const channel = addons.getChannel(); - manager.setChannel(channel); - - if (options) channel.emit('addon:knobs:setOptions', options); - - return mithrilHandler(channel, manager.knobStore); -} - -export function withKnobs(storyFn, context) { - return wrapperKnobs()(storyFn)(context); -} - -export function withKnobsOptions(options = {}) { - return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); -} +export const { withKnobs, withKnobsOptions } = makeDecorators(mithrilHandler); diff --git a/addons/knobs/src/polymer/index.js b/addons/knobs/src/polymer/index.js index fb19f65f636f..32df8b36a678 100644 --- a/addons/knobs/src/polymer/index.js +++ b/addons/knobs/src/polymer/index.js @@ -1,10 +1,9 @@ -import addons from '@storybook/addons'; import window from 'global'; import './WrapStory.html'; import { knob, - escapedText as text, + text, boolean, number, color, @@ -14,6 +13,7 @@ import { select, files, manager, + makeDecorators, } from '../base'; export { knob, text, boolean, number, color, object, array, date, select, files }; @@ -30,19 +30,4 @@ function prepareComponent({ getStory, context, channel, knobStore }) { export const polymerHandler = (channel, knobStore) => getStory => context => prepareComponent({ getStory, context, channel, knobStore }); -function wrapperKnobs(options) { - const channel = addons.getChannel(); - manager.setChannel(channel); - - if (options) channel.emit('addon:knobs:setOptions', options); - - return polymerHandler(channel, manager.knobStore); -} - -export function withKnobs(storyFn, context) { - return wrapperKnobs()(storyFn)(context); -} - -export function withKnobsOptions(options = {}) { - return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); -} +export const { withKnobs, withKnobsOptions } = makeDecorators(polymerHandler, { escapeHTML: true }); diff --git a/addons/knobs/src/react/index.js b/addons/knobs/src/react/index.js index b07851b30d79..109411497257 100644 --- a/addons/knobs/src/react/index.js +++ b/addons/knobs/src/react/index.js @@ -1,5 +1,4 @@ import React from 'react'; -import addons from '@storybook/addons'; import WrapStory from './WrapStory'; @@ -16,7 +15,7 @@ import { selectV2, button, files, - manager, + makeDecorators, } from '../base'; export { knob, text, boolean, number, color, object, array, date, select, selectV2, button, files }; @@ -27,19 +26,4 @@ export const reactHandler = (channel, knobStore) => getStory => context => { return ; }; -function wrapperKnobs(options) { - const channel = addons.getChannel(); - manager.setChannel(channel); - - if (options) channel.emit('addon:knobs:setOptions', options); - - return reactHandler(channel, manager.knobStore); -} - -export function withKnobs(storyFn, context) { - return wrapperKnobs()(storyFn)(context); -} - -export function withKnobsOptions(options = {}) { - return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); -} +export const { withKnobs, withKnobsOptions } = makeDecorators(reactHandler); diff --git a/addons/knobs/src/vue/index.js b/addons/knobs/src/vue/index.js index d80e9f3a3d92..df0049eee272 100644 --- a/addons/knobs/src/vue/index.js +++ b/addons/knobs/src/vue/index.js @@ -1,8 +1,6 @@ -import addons from '@storybook/addons'; - import { knob, - escapedText as text, + text, boolean, number, color, @@ -13,7 +11,7 @@ import { selectV2, button, files, - manager, + makeDecorators, } from '../base'; export { knob, text, boolean, number, color, object, array, date, select, selectV2, button, files }; @@ -74,19 +72,4 @@ export const vueHandler = (channel, knobStore) => getStory => context => ({ }, }); -function wrapperKnobs(options) { - const channel = addons.getChannel(); - manager.setChannel(channel); - - if (options) channel.emit('addon:knobs:setOptions', options); - - return vueHandler(channel, manager.knobStore); -} - -export function withKnobs(storyFn, context) { - return wrapperKnobs()(storyFn)(context); -} - -export function withKnobsOptions(options = {}) { - return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); -} +export const { withKnobs, withKnobsOptions } = makeDecorators(vueHandler, { escapeHTML: true });