diff --git a/.travis.yml b/.travis.yml index da8c805b..9149557c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,16 @@ language: node_js node_js: - '6.9' +cache: + directories: + - node_modules services: - docker branches: only: - master - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ +install: "npm i && npm update" before_deploy: - npm run build env: diff --git a/docs/version-detection.md b/docs/version-detection.md index 01bd4422..66277f71 100644 --- a/docs/version-detection.md +++ b/docs/version-detection.md @@ -20,8 +20,8 @@ Some distinct identifiers to Swagger-UI 3.X: If you've determined this is the version you have, to find the exact version: - Open your browser's web console (changes between browsers) -- Type `versions` in the console and execute the call. -- You might need to expand the result, until you get a string similar to `swaggerUi : Object { version: "3.1.6", gitRevision: "g786cd47", gitDirty: true, … }`. +- Type `JSON.stringify(versions)` in the console and execute the call. +- The result should look similar to `swaggerUi : Object { version: "3.1.6", gitRevision: "g786cd47", gitDirty: true, … }`. - The version taken from that example would be `3.1.6`. Note: This functionality was added in 3.0.8. If you're unable to execute it, you're likely to use an older version, and in that case the first step would be to upgrade. @@ -51,4 +51,4 @@ If you've determined this is the version you have, to find the exact version: * @link http://swagger.io * @license Apache-2.0 */ - ``` \ No newline at end of file + ``` diff --git a/package.json b/package.json index 50a0a51d..212ccd43 100644 --- a/package.json +++ b/package.json @@ -56,12 +56,12 @@ "memoizee": "0.4.1", "promise-worker": "^1.1.1", "prop-types": "^15.5.10", - "react": "^15.4.0", + "react": "^15.6.2", "react-addons-perf": "^15.4.0", "react-addons-shallow-compare": "0.14.8", - "react-addons-test-utils": "^15.4.0", + "react-addons-test-utils": "^15.6.2", "react-collapse": "2.3.1", - "react-dom": "^15.4.0", + "react-dom": "^15.6.2", "react-height": "^2.0.0", "react-hot-loader": "1.3.1", "react-immutable-proptypes": "2.1.0", @@ -84,6 +84,7 @@ "whatwg-fetch": "0.11.1", "worker-loader": "^0.7.1", "xml": "1.0.1", + "xml-but-prettier": "^1.0.1", "yaml-js": "0.2.0" }, "devDependencies": { diff --git a/src/core/components/array-model.jsx b/src/core/components/array-model.jsx index dae6645e..a68a3562 100644 --- a/src/core/components/array-model.jsx +++ b/src/core/components/array-model.jsx @@ -24,24 +24,23 @@ export default class ArrayModel extends Component { const Markdown = getComponent("Markdown") const ModelCollapse = getComponent("ModelCollapse") const Model = getComponent("Model") + const Property = getComponent("Property") const titleEl = title && { title } - /* + /* Note: we set `name={null}` in below because we don't want the name of the current Model passed (and displayed) as the name of the array element Model - */ + */ return expandDepth } collapsedContent="[...]"> [ { - properties.size ? properties.entrySeq().map( ( [ key, v ] ) => -
{ key }: { String(v) }
) - : null + properties.size ? properties.entrySeq().map( ( [ key, v ] ) => ) : null } { !description ? null : diff --git a/src/core/components/primitive-model.jsx b/src/core/components/primitive-model.jsx index f1fba8a5..c14413a9 100644 --- a/src/core/components/primitive-model.jsx +++ b/src/core/components/primitive-model.jsx @@ -28,6 +28,7 @@ export default class Primitive extends Component { let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 ) const Markdown = getComponent("Markdown") const EnumModel = getComponent("EnumModel") + const Property = getComponent("Property") return @@ -35,9 +36,7 @@ export default class Primitive extends Component { { type } { format && (${format})} { - properties.size ? properties.entrySeq().map( ( [ key, v ] ) => -
{ key }: { String(v) }
) - : null + properties.size ? properties.entrySeq().map( ( [ key, v ] ) => ) : null } { !description ? null : @@ -56,4 +55,4 @@ export default class Primitive extends Component {
} -} \ No newline at end of file +} diff --git a/src/core/components/property.jsx b/src/core/components/property.jsx new file mode 100644 index 00000000..0d129643 --- /dev/null +++ b/src/core/components/property.jsx @@ -0,0 +1,16 @@ +import React from "react" +import PropTypes from "prop-types" + +export const Property = ({ propKey, propVal, propStyle }) => { + return ( + +
{ propKey }: { String(propVal) }
+ ) +} +Property.propTypes = { + propKey: PropTypes.string, + propVal: PropTypes.any, + propStyle: PropTypes.object +} + +export default Property diff --git a/src/core/components/response-body.jsx b/src/core/components/response-body.jsx index c55c1d6d..e3c2aeb1 100644 --- a/src/core/components/response-body.jsx +++ b/src/core/components/response-body.jsx @@ -1,6 +1,6 @@ import React from "react" import PropTypes from "prop-types" -import { formatXml } from "core/utils" +import formatXml from "xml-but-prettier" import lowerCase from "lodash/lowerCase" export default class ResponseBody extends React.Component { @@ -31,7 +31,9 @@ export default class ResponseBody extends React.Component { // XML } else if (/xml/i.test(contentType)) { - body = formatXml(content) + body = formatXml(content, { + textNodesOnSameLine: true + }) bodyEl = // HTML or Plain Text diff --git a/src/core/index.js b/src/core/index.js index abc0ba36..76a0d705 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -58,13 +58,12 @@ module.exports = function SwaggerUI(opts) { plugins: [ ], + // Initial state + initialState: { }, + // Inline Plugin fn: { }, components: { }, - state: { }, - - // Override some core configs... at your own risk - store: { }, } let queryConfig = parseSearch() @@ -74,12 +73,12 @@ module.exports = function SwaggerUI(opts) { const constructorConfig = deepExtend({}, defaults, opts, queryConfig) - const storeConfigs = deepExtend({}, constructorConfig.store, { + const storeConfigs = { system: { configs: constructorConfig.configs }, plugins: constructorConfig.presets, - state: { + state: deepExtend({ layout: { layout: constructorConfig.layout, filter: constructorConfig.filter @@ -88,8 +87,8 @@ module.exports = function SwaggerUI(opts) { spec: "", url: constructorConfig.url } - } - }) + }, constructorConfig.initialState) + } let inlinePlugin = ()=> { return { diff --git a/src/core/plugins/oas3/components/http-auth.jsx b/src/core/plugins/oas3/components/http-auth.jsx index bb4727ee..9750f2dd 100644 --- a/src/core/plugins/oas3/components/http-auth.jsx +++ b/src/core/plugins/oas3/components/http-auth.jsx @@ -108,9 +108,6 @@ export default class HttpAuth extends React.Component { - -

In: { schema.get("in") }

-
{ diff --git a/src/core/plugins/oas3/spec-extensions/wrap-selectors.js b/src/core/plugins/oas3/spec-extensions/wrap-selectors.js index 21021251..d4808d0c 100644 --- a/src/core/plugins/oas3/spec-extensions/wrap-selectors.js +++ b/src/core/plugins/oas3/spec-extensions/wrap-selectors.js @@ -48,6 +48,10 @@ export const definitions = onlyOAS3(createSelector( spec => spec.getIn(["components", "schemas"]) || Map() )) +export const hasHost = onlyOAS3((state) => { + return spec(state).hasIn(["servers", 0]) +}) + export const securityDefinitions = onlyOAS3(createSelector( spec, spec => spec.getIn(["components", "securitySchemes"]) || null diff --git a/src/core/presets/base.js b/src/core/presets/base.js index ec005b37..d173336e 100644 --- a/src/core/presets/base.js +++ b/src/core/presets/base.js @@ -10,6 +10,7 @@ import auth from "core/plugins/auth" import util from "core/plugins/util" import SplitPaneModePlugin from "core/plugins/split-pane-mode" import downloadUrlPlugin from "core/plugins/download-url" +import configsPlugin from "plugins/configs" import deepLinkingPlugin from "core/plugins/deep-linking" import App from "core/components/app" @@ -52,6 +53,7 @@ import EnumModel from "core/components/enum-model" import ObjectModel from "core/components/object-model" import ArrayModel from "core/components/array-model" import PrimitiveModel from "core/components/primitive-model" +import Property from "core/components/property" import TryItOutButton from "core/components/try-it-out-button" import VersionStamp from "core/components/version-stamp" @@ -106,6 +108,7 @@ export default function() { ObjectModel, ArrayModel, PrimitiveModel, + Property, TryItOutButton, Markdown, BaseLayout, @@ -122,6 +125,7 @@ export default function() { } return [ + configsPlugin, util, logs, view, diff --git a/src/core/utils.js b/src/core/utils.js index 61cb6d2a..4595c798 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -155,83 +155,6 @@ export function getList(iterable, keys) { return Im.List.isList(val) ? val : Im.List() } -// Adapted from http://stackoverflow.com/a/2893259/454004 -// Note: directly ported from CoffeeScript -export function formatXml (xml) { - var contexp, fn, formatted, indent, l, lastType, len, lines, ln, reg, transitions, wsexp - reg = /(>)(<)(\/*)/g - wsexp = /[ ]*(.*)[ ]+\n/g - contexp = /(<.+>)(.+\n)/g - xml = xml.replace(/\r\n/g, "\n").replace(reg, "$1\n$2$3").replace(wsexp, "$1\n").replace(contexp, "$1\n$2") - formatted = "" - lines = xml.split("\n") - indent = 0 - lastType = "other" - transitions = { - "single->single": 0, - "single->closing": -1, - "single->opening": 0, - "single->other": 0, - "closing->single": 0, - "closing->closing": -1, - "closing->opening": 0, - "closing->other": 0, - "opening->single": 1, - "opening->closing": 0, - "opening->opening": 1, - "opening->other": 1, - "other->single": 0, - "other->closing": -1, - "other->opening": 0, - "other->other": 0 - } - fn = function(ln) { - var fromTo, key, padding, type, types, value - types = { - single: Boolean(ln.match(/<.+\/>/)), - closing: Boolean(ln.match(/<\/.+>/)), - opening: Boolean(ln.match(/<[^!?].*>/)) - } - type = ((function() { - var results - results = [] - for (key in types) { - value = types[key] - if (value) { - results.push(key) - } - } - return results - })())[0] - type = type === void 0 ? "other" : type - fromTo = lastType + "->" + type - lastType = type - padding = "" - indent += transitions[fromTo] - padding = ((function() { - /* eslint-disable no-unused-vars */ - var m, ref1, results, j - results = [] - for (j = m = 0, ref1 = indent; 0 <= ref1 ? m < ref1 : m > ref1; j = 0 <= ref1 ? ++m : --m) { - results.push(" ") - } - /* eslint-enable no-unused-vars */ - return results - })()).join("") - if (fromTo === "opening->closing") { - formatted = formatted.substr(0, formatted.length - 1) + ln + "\n" - } else { - formatted += padding + ln + "\n" - } - } - for (l = 0, len = lines.length; l < len; l++) { - ln = lines[l] - fn(ln) - } - return formatted -} - - /** * Adapted from http://github.com/asvd/microlight * @copyright 2016 asvd @@ -536,6 +459,13 @@ export const validateMinLength = (val, min) => { } } +export const validatePattern = (val, rxPattern) => { + var patt = new RegExp(rxPattern) + if (!patt.test(val)) { + return "Value must follow pattern " + rxPattern + } +} + // validation of parameters before execute export const validateParam = (param, isXml, isOAS3 = false) => { let errors = [] @@ -549,6 +479,8 @@ export const validateParam = (param, isXml, isOAS3 = false) => { let format = paramDetails.get("format") let maxLength = paramDetails.get("maxLength") let minLength = paramDetails.get("minLength") + let pattern = paramDetails.get("pattern") + /* If the parameter is required OR the parameter has a value (meaning optional, but filled in) @@ -570,6 +502,11 @@ export const validateParam = (param, isXml, isOAS3 = false) => { return errors } + if (pattern) { + let err = validatePattern(value, pattern) + if (err) errors.push(err) + } + if (maxLength || maxLength === 0) { let err = validateMaxLength(value, maxLength) if (err) errors.push(err) diff --git a/src/plugins/configs/actions.js b/src/plugins/configs/actions.js new file mode 100644 index 00000000..70588372 --- /dev/null +++ b/src/plugins/configs/actions.js @@ -0,0 +1,20 @@ +export const UPDATE_CONFIGS = "configs_update" +export const TOGGLE_CONFIGS = "configs_toggle" + +// Update the configs, with a merge ( not deep ) +export function update(configName, configValue) { + return { + type: UPDATE_CONFIGS, + payload: { + [configName]: configValue + }, + } +} + +// Toggle's the config, by name +export function toggle(configName) { + return { + type: TOGGLE_CONFIGS, + payload: configName, + } +} diff --git a/src/plugins/configs/index.js b/src/plugins/configs/index.js index 2269d873..58edbfa1 100644 --- a/src/plugins/configs/index.js +++ b/src/plugins/configs/index.js @@ -1,56 +1,66 @@ import YAML from "js-yaml" import yamlConfig from "../../../swagger-config.yaml" +import * as actions from "./actions" +import * as selectors from "./selectors" +import reducers from "./reducers" const parseYamlConfig = (yaml, system) => { - try { - return YAML.safeLoad(yaml) - } catch(e) { - if (system) { - system.errActions.newThrownErr( new Error(e) ) - } - return {} + try { + return YAML.safeLoad(yaml) + } catch(e) { + if (system) { + system.errActions.newThrownErr( new Error(e) ) } + return {} + } } -export default function configPlugin (toolbox) { - let { fn } = toolbox - - const actions = { - downloadConfig: (url) => () => { - let {fetch} = fn - return fetch(url) - }, - - getConfigByUrl: (configUrl, cb)=> ({ specActions }) => { - if (configUrl) { - return specActions.downloadConfig(configUrl).then(next, next) - } - - function next(res) { - if (res instanceof Error || res.status >= 400) { - specActions.updateLoadingStatus("failedConfig") - specActions.updateLoadingStatus("failedConfig") - specActions.updateUrl("") - console.error(res.statusText + " " + configUrl) - cb(null) - } else { - cb(parseYamlConfig(res.text)) - } - } - } +const specActions = { + downloadConfig: (url) => ({fn}) => { + let {fetch} = fn + return fetch(url) + }, + getConfigByUrl: (configUrl, cb)=> ({ specActions }) => { + if (configUrl) { + return specActions.downloadConfig(configUrl).then(next, next) } - const selectors = { - getLocalConfig: () => { - return parseYamlConfig(yamlConfig) - } - } - - return { - statePlugins: { - spec: { actions, selectors } - } + function next(res) { + if (res instanceof Error || res.status >= 400) { + specActions.updateLoadingStatus("failedConfig") + specActions.updateLoadingStatus("failedConfig") + specActions.updateUrl("") + console.error(res.statusText + " " + configUrl) + cb(null) + } else { + cb(parseYamlConfig(res.text)) + } } + } +} + +const specSelectors = { + getLocalConfig: () => { + return parseYamlConfig(yamlConfig) + } +} + + +export default function configsPlugin() { + + return { + statePlugins: { + spec: { + actions: specActions, + selectors: specSelectors, + }, + configs: { + reducers, + actions, + selectors, + } + } + } } diff --git a/src/plugins/configs/reducers.js b/src/plugins/configs/reducers.js new file mode 100644 index 00000000..5b38931d --- /dev/null +++ b/src/plugins/configs/reducers.js @@ -0,0 +1,20 @@ +import { fromJS } from "immutable" + +import { + UPDATE_CONFIGS, + TOGGLE_CONFIGS, +} from "./actions" + +export default { + + [UPDATE_CONFIGS]: (state, action) => { + return state.merge(fromJS(action.payload)) + }, + + [TOGGLE_CONFIGS]: (state, action) => { + const configName = action.payload + const oriVal = state.get(configName) + return state.set(configName, !oriVal) + }, + +} diff --git a/src/plugins/configs/selectors.js b/src/plugins/configs/selectors.js new file mode 100644 index 00000000..dcb6f670 --- /dev/null +++ b/src/plugins/configs/selectors.js @@ -0,0 +1,4 @@ +// Just get the config value ( it can possibly be an immutable object) +export const get = (state, path) => { + return state.getIn(Array.isArray(path) ? path : [path]) +} diff --git a/src/style/_authorize.scss b/src/style/_authorize.scss index d09fb8fd..55ecd01e 100644 --- a/src/style/_authorize.scss +++ b/src/style/_authorize.scss @@ -25,7 +25,7 @@ margin: 0 0 10px 0; padding: 10px 20px; - border-bottom: 1px solid #ebebeb; + border-bottom: 1px solid $auth-container-border-color; &:last-of-type { diff --git a/src/style/_buttons.scss b/src/style/_buttons.scss index 5450c814..8ebc7507 100644 --- a/src/style/_buttons.scss +++ b/src/style/_buttons.scss @@ -7,10 +7,10 @@ transition: all .3s; - border: 2px solid #888; + border: 2px solid $btn-border-color; border-radius: 4px; background: transparent; - box-shadow: 0 1px 2px rgba(#000,.1); + box-shadow: 0 1px 2px rgba($btn-box-shadow-color,.1); @include text_headline(); @@ -29,14 +29,14 @@ &:hover { - box-shadow: 0 0 5px rgba(#000,.3); + box-shadow: 0 0 5px rgba($btn-box-shadow-color,.3); } &.cancel { - border-color: #ff6060; - - @include text_headline(#ff6060); + border-color: $btn-cancel-border-color; + background-color: $btn-cancel-background-color; + @include text_headline($btn-cancel-font-color); } &.authorize @@ -45,9 +45,9 @@ display: inline; - color: $_color-post; - border-color: $_color-post; - + color: $btn-authorize-font-color; + border-color: $btn-authorize-border-color; + background-color: $btn-authorize-background-color; span { @@ -58,16 +58,17 @@ svg { - fill: $_color-post; + fill: $btn-authorize-svg-fill-color; } } &.execute { animation: swagger-ui-pulse 2s infinite; - - color: #fff; - border-color: #4990e2; + will-change: transform; + background-color: $btn-execute-background-color; + color: $btn-execute-font-color; + border-color: $btn-execute-border-color; } } @@ -76,21 +77,19 @@ { 0% { - color: #fff; - background: #4990e2; - box-shadow: 0 0 0 0 rgba(#4990e2, .8); + color: $btn-execute-font-color; + background: $btn-execute-background-color-alt; + box-shadow: 0 0 0 0 rgba($btn-execute-background-color-alt, .8); } 70% { - //color: #4990e2; - //background: transparent; - box-shadow: 0 0 0 5px rgba(#4990e2, 0); + box-shadow: 0 0 0 5px rgba($btn-execute-background-color-alt, 0); } 100% { - color: #fff; - background: #4990e2; - box-shadow: 0 0 0 0 rgba(#4990e2, 0); + color: $btn-execute-font-color; + background: $btn-execute-background-color-alt; + box-shadow: 0 0 0 0 rgba($btn-execute-background-color-alt, 0); } } @@ -155,7 +154,7 @@ { svg { - fill: #444; + fill: $expand-methods-svg-fill-color-hover; } } @@ -163,7 +162,7 @@ { transition: all .3s; - fill: #777; + fill: $expand-methods-svg-fill-color; } } diff --git a/src/style/_errors.scss b/src/style/_errors.scss index 1bba8652..05fe47db 100644 --- a/src/style/_errors.scss +++ b/src/style/_errors.scss @@ -27,7 +27,7 @@ small { - color: #666; + color: $errors-wrapper-errors-small-font-color; } } diff --git a/src/style/_form.scss b/src/style/_form.scss index 961ec7d7..c2c4ea24 100644 --- a/src/style/_form.scss +++ b/src/style/_form.scss @@ -5,11 +5,11 @@ select padding: 5px 40px 5px 10px; - border: 2px solid #41444e; + border: 2px solid $form-select-border-color; border-radius: 4px; - background: #f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat; + background: $form-select-background-color url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat; background-size: 20px; - box-shadow: 0 1px 2px 0 rgba(0,0,0,.25); + box-shadow: 0 1px 2px 0 rgba($form-select-box-shadow-color, .25); @include text_headline(); appearance: none; @@ -19,7 +19,7 @@ select margin: 5px 0; padding: 5px; - background: #f7f7f7; + background: $form-select-background-color; } &.invalid { @@ -57,9 +57,9 @@ input[type=file] margin: 5px 0; padding: 8px 10px; - border: 1px solid #d9d9d9; + border: 1px solid $form-input-border-color; border-radius: 4px; - background: #fff; + background: $form-input-background-color; @media (max-width: 768px) { max-width: 175px; } @@ -110,13 +110,13 @@ textarea border: none; border-radius: 4px; outline: none; - background: rgba(#fff,.8); + background: rgba($form-textarea-background-color,.8); @include text_code(); &:focus { - border: 2px solid $_color-get; + border: 2px solid $form-textarea-focus-border-color; } &.curl @@ -130,9 +130,9 @@ textarea resize: none; border-radius: 4px; - background: #41444e; + background: $form-textarea-curl-background-color; - @include text_code(#fff); + @include text_code($form-textarea-curl-font-color); } } @@ -143,7 +143,7 @@ textarea transition: opacity .5s; - color: #333; + color: $form-checkbox-label-font-color; label { @@ -179,8 +179,8 @@ textarea cursor: pointer; border-radius: 1px; - background: #e8e8e8; - box-shadow: 0 0 0 2px #e8e8e8; + background: $form-checkbox-background-color; + box-shadow: 0 0 0 2px $form-checkbox-box-shadow-color; flex: none; @@ -192,7 +192,7 @@ textarea &:checked + label > .item { - background: #e8e8e8 url(data:image/svg+xml,%0A%3Csvg%20width%3D%2210px%22%20height%3D%228px%22%20viewBox%3D%223%207%2010%208%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2042%20%2836781%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cpolygon%20id%3D%22Rectangle-34%22%20stroke%3D%22none%22%20fill%3D%22%2341474E%22%20fill-rule%3D%22evenodd%22%20points%3D%226.33333333%2015%203%2011.6666667%204.33333333%2010.3333333%206.33333333%2012.3333333%2011.6666667%207%2013%208.33333333%22%3E%3C/polygon%3E%0A%3C/svg%3E) center center no-repeat; + background: $form-checkbox-background-color url(data:image/svg+xml,%0A%3Csvg%20width%3D%2210px%22%20height%3D%228px%22%20viewBox%3D%223%207%2010%208%22%20version%3D%221.1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%3E%0A%20%20%20%20%3C%21--%20Generator%3A%20Sketch%2042%20%2836781%29%20-%20http%3A//www.bohemiancoding.com/sketch%20--%3E%0A%20%20%20%20%3Cdesc%3ECreated%20with%20Sketch.%3C/desc%3E%0A%20%20%20%20%3Cdefs%3E%3C/defs%3E%0A%20%20%20%20%3Cpolygon%20id%3D%22Rectangle-34%22%20stroke%3D%22none%22%20fill%3D%22%2341474E%22%20fill-rule%3D%22evenodd%22%20points%3D%226.33333333%2015%203%2011.6666667%204.33333333%2010.3333333%206.33333333%2012.3333333%2011.6666667%207%2013%208.33333333%22%3E%3C/polygon%3E%0A%3C/svg%3E) center center no-repeat; } } } diff --git a/src/style/_information.scss b/src/style/_information.scss index 459e1c04..5909d171 100644 --- a/src/style/_information.scss +++ b/src/style/_information.scss @@ -30,9 +30,9 @@ padding: 3px 5px; border-radius: 4px; - background: rgba(#000,.05); + background: rgba($info-code-background-color,.05); - @include text_code(#9012fe); + @include text_code($info-code-font-color); } a @@ -41,11 +41,11 @@ transition: all .4s; - @include text_body(#4990e2); + @include text_body($info-link-font-color); &:hover { - color: darken(#4990e2, 15%); + color: darken($info-link-font-color-hover, 15%); } } > div @@ -86,13 +86,13 @@ vertical-align: super; border-radius: 57px; - background: #7d8492; + background: $info-title-small-background-color; pre { margin: 0; - @include text_headline(#fff); + @include text_headline($info-title-small-pre-font-color); } } } diff --git a/src/style/_layout.scss b/src/style/_layout.scss index 5c0c9a1f..327c7633 100644 --- a/src/style/_layout.scss +++ b/src/style/_layout.scss @@ -34,11 +34,11 @@ cursor: pointer; transition: all .2s; - border-bottom: 1px solid rgba(#3b4151, .3); + border-bottom: 1px solid rgba($opblock-tag-border-bottom-color, .3); &:hover { - background: rgba(#000,.02); + background: rgba($opblock-tag-background-color-hover,.02); } } @@ -127,9 +127,9 @@ { margin: 0 0 15px 0; - border: 1px solid #000; + border: 1px solid $opblock-border-color; border-radius: 4px; - box-shadow: 0 0 3px rgba(#000,.19); + box-shadow: 0 0 3px rgba($opblock-box-shadow-color,.19); .tab-header { @@ -168,7 +168,7 @@ content: ''; transform: translateX(-50%); - background: #888; + background: $opblock-tab-header-tab-item-active-h4-span-after-background-color; } } } @@ -181,7 +181,7 @@ { .opblock-summary { - border-bottom: 1px solid #000; + border-bottom: 1px solid $opblock-isopen-summary-border-bottom-color; } } @@ -194,8 +194,8 @@ min-height: 50px; - background: rgba(#fff,.8); - box-shadow: 0 1px 2px rgba(#000,.1); + background: rgba($opblock-isopen-section-header-background-color,.8); + box-shadow: 0 1px 2px rgba($opblock-isopen-section-header-box-shadow-color,.1); label { @@ -239,10 +239,10 @@ text-align: center; border-radius: 3px; - background: #000; - text-shadow: 0 1px 0 rgba(#000,.1); + background: $opblock-summary-method-background-color; + text-shadow: 0 1px 0 rgba($opblock-summary-method-text-shadow-color,.1); - @include text_headline(#fff); + @include text_headline($opblock-summary-method-font-color); } .opblock-summary-path, @@ -377,7 +377,7 @@ margin: 20px 0; padding: 10px 10px; - border: 2px solid #d8dde7; + border: 2px solid $operational-filter-input-border-color; } } @@ -420,7 +420,7 @@ content: ''; - background: rgba(#000,.2); + background: rgba($tab-list-item-first-background-color,.2); } } @@ -525,7 +525,7 @@ { font-size: 11px; - @include text_code(#999); + @include text_code($response-col-status-undocumented-font-color); } } @@ -541,7 +541,7 @@ { font-size: 11px; - @include text_code(#999); + @include text_code($response-col-links-font-color); } } @@ -558,9 +558,9 @@ padding: 10px; border-radius: 4px; - background: #41444e; + background: $response-col-description-inner-markdown-background-color; - @include text_code(#fff); + @include text_code($response-col-description-inner-markdown-font-color); p { @@ -569,10 +569,10 @@ a { - @include text_code(#89bf04); + @include text_code($response-col-description-inner-markdown-link-font-color); text-decoration: underline; &:hover { - color: #81b10c; + color: $response-col-description-inner-markdown-link-font-color-hover; } } } @@ -593,13 +593,13 @@ hyphens: auto; border-radius: 4px; - background: #41444e; + background: $opblock-body-background-color; overflow-wrap: break-word; - @include text_code(#fff); + @include text_code($opblock-body-font-color); span { - color: #fff !important; + color: $opblock-body-font-color !important; } .headerline @@ -613,8 +613,8 @@ margin: 0 0 20px 0; padding: 30px 0; - background: #fff; - box-shadow: 0 1px 2px 0 rgba(0,0,0,.15); + background: $scheme-container-background-color; + box-shadow: 0 1px 2px 0 rgba($scheme-container-box-shadow-color,.15); .schemes { @@ -648,14 +648,14 @@ margin: 0 0 20px 0; padding: 30px 0; - background: #fff; - box-shadow: 0 1px 2px 0 rgba(0,0,0,.15); + background: $server-container-background-color; + box-shadow: 0 1px 2px 0 rgba($server-container-box-shadow-color,.15); .computed-url { margin: 2em 0; code { - color: grey; + color: $server-container-computed-url-code-font-color; display: inline-block; padding: 4px; font-size: 16px; @@ -755,8 +755,8 @@ animation: rotation 1s infinite linear, opacity .5s; opacity: 1; - border: 2px solid rgba(#555, .1); - border-top-color: rgba(#000, .6); + border: 2px solid rgba($loading-container-before-border-color, .1); + border-top-color: rgba($loading-container-before-border-top-color, .6); border-radius: 100%; backface-visibility: hidden; @@ -777,11 +777,11 @@ &.controls-accept-header { select { - border-color: green; + border-color: $response-content-type-controls-accept-header-select-border-color; } small { - color: green; + color: $response-content-type-controls-accept-header-small-font-color; font-size: .7em; } } diff --git a/src/style/_modal.scss b/src/style/_modal.scss index 4e13a21e..374365f6 100644 --- a/src/style/_modal.scss +++ b/src/style/_modal.scss @@ -15,7 +15,7 @@ bottom: 0; left: 0; - background: rgba(#000,.8); + background: rgba($dialog-ux-backdrop-background-color,.8); } .modal-ux @@ -31,10 +31,10 @@ transform: translate(-50%,-50%); - border: 1px solid #ebebeb; + border: 1px solid $dialog-ux-modal-border-color; border-radius: 4px; - background: #fff; - box-shadow: 0 10px 30px 0 rgba(0,0,0,.20); + background: $dialog-ux-modal-background-color; + box-shadow: 0 10px 30px 0 rgba($dialog-ux-modal-box-shadow-color,.20); } .modal-ux-content @@ -50,7 +50,7 @@ margin: 0 0 5px 0; - color: #41444e; + color: $dialog-ux-modal-content-font-color; @include text_body(); } @@ -72,7 +72,7 @@ padding: 12px 0; - border-bottom: 1px solid #ebebeb; + border-bottom: 1px solid $dialog-ux-modal-header-border-bottom-color; align-items: center; diff --git a/src/style/_models.scss b/src/style/_models.scss index c3a901d9..23b349d6 100644 --- a/src/style/_models.scss +++ b/src/style/_models.scss @@ -6,7 +6,7 @@ .deprecated { span, td { - color: #aaa !important; + color: $model-deprecated-font-color !important; } } @@ -82,9 +82,9 @@ white-space: nowrap; - color: #ebebeb; + color: $model-hint-font-color; border-radius: 4px; - background: rgba(#000,.7); + background: rgba($model-hint-background-color,.7); } p { @@ -97,7 +97,7 @@ section.models { margin: 30px 0; - border: 1px solid rgba(#3b4151, .3); + border: 1px solid rgba($section-models-border-color, .3); border-radius: 4px; &.is-open @@ -106,7 +106,7 @@ section.models h4 { margin: 0 0 5px 0; - border-bottom: 1px solid rgba(#3b4151, .3); + border-bottom: 1px solid rgba($section-models-isopen-h4-border-bottom-color, .3); } } h4 @@ -121,7 +121,7 @@ section.models cursor: pointer; transition: all .2s; - @include text_headline(#777); + @include text_headline($section-models-h4-font-color); align-items: center; svg @@ -136,7 +136,7 @@ section.models &:hover { - background: rgba(#000,.02); + background: rgba($section-models-h4-background-color-hover,.02); } } @@ -146,7 +146,7 @@ section.models margin: 0 0 10px 0; - @include text_headline(#777); + @include text_headline($section-models-h5-font-color); } .model-jump-to-path @@ -162,11 +162,11 @@ section.models transition: all .5s; border-radius: 4px; - background: rgba(#000,.05); + background: rgba($section-models-model-container-background-color,.05); &:hover { - background: rgba(#000,.07); + background: rgba($section-models-model-container-background-color,.07); } &:first-of-type @@ -192,7 +192,7 @@ section.models padding: 10px; border-radius: 4px; - background: rgba(#000,.1); + background: rgba($section-models-model-box-background-color,.1); .model-jump-to-path { @@ -211,7 +211,7 @@ section.models { font-size: 16px; - @include text_headline(#555); + @include text_headline($section-models-model-title-font-color); } .model-deprecated-warning @@ -243,7 +243,7 @@ span .prop-type { - color: #55a; + color: $prop-type-font-color; } .prop-enum @@ -252,5 +252,5 @@ span } .prop-format { - color: #999; + color: $prop-format-font-color; } diff --git a/src/style/_table.scss b/src/style/_table.scss index 7f065529..02dd92ba 100644 --- a/src/style/_table.scss +++ b/src/style/_table.scss @@ -74,7 +74,7 @@ table text-align: left; - border-bottom: 1px solid rgba(#3b4151, .2); + border-bottom: 1px solid rgba($table-thead-td-border-bottom-color, .2); @include text_body(); } @@ -126,7 +126,7 @@ table content: 'required'; - color: rgba(#f00, .6); + color: rgba($table-parameter-name-required-font-color, .6); } } } @@ -136,7 +136,7 @@ table font-size: 12px; font-style: italic; - @include text_code(#888); + @include text_code($table-parameter-in-font-color); } .parameter__deprecated @@ -144,7 +144,7 @@ table font-size: 12px; font-style: italic; - @include text_code(#f00); + @include text_code($table-parameter-deprecated-font-color); } diff --git a/src/style/_topbar.scss b/src/style/_topbar.scss index 653598b6..f9f5bfbf 100644 --- a/src/style/_topbar.scss +++ b/src/style/_topbar.scss @@ -2,7 +2,7 @@ { padding: 8px 0; - background-color: #89bf04; + background-color: $topbar-background-color; .topbar-wrapper { display: flex; @@ -21,7 +21,7 @@ text-decoration: none; - @include text_headline(#fff); + @include text_headline($topbar-link-font-color); span { @@ -41,7 +41,7 @@ width: 100%; margin: 0; - border: 2px solid #547f00; + border: 2px solid $topbar-download-url-wrapper-element-border-color; border-radius: 4px 0 0 4px; outline: none; } @@ -71,7 +71,7 @@ width: 100%; - border: 2px solid #547f00; + border: 2px solid $topbar-download-url-wrapper-element-border-color; outline: none; box-shadow: none; } @@ -87,9 +87,9 @@ border: none; border-radius: 0 4px 4px 0; - background: #547f00; + background: $topbar-download-url-button-background-color; - @include text_headline(#fff); + @include text_headline($topbar-download-url-button-font-color); } } } diff --git a/src/style/_type.scss b/src/style/_type.scss index 16c08df6..cf66896f 100644 --- a/src/style/_type.scss +++ b/src/style/_type.scss @@ -1,11 +1,11 @@ -@mixin text_body($color: #3b4151) +@mixin text_body($color: $text-body-default-font-color) { font-family: 'Open Sans', sans-serif; color: $color; } -@mixin text_code($color: #3b4151) +@mixin text_code($color: $text-code-default-font-color) { font-family: 'Source Code Pro', monospace; font-weight: 600; @@ -13,7 +13,7 @@ color: $color; } -@mixin text_headline($color: #3b4151) +@mixin text_headline($color: $text-headline-default-font-color) { font-family: 'Titillium Web', sans-serif; diff --git a/src/style/_variables.scss b/src/style/_variables.scss index e69de29b..f5e6a697 100644 --- a/src/style/_variables.scss +++ b/src/style/_variables.scss @@ -0,0 +1,218 @@ + +$gray-base: #000 !default; +$white: #fff !default; +$gray-50: #ebebeb !default; +$gray-100: #d8dde7 !default; +$gray-200: lighten($gray-base, 62.75%) !default; // #aaa +$gray-300: lighten($gray-base, 56.5%) !default; // #999 +$gray-400: lighten($gray-base, 50%) !default; // #888 +$gray-500: lighten($gray-base, 43.75%) !default; // #777 +$gray-600: lighten($gray-base, 37.5%) !default; // #666 +$gray-650: lighten($gray-base, 33.3%) !default; // ##555555 +$gray-700: lighten($gray-base, 31.25%) !default; // #555 +$gray-800: lighten($gray-base, 25%) !default; // #444 +$gray-900: lighten($gray-base, 18.75%) !default; // #333 + +$gray-custom-1: #41444e !default; +$gray-custom-2: #3b4151 !default; + +$color-primary: #89bf04 !default; +$color-secondary: #9012fe !default; +$color-info: #4990e2 !default; +$color-warning: #ff6060 !default; +$color-danger: #f00 !default; + +$_color-post: #49cc90 !default; +$_color-get: #61affe !default; +$_color-put: #fca130 !default; +$_color-delete: #f93e3e !default; +$_color-head: #9012fe !default; +$_color-patch: #50e3c2 !default; +$_color-disabled: #ebebeb !default; +$_color-options: #0d5aa7 !default; + +$color-green: #008000 !default; + +$color-primary-hover: #81b10c !default; + +// Authorize + +$auth-container-border-color: $gray-50 !default; + +// Buttons + +$btn-background-color: transparent !default; +$btn-border-color: $gray-400 !default; +$btn-font-color: inherit !default; +$btn-box-shadow-color: $gray-base !default; + +$btn-authorize-background-color: transparent !default; +$btn-authorize-border-color: $_color-post !default; +$btn-authorize-font-color: $_color-post !default; +$btn-authorize-svg-fill-color: $_color-post !default; + +$btn-cancel-background-color: transparent !default; +$btn-cancel-border-color: $color-warning !default; +$btn-cancel-font-color: $color-warning !default; + +$btn-execute-background-color: transparent !default; +$btn-execute-border-color: $color-info !default; +$btn-execute-font-color: $white !default; +$btn-execute-background-color-alt: $color-info !default; + +$expand-methods-svg-fill-color: $gray-500 !default; +$expand-methods-svg-fill-color-hover: $gray-800 !default; + +// Errors + +$errors-wrapper-background-color: $_color-delete !default; +$errors-wrapper-border-color: $_color-delete !default; + +$errors-wrapper-errors-small-font-color: $gray-600 !default; + +// Form + +$form-select-background-color: #f7f7f7 !default; +$form-select-border-color: $gray-custom-1 !default; +$form-select-box-shadow-color: $gray-base !default; + +$form-input-border-color: #d9d9d9 !default; +$form-input-background-color: $white !default; + +$form-textarea-background-color: $white !default; +$form-textarea-focus-border-color: $_color-get !default; + +$form-textarea-curl-background-color: $gray-custom-1 !default; +$form-textarea-curl-font-color: $white !default; + +$form-checkbox-label-font-color: $gray-900 !default; +$form-checkbox-background-color: #e8e8e8 !default; +$form-checkbox-box-shadow-color: #e8e8e8 !default; + +// Information + +$info-code-background-color: $gray-base !default; +$info-code-font-color: $_color-head !default; + +$info-link-font-color: $color-info !default; +$info-link-font-color-hover: $info-link-font-color !default; + +$info-title-small-background-color: #7d8492 !default; + +$info-title-small-pre-font-color: $white !default; + +// Layout + +$opblock-border-color: $gray-base !default; +$opblock-box-shadow-color: $gray-base !default; + +$opblock-tag-border-bottom-color: $gray-custom-2 !default; +$opblock-tag-background-color-hover: $gray-base !default; + +$opblock-tab-header-tab-item-active-h4-span-after-background-color: $gray-400 !default; + +$opblock-isopen-summary-border-bottom-color: $gray-base !default; + +$opblock-isopen-section-header-background-color: $white !default; +$opblock-isopen-section-header-box-shadow-color: $gray-base !default; + +$opblock-summary-method-background-color: $gray-base !default; +$opblock-summary-method-font-color: $white !default; +$opblock-summary-method-text-shadow-color: $gray-base !default; + +$operational-filter-input-border-color: #d8dde7 !default; + +$tab-list-item-first-background-color: $gray-base !default; + +$response-col-status-undocumented-font-color: $gray-300 !default; + +$response-col-links-font-color: $gray-300 !default; + +$response-col-description-inner-markdown-font-color: $white !default; +$response-col-description-inner-markdown-background-color: $gray-custom-1 !default; + +$response-col-description-inner-markdown-link-font-color: $color-primary !default; +$response-col-description-inner-markdown-link-font-color-hover: $color-primary-hover !default; + +$opblock-body-background-color: $gray-custom-1 !default; +$opblock-body-font-color: $white !default; + +$scheme-container-background-color: $white !default; +$scheme-container-box-shadow-color: $gray-base !default; + +$server-container-background-color: $white !default; +$server-container-box-shadow-color: $gray-base !default; + +$server-container-computed-url-code-font-color: $gray-400 !default; + +$loading-container-before-border-color: $gray-650 !default; +$loading-container-before-border-top-color: $gray-base !default; + +$response-content-type-controls-accept-header-select-border-color: $color-green !default; +$response-content-type-controls-accept-header-small-font-color: $color-green !default; + +// Modal + +$dialog-ux-backdrop-background-color: $gray-base !default; + + +$dialog-ux-modal-background-color: $white !default; +$dialog-ux-modal-border-color: $gray-50 !default; +$dialog-ux-modal-box-shadow-color: $gray-base !default; + +$dialog-ux-modal-content-font-color: $gray-custom-1 !default; + +$dialog-ux-modal-header-border-bottom-color: $gray-50 !default; + +// Models + +$model-deprecated-font-color: $gray-200 !default; + +$model-hint-font-color: $gray-50 !default; +$model-hint-background-color: $gray-base !default; + +$section-models-border-color: $gray-custom-2 !default; + +$section-models-isopen-h4-border-bottom-color: $section-models-border-color !default; + +$section-models-h4-font-color: $gray-500 !default; +$section-models-h4-background-color-hover: $gray-base !default; + +$section-models-h5-font-color: $gray-500 !default; + +$section-models-model-container-background-color: $gray-base !default; + +$section-models-model-box-background-color: $gray-base !default; + +$section-models-model-title-font-color: $gray-700 !default; + +$prop-type-font-color: #55a !default; + +$prop-format-font-color: $gray-300 !default; + +// Tables + +$table-thead-td-border-bottom-color: $gray-custom-2 !default; + +$table-parameter-name-required-font-color: $color-danger !default; + +$table-parameter-in-font-color: $gray-400 !default; + +$table-parameter-deprecated-font-color: $color-danger !default; + +// Topbar + +$topbar-background-color: $color-primary !default; + +$topbar-link-font-color: $white !default; + +$topbar-download-url-wrapper-element-border-color: #547f00 !default; + +$topbar-download-url-button-background-color: #547f00 !default; +$topbar-download-url-button-font-color: $white !default; + +// Type + +$text-body-default-font-color: $gray-custom-2 !default; +$text-code-default-font-color: $gray-custom-2 !default; +$text-headline-default-font-color: $gray-custom-2 !default; diff --git a/test/core/utils.js b/test/core/utils.js index 36e60a7a..918d8431 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -3,6 +3,7 @@ import expect from "expect" import { fromJS, OrderedMap } from "immutable" import { mapToList, + validatePattern, validateMinLength, validateMaxLength, validateDateTime, @@ -216,9 +217,9 @@ describe("utils", function() { expect(validateFile(1)).toEqual(errorMessage) expect(validateFile("string")).toEqual(errorMessage) }) - }) + }) - describe("validateDateTime", function() { + describe("validateDateTime", function() { let errorMessage = "Value must be a DateTime" it("doesn't return for valid dates", function() { @@ -229,7 +230,7 @@ describe("utils", function() { expect(validateDateTime(null)).toEqual(errorMessage) expect(validateDateTime("string")).toEqual(errorMessage) }) - }) + }) describe("validateGuid", function() { let errorMessage = "Value must be a Guid" @@ -243,9 +244,9 @@ describe("utils", function() { expect(validateGuid(1)).toEqual(errorMessage) expect(validateGuid("string")).toEqual(errorMessage) }) - }) + }) - describe("validateMaxLength", function() { + describe("validateMaxLength", function() { let errorMessage = "Value must be less than MaxLength" it("doesn't return for valid guid", function() { @@ -258,9 +259,9 @@ describe("utils", function() { expect(validateMaxLength("abc", 1)).toEqual(errorMessage) expect(validateMaxLength("abc", 2)).toEqual(errorMessage) }) - }) + }) - describe("validateMinLength", function() { + describe("validateMinLength", function() { let errorMessage = "Value must be greater than MinLength" it("doesn't return for valid guid", function() { @@ -272,7 +273,29 @@ describe("utils", function() { expect(validateMinLength("abc", 5)).toEqual(errorMessage) expect(validateMinLength("abc", 8)).toEqual(errorMessage) }) - }) + }) + + describe("validatePattern", function() { + let rxPattern = "^(red|blue)" + let errorMessage = "Value must follow pattern " + rxPattern + + it("doesn't return for a match", function() { + expect(validatePattern("red", rxPattern)).toBeFalsy() + expect(validatePattern("blue", rxPattern)).toBeFalsy() + }) + + it("returns a message for invalid pattern", function() { + expect(validatePattern("pink", rxPattern)).toEqual(errorMessage) + expect(validatePattern("123", rxPattern)).toEqual(errorMessage) + }) + + it("fails gracefully when an invalid regex value is passed", function() { + expect(() => validatePattern("aValue", "---")).toNotThrow() + expect(() => validatePattern("aValue", 1234)).toNotThrow() + expect(() => validatePattern("aValue", null)).toNotThrow() + expect(() => validatePattern("aValue", [])).toNotThrow() + }) + }) describe("validateParam", function() { let param = null @@ -925,4 +948,5 @@ sbG8iKTs8L3NjcmlwdD4=`) expect(sanitizeUrl({})).toEqual("") }) }) + })