gitea源码

api.js 39KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170
  1. /*!
  2. * # Fomantic-UI - API
  3. * http://github.com/fomantic/Fomantic-UI/
  4. *
  5. *
  6. * Released under the MIT license
  7. * http://opensource.org/licenses/MIT
  8. *
  9. */
  10. ;(function ($, window, document, undefined) {
  11. 'use strict';
  12. $.isWindow = $.isWindow || function(obj) {
  13. return obj != null && obj === obj.window;
  14. };
  15. window = (typeof window != 'undefined' && window.Math == Math)
  16. ? window
  17. : (typeof self != 'undefined' && self.Math == Math)
  18. ? self
  19. : Function('return this')()
  20. ;
  21. $.api = $.fn.api = function(parameters) {
  22. var
  23. // use window context if none specified
  24. $allModules = $.isFunction(this)
  25. ? $(window)
  26. : $(this),
  27. moduleSelector = $allModules.selector || '',
  28. time = new Date().getTime(),
  29. performance = [],
  30. query = arguments[0],
  31. methodInvoked = (typeof query == 'string'),
  32. queryArguments = [].slice.call(arguments, 1),
  33. returnedValue
  34. ;
  35. $allModules
  36. .each(function() {
  37. var
  38. settings = ( $.isPlainObject(parameters) )
  39. ? $.extend(true, {}, $.fn.api.settings, parameters)
  40. : $.extend({}, $.fn.api.settings),
  41. // internal aliases
  42. namespace = settings.namespace,
  43. metadata = settings.metadata,
  44. selector = settings.selector,
  45. error = settings.error,
  46. className = settings.className,
  47. // define namespaces for modules
  48. eventNamespace = '.' + namespace,
  49. moduleNamespace = 'module-' + namespace,
  50. // element that creates request
  51. $module = $(this),
  52. $form = $module.closest(selector.form),
  53. // context used for state
  54. $context = (settings.stateContext)
  55. ? $(settings.stateContext)
  56. : $module,
  57. // request details
  58. ajaxSettings,
  59. requestSettings,
  60. url,
  61. data,
  62. requestStartTime,
  63. // standard module
  64. element = this,
  65. context = $context[0],
  66. instance = $module.data(moduleNamespace),
  67. module
  68. ;
  69. module = {
  70. initialize: function() {
  71. if(!methodInvoked) {
  72. module.bind.events();
  73. }
  74. module.instantiate();
  75. },
  76. instantiate: function() {
  77. module.verbose('Storing instance of module', module);
  78. instance = module;
  79. $module
  80. .data(moduleNamespace, instance)
  81. ;
  82. },
  83. destroy: function() {
  84. module.verbose('Destroying previous module for', element);
  85. $module
  86. .removeData(moduleNamespace)
  87. .off(eventNamespace)
  88. ;
  89. },
  90. bind: {
  91. events: function() {
  92. var
  93. triggerEvent = module.get.event()
  94. ;
  95. if( triggerEvent ) {
  96. module.verbose('Attaching API events to element', triggerEvent);
  97. $module
  98. .on(triggerEvent + eventNamespace, module.event.trigger)
  99. ;
  100. }
  101. else if(settings.on == 'now') {
  102. module.debug('Querying API endpoint immediately');
  103. module.query();
  104. }
  105. }
  106. },
  107. decode: {
  108. json: function(response) {
  109. if(response !== undefined && typeof response == 'string') {
  110. try {
  111. response = JSON.parse(response);
  112. }
  113. catch(e) {
  114. // isnt json string
  115. }
  116. }
  117. return response;
  118. }
  119. },
  120. read: {
  121. cachedResponse: function(url) {
  122. var
  123. response
  124. ;
  125. if(window.Storage === undefined) {
  126. module.error(error.noStorage);
  127. return;
  128. }
  129. response = sessionStorage.getItem(url);
  130. module.debug('Using cached response', url, response);
  131. response = module.decode.json(response);
  132. return response;
  133. }
  134. },
  135. write: {
  136. cachedResponse: function(url, response) {
  137. if(response && response === '') {
  138. module.debug('Response empty, not caching', response);
  139. return;
  140. }
  141. if(window.Storage === undefined) {
  142. module.error(error.noStorage);
  143. return;
  144. }
  145. if( $.isPlainObject(response) ) {
  146. response = JSON.stringify(response);
  147. }
  148. sessionStorage.setItem(url, response);
  149. module.verbose('Storing cached response for url', url, response);
  150. }
  151. },
  152. query: function() {
  153. if(module.is.disabled()) {
  154. module.debug('Element is disabled API request aborted');
  155. return;
  156. }
  157. if(module.is.loading()) {
  158. if(settings.interruptRequests) {
  159. module.debug('Interrupting previous request');
  160. module.abort();
  161. }
  162. else {
  163. module.debug('Cancelling request, previous request is still pending');
  164. return;
  165. }
  166. }
  167. // pass element metadata to url (value, text)
  168. if(settings.defaultData) {
  169. $.extend(true, settings.urlData, module.get.defaultData());
  170. }
  171. // Add form content
  172. if(settings.serializeForm) {
  173. settings.data = module.add.formData(settings.data);
  174. }
  175. // call beforesend and get any settings changes
  176. requestSettings = module.get.settings();
  177. // check if before send cancelled request
  178. if(requestSettings === false) {
  179. module.cancelled = true;
  180. module.error(error.beforeSend);
  181. return;
  182. }
  183. else {
  184. module.cancelled = false;
  185. }
  186. // get url
  187. url = module.get.templatedURL();
  188. if(!url && !module.is.mocked()) {
  189. module.error(error.missingURL);
  190. return;
  191. }
  192. // replace variables
  193. url = module.add.urlData( url );
  194. // missing url parameters
  195. if( !url && !module.is.mocked()) {
  196. return;
  197. }
  198. requestSettings.url = settings.base + url;
  199. // look for jQuery ajax parameters in settings
  200. ajaxSettings = $.extend(true, {}, settings, {
  201. type : settings.method || settings.type,
  202. data : data,
  203. url : settings.base + url,
  204. beforeSend : settings.beforeXHR,
  205. success : function() {},
  206. failure : function() {},
  207. complete : function() {}
  208. });
  209. module.debug('Querying URL', ajaxSettings.url);
  210. module.verbose('Using AJAX settings', ajaxSettings);
  211. if(settings.cache === 'local' && module.read.cachedResponse(url)) {
  212. module.debug('Response returned from local cache');
  213. module.request = module.create.request();
  214. module.request.resolveWith(context, [ module.read.cachedResponse(url) ]);
  215. return;
  216. }
  217. if( !settings.throttle ) {
  218. module.debug('Sending request', data, ajaxSettings.method);
  219. module.send.request();
  220. }
  221. else {
  222. if(!settings.throttleFirstRequest && !module.timer) {
  223. module.debug('Sending request', data, ajaxSettings.method);
  224. module.send.request();
  225. module.timer = setTimeout(function(){}, settings.throttle);
  226. }
  227. else {
  228. module.debug('Throttling request', settings.throttle);
  229. clearTimeout(module.timer);
  230. module.timer = setTimeout(function() {
  231. if(module.timer) {
  232. delete module.timer;
  233. }
  234. module.debug('Sending throttled request', data, ajaxSettings.method);
  235. module.send.request();
  236. }, settings.throttle);
  237. }
  238. }
  239. },
  240. should: {
  241. removeError: function() {
  242. return ( settings.hideError === true || (settings.hideError === 'auto' && !module.is.form()) );
  243. }
  244. },
  245. is: {
  246. disabled: function() {
  247. return ($module.filter(selector.disabled).length > 0);
  248. },
  249. expectingJSON: function() {
  250. return settings.dataType === 'json' || settings.dataType === 'jsonp';
  251. },
  252. form: function() {
  253. return $module.is('form') || $context.is('form');
  254. },
  255. mocked: function() {
  256. return (settings.mockResponse || settings.mockResponseAsync || settings.response || settings.responseAsync);
  257. },
  258. input: function() {
  259. return $module.is('input');
  260. },
  261. loading: function() {
  262. return (module.request)
  263. ? (module.request.state() == 'pending')
  264. : false
  265. ;
  266. },
  267. abortedRequest: function(xhr) {
  268. if(xhr && xhr.readyState !== undefined && xhr.readyState === 0) {
  269. module.verbose('XHR request determined to be aborted');
  270. return true;
  271. }
  272. else {
  273. module.verbose('XHR request was not aborted');
  274. return false;
  275. }
  276. },
  277. validResponse: function(response) {
  278. if( (!module.is.expectingJSON()) || !$.isFunction(settings.successTest) ) {
  279. module.verbose('Response is not JSON, skipping validation', settings.successTest, response);
  280. return true;
  281. }
  282. module.debug('Checking JSON returned success', settings.successTest, response);
  283. if( settings.successTest(response) ) {
  284. module.debug('Response passed success test', response);
  285. return true;
  286. }
  287. else {
  288. module.debug('Response failed success test', response);
  289. return false;
  290. }
  291. }
  292. },
  293. was: {
  294. cancelled: function() {
  295. return (module.cancelled || false);
  296. },
  297. succesful: function() {
  298. module.verbose('This behavior will be deleted due to typo. Use "was successful" instead.');
  299. return module.was.successful();
  300. },
  301. successful: function() {
  302. return (module.request && module.request.state() == 'resolved');
  303. },
  304. failure: function() {
  305. return (module.request && module.request.state() == 'rejected');
  306. },
  307. complete: function() {
  308. return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected') );
  309. }
  310. },
  311. add: {
  312. urlData: function(url, urlData) {
  313. var
  314. requiredVariables,
  315. optionalVariables
  316. ;
  317. if(url) {
  318. requiredVariables = url.match(settings.regExp.required);
  319. optionalVariables = url.match(settings.regExp.optional);
  320. urlData = urlData || settings.urlData;
  321. if(requiredVariables) {
  322. module.debug('Looking for required URL variables', requiredVariables);
  323. $.each(requiredVariables, function(index, templatedString) {
  324. var
  325. // allow legacy {$var} style
  326. variable = (templatedString.indexOf('$') !== -1)
  327. ? templatedString.substr(2, templatedString.length - 3)
  328. : templatedString.substr(1, templatedString.length - 2),
  329. value = ($.isPlainObject(urlData) && urlData[variable] !== undefined)
  330. ? urlData[variable]
  331. : ($module.data(variable) !== undefined)
  332. ? $module.data(variable)
  333. : ($context.data(variable) !== undefined)
  334. ? $context.data(variable)
  335. : urlData[variable]
  336. ;
  337. // remove value
  338. if(value === undefined) {
  339. module.error(error.requiredParameter, variable, url);
  340. url = false;
  341. return false;
  342. }
  343. else {
  344. module.verbose('Found required variable', variable, value);
  345. value = (settings.encodeParameters)
  346. ? module.get.urlEncodedValue(value)
  347. : value
  348. ;
  349. url = url.replace(templatedString, value);
  350. }
  351. });
  352. }
  353. if(optionalVariables) {
  354. module.debug('Looking for optional URL variables', requiredVariables);
  355. $.each(optionalVariables, function(index, templatedString) {
  356. var
  357. // allow legacy {/$var} style
  358. variable = (templatedString.indexOf('$') !== -1)
  359. ? templatedString.substr(3, templatedString.length - 4)
  360. : templatedString.substr(2, templatedString.length - 3),
  361. value = ($.isPlainObject(urlData) && urlData[variable] !== undefined)
  362. ? urlData[variable]
  363. : ($module.data(variable) !== undefined)
  364. ? $module.data(variable)
  365. : ($context.data(variable) !== undefined)
  366. ? $context.data(variable)
  367. : urlData[variable]
  368. ;
  369. // optional replacement
  370. if(value !== undefined) {
  371. module.verbose('Optional variable Found', variable, value);
  372. url = url.replace(templatedString, value);
  373. }
  374. else {
  375. module.verbose('Optional variable not found', variable);
  376. // remove preceding slash if set
  377. if(url.indexOf('/' + templatedString) !== -1) {
  378. url = url.replace('/' + templatedString, '');
  379. }
  380. else {
  381. url = url.replace(templatedString, '');
  382. }
  383. }
  384. });
  385. }
  386. }
  387. return url;
  388. },
  389. formData: function(data) {
  390. var
  391. canSerialize = ($.fn.serializeObject !== undefined),
  392. formData = (canSerialize)
  393. ? $form.serializeObject()
  394. : $form.serialize(),
  395. hasOtherData
  396. ;
  397. data = data || settings.data;
  398. hasOtherData = $.isPlainObject(data);
  399. if(hasOtherData) {
  400. if(canSerialize) {
  401. module.debug('Extending existing data with form data', data, formData);
  402. data = $.extend(true, {}, data, formData);
  403. }
  404. else {
  405. module.error(error.missingSerialize);
  406. module.debug('Cant extend data. Replacing data with form data', data, formData);
  407. data = formData;
  408. }
  409. }
  410. else {
  411. module.debug('Adding form data', formData);
  412. data = formData;
  413. }
  414. return data;
  415. }
  416. },
  417. send: {
  418. request: function() {
  419. module.set.loading();
  420. module.request = module.create.request();
  421. if( module.is.mocked() ) {
  422. module.mockedXHR = module.create.mockedXHR();
  423. }
  424. else {
  425. module.xhr = module.create.xhr();
  426. }
  427. settings.onRequest.call(context, module.request, module.xhr);
  428. }
  429. },
  430. event: {
  431. trigger: function(event) {
  432. module.query();
  433. if(event.type == 'submit' || event.type == 'click') {
  434. event.preventDefault();
  435. }
  436. },
  437. xhr: {
  438. always: function() {
  439. // nothing special
  440. },
  441. done: function(response, textStatus, xhr) {
  442. var
  443. context = this,
  444. elapsedTime = (new Date().getTime() - requestStartTime),
  445. timeLeft = (settings.loadingDuration - elapsedTime),
  446. translatedResponse = ( $.isFunction(settings.onResponse) )
  447. ? module.is.expectingJSON() && !settings.rawResponse
  448. ? settings.onResponse.call(context, $.extend(true, {}, response))
  449. : settings.onResponse.call(context, response)
  450. : false
  451. ;
  452. timeLeft = (timeLeft > 0)
  453. ? timeLeft
  454. : 0
  455. ;
  456. if(translatedResponse) {
  457. module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response);
  458. response = translatedResponse;
  459. }
  460. if(timeLeft > 0) {
  461. module.debug('Response completed early delaying state change by', timeLeft);
  462. }
  463. setTimeout(function() {
  464. if( module.is.validResponse(response) ) {
  465. module.request.resolveWith(context, [response, xhr]);
  466. }
  467. else {
  468. module.request.rejectWith(context, [xhr, 'invalid']);
  469. }
  470. }, timeLeft);
  471. },
  472. fail: function(xhr, status, httpMessage) {
  473. var
  474. context = this,
  475. elapsedTime = (new Date().getTime() - requestStartTime),
  476. timeLeft = (settings.loadingDuration - elapsedTime)
  477. ;
  478. timeLeft = (timeLeft > 0)
  479. ? timeLeft
  480. : 0
  481. ;
  482. if(timeLeft > 0) {
  483. module.debug('Response completed early delaying state change by', timeLeft);
  484. }
  485. setTimeout(function() {
  486. if( module.is.abortedRequest(xhr) ) {
  487. module.request.rejectWith(context, [xhr, 'aborted', httpMessage]);
  488. }
  489. else {
  490. module.request.rejectWith(context, [xhr, 'error', status, httpMessage]);
  491. }
  492. }, timeLeft);
  493. }
  494. },
  495. request: {
  496. done: function(response, xhr) {
  497. module.debug('Successful API Response', response);
  498. if(settings.cache === 'local' && url) {
  499. module.write.cachedResponse(url, response);
  500. module.debug('Saving server response locally', module.cache);
  501. }
  502. settings.onSuccess.call(context, response, $module, xhr);
  503. },
  504. complete: function(firstParameter, secondParameter) {
  505. var
  506. xhr,
  507. response
  508. ;
  509. // have to guess callback parameters based on request success
  510. if( module.was.successful() ) {
  511. response = firstParameter;
  512. xhr = secondParameter;
  513. }
  514. else {
  515. xhr = firstParameter;
  516. response = module.get.responseFromXHR(xhr);
  517. }
  518. module.remove.loading();
  519. settings.onComplete.call(context, response, $module, xhr);
  520. },
  521. fail: function(xhr, status, httpMessage) {
  522. var
  523. // pull response from xhr if available
  524. response = module.get.responseFromXHR(xhr),
  525. errorMessage = module.get.errorFromRequest(response, status, httpMessage)
  526. ;
  527. if(status == 'aborted') {
  528. module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage);
  529. settings.onAbort.call(context, status, $module, xhr);
  530. return true;
  531. }
  532. else if(status == 'invalid') {
  533. module.debug('JSON did not pass success test. A server-side error has most likely occurred', response);
  534. }
  535. else if(status == 'error') {
  536. if(xhr !== undefined) {
  537. module.debug('XHR produced a server error', status, httpMessage);
  538. // make sure we have an error to display to console
  539. if( (xhr.status < 200 || xhr.status >= 300) && httpMessage !== undefined && httpMessage !== '') {
  540. module.error(error.statusMessage + httpMessage, ajaxSettings.url);
  541. }
  542. settings.onError.call(context, errorMessage, $module, xhr);
  543. }
  544. }
  545. if(settings.errorDuration && status !== 'aborted') {
  546. module.debug('Adding error state');
  547. module.set.error();
  548. if( module.should.removeError() ) {
  549. setTimeout(module.remove.error, settings.errorDuration);
  550. }
  551. }
  552. module.debug('API Request failed', errorMessage, xhr);
  553. settings.onFailure.call(context, response, $module, xhr);
  554. }
  555. }
  556. },
  557. create: {
  558. request: function() {
  559. // api request promise
  560. return $.Deferred()
  561. .always(module.event.request.complete)
  562. .done(module.event.request.done)
  563. .fail(module.event.request.fail)
  564. ;
  565. },
  566. mockedXHR: function () {
  567. var
  568. // xhr does not simulate these properties of xhr but must return them
  569. textStatus = false,
  570. status = false,
  571. httpMessage = false,
  572. responder = settings.mockResponse || settings.response,
  573. asyncResponder = settings.mockResponseAsync || settings.responseAsync,
  574. asyncCallback,
  575. response,
  576. mockedXHR
  577. ;
  578. mockedXHR = $.Deferred()
  579. .always(module.event.xhr.complete)
  580. .done(module.event.xhr.done)
  581. .fail(module.event.xhr.fail)
  582. ;
  583. if(responder) {
  584. if( $.isFunction(responder) ) {
  585. module.debug('Using specified synchronous callback', responder);
  586. response = responder.call(context, requestSettings);
  587. }
  588. else {
  589. module.debug('Using settings specified response', responder);
  590. response = responder;
  591. }
  592. // simulating response
  593. mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]);
  594. }
  595. else if( $.isFunction(asyncResponder) ) {
  596. asyncCallback = function(response) {
  597. module.debug('Async callback returned response', response);
  598. if(response) {
  599. mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]);
  600. }
  601. else {
  602. mockedXHR.rejectWith(context, [{ responseText: response }, status, httpMessage]);
  603. }
  604. };
  605. module.debug('Using specified async response callback', asyncResponder);
  606. asyncResponder.call(context, requestSettings, asyncCallback);
  607. }
  608. return mockedXHR;
  609. },
  610. xhr: function() {
  611. var
  612. xhr
  613. ;
  614. // ajax request promise
  615. xhr = $.ajax(ajaxSettings)
  616. .always(module.event.xhr.always)
  617. .done(module.event.xhr.done)
  618. .fail(module.event.xhr.fail)
  619. ;
  620. module.verbose('Created server request', xhr, ajaxSettings);
  621. return xhr;
  622. }
  623. },
  624. set: {
  625. error: function() {
  626. module.verbose('Adding error state to element', $context);
  627. $context.addClass(className.error);
  628. },
  629. loading: function() {
  630. module.verbose('Adding loading state to element', $context);
  631. $context.addClass(className.loading);
  632. requestStartTime = new Date().getTime();
  633. }
  634. },
  635. remove: {
  636. error: function() {
  637. module.verbose('Removing error state from element', $context);
  638. $context.removeClass(className.error);
  639. },
  640. loading: function() {
  641. module.verbose('Removing loading state from element', $context);
  642. $context.removeClass(className.loading);
  643. }
  644. },
  645. get: {
  646. responseFromXHR: function(xhr) {
  647. return $.isPlainObject(xhr)
  648. ? (module.is.expectingJSON())
  649. ? module.decode.json(xhr.responseText)
  650. : xhr.responseText
  651. : false
  652. ;
  653. },
  654. errorFromRequest: function(response, status, httpMessage) {
  655. return ($.isPlainObject(response) && response.error !== undefined)
  656. ? response.error // use json error message
  657. : (settings.error[status] !== undefined) // use server error message
  658. ? settings.error[status]
  659. : httpMessage
  660. ;
  661. },
  662. request: function() {
  663. return module.request || false;
  664. },
  665. xhr: function() {
  666. return module.xhr || false;
  667. },
  668. settings: function() {
  669. var
  670. runSettings
  671. ;
  672. runSettings = settings.beforeSend.call($module, settings);
  673. if(runSettings) {
  674. if(runSettings.success !== undefined) {
  675. module.debug('Legacy success callback detected', runSettings);
  676. module.error(error.legacyParameters, runSettings.success);
  677. runSettings.onSuccess = runSettings.success;
  678. }
  679. if(runSettings.failure !== undefined) {
  680. module.debug('Legacy failure callback detected', runSettings);
  681. module.error(error.legacyParameters, runSettings.failure);
  682. runSettings.onFailure = runSettings.failure;
  683. }
  684. if(runSettings.complete !== undefined) {
  685. module.debug('Legacy complete callback detected', runSettings);
  686. module.error(error.legacyParameters, runSettings.complete);
  687. runSettings.onComplete = runSettings.complete;
  688. }
  689. }
  690. if(runSettings === undefined) {
  691. module.error(error.noReturnedValue);
  692. }
  693. if(runSettings === false) {
  694. return runSettings;
  695. }
  696. return (runSettings !== undefined)
  697. ? $.extend(true, {}, runSettings)
  698. : $.extend(true, {}, settings)
  699. ;
  700. },
  701. urlEncodedValue: function(value) {
  702. // GITEA-PATCH: always encode the value.
  703. // Old code does "decodeURIComponent" first to guess whether the value is encoded, it is not right.
  704. return window.encodeURIComponent(value);
  705. },
  706. defaultData: function() {
  707. var
  708. data = {}
  709. ;
  710. if( !$.isWindow(element) ) {
  711. if( module.is.input() ) {
  712. data.value = $module.val();
  713. }
  714. else if( module.is.form() ) {
  715. }
  716. else {
  717. data.text = $module.text();
  718. }
  719. }
  720. return data;
  721. },
  722. event: function() {
  723. if( $.isWindow(element) || settings.on == 'now' ) {
  724. module.debug('API called without element, no events attached');
  725. return false;
  726. }
  727. else if(settings.on == 'auto') {
  728. if( $module.is('input') ) {
  729. return (element.oninput !== undefined)
  730. ? 'input'
  731. : (element.onpropertychange !== undefined)
  732. ? 'propertychange'
  733. : 'keyup'
  734. ;
  735. }
  736. else if( $module.is('form') ) {
  737. return 'submit';
  738. }
  739. else {
  740. return 'click';
  741. }
  742. }
  743. else {
  744. return settings.on;
  745. }
  746. },
  747. templatedURL: function(action) {
  748. action = action || $module.data(metadata.action) || settings.action || false;
  749. url = $module.data(metadata.url) || settings.url || false;
  750. if(url) {
  751. module.debug('Using specified url', url);
  752. return url;
  753. }
  754. if(action) {
  755. module.debug('Looking up url for action', action, settings.api);
  756. if(settings.api[action] === undefined && !module.is.mocked()) {
  757. module.error(error.missingAction, settings.action, settings.api);
  758. return;
  759. }
  760. url = settings.api[action];
  761. }
  762. else if( module.is.form() ) {
  763. url = $module.attr('action') || $context.attr('action') || false;
  764. module.debug('No url or action specified, defaulting to form action', url);
  765. }
  766. return url;
  767. }
  768. },
  769. abort: function() {
  770. var
  771. xhr = module.get.xhr()
  772. ;
  773. if( xhr && xhr.state() !== 'resolved') {
  774. module.debug('Cancelling API request');
  775. xhr.abort();
  776. }
  777. },
  778. // reset state
  779. reset: function() {
  780. module.remove.error();
  781. module.remove.loading();
  782. },
  783. setting: function(name, value) {
  784. module.debug('Changing setting', name, value);
  785. if( $.isPlainObject(name) ) {
  786. $.extend(true, settings, name);
  787. }
  788. else if(value !== undefined) {
  789. if($.isPlainObject(settings[name])) {
  790. $.extend(true, settings[name], value);
  791. }
  792. else {
  793. settings[name] = value;
  794. }
  795. }
  796. else {
  797. return settings[name];
  798. }
  799. },
  800. internal: function(name, value) {
  801. if( $.isPlainObject(name) ) {
  802. $.extend(true, module, name);
  803. }
  804. else if(value !== undefined) {
  805. module[name] = value;
  806. }
  807. else {
  808. return module[name];
  809. }
  810. },
  811. debug: function() {
  812. if(!settings.silent && settings.debug) {
  813. if(settings.performance) {
  814. module.performance.log(arguments);
  815. }
  816. else {
  817. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  818. module.debug.apply(console, arguments);
  819. }
  820. }
  821. },
  822. verbose: function() {
  823. if(!settings.silent && settings.verbose && settings.debug) {
  824. if(settings.performance) {
  825. module.performance.log(arguments);
  826. }
  827. else {
  828. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  829. module.verbose.apply(console, arguments);
  830. }
  831. }
  832. },
  833. error: function() {
  834. if(!settings.silent) {
  835. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  836. module.error.apply(console, arguments);
  837. }
  838. },
  839. performance: {
  840. log: function(message) {
  841. var
  842. currentTime,
  843. executionTime,
  844. previousTime
  845. ;
  846. if(settings.performance) {
  847. currentTime = new Date().getTime();
  848. previousTime = time || currentTime;
  849. executionTime = currentTime - previousTime;
  850. time = currentTime;
  851. performance.push({
  852. 'Name' : message[0],
  853. 'Arguments' : [].slice.call(message, 1) || '',
  854. //'Element' : element,
  855. 'Execution Time' : executionTime
  856. });
  857. }
  858. clearTimeout(module.performance.timer);
  859. module.performance.timer = setTimeout(module.performance.display, 500);
  860. },
  861. display: function() {
  862. var
  863. title = settings.name + ':',
  864. totalTime = 0
  865. ;
  866. time = false;
  867. clearTimeout(module.performance.timer);
  868. $.each(performance, function(index, data) {
  869. totalTime += data['Execution Time'];
  870. });
  871. title += ' ' + totalTime + 'ms';
  872. if(moduleSelector) {
  873. title += ' \'' + moduleSelector + '\'';
  874. }
  875. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  876. console.groupCollapsed(title);
  877. if(console.table) {
  878. console.table(performance);
  879. }
  880. else {
  881. $.each(performance, function(index, data) {
  882. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  883. });
  884. }
  885. console.groupEnd();
  886. }
  887. performance = [];
  888. }
  889. },
  890. invoke: function(query, passedArguments, context) {
  891. var
  892. object = instance,
  893. maxDepth,
  894. found,
  895. response
  896. ;
  897. passedArguments = passedArguments || queryArguments;
  898. context = element || context;
  899. if(typeof query == 'string' && object !== undefined) {
  900. query = query.split(/[\. ]/);
  901. maxDepth = query.length - 1;
  902. $.each(query, function(depth, value) {
  903. var camelCaseValue = (depth != maxDepth)
  904. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  905. : query
  906. ;
  907. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  908. object = object[camelCaseValue];
  909. }
  910. else if( object[camelCaseValue] !== undefined ) {
  911. found = object[camelCaseValue];
  912. return false;
  913. }
  914. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  915. object = object[value];
  916. }
  917. else if( object[value] !== undefined ) {
  918. found = object[value];
  919. return false;
  920. }
  921. else {
  922. module.error(error.method, query);
  923. return false;
  924. }
  925. });
  926. }
  927. if ( $.isFunction( found ) ) {
  928. response = found.apply(context, passedArguments);
  929. }
  930. else if(found !== undefined) {
  931. response = found;
  932. }
  933. if(Array.isArray(returnedValue)) {
  934. returnedValue.push(response);
  935. }
  936. else if(returnedValue !== undefined) {
  937. returnedValue = [returnedValue, response];
  938. }
  939. else if(response !== undefined) {
  940. returnedValue = response;
  941. }
  942. return found;
  943. }
  944. };
  945. if(methodInvoked) {
  946. if(instance === undefined) {
  947. module.initialize();
  948. }
  949. module.invoke(query);
  950. }
  951. else {
  952. if(instance !== undefined) {
  953. instance.invoke('destroy');
  954. }
  955. module.initialize();
  956. }
  957. })
  958. ;
  959. return (returnedValue !== undefined)
  960. ? returnedValue
  961. : this
  962. ;
  963. };
  964. $.api.settings = {
  965. name : 'API',
  966. namespace : 'api',
  967. debug : false,
  968. verbose : false,
  969. performance : true,
  970. // object containing all templates endpoints
  971. api : {},
  972. // whether to cache responses
  973. cache : true,
  974. // whether new requests should abort previous requests
  975. interruptRequests : true,
  976. // event binding
  977. on : 'auto',
  978. // context for applying state classes
  979. stateContext : false,
  980. // duration for loading state
  981. loadingDuration : 0,
  982. // whether to hide errors after a period of time
  983. hideError : 'auto',
  984. // duration for error state
  985. errorDuration : 2000,
  986. // whether parameters should be encoded with encodeURIComponent
  987. encodeParameters : true,
  988. // API action to use
  989. action : false,
  990. // templated URL to use
  991. url : false,
  992. // base URL to apply to all endpoints
  993. base : '',
  994. // data that will
  995. urlData : {},
  996. // whether to add default data to url data
  997. defaultData : true,
  998. // whether to serialize closest form
  999. serializeForm : false,
  1000. // how long to wait before request should occur
  1001. throttle : 0,
  1002. // whether to throttle first request or only repeated
  1003. throttleFirstRequest : true,
  1004. // standard ajax settings
  1005. method : 'get',
  1006. data : {},
  1007. dataType : 'json',
  1008. // mock response
  1009. mockResponse : false,
  1010. mockResponseAsync : false,
  1011. // aliases for mock
  1012. response : false,
  1013. responseAsync : false,
  1014. // whether onResponse should work with response value without force converting into an object
  1015. rawResponse : false,
  1016. // callbacks before request
  1017. beforeSend : function(settings) { return settings; },
  1018. beforeXHR : function(xhr) {},
  1019. onRequest : function(promise, xhr) {},
  1020. // after request
  1021. onResponse : false, // function(response) { },
  1022. // response was successful, if JSON passed validation
  1023. onSuccess : function(response, $module) {},
  1024. // request finished without aborting
  1025. onComplete : function(response, $module) {},
  1026. // failed JSON success test
  1027. onFailure : function(response, $module) {},
  1028. // server error
  1029. onError : function(errorMessage, $module) {},
  1030. // request aborted
  1031. onAbort : function(errorMessage, $module) {},
  1032. successTest : false,
  1033. // errors
  1034. error : {
  1035. beforeSend : 'The before send function has aborted the request',
  1036. error : 'There was an error with your request',
  1037. exitConditions : 'API Request Aborted. Exit conditions met',
  1038. JSONParse : 'JSON could not be parsed during error handling',
  1039. legacyParameters : 'You are using legacy API success callback names',
  1040. method : 'The method you called is not defined',
  1041. missingAction : 'API action used but no url was defined',
  1042. missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object',
  1043. missingURL : 'No URL specified for api event',
  1044. noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.',
  1045. noStorage : 'Caching responses locally requires session storage',
  1046. parseError : 'There was an error parsing your request',
  1047. requiredParameter : 'Missing a required URL parameter: ',
  1048. statusMessage : 'Server gave an error: ',
  1049. timeout : 'Your request timed out'
  1050. },
  1051. regExp : {
  1052. required : /\{\$*[A-z0-9]+\}/g,
  1053. optional : /\{\/\$*[A-z0-9]+\}/g,
  1054. },
  1055. className: {
  1056. loading : 'loading',
  1057. error : 'error'
  1058. },
  1059. selector: {
  1060. disabled : '.disabled',
  1061. form : 'form'
  1062. },
  1063. metadata: {
  1064. action : 'action',
  1065. url : 'url'
  1066. }
  1067. };
  1068. })( jQuery, window, document );