PageRenderTime 52ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/public/javascript/pump/model.js

https://gitlab.com/jslee1/pump.io
JavaScript | 980 lines | 808 code | 117 blank | 55 comment | 203 complexity | 110af5fab880fa112b94c3734a9d517f MD5 | raw file
  1. // pump/model.js
  2. //
  3. // Backbone models for the pump.io client UI
  4. //
  5. // Copyright 2011-2012, E14N https://e14n.com/
  6. //
  7. // @licstart The following is the entire license notice for the
  8. // JavaScript code in this page.
  9. //
  10. // Licensed under the Apache License, Version 2.0 (the "License");
  11. // you may not use this file except in compliance with the License.
  12. // You may obtain a copy of the License at
  13. //
  14. // http://www.apache.org/licenses/LICENSE-2.0
  15. //
  16. // Unless required by applicable law or agreed to in writing, software
  17. // distributed under the License is distributed on an "AS IS" BASIS,
  18. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  19. // See the License for the specific language governing permissions and
  20. // limitations under the License.
  21. //
  22. // @licend The above is the entire license notice
  23. // for the JavaScript code in this page.
  24. (function(_, $, Backbone, Pump) {
  25. // Override backbone sync to use OAuth
  26. Backbone.sync = function(method, model, options) {
  27. var getValue = function(object, prop) {
  28. if (_.isFunction(object[prop])) {
  29. return object[prop]();
  30. } else if (object[prop]) {
  31. return object[prop];
  32. } else if (object.has && object.has(prop)) {
  33. return object.get(prop);
  34. } else {
  35. return null;
  36. }
  37. };
  38. var methodMap = {
  39. "create": "POST",
  40. "update": "PUT",
  41. "delete": "DELETE",
  42. "read": "GET"
  43. };
  44. var type = methodMap[method];
  45. // Default options, unless specified.
  46. options = options || {};
  47. // Default JSON-request options.
  48. var params = {type: type, dataType: "json"};
  49. // Ensure that we have a URL.
  50. if (!options.url) {
  51. if (type == "POST") {
  52. params.url = getValue(model.collection, "url");
  53. } else if (model.proxyURL) {
  54. params.url = model.proxyURL;
  55. } else {
  56. params.url = getValue(model, "url");
  57. }
  58. if (!params.url || !_.isString(params.url)) {
  59. throw new Error("No URL");
  60. }
  61. }
  62. // Ensure that we have the appropriate request data.
  63. if (!options.data && model && (method == "create" || method == "update")) {
  64. params.contentType = "application/json";
  65. params.data = JSON.stringify(model.toJSON());
  66. }
  67. // Don't process data on a non-GET request.
  68. if (params.type !== "GET" && !Backbone.emulateJSON) {
  69. params.processData = false;
  70. }
  71. params = _.extend(params, options);
  72. Pump.ajax(params);
  73. return null;
  74. };
  75. // A little bit of model sugar
  76. // Create Model attributes for our object-y things
  77. Pump.Model = Backbone.Model.extend({
  78. activityObjects: [],
  79. activityObjectBags: [],
  80. activityObjectStreams: [],
  81. activityStreams: [],
  82. peopleStreams: [],
  83. listStreams: [],
  84. people: [],
  85. initialize: function() {
  86. var obj = this,
  87. neverNew = function() { // XXX: neverNude
  88. return false;
  89. },
  90. initer = function(obj, model) {
  91. return function(name) {
  92. var raw = obj.get(name);
  93. if (raw) {
  94. // use unique for cached stuff
  95. if (model.unique) {
  96. obj[name] = model.unique(raw);
  97. } else {
  98. obj[name] = new model(raw);
  99. }
  100. obj[name].isNew = neverNew;
  101. }
  102. obj.on("change:"+name, function(changed) {
  103. var raw = obj.get(name);
  104. if (obj[name] && obj[name].set) {
  105. obj[name].set(raw);
  106. } else if (raw) {
  107. if (model.unique) {
  108. obj[name] = model.unique(raw);
  109. } else {
  110. obj[name] = new model(raw);
  111. }
  112. obj[name].isNew = neverNew;
  113. }
  114. });
  115. };
  116. };
  117. _.each(obj.activityObjects, initer(obj, Pump.ActivityObject));
  118. _.each(obj.activityObjectBags, initer(obj, Pump.ActivityObjectBag));
  119. _.each(obj.activityObjectStreams, initer(obj, Pump.ActivityObjectStream));
  120. _.each(obj.activityStreams, initer(obj, Pump.ActivityStream));
  121. _.each(obj.peopleStreams, initer(obj, Pump.PeopleStream));
  122. _.each(obj.listStreams, initer(obj, Pump.ListStream));
  123. _.each(obj.people, initer(obj, Pump.Person));
  124. },
  125. toJSONRef: function() {
  126. var obj = this;
  127. return {
  128. id: obj.get(obj.idAttribute),
  129. objectType: obj.getObjectType()
  130. };
  131. },
  132. getObjectType: function() {
  133. var obj = this;
  134. return obj.get("objectType");
  135. },
  136. toJSON: function(seen) {
  137. var obj = this,
  138. id = obj.get(obj.idAttribute),
  139. json = _.clone(obj.attributes),
  140. jsoner = function(name) {
  141. if (_.has(obj, name)) {
  142. json[name] = obj[name].toJSON(seenNow);
  143. }
  144. },
  145. seenNow;
  146. if (seen && id && _.contains(seen, id)) {
  147. json = obj.toJSONRef();
  148. } else {
  149. if (seen) {
  150. seenNow = seen.slice(0);
  151. } else {
  152. seenNow = [];
  153. }
  154. if (id) {
  155. seenNow.push(id);
  156. }
  157. _.each(obj.activityObjects, jsoner);
  158. _.each(obj.activityObjectBags, jsoner);
  159. _.each(obj.activityObjectStreams, jsoner);
  160. _.each(obj.activityStreams, jsoner);
  161. _.each(obj.peopleStreams, jsoner);
  162. _.each(obj.listStreams, jsoner);
  163. _.each(obj.people, jsoner);
  164. }
  165. return json;
  166. },
  167. set: function(props) {
  168. var model = this;
  169. if (_.has(props, "items")) {
  170. Pump.debug("Setting property 'items' for model " + model.id);
  171. }
  172. return Backbone.Model.prototype.set.apply(model, arguments);
  173. },
  174. merge: function(props) {
  175. var model = this,
  176. complicated = model.complicated();
  177. Pump.debug("Merging " + model.id + " with " + (props.id || props.url || props.nickname || "unknown"));
  178. _.each(props, function(value, key) {
  179. if (!model.has(key)) {
  180. model.set(key, value);
  181. } else if (_.contains(complicated, key) && model[key] && _.isFunction(model[key].merge)) {
  182. model[key].merge(value);
  183. } else {
  184. // XXX: resolve non-complicated stuff
  185. }
  186. });
  187. },
  188. complicated: function() {
  189. var attrs = ["activityObjects",
  190. "activityObjectBags",
  191. "activityObjectStreams",
  192. "activityStreams",
  193. "peopleStreams",
  194. "listStreams",
  195. "people"],
  196. names = [],
  197. model = this;
  198. _.each(attrs, function(attr) {
  199. if (_.isArray(model[attr])) {
  200. names = names.concat(model[attr]);
  201. }
  202. });
  203. return names;
  204. }
  205. },
  206. {
  207. cache: {},
  208. keyAttr: "id", // works for activities and activityobjects
  209. unique: function(props) {
  210. var inst,
  211. cls = this,
  212. key = props[cls.keyAttr];
  213. if (key && _.has(cls.cache, key)) {
  214. inst = cls.cache[key];
  215. // Check the updated flag
  216. inst.merge(props);
  217. } else {
  218. inst = new cls(props);
  219. if (key) {
  220. cls.cache[key] = inst;
  221. }
  222. inst.on("change:"+cls.keyAttr, function(model, key) {
  223. var oldKey = model.previous(cls.keyAttr);
  224. if (oldKey && _.has(cls.cache, oldKey)) {
  225. delete cls.cache[oldKey];
  226. }
  227. cls.cache[key] = inst;
  228. });
  229. }
  230. return inst;
  231. },
  232. clearCache: function() {
  233. this.cache = {};
  234. }
  235. });
  236. // An array of objects, usually the "items" in a stream
  237. Pump.Items = Backbone.Collection.extend({
  238. constructor: function(models, options) {
  239. var items = this;
  240. // Use unique() to get unique items
  241. models = _.map(models, function(raw) {
  242. return items.model.unique(raw);
  243. });
  244. Backbone.Collection.apply(this, [models, options]);
  245. },
  246. url: function() {
  247. var items = this;
  248. return items.stream.url();
  249. },
  250. toJSON: function(seen) {
  251. var items = this;
  252. return items.map(function(item) {
  253. return item.toJSON(seen);
  254. });
  255. },
  256. merge: function(props) {
  257. var items = this,
  258. unique;
  259. if (_.isArray(props)) {
  260. Pump.debug("Merging items of " + items.url() + " of length " + items.length + " with array of length " + props.length);
  261. unique = props.map(function(item) {
  262. return items.model.unique(item);
  263. });
  264. items.add(unique);
  265. } else {
  266. Pump.debug("Non-array passed to items.merge()");
  267. }
  268. }
  269. });
  270. // A stream of objects. It maps to the ActivityStreams collection
  271. // representation -- some wrap-up data like url and totalItems, plus an array of items.
  272. Pump.Stream = Pump.Model.extend({
  273. people: ["author"],
  274. itemsClass: Pump.Items,
  275. idAttribute: "url",
  276. getObjectType: function() {
  277. var obj = this;
  278. return "collection";
  279. },
  280. initialize: function() {
  281. var str = this,
  282. items = str.get("items");
  283. Pump.Model.prototype.initialize.apply(str);
  284. // We should always have items
  285. if (_.isArray(items)) {
  286. str.items = new str.itemsClass(items);
  287. } else {
  288. str.items = new str.itemsClass([]);
  289. }
  290. str.items.stream = str;
  291. str.on("change:items", function(newStr, items) {
  292. var str = this;
  293. Pump.debug("Resetting items of " + str.url() + " to new array of length " + items.length);
  294. str.items.reset(items);
  295. });
  296. },
  297. url: function() {
  298. var str = this;
  299. if (str.has("pump_io") && _.has(str.get("pump_io"), "proxyURL")) {
  300. return str.get("pump_io").proxyURL;
  301. } else {
  302. return str.get("url");
  303. }
  304. },
  305. nextLink: function(count) {
  306. var str = this,
  307. url,
  308. item;
  309. if (_.isUndefined(count)) {
  310. count = 20;
  311. }
  312. if (str.has("links") && _.has(str.get("links"), "next")) {
  313. url = str.get("links").next.href;
  314. } else if (str.items && str.items.length > 0) {
  315. item = str.items.at(str.items.length-1);
  316. url = str.url() + "?before=" + item.id + "&type=" + item.get("objectType");
  317. } else {
  318. url = null;
  319. }
  320. if (url && count != 20) {
  321. url = url + "&count=" + count;
  322. }
  323. return url;
  324. },
  325. prevLink: function(count) {
  326. var str = this,
  327. url,
  328. item;
  329. if (_.isUndefined(count)) {
  330. count = 20;
  331. }
  332. if (str.has("links") && _.has(str.get("links"), "prev")) {
  333. url = str.get("links").prev.href;
  334. } else if (str.items && str.items.length > 0) {
  335. item = str.items.at(0);
  336. url = str.url() + "?since=" + item.id + "&type=" + item.get("objectType");
  337. } else {
  338. url = null;
  339. }
  340. if (url && count != 20) {
  341. url = url + "&count=" + count;
  342. }
  343. return url;
  344. },
  345. getPrev: function(count, callback) { // Get stuff later than the current group
  346. var stream = this,
  347. prevLink,
  348. options;
  349. if (!callback) {
  350. // This can also be undefined, btw
  351. callback = count;
  352. count = 20;
  353. }
  354. prevLink = stream.prevLink(count);
  355. if (!prevLink) {
  356. if (_.isFunction(callback)) {
  357. callback(new Error("Can't get prevLink for stream " + stream.url()), null);
  358. }
  359. return;
  360. }
  361. options = {
  362. type: "GET",
  363. dataType: "json",
  364. url: prevLink,
  365. success: function(data) {
  366. if (data.items && data.items.length > 0) {
  367. if (stream.items) {
  368. stream.items.add(data.items, {at: 0});
  369. } else {
  370. stream.items = new stream.itemsClass(data.items);
  371. }
  372. }
  373. if (data.links && data.links.prev && data.links.prev.href) {
  374. if (stream.has("links")) {
  375. stream.get("links").prev = data.links.prev;
  376. } else {
  377. stream.set("links", {"prev": {"href": data.links.prev.href}});
  378. }
  379. }
  380. if (_.isFunction(callback)) {
  381. callback(null, data);
  382. }
  383. },
  384. error: function(jqxhr) {
  385. if (_.isFunction(callback)) {
  386. callback(Pump.jqxhrError(jqxhr), null);
  387. }
  388. }
  389. };
  390. Pump.ajax(options);
  391. },
  392. getNext: function(count, callback) { // Get stuff earlier than the current group
  393. var stream = this,
  394. nextLink,
  395. options;
  396. if (!callback) {
  397. // This can also be undefined, btw
  398. callback = count;
  399. count = 20;
  400. }
  401. nextLink = stream.nextLink(count);
  402. if (!nextLink) {
  403. if (_.isFunction(callback)) {
  404. callback(new Error("Can't get nextLink for stream " + stream.url()), null);
  405. }
  406. return;
  407. }
  408. options = {
  409. type: "GET",
  410. dataType: "json",
  411. url: nextLink,
  412. success: function(data) {
  413. if (data.items) {
  414. if (stream.items) {
  415. // Add them at the end
  416. stream.items.add(data.items, {at: stream.items.length});
  417. } else {
  418. stream.items = new stream.itemsClass(data.items);
  419. }
  420. }
  421. if (data.links) {
  422. if (data.links.next && data.links.next.href) {
  423. if (stream.has("links")) {
  424. stream.get("links").next = data.links.next;
  425. } else {
  426. stream.set("links", {"next": {"href": data.links.next.href}});
  427. }
  428. } else {
  429. if (stream.has("links")) {
  430. delete stream.get("links").next;
  431. }
  432. }
  433. }
  434. if (_.isFunction(callback)) {
  435. callback(null, data);
  436. }
  437. },
  438. error: function(jqxhr) {
  439. if (_.isFunction(callback)) {
  440. callback(Pump.jqxhrError(jqxhr), null);
  441. }
  442. }
  443. };
  444. Pump.ajax(options);
  445. },
  446. getAllNext: function(callback) {
  447. var stream = this;
  448. stream.getNext(stream.maxCount(), function(err, data) {
  449. if (err) {
  450. callback(err);
  451. } else if (data.items && data.items.length > 0 && stream.items.length < stream.get("totalItems")) {
  452. // recurse
  453. stream.getAllNext(callback);
  454. } else {
  455. callback(null);
  456. }
  457. });
  458. },
  459. getAllPrev: function(callback) {
  460. var stream = this;
  461. stream.getPrev(stream.maxCount(), function(err, data) {
  462. if (err) {
  463. callback(err);
  464. } else if (data.items && data.items.length > 0 && stream.items.length < stream.get("totalItems")) {
  465. // recurse
  466. stream.getAllPrev(callback);
  467. } else {
  468. callback(null);
  469. }
  470. });
  471. },
  472. getAll: function(callback) { // Get stuff later than the current group
  473. var stream = this,
  474. url = stream.url(),
  475. count,
  476. options,
  477. nl,
  478. pl;
  479. if (!url) {
  480. if (_.isFunction(callback)) {
  481. callback(new Error("No url for stream"), null);
  482. }
  483. return;
  484. }
  485. pl = stream.prevLink();
  486. nl = stream.nextLink();
  487. if (nl || pl) {
  488. var ndone = false,
  489. nerror = false,
  490. pdone = false,
  491. perror = false;
  492. stream.getAllNext(function(err) {
  493. ndone = true;
  494. if (err) {
  495. nerror = true;
  496. if (!perror) {
  497. callback(err);
  498. }
  499. } else {
  500. if (pdone) {
  501. callback(null);
  502. }
  503. }
  504. });
  505. stream.getAllPrev(function(err) {
  506. pdone = true;
  507. if (err) {
  508. perror = true;
  509. if (!nerror) {
  510. callback(err);
  511. }
  512. } else {
  513. if (ndone) {
  514. callback(null);
  515. }
  516. }
  517. });
  518. } else {
  519. count = stream.maxCount();
  520. options = {
  521. type: "GET",
  522. dataType: "json",
  523. url: url + "?count=" + count,
  524. success: function(data) {
  525. if (data.items) {
  526. if (stream.items) {
  527. stream.items.add(data.items);
  528. } else {
  529. stream.items = new stream.itemsClass(data.items);
  530. }
  531. }
  532. if (data.links && data.links.next && data.links.next.href) {
  533. if (stream.has("links")) {
  534. stream.get("links").next = data.links.next;
  535. } else {
  536. stream.set("links", data.links);
  537. }
  538. } else {
  539. // XXX: end-of-collection indicator?
  540. }
  541. stream.trigger("getall");
  542. if (_.isFunction(callback)) {
  543. callback(null, data);
  544. }
  545. },
  546. error: function(jqxhr) {
  547. if (_.isFunction(callback)) {
  548. callback(Pump.jqxhrError(jqxhr), null);
  549. }
  550. }
  551. };
  552. Pump.ajax(options);
  553. }
  554. },
  555. maxCount: function() {
  556. var stream = this,
  557. count,
  558. total = stream.get("totalItems");
  559. if (_.isNumber(total)) {
  560. count = Math.min(total, 200);
  561. } else {
  562. count = 200;
  563. }
  564. return count;
  565. },
  566. toJSONRef: function() {
  567. var str = this;
  568. return {
  569. totalItems: str.get("totalItems"),
  570. url: str.get("url")
  571. };
  572. },
  573. toJSON: function(seen) {
  574. var str = this,
  575. url = str.get("url"),
  576. json,
  577. seenNow;
  578. json = Pump.Model.prototype.toJSON.apply(str, [seen]);
  579. if (!seen || (url && !_.contains(seen, url))) {
  580. if (seen) {
  581. seenNow = seen.slice(0);
  582. } else {
  583. seenNow = [];
  584. }
  585. if (url) {
  586. seenNow.push(url);
  587. }
  588. json.items = str.items.toJSON(seenNow);
  589. }
  590. return json;
  591. },
  592. complicated: function() {
  593. var str = this,
  594. names = Pump.Model.prototype.complicated.apply(str);
  595. names.push("items");
  596. return names;
  597. }
  598. },
  599. {
  600. keyAttr: "url"
  601. });
  602. // A social activity.
  603. Pump.Activity = Pump.Model.extend({
  604. activityObjects: ["actor", "object", "target", "generator", "provider", "location"],
  605. activityObjectBags: ["to", "cc", "bto", "bcc"],
  606. url: function() {
  607. var links = this.get("links"),
  608. pump_io = this.get("pump_io"),
  609. uuid = this.get("uuid");
  610. if (pump_io && pump_io.proxyURL) {
  611. return pump_io.proxyURL;
  612. } else if (links && _.isObject(links) && links.self) {
  613. return links.self;
  614. } else if (uuid) {
  615. return "/api/activity/" + uuid;
  616. } else {
  617. return null;
  618. }
  619. },
  620. pubDate: function() {
  621. return Date.parse(this.published);
  622. },
  623. initialize: function() {
  624. var activity = this;
  625. Pump.Model.prototype.initialize.apply(activity);
  626. // For "post" activities we strip the author
  627. // This adds it back in; important for uniquified stuff
  628. if (activity.verb == "post" &&
  629. activity.object &&
  630. !activity.object.author &&
  631. activity.actor) {
  632. activity.object.author = activity.actor;
  633. }
  634. }
  635. });
  636. Pump.ActivityItems = Pump.Items.extend({
  637. model: Pump.Activity,
  638. add: function(models, options) {
  639. // Usually add at the beginning of the list
  640. if (!options) {
  641. options = {};
  642. }
  643. if (!_.has(options, "at")) {
  644. options.at = 0;
  645. }
  646. Backbone.Collection.prototype.add.apply(this, [models, options]);
  647. // Don't apply changes yet.
  648. // this.applyChanges(models);
  649. },
  650. comparator: function(first, second) {
  651. var d1 = first.pubDate(),
  652. d2 = second.pubDate();
  653. if (d1 > d2) {
  654. return -1;
  655. } else if (d2 > d1) {
  656. return 1;
  657. } else {
  658. return 0;
  659. }
  660. },
  661. applyChanges: function(models) {
  662. var items = this;
  663. if (!_.isArray(models)) {
  664. models = [models];
  665. }
  666. _.each(models, function(act) {
  667. if (!(act instanceof Pump.Activity)) {
  668. act = Pump.Activity.unique(act);
  669. }
  670. switch (act.get("verb")) {
  671. case "post":
  672. case "create":
  673. if (act.object.inReplyTo) {
  674. if (!act.object.author) {
  675. act.object.author = act.actor;
  676. }
  677. if (!act.object.inReplyTo.replies) {
  678. act.object.inReplyTo.replies = new Pump.ActivityObjectStream();
  679. }
  680. if (!act.object.inReplyTo.replies.items) {
  681. act.object.inReplyTo.replies.items = new Pump.ActivityObjectItems();
  682. }
  683. act.object.inReplyTo.replies.items.add(act.object);
  684. }
  685. break;
  686. case "like":
  687. case "favorite":
  688. if (!act.object.likes) {
  689. act.object.likes = new Pump.ActivityObjectStream();
  690. }
  691. if (!act.object.likes.items) {
  692. act.object.likes.items = new Pump.ActivityObjectItems();
  693. }
  694. act.object.likes.items.add(act.actor);
  695. break;
  696. case "unlike":
  697. case "unfavorite":
  698. if (act.object.likes && act.object.likes.items) {
  699. act.object.likes.items.remove(act.actor);
  700. }
  701. break;
  702. case "share":
  703. if (!act.object.shares) {
  704. act.object.shares = new Pump.ActivityObjectStream();
  705. }
  706. if (!act.object.shares.items) {
  707. act.object.shares.items = new Pump.ActivityObjectItems();
  708. }
  709. act.object.shares.items.add(act.actor);
  710. break;
  711. case "unshare":
  712. if (act.object.shares && act.object.shares.items) {
  713. act.object.shares.items.remove(act.actor);
  714. }
  715. break;
  716. }
  717. });
  718. }
  719. });
  720. Pump.ActivityStream = Pump.Stream.extend({
  721. itemsClass: Pump.ActivityItems
  722. });
  723. Pump.ActivityObject = Pump.Model.extend({
  724. activityObjects: ["author", "location", "inReplyTo"],
  725. activityObjectBags: ["attachments", "tags"],
  726. activityObjectStreams: ["likes", "replies", "shares"],
  727. url: function() {
  728. var links = this.get("links"),
  729. pump_io = this.get("pump_io"),
  730. uuid = this.get("uuid"),
  731. objectType = this.get("objectType");
  732. if (pump_io && pump_io.proxyURL) {
  733. return pump_io.proxyURL;
  734. } else if (links &&
  735. _.isObject(links) &&
  736. _.has(links, "self") &&
  737. _.isObject(links.self) &&
  738. _.has(links.self, "href") &&
  739. _.isString(links.self.href)) {
  740. return links.self.href;
  741. } else if (objectType) {
  742. return "/api/"+objectType+"/" + uuid;
  743. } else {
  744. return null;
  745. }
  746. }
  747. });
  748. // XXX: merge with Pump.Stream?
  749. Pump.List = Pump.ActivityObject.extend({
  750. objectType: "collection",
  751. peopleStreams: ["members"],
  752. initialize: function() {
  753. Pump.Model.prototype.initialize.apply(this, arguments);
  754. }
  755. });
  756. Pump.Person = Pump.ActivityObject.extend({
  757. objectType: "person",
  758. activityObjectStreams: ["favorites"],
  759. listStreams: ["lists"],
  760. peopleStreams: ["followers", "following"],
  761. initialize: function() {
  762. Pump.Model.prototype.initialize.apply(this, arguments);
  763. }
  764. });
  765. Pump.ActivityObjectItems = Pump.Items.extend({
  766. model: Pump.ActivityObject
  767. });
  768. Pump.ActivityObjectStream = Pump.Stream.extend({
  769. itemsClass: Pump.ActivityObjectItems
  770. });
  771. Pump.ListItems = Pump.Items.extend({
  772. model: Pump.List
  773. });
  774. Pump.ListStream = Pump.Stream.extend({
  775. itemsClass: Pump.ListItems
  776. });
  777. // Unordered, doesn't have an URL
  778. Pump.ActivityObjectBag = Backbone.Collection.extend({
  779. model: Pump.ActivityObject,
  780. merge: function(models, options) {
  781. var bag = this,
  782. Model = bag.model,
  783. mapped;
  784. mapped = models.map(function(item) {
  785. return Model.unique(item);
  786. });
  787. bag.add(mapped);
  788. }
  789. });
  790. Pump.PeopleItems = Pump.Items.extend({
  791. model: Pump.Person
  792. });
  793. Pump.PeopleStream = Pump.ActivityObjectStream.extend({
  794. itemsClass: Pump.PeopleItems,
  795. nextLink: function() {
  796. var str = this,
  797. url;
  798. url = Pump.ActivityObjectStream.prototype.nextLink.apply(str, arguments);
  799. if (url && url.indexOf("&type=person") == -1) {
  800. url = url + "&type=person";
  801. }
  802. return url;
  803. },
  804. prevLink: function() {
  805. var str = this,
  806. url;
  807. url = Pump.ActivityObjectStream.prototype.prevLink.apply(str, arguments);
  808. if (url && url.indexOf("&type=person") == -1) {
  809. url = url + "&type=person";
  810. }
  811. return url;
  812. }
  813. });
  814. Pump.User = Pump.Model.extend({
  815. idAttribute: "nickname",
  816. people: ["profile"],
  817. initialize: function() {
  818. var user = this,
  819. streamUrl = function(rel) {
  820. return Pump.fullURL("/api/user/" + user.get("nickname") + rel);
  821. },
  822. userStream = function(rel) {
  823. return Pump.ActivityStream.unique({url: streamUrl(rel)});
  824. };
  825. Pump.Model.prototype.initialize.apply(this, arguments);
  826. // XXX: maybe move some of these to Person...?
  827. user.inbox = userStream("/inbox");
  828. user.majorInbox = userStream("/inbox/major");
  829. user.minorInbox = userStream("/inbox/minor");
  830. user.directInbox = userStream("/inbox/direct");
  831. user.majorDirectInbox = userStream("/inbox/direct/major");
  832. user.minorDirectInbox = userStream("/inbox/direct/minor");
  833. user.stream = userStream("/feed");
  834. user.majorStream = userStream("/feed/major");
  835. user.minorStream = userStream("/feed/minor");
  836. user.on("change:nickname", function() {
  837. user.inbox.url = streamUrl("/inbox");
  838. user.majorInbox.url = streamUrl("/inbox/major");
  839. user.minorInbox.url = streamUrl("/inbox/minor");
  840. user.directInbox.url = streamUrl("/inbox/direct");
  841. user.majorDirectInbox.url = streamUrl("/inbox/direct/major");
  842. user.minorDirectInbox.url = streamUrl("/inbox/direct/minor");
  843. user.stream.url = streamUrl("/feed");
  844. user.majorStream.url = streamUrl("/feed/major");
  845. user.minorStream.url = streamUrl("/feed/minor");
  846. });
  847. },
  848. isNew: function() {
  849. // Always PUT
  850. return false;
  851. },
  852. url: function() {
  853. return Pump.fullURL("/api/user/" + this.get("nickname"));
  854. }
  855. },
  856. {
  857. cache: {}, // separate cache
  858. keyAttr: "nickname", // cache by nickname
  859. clearCache: function() {
  860. this.cache = {};
  861. }
  862. });
  863. })(window._, window.$, window.Backbone, window.Pump);