This removes substitution of \r\n, \r, \n to <br>, since the subsequent set of renderGFM() gets confused by them. Furthermore, the marked module already does a decent job of substituting \n with proper HTML (<p>, if I'm not mistaken).
627 lines
20 KiB
JavaScript
627 lines
20 KiB
JavaScript
'use strict';
|
|
|
|
SwaggerUi.Views.OperationView = Backbone.View.extend({
|
|
invocationUrl: null,
|
|
|
|
events: {
|
|
'submit .sandbox' : 'submitOperation',
|
|
'click .submit' : 'submitOperation',
|
|
'click .response_hider' : 'hideResponse',
|
|
'click .toggleOperation' : 'toggleOperationContent',
|
|
'mouseenter .api-ic' : 'mouseEnter',
|
|
'mouseout .api-ic' : 'mouseExit',
|
|
},
|
|
|
|
initialize: function(opts) {
|
|
opts = opts || {};
|
|
this.router = opts.router;
|
|
this.auths = opts.auths;
|
|
this.parentId = this.model.parentId;
|
|
this.nickname = this.model.nickname;
|
|
this.model.encodedParentId = encodeURIComponent(this.parentId);
|
|
return this;
|
|
},
|
|
|
|
mouseEnter: function(e) {
|
|
var elem = $(this.el).find('.content');
|
|
var x = e.pageX;
|
|
var y = e.pageY;
|
|
var scX = $(window).scrollLeft();
|
|
var scY = $(window).scrollTop();
|
|
var scMaxX = scX + $(window).width();
|
|
var scMaxY = scY + $(window).height();
|
|
var wd = elem.width();
|
|
var hgh = elem.height();
|
|
|
|
if (x + wd > scMaxX) {
|
|
x = scMaxX - wd;
|
|
}
|
|
|
|
if (x < scX) {
|
|
x = scX;
|
|
}
|
|
|
|
if (y + hgh > scMaxY) {
|
|
y = scMaxY - hgh;
|
|
}
|
|
|
|
if (y < scY) {
|
|
y = scY;
|
|
}
|
|
|
|
var pos = {};
|
|
pos.top = y;
|
|
pos.left = x;
|
|
elem.css(pos);
|
|
$(e.currentTarget.parentNode).find('#api_information_panel').show();
|
|
},
|
|
|
|
mouseExit: function(e) {
|
|
$(e.currentTarget.parentNode).find('#api_information_panel').hide();
|
|
},
|
|
|
|
// Note: copied from CoffeeScript compiled file
|
|
// TODO: redactor
|
|
render: function() {
|
|
var a, auth, auths, code, contentTypeModel, isMethodSubmissionSupported, k, key, l, len, len1, len2, len3, len4, m, modelAuths, n, o, p, param, q, ref, ref1, ref2, ref3, ref4, ref5, responseContentTypeView, responseSignatureView, schema, schemaObj, scopeIndex, signatureModel, statusCode, successResponse, type, v, value;
|
|
isMethodSubmissionSupported = jQuery.inArray(this.model.method, this.model.supportedSubmitMethods()) >= 0;
|
|
if (!isMethodSubmissionSupported) {
|
|
this.model.isReadOnly = true;
|
|
}
|
|
this.model.description = this.model.description || this.model.notes;
|
|
this.model.oauth = null;
|
|
modelAuths = this.model.authorizations || this.model.security;
|
|
if (modelAuths) {
|
|
if (Array.isArray(modelAuths)) {
|
|
for (l = 0, len = modelAuths.length; l < len; l++) {
|
|
auths = modelAuths[l];
|
|
for (key in auths) {
|
|
auth = auths[key];
|
|
for (a in this.auths) {
|
|
auth = this.auths[a];
|
|
if (auth.type === 'oauth2') {
|
|
this.model.oauth = {};
|
|
this.model.oauth.scopes = [];
|
|
ref1 = auth.value.scopes;
|
|
for (k in ref1) {
|
|
v = ref1[k];
|
|
scopeIndex = auths[key].indexOf(k);
|
|
if (scopeIndex >= 0) {
|
|
o = {
|
|
scope: k,
|
|
description: v
|
|
};
|
|
this.model.oauth.scopes.push(o);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for (k in modelAuths) {
|
|
v = modelAuths[k];
|
|
if (k === 'oauth2') {
|
|
if (this.model.oauth === null) {
|
|
this.model.oauth = {};
|
|
}
|
|
if (this.model.oauth.scopes === void 0) {
|
|
this.model.oauth.scopes = [];
|
|
}
|
|
for (m = 0, len1 = v.length; m < len1; m++) {
|
|
o = v[m];
|
|
this.model.oauth.scopes.push(o);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (typeof this.model.responses !== 'undefined') {
|
|
this.model.responseMessages = [];
|
|
ref2 = this.model.responses;
|
|
for (code in ref2) {
|
|
value = ref2[code];
|
|
schema = null;
|
|
schemaObj = this.model.responses[code].schema;
|
|
if (schemaObj && schemaObj.$ref) {
|
|
schema = schemaObj.$ref;
|
|
if (schema.indexOf('#/definitions/') === 0) {
|
|
schema = schema.substring('#/definitions/'.length);
|
|
}
|
|
}
|
|
this.model.responseMessages.push({
|
|
code: code,
|
|
message: value.description,
|
|
responseModel: schema
|
|
});
|
|
}
|
|
}
|
|
if (typeof this.model.responseMessages === 'undefined') {
|
|
this.model.responseMessages = [];
|
|
}
|
|
signatureModel = null;
|
|
if (this.model.successResponse) {
|
|
successResponse = this.model.successResponse;
|
|
for (key in successResponse) {
|
|
value = successResponse[key];
|
|
this.model.successCode = key;
|
|
if (typeof value === 'object' && typeof value.createJSONSample === 'function') {
|
|
signatureModel = {
|
|
sampleJSON: JSON.stringify(value.createJSONSample(), void 0, 2),
|
|
isParam: false,
|
|
signature: value.getMockSignature()
|
|
};
|
|
}
|
|
}
|
|
} else if (this.model.responseClassSignature && this.model.responseClassSignature !== 'string') {
|
|
signatureModel = {
|
|
sampleJSON: this.model.responseSampleJSON,
|
|
isParam: false,
|
|
signature: this.model.responseClassSignature
|
|
};
|
|
}
|
|
$(this.el).html(Handlebars.templates.operation(this.model));
|
|
if (signatureModel) {
|
|
responseSignatureView = new SwaggerUi.Views.SignatureView({
|
|
model: signatureModel,
|
|
router: this.router,
|
|
tagName: 'div'
|
|
});
|
|
$('.model-signature', $(this.el)).append(responseSignatureView.render().el);
|
|
} else {
|
|
this.model.responseClassSignature = 'string';
|
|
$('.model-signature', $(this.el)).html(this.model.type);
|
|
}
|
|
contentTypeModel = {
|
|
isParam: false
|
|
};
|
|
contentTypeModel.consumes = this.model.consumes;
|
|
contentTypeModel.produces = this.model.produces;
|
|
ref3 = this.model.parameters;
|
|
for (n = 0, len2 = ref3.length; n < len2; n++) {
|
|
param = ref3[n];
|
|
type = param.type || param.dataType || '';
|
|
if (typeof type === 'undefined') {
|
|
schema = param.schema;
|
|
if (schema && schema.$ref) {
|
|
ref = schema.$ref;
|
|
if (ref.indexOf('#/definitions/') === 0) {
|
|
type = ref.substring('#/definitions/'.length);
|
|
} else {
|
|
type = ref;
|
|
}
|
|
}
|
|
}
|
|
if (type && type.toLowerCase() === 'file') {
|
|
if (!contentTypeModel.consumes) {
|
|
contentTypeModel.consumes = 'multipart/form-data';
|
|
}
|
|
}
|
|
param.type = type;
|
|
}
|
|
responseContentTypeView = new SwaggerUi.Views.ResponseContentTypeView({
|
|
model: contentTypeModel,
|
|
router: this.router
|
|
});
|
|
$('.response-content-type', $(this.el)).append(responseContentTypeView.render().el);
|
|
ref4 = this.model.parameters;
|
|
for (p = 0, len3 = ref4.length; p < len3; p++) {
|
|
param = ref4[p];
|
|
this.addParameter(param, contentTypeModel.consumes);
|
|
}
|
|
ref5 = this.model.responseMessages;
|
|
for (q = 0, len4 = ref5.length; q < len4; q++) {
|
|
statusCode = ref5[q];
|
|
this.addStatusCode(statusCode);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
addParameter: function(param, consumes) {
|
|
// Render a parameter
|
|
param.consumes = consumes;
|
|
var paramView = new SwaggerUi.Views.ParameterView({
|
|
model: param,
|
|
tagName: 'tr',
|
|
readOnly: this.model.isReadOnly
|
|
});
|
|
$('.operation-params', $(this.el)).append(paramView.render().el);
|
|
},
|
|
|
|
addStatusCode: function(statusCode) {
|
|
// Render status codes
|
|
var statusCodeView = new SwaggerUi.Views.StatusCodeView({
|
|
model: statusCode,
|
|
tagName: 'tr',
|
|
router: this.router
|
|
});
|
|
$('.operation-status', $(this.el)).append(statusCodeView.render().el);
|
|
},
|
|
|
|
// Note: copied from CoffeeScript compiled file
|
|
// TODO: redactor
|
|
submitOperation: function(e) {
|
|
var error_free, form, isFileUpload, l, len, len1, len2, m, map, n, o, opts, ref1, ref2, ref3, val;
|
|
if (e !== null) {
|
|
e.preventDefault();
|
|
}
|
|
form = $('.sandbox', $(this.el));
|
|
error_free = true;
|
|
form.find('input.required').each(function() {
|
|
$(this).removeClass('error');
|
|
if (jQuery.trim($(this).val()) === '') {
|
|
$(this).addClass('error');
|
|
$(this).wiggle({
|
|
callback: (function(_this) {
|
|
return function() {
|
|
$(_this).focus();
|
|
};
|
|
})(this)
|
|
});
|
|
error_free = false;
|
|
}
|
|
});
|
|
form.find('textarea.required').each(function() {
|
|
$(this).removeClass('error');
|
|
if (jQuery.trim($(this).val()) === '') {
|
|
$(this).addClass('error');
|
|
$(this).wiggle({
|
|
callback: (function(_this) {
|
|
return function() {
|
|
return $(_this).focus();
|
|
};
|
|
})(this)
|
|
});
|
|
error_free = false;
|
|
}
|
|
});
|
|
if (error_free) {
|
|
map = {};
|
|
opts = {
|
|
parent: this
|
|
};
|
|
isFileUpload = false;
|
|
ref1 = form.find('input');
|
|
for (l = 0, len = ref1.length; l < len; l++) {
|
|
o = ref1[l];
|
|
if ((o.value !== null) && jQuery.trim(o.value).length > 0) {
|
|
map[o.name] = o.value;
|
|
}
|
|
if (o.type === 'file') {
|
|
map[o.name] = o.files[0];
|
|
isFileUpload = true;
|
|
}
|
|
}
|
|
ref2 = form.find('textarea');
|
|
for (m = 0, len1 = ref2.length; m < len1; m++) {
|
|
o = ref2[m];
|
|
if ((o.value !== null) && jQuery.trim(o.value).length > 0) {
|
|
map[o.name] = o.value;
|
|
}
|
|
}
|
|
ref3 = form.find('select');
|
|
for (n = 0, len2 = ref3.length; n < len2; n++) {
|
|
o = ref3[n];
|
|
val = this.getSelectedValue(o);
|
|
if ((val !== null) && jQuery.trim(val).length > 0) {
|
|
map[o.name] = val;
|
|
}
|
|
}
|
|
opts.responseContentType = $('div select[name=responseContentType]', $(this.el)).val();
|
|
opts.requestContentType = $('div select[name=parameterContentType]', $(this.el)).val();
|
|
$('.response_throbber', $(this.el)).show();
|
|
if (isFileUpload) {
|
|
return this.handleFileUpload(map, form);
|
|
} else {
|
|
return this.model['do'](map, opts, this.showCompleteStatus, this.showErrorStatus, this);
|
|
}
|
|
}
|
|
},
|
|
|
|
success: function(response, parent) {
|
|
parent.showCompleteStatus(response);
|
|
},
|
|
|
|
// Note: This is compiled code
|
|
// TODO: Refactor
|
|
handleFileUpload: function(map, form) {
|
|
var bodyParam, el, headerParams, l, len, len1, len2, len3, m, n, o, obj, p, param, params, ref1, ref2, ref3, ref4;
|
|
ref1 = form.serializeArray();
|
|
for (l = 0, len = ref1.length; l < len; l++) {
|
|
o = ref1[l];
|
|
if ((o.value !== null) && jQuery.trim(o.value).length > 0) {
|
|
map[o.name] = o.value;
|
|
}
|
|
}
|
|
bodyParam = new FormData();
|
|
params = 0;
|
|
ref2 = this.model.parameters;
|
|
for (m = 0, len1 = ref2.length; m < len1; m++) {
|
|
param = ref2[m];
|
|
if (param.paramType === 'form' || param['in'] === 'formData') {
|
|
if (param.type.toLowerCase() !== 'file' && map[param.name] !== void 0) {
|
|
bodyParam.append(param.name, map[param.name]);
|
|
}
|
|
}
|
|
}
|
|
headerParams = {};
|
|
ref3 = this.model.parameters;
|
|
for (n = 0, len2 = ref3.length; n < len2; n++) {
|
|
param = ref3[n];
|
|
if (param.paramType === 'header') {
|
|
headerParams[param.name] = map[param.name];
|
|
}
|
|
}
|
|
ref4 = form.find('input[type~="file"]');
|
|
for (p = 0, len3 = ref4.length; p < len3; p++) {
|
|
el = ref4[p];
|
|
if (typeof el.files[0] !== 'undefined') {
|
|
bodyParam.append($(el).attr('name'), el.files[0]);
|
|
params += 1;
|
|
}
|
|
}
|
|
this.invocationUrl = this.model.supportHeaderParams() ? (headerParams = this.model.getHeaderParams(map), delete headerParams['Content-Type'], this.model.urlify(map, false)) : this.model.urlify(map, true);
|
|
$('.request_url', $(this.el)).html('<pre></pre>');
|
|
$('.request_url pre', $(this.el)).text(this.invocationUrl);
|
|
obj = {
|
|
type: this.model.method,
|
|
url: this.invocationUrl,
|
|
headers: headerParams,
|
|
data: bodyParam,
|
|
dataType: 'json',
|
|
contentType: false,
|
|
processData: false,
|
|
error: (function(_this) {
|
|
return function(data) {
|
|
return _this.showErrorStatus(_this.wrap(data), _this);
|
|
};
|
|
})(this),
|
|
success: (function(_this) {
|
|
return function(data) {
|
|
return _this.showResponse(data, _this);
|
|
};
|
|
})(this),
|
|
complete: (function(_this) {
|
|
return function(data) {
|
|
return _this.showCompleteStatus(_this.wrap(data), _this);
|
|
};
|
|
})(this)
|
|
};
|
|
if (window.authorizations) {
|
|
window.authorizations.apply(obj);
|
|
}
|
|
jQuery.ajax(obj);
|
|
return false;
|
|
// end of file-upload nastiness
|
|
},
|
|
// wraps a jquery response as a shred response
|
|
|
|
wrap: function(data) {
|
|
var h, headerArray, headers, i, l, len, o;
|
|
headers = {};
|
|
headerArray = data.getAllResponseHeaders().split('\r');
|
|
for (l = 0, len = headerArray.length; l < len; l++) {
|
|
i = headerArray[l];
|
|
h = i.match(/^([^:]*?):(.*)$/);
|
|
if (!h) {
|
|
h = [];
|
|
}
|
|
h.shift();
|
|
if (h[0] !== void 0 && h[1] !== void 0) {
|
|
headers[h[0].trim()] = h[1].trim();
|
|
}
|
|
}
|
|
o = {};
|
|
o.content = {};
|
|
o.content.data = data.responseText;
|
|
o.headers = headers;
|
|
o.request = {};
|
|
o.request.url = this.invocationUrl;
|
|
o.status = data.status;
|
|
return o;
|
|
},
|
|
|
|
getSelectedValue: function(select) {
|
|
if (!select.multiple) {
|
|
return select.value;
|
|
} else {
|
|
var options = [];
|
|
for (var l = 0, len = select.options.length; l < len; l++) {
|
|
var opt = select.options[l];
|
|
if (opt.selected) {
|
|
options.push(opt.value);
|
|
}
|
|
}
|
|
if (options.length > 0) {
|
|
return options;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
},
|
|
|
|
// handler for hide response link
|
|
hideResponse: function(e) {
|
|
if (e) { e.preventDefault(); }
|
|
$('.response', $(this.el)).slideUp();
|
|
$('.response_hider', $(this.el)).fadeOut();
|
|
},
|
|
|
|
// Show response from server
|
|
showResponse: function(response) {
|
|
var prettyJson = JSON.stringify(response, null, '\t').replace(/\n/g, '<br>');
|
|
$('.response_body', $(this.el)).html(_.escape(prettyJson));
|
|
},
|
|
|
|
// Show error from server
|
|
showErrorStatus: function(data, parent) {
|
|
parent.showStatus(data);
|
|
},
|
|
|
|
// show the status codes
|
|
showCompleteStatus: function(data, parent){
|
|
parent.showStatus(data);
|
|
},
|
|
|
|
// Adapted from http://stackoverflow.com/a/2893259/454004
|
|
// Note: directly ported from CoffeeScript
|
|
// TODO: Cleanup CoffeeScript artifacts
|
|
formatXml: function(xml) {
|
|
var contexp, fn, formatted, indent, l, lastType, len, lines, ln, pad, reg, transitions, wsexp;
|
|
reg = /(>)(<)(\/*)/g;
|
|
wsexp = /[ ]*(.*)[ ]+\n/g;
|
|
contexp = /(<.+>)(.+\n)/g;
|
|
xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
|
|
pad = 0;
|
|
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, j, 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() {
|
|
var m, ref1, results;
|
|
results = [];
|
|
for (j = m = 0, ref1 = indent; 0 <= ref1 ? m < ref1 : m > ref1; j = 0 <= ref1 ? ++m : --m) {
|
|
results.push(' ');
|
|
}
|
|
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;
|
|
},
|
|
|
|
// puts the response data in UI
|
|
showStatus: function(response) {
|
|
var url, content;
|
|
if (response.content === undefined) {
|
|
content = response.data;
|
|
url = response.url;
|
|
} else {
|
|
content = response.content.data;
|
|
url = response.request.url;
|
|
}
|
|
var headers = response.headers;
|
|
|
|
// if server is nice, and sends content-type back, we can use it
|
|
var contentType = null;
|
|
if (headers) {
|
|
contentType = headers['Content-Type'] || headers['content-type'];
|
|
if (contentType) {
|
|
contentType = contentType.split(';')[0].trim();
|
|
}
|
|
}
|
|
$('.response_body', $(this.el)).removeClass('json');
|
|
$('.response_body', $(this.el)).removeClass('xml');
|
|
|
|
var supportsAudioPlayback = function(contentType){
|
|
var audioElement = document.createElement('audio');
|
|
return !!(audioElement.canPlayType && audioElement.canPlayType(contentType).replace(/no/, ''));
|
|
};
|
|
|
|
var pre;
|
|
var code;
|
|
if (!content) {
|
|
code = $('<code />').text('no content');
|
|
pre = $('<pre class="json" />').append(code);
|
|
} else if (contentType === 'application/json' || /\+json$/.test(contentType)) {
|
|
var json = null;
|
|
try {
|
|
json = JSON.stringify(JSON.parse(content), null, ' ');
|
|
} catch (_error) {
|
|
json = 'can\'t parse JSON. Raw result:\n\n' + content;
|
|
}
|
|
code = $('<code />').text(json);
|
|
pre = $('<pre class="json" />').append(code);
|
|
} else if (contentType === 'application/xml' || /\+xml$/.test(contentType)) {
|
|
code = $('<code />').text(this.formatXml(content));
|
|
pre = $('<pre class="xml" />').append(code);
|
|
} else if (contentType === 'text/html') {
|
|
code = $('<code />').html(_.escape(content));
|
|
pre = $('<pre class="xml" />').append(code);
|
|
} else if (/^image\//.test(contentType)) {
|
|
pre = $('<img>').attr('src', url);
|
|
} else if (/^audio\//.test(contentType) && supportsAudioPlayback(contentType)) {
|
|
pre = $('<audio controls>').append($('<source>').attr('src', url).attr('type', contentType));
|
|
} else {
|
|
code = $('<code />').text(content);
|
|
pre = $('<pre class="json" />').append(code);
|
|
}
|
|
var response_body = pre;
|
|
$('.request_url', $(this.el)).html('<pre></pre>');
|
|
$('.request_url pre', $(this.el)).text(url);
|
|
$('.response_code', $(this.el)).html('<pre>' + response.status + '</pre>');
|
|
$('.response_body', $(this.el)).html(response_body);
|
|
$('.response_headers', $(this.el)).html('<pre>' + _.escape(JSON.stringify(response.headers, null, ' ')).replace(/\n/g, '<br>') + '</pre>');
|
|
$('.response', $(this.el)).slideDown();
|
|
$('.response_hider', $(this.el)).show();
|
|
$('.response_throbber', $(this.el)).hide();
|
|
var response_body_el = $('.response_body', $(this.el))[0];
|
|
|
|
// only highlight the response if response is less than threshold, default state is highlight response
|
|
var opts = this.options.swaggerOptions;
|
|
if (opts.highlightSizeThreshold && response.data.length > opts.highlightSizeThreshold) {
|
|
return response_body_el;
|
|
} else {
|
|
return hljs.highlightBlock(response_body_el);
|
|
}
|
|
},
|
|
|
|
toggleOperationContent: function() {
|
|
var elem = $('#' + Docs.escapeResourceName(this.parentId + '_' + this.nickname + '_content'));
|
|
if (elem.is(':visible')){
|
|
Docs.collapseOperation(elem);
|
|
} else {
|
|
Docs.expandOperation(elem);
|
|
}
|
|
}
|
|
});
|