soup.io: EmbedFix

Repairs embeds in your own and your friend soup. Fixes embeds everywhere if there is a link in the description

当前为 2017-10-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name soup.io: EmbedFix
  3. // @namespace http://xcvbnm.org/
  4. // @author Nordern
  5. // @description Repairs embeds in your own and your friend soup. Fixes embeds everywhere if there is a link in the description
  6. // @version 0.2
  7. // @match http://*.soup.io/*
  8. // @match https://*.soup.io/*
  9. // @exclude http://www.soup.io/frames/*
  10. // @exclude http://www.soup.io/remote/*
  11. // @license public domain, MediaEmbed has MIT Licence
  12. // @run-at document-end
  13. // ==/UserScript==
  14. // Available on github under: https://github.com/edave64/souplements/blob/master/youtube-fix/
  15. (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  16. /**
  17. * This code snippet allows you to intercept the loading of new elements and modify the loaded posts.
  18. * The basic idea is to allow filtering out posts before they are inserted into the dom, and thus before their assets
  19. * are loaded. This reduces stress on both the client and the asset servers.
  20. *
  21. * To use the filter, register the event "processBatch" in SOUP.Endless.
  22. * Example:
  23. *
  24. * SOUP.Endless.on("processBatch", function (doc) {
  25. * // your code here
  26. * });
  27. *
  28. * The doc argument represents a temporary HTMLDocument node, storing the new loaded posts. You can work with it
  29. * like you can work with ''document''.
  30. *
  31. * Be careful to never remove all posts, otherwise Soup will assume you reached the end.
  32. *
  33. * Please keep in mind that soup already has some filters build in: http://faq.soup.io/post/4328678
  34. * These are probably easier on the servers.
  35. *
  36. * Licence: Public domain
  37. *
  38. * UPDATE 1.1:
  39. * The soup event-api was used! I just used it wrong. It is supposed to be a template. The event is now fired on
  40. * SOUP.Endless instead of SOUP.Events
  41. *
  42. * UPDATE 1.2:
  43. * Also trigger for loaded in reactions
  44. */
  45. (function () {
  46. // Add events to SOUP.Endless
  47. if (!SOUP.Endless.trigger) {
  48. SOUP.tools.extend(SOUP.Endless, SOUP.Events);
  49. }
  50. if (Ajax.Request._EndlessFilter) return;
  51. var oldRequest = Ajax.Request;
  52. function getLoadAboveURL() {
  53. var url = $("endless_top_post").href;
  54. return url.match(/[&?]newer=1/) ? url : url + (url.indexOf("?") >= 0 ? "&" : "?") + "newer=1";
  55. }
  56. function getLoadBelowURL() {
  57. return SOUP.Endless.next_url.replace(/&?newer=1&?/g, "");
  58. }
  59. function catchBatchLoad (path, options) {
  60. var oldSuccess = options.onSuccess;
  61. options.onSuccess = function (response) {
  62. var text = response.responseText,
  63. pipePosition = text.indexOf("|"),
  64. nextPath = text.substring(0, pipePosition),
  65. content = text.substring(pipePosition + 1),
  66. parser = new DOMParser(),
  67. xmlDoc = parser.parseFromString(content, "text/html"),
  68. root = xmlDoc.body;
  69. root.setAttribute("id", "posts");
  70. SOUP.Endless.trigger("processBatch", xmlDoc);
  71. response.responseText = nextPath + "|" + root.innerHTML;
  72. return oldSuccess.apply(this, arguments);
  73. };
  74. return oldRequest.apply(this, arguments);
  75. }
  76. function catchPreviewLoad (path, options) {
  77. var oldSuccess = options.onSuccess;
  78. options.onSuccess = function (response) {
  79. var content = response.responseText,
  80. parser = new DOMParser(),
  81. xmlDoc = parser.parseFromString(content, "text/html"),
  82. root = xmlDoc.body;
  83. root.setAttribute("id", "posts");
  84. SOUP.Endless.trigger("processBatch", xmlDoc);
  85. response.responseText = root.innerHTML;
  86. return oldSuccess.apply(this, arguments);
  87. };
  88. return oldRequest.apply(this, arguments);
  89. }
  90. Ajax.Request = function (path, options) {
  91. var aboveURL = getLoadAboveURL();
  92. var belowURL = getLoadBelowURL();
  93. if (path === aboveURL || path === belowURL) {
  94. return catchBatchLoad.apply(this, arguments);
  95. }
  96. if (path.startsWith("http://" + document.location.host + "/preview/")) {
  97. return catchPreviewLoad.apply(this, arguments);
  98. }
  99. return oldRequest.apply(this, arguments);
  100. };
  101. Ajax.Request._EndlessFilter = true;
  102. Ajax.Request.Events = oldRequest.Events;
  103. Ajax.Request.prototype = oldRequest.prototype;
  104. }());
  105. },{}],2:[function(require,module,exports){
  106. require("../endlessFilter");
  107. const MediaEmbedder = require("media-embedder");
  108. if (!SOUP.EmbedFix) {
  109. SOUP.EmbedFix = true;
  110. function fixAll (doc) {
  111. var video_posts = [].slice.call(doc.getElementsByClassName("post_video"));
  112. const firstPost = document.querySelector(".post .content");
  113. let width = 500;
  114. if (firstPost) {
  115. width = parseInt(window.getComputedStyle(firstPost).width);
  116. }
  117. const height = (width / 16 * 9)|0;
  118. for (const video_post of video_posts) {
  119. const embed = video_post.getElementsByClassName("embed")[0];
  120. if (embed.children.length === 0) {
  121. const textarea = video_post.querySelector("[name='post[embedcode_or_url]']");
  122. // Turns out: Edge doesn't support innerText on DomParser elements. or something
  123. let mediaData = MediaEmbedder.detect(textarea.childNodes[0] ? textarea.childNodes[0].nodeValue : "");
  124. if (!mediaData) {
  125. const description = video_post.getElementsByClassName("body")[0];
  126. if (description) {
  127. mediaData = MediaEmbedder.detect(description.innerHTML);
  128. }
  129. }
  130. if (mediaData) {
  131. mediaData.width = width;
  132. mediaData.height = height;
  133. embed.innerHTML = MediaEmbedder.buildIframe(mediaData);
  134. }
  135. }
  136. }
  137. }
  138. SOUP.Endless.on("processBatch", function (doc) {
  139. fixAll(doc);
  140. });
  141. fixAll(document);
  142. }
  143. },{"../endlessFilter":1,"media-embedder":4}],3:[function(require,module,exports){
  144. module.exports = {
  145. parse: function (text) {
  146. const a = document.createElement("a");
  147. a.href = text;
  148. a.query = a.search.substring(1);
  149. return a;
  150. }
  151. };
  152. },{}],4:[function(require,module,exports){
  153. (function (global){
  154. "use strict";
  155. /**
  156. * @typedef MediaInfo
  157. * @name MediaInfo
  158. * @type {object}
  159. * @property {string} platform - The name of the media platfrom.
  160. * @property {string} mediaid - A string uniquely identifying on piece of media on the platform
  161. * @property {number} [height] - The height of the embeded player
  162. * @property {number} [width] - The width of the embeded player
  163. * @property {boolean} [allowFullscreen] - True if the player can enter fullscreen
  164. * @property {boolean} [loop] - True if the player will start over at the end
  165. * @property {number} [timestamp] - The number of seconds at the begining that will be skiped
  166. */
  167. /**
  168. * @typedef MediaPlatform
  169. * @name MediaPlatform
  170. */
  171. /**
  172. * Parses a text can either be a url to a video on a platform, or an html
  173. * snipplet containing an embedding code for one of these platforms.
  174. *
  175. * It attemps to extract as much information as possible from this text.
  176. *
  177. * @method MediaPlatform~detect
  178. * @param {string} test - A URL or an html snipplet containing an embed code
  179. * @returns {MediaInfo|undefined} Information found in the string, undefined if none where found.
  180. */
  181. /**
  182. * Generates a iframe embed html snipplet from a descriptor
  183. *
  184. * @method MediaPlatform~buildIframe
  185. * @param {MediaInfo} descriptor
  186. * @returns {string} An html snipplet
  187. */
  188. /**
  189. * Generates a link url from a descriptor
  190. *
  191. * @method MediaPlatform~buildLink
  192. * @param {MediaInfo} descriptor
  193. * @returns {string} A url
  194. */
  195. /** @type {Object.<string, MediaPlatform>} */
  196. const platforms = {
  197. youtube: require("./platforms/youtube"),
  198. dailymotion: require("./platforms/dailymotion"),
  199. vimeo: require("./platforms/vimeo")
  200. };
  201. /** @type {MediaPlatform} */
  202. global.test = module.exports = {
  203. detect: function(text) {
  204. for (const platform in platforms) {
  205. const ret = platforms[platform].detect(text);
  206. if (ret) {
  207. ret.platform = platform;
  208. return ret;
  209. }
  210. }
  211. },
  212. buildIframe: function (descriptor) {
  213. const platform = platforms[descriptor.platform];
  214. return platform.buildIframe(descriptor);
  215. },
  216. buildLink: function (descriptor) {
  217. const platform = platforms[descriptor.platform];
  218. return platform.buildLink(descriptor);
  219. }
  220. };
  221. }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
  222. },{"./platforms/dailymotion":6,"./platforms/vimeo":7,"./platforms/youtube":8}],5:[function(require,module,exports){
  223. "use strict";
  224. const querystring = require("querystring");
  225. const UrlHelper = require("./utils/url_helper");
  226. const DocHelper = require("./utils/doc_helper");
  227. /**
  228. * @callback processor
  229. * @param {UrlObject} url
  230. * @param {Object} queryObject
  231. * @return {MediaInfo}
  232. */
  233. /**
  234. * @callback generator
  235. * @param {MediaInfo} mediaInfo
  236. * @param {boolean} embedded
  237. * @param {string[]} queryParts
  238. * @returns {string} Url
  239. */
  240. /**
  241. * @param {processor} urlProcessor
  242. * @param {generator} urlGenerator
  243. * @returns {MediaPlatform}
  244. */
  245. module.exports = function (urlProcessor, urlGenerator) {
  246. function wrappedProcessor (text) {
  247. try {
  248. const urlData = UrlHelper.parse(text);
  249. const qs = querystring.parse(urlData.query);
  250. return urlProcessor(urlData, qs);
  251. } catch (e) {
  252. console.error(e);
  253. }
  254. }
  255. function wrappedGenerator (data, embed) {
  256. const queryData = [];
  257. queryData.when = function (condition, val) { if (condition) this.push(val); };
  258. let url = urlGenerator(data, embed, queryData);
  259. if (queryData.length > 0) {
  260. url += "?" + queryData.join("&");
  261. }
  262. return url;
  263. }
  264. return {
  265. detect: (text) => {
  266. // is entire text a url?
  267. let entire = wrappedProcessor(text),
  268. ret;
  269. if (entire) {
  270. return entire;
  271. }
  272. for (const tag of DocHelper.get_nodes(text, "iframe", "embed", "a")) {
  273. switch (tag.nodeName.toLowerCase()) {
  274. case "iframe":
  275. case "embed":
  276. ret = DocHelper.processIframe(tag, wrappedProcessor);
  277. if (ret) {
  278. return ret;
  279. }
  280. break;
  281. case "a":
  282. ret = wrappedProcessor(tag.getAttribute("href"));
  283. if (ret) {
  284. return ret;
  285. }
  286. break;
  287. }
  288. }
  289. },
  290. buildIframe: (data) => {
  291. return DocHelper.buildIframe(wrappedGenerator(data, true), data.height, data.width, data.allowFullscreen);
  292. },
  293. buildLink: (data) => {
  294. return wrappedGenerator(data, false);
  295. }
  296. };
  297. };
  298. },{"./utils/doc_helper":9,"./utils/url_helper":10,"querystring":13}],6:[function(require,module,exports){
  299. "use strict";
  300. const urlParse = /^\/(embed\/video|video|swf)\/([0-9a-zA-Z]*)/
  301. module.exports = require("../platform_base")(
  302. function (urlData, qs) {
  303. if (urlData.normalizedHost === "dailymotion.com") {
  304. const urlMatch = urlData.pathname.match(urlParse);
  305. if (urlMatch) {
  306. return {
  307. mediaid: urlMatch[2],
  308. height: null,
  309. width: null,
  310. allowFullscreen: null,
  311. loop: null,
  312. timestamp: qs.start || null,
  313. autoplay: qs.autoplay === "1" || qs.autoPlay === "1"
  314. }
  315. }
  316. }
  317. },
  318. function (data, embed, query) {
  319. let url = embed ? "https://www.dailymotion.com/embed/video/" : "https://www.dailymotion.com/video/";
  320. url += data.mediaid.replace(/[^0-9a-zA-Z]/g, ""); // sanitize mediaid
  321. query.when(data.allowFullscreen === false, "fullscreen=1");
  322. query.when(data.autoplay , "autoplay=1");
  323. query.when(data.timestamp , "start=" + parseInt(data.timestamp));
  324. return url;
  325. }
  326. );
  327. },{"../platform_base":5}],7:[function(require,module,exports){
  328. "use strict";
  329. const urlParse = /^\/(video\/)?([0-9]*)$/;
  330. /**
  331. * @param {string} videoId
  332. * @param {object} param
  333. * @returns {MediaInfo}
  334. */
  335. function generate (videoId, param) {
  336. return {
  337. mediaid: videoId,
  338. height: null, width: null, timestamp: null,
  339. allowFullscreen: null,
  340. loop: param.loop === "1",
  341. autoplay: param.autoplay === "1"
  342. }
  343. }
  344. module.exports = require("../platform_base")(
  345. function (urlData, qs) {
  346. if (urlData.normalizedHost === "vimeo.com" || urlData.normalizedHost === "player.vimeo.com") {
  347. if (urlData.pathname === "/moogaloop.swf") {
  348. return generate(qs.clip_id, qs);
  349. }
  350. const urlMatch = urlData.pathname.match(urlParse);
  351. if (urlMatch) {
  352. return generate(urlMatch[2], qs);
  353. }
  354. }
  355. },
  356. function (data, embed, query) {
  357. let url = embed ? "https://player.vimeo.com/video/" : "https://vimeo.com/";
  358. url += data.mediaid.replace(/[^0-9]/g, ""); // sanitize mediaid
  359. query.when(data.loop, "loop=1");
  360. query.when(data.autoplay, "autoplay=1");
  361. return url;
  362. }
  363. );
  364. },{"../platform_base":5}],8:[function(require,module,exports){
  365. "use strict";
  366. const querystring = require("querystring");
  367. const embedUrlParse = /^\/(embed|v)\/([0-9a-zA-Z\-_]*)(.*)/;
  368. /**
  369. * @param {string} videoId
  370. * @param {object} param
  371. * @returns {MediaInfo}
  372. */
  373. function generate (videoId, param) {
  374. return {
  375. mediaid: videoId,
  376. height: null, width: null,
  377. allowFullscreen: param.fs !== "0",
  378. timestamp: param.t || param.time || param.start || null,
  379. loop: param.loop === "1",
  380. autoplay: param.autoplay === "1"
  381. }
  382. }
  383. module.exports = require("../platform_base")(
  384. function (urlData, qs) {
  385. if (urlData.normalizedHost === "youtube.com") {
  386. if (qs.v) {
  387. return generate(qs.v, qs);
  388. }
  389. const embedUrlMatch = urlData.pathname.match(embedUrlParse);
  390. if (embedUrlMatch) {
  391. // youtube /v/ urls can be kind of odd and and append the query with & to the path
  392. const vUrlQs = querystring.parse(embedUrlMatch[3]);
  393. return generate(embedUrlMatch[2], Object.assign({}, qs, vUrlQs));
  394. }
  395. } else if (urlData.normalizedHost === "youtu.be") {
  396. const qs = querystring.parse(urlData.query);
  397. return generate(urlData.pathname.slice(1), qs);
  398. }
  399. },
  400. function (data, embed, query) {
  401. let url = embed ? "https://www.youtube.com/embed/" : "https://www.youtube.com/watch";
  402. const mediaid = data.mediaid.replace(/[^0-9a-zA-Z\-_]/g, ""); // sanitize mediaid
  403. query.when(data.allowFullscreen === false, "fs=1");
  404. query.when(data.loop, "loop=1");
  405. query.when(data.autoplay, "autoplay=1");
  406. if (data.timestamp) {
  407. query.push("start=" + data.timestamp.replace(/[^0-9hms]/g, ""));
  408. }
  409. if (embed) {
  410. url += mediaid;
  411. } else {
  412. query.push("v=" + mediaid)
  413. }
  414. return url;
  415. }
  416. );
  417. },{"../platform_base":5,"querystring":13}],9:[function(require,module,exports){
  418. const DOMParser = (window.window).DOMParser;
  419. module.exports = {
  420. get_nodes: function (text, ...node_types) {
  421. let parser = (new DOMParser ()).parseFromString("<html><body>" + text + "</body></html>", "text/html");
  422. var tags = [];
  423. for (const node_type of node_types) {
  424. tags.push.apply(tags, parser.getElementsByTagName(node_type));
  425. }
  426. return tags;
  427. },
  428. processIframe: function (iframe, urlProcessor) {
  429. const ret = urlProcessor(iframe.getAttribute("src"));
  430. if (ret) {
  431. ret.allowFullscreen = iframe.getAttribute("allowfullscreen") != null;
  432. ret.height = iframe.getAttribute("height");
  433. ret.width = iframe.getAttribute("width");
  434. return ret;
  435. }
  436. },
  437. buildIframe: function (src, height, width, allowFullscreen) {
  438. let ret = "<iframe "
  439. if (height != null) {
  440. ret += 'height="' + parseInt(height) + '" '
  441. }
  442. if (width != null) {
  443. ret += 'width="' + parseInt(width) + '" '
  444. }
  445. if (allowFullscreen === true) {
  446. ret += 'allowfullscreen webkitallowfullscreen mozallowfullscreen '
  447. }
  448. ret += 'frameborder="0" src="' + src + '"></iframe>'
  449. return ret;
  450. }
  451. };
  452. },{}],10:[function(require,module,exports){
  453. const url = require("url");
  454. const querystring = require("querystring");
  455. module.exports = {
  456. parse: function (text) {
  457. let fullUrl = url.parse(text);
  458. if (fullUrl.protocol === null) {
  459. fullUrl = url.parse("test:" + (text.startsWith("//") ? "" : "//") + text);
  460. }
  461. if (fullUrl.host) {
  462. fullUrl.normalizedHost = fullUrl.host.startsWith("www.") ? fullUrl.host.substring(4) : fullUrl.host;
  463. }
  464. return fullUrl;
  465. }
  466. };
  467. },{"querystring":13,"url":3}],11:[function(require,module,exports){
  468. // Copyright Joyent, Inc. and other Node contributors.
  469. //
  470. // Permission is hereby granted, free of charge, to any person obtaining a
  471. // copy of this software and associated documentation files (the
  472. // "Software"), to deal in the Software without restriction, including
  473. // without limitation the rights to use, copy, modify, merge, publish,
  474. // distribute, sublicense, and/or sell copies of the Software, and to permit
  475. // persons to whom the Software is furnished to do so, subject to the
  476. // following conditions:
  477. //
  478. // The above copyright notice and this permission notice shall be included
  479. // in all copies or substantial portions of the Software.
  480. //
  481. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  482. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  483. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  484. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  485. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  486. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  487. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  488. 'use strict';
  489. // If obj.hasOwnProperty has been overridden, then calling
  490. // obj.hasOwnProperty(prop) will break.
  491. // See: https://github.com/joyent/node/issues/1707
  492. function hasOwnProperty(obj, prop) {
  493. return Object.prototype.hasOwnProperty.call(obj, prop);
  494. }
  495. module.exports = function(qs, sep, eq, options) {
  496. sep = sep || '&';
  497. eq = eq || '=';
  498. var obj = {};
  499. if (typeof qs !== 'string' || qs.length === 0) {
  500. return obj;
  501. }
  502. var regexp = /\+/g;
  503. qs = qs.split(sep);
  504. var maxKeys = 1000;
  505. if (options && typeof options.maxKeys === 'number') {
  506. maxKeys = options.maxKeys;
  507. }
  508. var len = qs.length;
  509. // maxKeys <= 0 means that we should not limit keys count
  510. if (maxKeys > 0 && len > maxKeys) {
  511. len = maxKeys;
  512. }
  513. for (var i = 0; i < len; ++i) {
  514. var x = qs[i].replace(regexp, '%20'),
  515. idx = x.indexOf(eq),
  516. kstr, vstr, k, v;
  517. if (idx >= 0) {
  518. kstr = x.substr(0, idx);
  519. vstr = x.substr(idx + 1);
  520. } else {
  521. kstr = x;
  522. vstr = '';
  523. }
  524. k = decodeURIComponent(kstr);
  525. v = decodeURIComponent(vstr);
  526. if (!hasOwnProperty(obj, k)) {
  527. obj[k] = v;
  528. } else if (isArray(obj[k])) {
  529. obj[k].push(v);
  530. } else {
  531. obj[k] = [obj[k], v];
  532. }
  533. }
  534. return obj;
  535. };
  536. var isArray = Array.isArray || function (xs) {
  537. return Object.prototype.toString.call(xs) === '[object Array]';
  538. };
  539. },{}],12:[function(require,module,exports){
  540. // Copyright Joyent, Inc. and other Node contributors.
  541. //
  542. // Permission is hereby granted, free of charge, to any person obtaining a
  543. // copy of this software and associated documentation files (the
  544. // "Software"), to deal in the Software without restriction, including
  545. // without limitation the rights to use, copy, modify, merge, publish,
  546. // distribute, sublicense, and/or sell copies of the Software, and to permit
  547. // persons to whom the Software is furnished to do so, subject to the
  548. // following conditions:
  549. //
  550. // The above copyright notice and this permission notice shall be included
  551. // in all copies or substantial portions of the Software.
  552. //
  553. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  554. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  555. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  556. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  557. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  558. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  559. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  560. 'use strict';
  561. var stringifyPrimitive = function(v) {
  562. switch (typeof v) {
  563. case 'string':
  564. return v;
  565. case 'boolean':
  566. return v ? 'true' : 'false';
  567. case 'number':
  568. return isFinite(v) ? v : '';
  569. default:
  570. return '';
  571. }
  572. };
  573. module.exports = function(obj, sep, eq, name) {
  574. sep = sep || '&';
  575. eq = eq || '=';
  576. if (obj === null) {
  577. obj = undefined;
  578. }
  579. if (typeof obj === 'object') {
  580. return map(objectKeys(obj), function(k) {
  581. var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
  582. if (isArray(obj[k])) {
  583. return map(obj[k], function(v) {
  584. return ks + encodeURIComponent(stringifyPrimitive(v));
  585. }).join(sep);
  586. } else {
  587. return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
  588. }
  589. }).join(sep);
  590. }
  591. if (!name) return '';
  592. return encodeURIComponent(stringifyPrimitive(name)) + eq +
  593. encodeURIComponent(stringifyPrimitive(obj));
  594. };
  595. var isArray = Array.isArray || function (xs) {
  596. return Object.prototype.toString.call(xs) === '[object Array]';
  597. };
  598. function map (xs, f) {
  599. if (xs.map) return xs.map(f);
  600. var res = [];
  601. for (var i = 0; i < xs.length; i++) {
  602. res.push(f(xs[i], i));
  603. }
  604. return res;
  605. }
  606. var objectKeys = Object.keys || function (obj) {
  607. var res = [];
  608. for (var key in obj) {
  609. if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key);
  610. }
  611. return res;
  612. };
  613. },{}],13:[function(require,module,exports){
  614. 'use strict';
  615. exports.decode = exports.parse = require('./decode');
  616. exports.encode = exports.stringify = require('./encode');
  617. },{"./decode":11,"./encode":12}]},{},[2]);