wLib

A library for WME script developers.

当前为 2016-02-05 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/9794/105726/wLib.js

  1. // ==UserScript==
  2. // @name wLib
  3. // @description A library for WME script developers.
  4. // @version 1.0.5
  5. // @author SAR85
  6. // @copyright SAR85
  7. // @license CC BY-NC-ND
  8. // @grant none
  9. // @include https://www.waze.com/editor/*
  10. // @include https://www.waze.com/*/editor/*
  11. // @include https://editor-beta.waze.com/*
  12. // @namespace https://gf.qytechs.cn/users/9321
  13. // ==/UserScript==
  14.  
  15. /* global W */
  16. /* global OL */
  17. /* global wLib */
  18.  
  19. (function ($, Backbone, _) {
  20. /**
  21. * The wLib namespace.
  22. * @namespace {Object} wLib
  23. * @global
  24. */
  25. var wLib = {};
  26. /**
  27. * The current version.
  28. */
  29. wLib.VERSION = '1.0.5';
  30. /**
  31. * Namespace for functions related to geometry.
  32. * @namespace {Object} wLib.Geometry
  33. */
  34. wLib.Geometry = {};
  35. /**
  36. * Namespace for functions related to the model.
  37. * @namespace {Object} wLib.Model
  38. */
  39. wLib.Model = {};
  40. /**
  41. * Namespace for functions related to the WME interface
  42. * @namespace {Obect} wLib.Interface
  43. */
  44. wLib.Interface = {};
  45. /**
  46. * Namespace for utility functions.
  47. * @namespace {Object} wLib.Util
  48. */
  49. wLib.Util = {};
  50. /**
  51. * Namespace for functions related to WME actions.
  52. * @namespace {Object} wLib.api
  53. */
  54. wLib.api = {};
  55.  
  56. this.wLib = wLib;
  57.  
  58. } (jQuery, Backbone, _));
  59.  
  60. /*** GEOMETRY ***/
  61. (function () {
  62. /**
  63. * Determines if an {OpenLayers.Geometry} is within the map view.
  64. * @function wLib.Geometry.isGeometryInMapExtent
  65. * @param geometry {OpenLayers.Geometry}
  66. * @return {Boolean} Whether or not the geometry is in the map extent.
  67. */
  68. this.isGeometryInMapExtent = function (geometry) {
  69. 'use strict';
  70. return geometry && geometry.getBounds &&
  71. W.map.getExtent().intersectsBounds(geometry.getBounds());
  72. };
  73. /**
  74. * Determines if an {OpenLayers.LonLat} is within the map view.
  75. * @function wLib.Geometry.isLonLatInMapExtent
  76. * @param {OpenLayers.LonLat} lonlat
  77. * @return {Boolean} Whether or not the LonLat is in the map extent.
  78. */
  79. this.isLonLatInMapExtent = function (lonlat) {
  80. 'use strict';
  81. return lonlat && W.map.getExtent().containsLonLat(lonlat);
  82. };
  83. }).call(wLib.Geometry);
  84.  
  85. /*** MODEL ***/
  86. (function () {
  87. /**
  88. * Gets the IDs of any selected segments.
  89. * @function wLib.Model.getSelectedSegmentIDs
  90. * @return {Array} Array containing the IDs of selected segments.
  91. */
  92. this.getSelectedSegmentIDs = function () {
  93. 'use strict';
  94. var i, n, selectedItems, item, segments = [];
  95. if (!W.selectionManager.hasSelectedItems()) {
  96. return false;
  97. } else {
  98. selectedItems = W.selectionManager.selectedItems;
  99. for (i = 0, n = selectedItems.length; i < n; i++) {
  100. item = selectedItems[i].model;
  101. if ('segment' === item.type) {
  102. segments.push(item.attributes.id);
  103. }
  104. }
  105. return segments.length === 0 ? false : segments;
  106. }
  107. };
  108.  
  109. /**
  110. * Defers execution of a callback function until the WME map and data
  111. * model are ready. Call this function before calling a function that
  112. * causes a map and model reload, such as W.map.moveTo(). After the move is
  113. * completed the callback function will be executed.
  114. * @function wLib.Model.onModelReady
  115. * @param {Function} callback The callback function to be executed.
  116. * @param {Boolean} now Whether or not to call the callback now if the
  117. * model is currently ready.
  118. * @param {Object} context The context in which to call the callback.
  119. */
  120. this.onModelReady = function (callback, now, context) {
  121. var deferModelReady = function () {
  122. return $.Deferred(function (dfd) {
  123. var resolve = function () {
  124. dfd.resolve();
  125. W.model.events.unregister('mergeend', null, resolve);
  126. };
  127. W.model.events.register('mergeend', null, resolve);
  128. }).promise();
  129. };
  130. var deferMapReady = function () {
  131. return $.Deferred(function (dfd) {
  132. var resolve = function () {
  133. dfd.resolve();
  134. W.vent.off('operationDone', resolve);
  135. };
  136. W.vent.on('operationDone', resolve);
  137. }).promise();
  138. };
  139.  
  140. if (typeof callback === 'function') {
  141. context = context || callback;
  142. if (now && wLib.Util.mapReady() && wLib.Util.modelReady()) {
  143. callback.call(context);
  144. } else {
  145. $.when(deferMapReady() && deferModelReady()).then(function () {
  146. callback.call(context);
  147. });
  148. }
  149. }
  150. };
  151. /**
  152. * Retrives a route from the Waze Live Map.
  153. * @class
  154. * @name wLib.Model.RouteSelection
  155. * @param firstSegment The segment to use as the start of the route.
  156. * @param lastSegment The segment to use as the destination for the route.
  157. * @param {Array|Function} callback A function or array of funcitons to be
  158. * executed after the route
  159. * is retrieved. 'This' in the callback functions will refer to the
  160. * RouteSelection object.
  161. * @param {Object} options A hash of options for determining route. Valid
  162. * options are:
  163. * fastest: {Boolean} Whether or not the fastest route should be used.
  164. * Default is false, which selects the shortest route.
  165. * freeways: {Boolean} Whether or not to avoid freeways. Default is false.
  166. * dirt: {Boolean} Whether or not to avoid dirt roads. Default is false.
  167. * longtrails: {Boolean} Whether or not to avoid long dirt roads. Default
  168. * is false.
  169. * uturns: {Boolean} Whether or not to allow U-turns. Default is true.
  170. * @return {wLib.Model.RouteSelection} The new RouteSelection object.
  171. * @example: // The following example will retrieve a route from the Live Map and select the segments in the route.
  172. * selection = W.selectionManager.selectedItems;
  173. * myRoute = new wLib.Model.RouteSelection(selection[0], selection[1], function(){this.selectRouteSegments();}, {fastest: true});
  174. */
  175. this.RouteSelection = function (firstSegment, lastSegment, callback, options) {
  176. var i,
  177. n,
  178. start = this.getSegmentCenterLonLat(firstSegment),
  179. end = this.getSegmentCenterLonLat(lastSegment);
  180. this.options = {
  181. fastest: options && options.fastest || false,
  182. freeways: options && options.freeways || false,
  183. dirt: options && options.dirt || false,
  184. longtrails: options && options.longtrails || false,
  185. uturns: options && options.uturns || true
  186. };
  187. this.requestData = {
  188. from: 'x:' + start.x + ' y:' + start.y + ' bd:true',
  189. to: 'x:' + end.x + ' y:' + end.y + ' bd:true',
  190. returnJSON: true,
  191. returnGeometries: true,
  192. returnInstructions: false,
  193. type: this.options.fastest ? 'HISTORIC_TIME' : 'DISTANCE',
  194. clientVersion: '4.0.0',
  195. timeout: 60000,
  196. nPaths: 3,
  197. options: this.setRequestOptions(this.options)
  198. };
  199. this.callbacks = [];
  200. if (callback) {
  201. if (!(callback instanceof Array)) {
  202. callback = [callback];
  203. }
  204. for (i = 0, n = callback.length; i < n; i++) {
  205. if ('function' === typeof callback[i]) {
  206. this.callbacks.push(callback[i]);
  207. }
  208. }
  209. }
  210. this.routeData = null;
  211. this.getRouteData();
  212. };
  213. this.RouteSelection.prototype =
  214. /** @lends wLib.Model.RouteSelection.prototype */ {
  215. /**
  216. * Formats the routing options string for the ajax request.
  217. * @private
  218. * @param {Object} options Object containing the routing options.
  219. * @return {String} String containing routing options.
  220. */
  221. setRequestOptions: function (options) {
  222. return 'AVOID_TOLL_ROADS:' + (options.tolls ? 't' : 'f') + ',' +
  223. 'AVOID_PRIMARIES:' + (options.freeways ? 't' : 'f') + ',' +
  224. 'AVOID_TRAILS:' + (options.dirt ? 't' : 'f') + ',' +
  225. 'AVOID_LONG_TRAILS:' + (options.longtrails ? 't' : 'f') + ',' +
  226. 'ALLOW_UTURNS:' + (options.uturns ? 't' : 'f');
  227. },
  228. /**
  229. * Gets the center of a segment in LonLat form.
  230. * @private
  231. * @param segment A Waze model segment object.
  232. * @return {OpenLayers.LonLat} The LonLat object corresponding to the
  233. * center of the segment.
  234. */
  235. getSegmentCenterLonLat: function (segment) {
  236. var x, y, componentsLength, midPoint;
  237. if (segment) {
  238. componentsLength = segment.geometry.components.length;
  239. midPoint = Math.floor(componentsLength / 2);
  240. if (componentsLength % 2 === 1) {
  241. x = segment.geometry.components[midPoint].x;
  242. y = segment.geometry.components[midPoint].y;
  243. } else {
  244. x = (segment.geometry.components[midPoint - 1].x +
  245. segment.geometry.components[midPoint].x) / 2;
  246. y = (segment.geometry.components[midPoint - 1].y +
  247. segment.geometry.components[midPoint].y) / 2;
  248. }
  249. return new OL.Geometry.Point(x, y).
  250. transform(W.map.getProjectionObject(), 'EPSG:4326');
  251. }
  252.  
  253. },
  254. /**
  255. * Gets the route from Live Map and executes any callbacks upon success.
  256. * @private
  257. * @returns The ajax request object. The responseJSON property of the
  258. * returned object
  259. * contains the route information.
  260. *
  261. */
  262. getRouteData: function () {
  263. var i,
  264. n,
  265. that = this;
  266. return $.ajax({
  267. dataType: 'json',
  268. url: this.getURL(),
  269. data: this.requestData,
  270. dataFilter: function (data, dataType) {
  271. return data.replace(/NaN/g, '0');
  272. },
  273. success: function (data) {
  274. that.routeData = data;
  275. for (i = 0, n = that.callbacks.length; i < n; i++) {
  276. that.callbacks[i].call(that);
  277. }
  278. }
  279. });
  280. },
  281. /**
  282. * Extracts the IDs from all segments on the route.
  283. * @private
  284. * @return {Array} Array containing an array of segment IDs for
  285. * each route alternative.
  286. */
  287. getRouteSegmentIDs: function () {
  288. var i, j, route, len1, len2, segIDs = [],
  289. routeArray = [],
  290. data = this.routeData;
  291. if ('undefined' !== typeof data.alternatives) {
  292. for (i = 0, len1 = data.alternatives.length; i < len1; i++) {
  293. route = data.alternatives[i].response.results;
  294. for (j = 0, len2 = route.length; j < len2; j++) {
  295. routeArray.push(route[j].path.segmentId);
  296. }
  297. segIDs.push(routeArray);
  298. routeArray = [];
  299. }
  300. } else {
  301. route = data.response.results;
  302. for (i = 0, len1 = route.length; i < len1; i++) {
  303. routeArray.push(route[i].path.segmentId);
  304. }
  305. segIDs.push(routeArray);
  306. }
  307. return segIDs;
  308. },
  309. /**
  310. * Gets the URL to use for the ajax request based on country.
  311. * @private
  312. * @return {String} Relative URl to use for route ajax request.
  313. */
  314. getURL: function () {
  315. if (W.model.countries.get(235) || W.model.countries.get(40)) {
  316. return '/RoutingManager/routingRequest';
  317. } else if (W.model.countries.get(106)) {
  318. return '/il-RoutingManager/routingRequest';
  319. } else {
  320. return '/row-RoutingManager/routingRequest';
  321. }
  322. },
  323. /**
  324. * Selects all segments on the route in the editor.
  325. * @param {Integer} routeIndex The index of the alternate route.
  326. * Default route to use is the first one, which is 0.
  327. */
  328. selectRouteSegments: function (routeIndex) {
  329. var i, n, seg,
  330. segIDs = this.getRouteSegmentIDs()[Math.floor(routeIndex) || 0],
  331. segments = [];
  332. if ('undefined' === typeof segIDs) {
  333. return;
  334. }
  335. for (i = 0, n = segIDs.length; i < n; i++) {
  336. seg = W.model.segments.get(segIDs[i]);
  337. if ('undefined' !== seg) {
  338. segments.push(seg);
  339. }
  340. }
  341. return W.selectionManager.select(segments);
  342. }
  343. };
  344. }).call(wLib.Model);
  345.  
  346. /*** INTERFACE ***/
  347. (function () {
  348. /**
  349. * Generates id for message bars.
  350. * @private
  351. */
  352. var getNextID = function () {
  353. var id = 1;
  354. return function () {
  355. return id++;
  356. };
  357. } ();
  358.  
  359. this.MessageBar = OL.Class(this, /** @lends wLib.Interface.MessageBar.prototype */ {
  360. $el: null,
  361. id: null,
  362. elementID: null,
  363. divStyle: {
  364. 'margin': 'auto',
  365. 'border-radius': '10px',
  366. 'text-align': 'center',
  367. 'width': '40%',
  368. 'font-size': '1em',
  369. 'font-weight': 'bold',
  370. 'color': 'white'
  371. },
  372. /**
  373. * Class to store individual message information.
  374. * @class {Object} Message
  375. * @private
  376. */
  377. Message: Backbone.Model.extend({
  378. defaults: {
  379. messageName: null,
  380. messageType: 'info',
  381. messageText: '',
  382. displayDuration: null,
  383. skipPrefix: false
  384. }
  385. }),
  386. /**
  387. * Class to display messages on page.
  388. * @class {Object} MessageView
  389. * @private
  390. */
  391. MessageView: Backbone.View.extend({
  392. styles: {
  393. defaultStyle: {
  394. 'border-radius': '20px',
  395. 'display': 'inline-block',
  396. 'padding': '5px',
  397. 'background-color': 'rgba(0,0,0,0.7)'
  398. },
  399. error: {
  400. 'border-radius': '20px',
  401. 'display': 'inline-block',
  402. 'padding': '5px',
  403. 'background-color': 'rgba(180,0,0,0.9)',
  404. 'color': 'black'
  405. },
  406. warn: {
  407. 'border-radius': '20px',
  408. 'display': 'inline-block',
  409. 'padding': '5px',
  410. 'background-color': 'rgba(230,230,0,0.9)',
  411. 'color': 'black'
  412. },
  413. info: {
  414. 'border-radius': '20px',
  415. 'display': 'inline-block',
  416. 'padding': '5px',
  417. 'background-color': 'rgba(0,0,230,0.9)'
  418. }
  419. },
  420. template: function () {
  421. var messageText = '',
  422. style,
  423. $messageEl = $('<p/>');
  424.  
  425. if (!this.model.attributes.skipPrefix && this.messagePrefix) {
  426. messageText = this.messagePrefix + ' ';
  427. }
  428.  
  429. messageText += this.model.attributes.messageText;
  430.  
  431. style = (this.model.attributes.messageType &&
  432. this.styles[this.model.attributes.messageType]) ?
  433. this.styles[this.model.attributes.messageType] : this.styles.defaultStyle;
  434.  
  435. $messageEl.
  436. css(style).
  437. text(messageText);
  438.  
  439. return $messageEl;
  440. },
  441. initialize: function () {
  442. this.render();
  443. },
  444. render: function () {
  445. this.$el.
  446. append(this.template()).
  447. appendTo(this.messageBar.$el).
  448. fadeIn('fast').
  449. delay(this.model.attributes.displayDuration ||
  450. this.displayDuration || 5000).
  451. fadeOut('slow', function () {
  452. $(this).remove();
  453. });
  454. return this;
  455. }
  456. }),
  457. /**
  458. * Class to hold Messages.
  459. * @class {Object} MessageCollection
  460. * @private
  461. */
  462. MessageCollection: Backbone.Collection.extend(),
  463.  
  464. messages: null,
  465. /**
  466. * Creates a new {wLib.Interface.MessageBar}.
  467. * @class
  468. * @name wLib.Interface.MessageBar
  469. * @param options {Object} Object containing options to use for the
  470. * message bar. Valid options are:
  471. * messages (an array of objects containing message info).
  472. * messagePrefix (prefix to prepend to each message; can be
  473. * disabled per message by using skipPrefix),.
  474. * displayDuration (default duration to display messages).
  475. * styles (object with keys representing a name for the style, i.e.
  476. * messageType, and values containing objects with css properties for
  477. * the messageType).
  478. */
  479. initialize: function (options) {
  480. var $insertTarget = $('#search');
  481.  
  482. options = _.defaults(options || {}, {
  483. messagePrefix: null,
  484. messages: [],
  485. styles: {},
  486. displayDuration: 5000
  487. });
  488.  
  489. this.messages = new this.MessageCollection({ model: this.Message });
  490. this.id = getNextID();
  491. this.elementID = 'wlib-messagebar-' + this.id;
  492.  
  493. _(options.styles).each(function (style, name) {
  494. this.addMessageType(name, style);
  495. }, this);
  496.  
  497. _(options.messages).each(function (message) {
  498. this.messages.add(message);
  499. }, this);
  500.  
  501. this.MessageView.prototype.messagePrefix = options.messagePrefix;
  502. this.MessageView.prototype.displayDuration = options.displayDuration;
  503. this.MessageView.prototype.messageBar = this;
  504.  
  505. this.$el = $('<div/>').
  506. css(this.divStyle).
  507. attr('id', this.elementID);
  508.  
  509. wLib.Util.waitForElement($insertTarget, function () {
  510. this.$el.insertAfter($insertTarget);
  511. }, this);
  512. },
  513. /**
  514. * Adds a style for a message type.
  515. * @param {String} name The name of the message type/style.
  516. * @param style {Object} Object containing css properties and
  517. * values to use for the new messageType.
  518. */
  519. addMessageType: function (name, style) {
  520. style = style || {};
  521.  
  522. if (name) {
  523. this.MessageView.prototype.styles[name] = style;
  524. }
  525.  
  526. return this;
  527. },
  528. /**
  529. * Removes the message bar from the page.
  530. */
  531. destroy: function () {
  532. this.messages.reset();
  533. this.$el.remove();
  534. },
  535. /**
  536. * Displays a message.
  537. * @param message The message object or the name of the message to
  538. * look up.
  539. */
  540. displayMessage: function (message) {
  541. if (typeof message === 'string') {
  542. // look up message by name and display
  543. message = this.messages.findWhere({ 'messageName': message });
  544. } else {
  545. // add the new message object to the collection and display
  546. message = this.messages.add(message);
  547. }
  548. new this.MessageView({
  549. model: message
  550. });
  551. if (!message.attributes.messageName) {
  552. this.messages.remove(message);
  553. }
  554. },
  555. /**
  556. * Adds a new message to the collection of saved messages.
  557. * @param {Object} messageObject An object containing one or more of the following options:
  558. * messageName: a string containing the name of the message (for lookup
  559. * later).
  560. * messageType: a string containing the style/type of message.
  561. * messageText: the text of the message.
  562. * displayDuration: the duration to display the message in
  563. * milliseconds. This value overrides any default value set upon
  564. * creation of the MessageBar.
  565. * skipPrefix: a boolean for whether to show the default message prefix
  566. * or not.
  567. */
  568. saveMessage: function (messageObject) {
  569. this.messages.add(messageObject);
  570. return this;
  571. },
  572. });
  573.  
  574. this.Options = OL.Class(this,
  575. /** @lends wLib.Interface.Options.prototype */ {
  576. localStorageName: null,
  577. options: {},
  578. /**
  579. * Creates a new Options object to handle saving and retrieving
  580. * script options. During initialization, any options stored under
  581. * the named key in localStorage will be loaded. Any options
  582. * provided as a parameter to the constructor will be applied to
  583. * the retrieved options and thus may overwrite any stored values.
  584. * @name wLib.Interface.Options
  585. * @class
  586. * @param {String} name The string used as the localStorage key
  587. * under which to store the options.
  588. * @param {Object} options A hash containing options to set during
  589. * initialization.
  590. * @example var myOptions = new wLib.Interface.Options(thebestscriptever, {scriptVersion: x});
  591. * myOptions.set('option1', true);
  592. * myOptions.set({'option2': false, 'option3': 'very true'});
  593. * myOptions.get('option2') === false // true;
  594. */
  595. initialize: function (name, options) {
  596. var i = 1;
  597.  
  598. if (window.localStorage && typeof name === 'string') {
  599.  
  600. this.localStorageName = name.toLowerCase().
  601. replace(/[^a-z]/g, '');
  602.  
  603. if (localStorage.getItem(this.localStorageName)) {
  604. while (localStorage.getItem(
  605. this.localStorageName + i)) {
  606. i += 1;
  607. }
  608. this.localStorageName = this.localStorageName + i;
  609. }
  610.  
  611. this.retrieveOptions();
  612.  
  613. if (options && _.isObject(options)) {
  614. this.set(options);
  615. }
  616. }
  617. },
  618. /**
  619. * Clears all stored options.
  620. */
  621. clear: function () {
  622. this.options = null;
  623. this.saveOptions();
  624. return this;
  625. },
  626. /**
  627. * Retrieves a stored value. If no key is specified, the entire
  628. * options object is returned.
  629. * @param {String} key Optional. The key to retrieve.
  630. */
  631. get: function (key) {
  632. return key && this.options[key] || this.options;
  633. },
  634. /**
  635. * Saves the options to localStorage
  636. * @private
  637. */
  638. saveOptions: function () {
  639. localStorage[this.localStorageName] =
  640. JSON.stringify(this.options);
  641. },
  642. /**
  643. * Stores a value under the provided key. Provide either an object
  644. * hash of keys and values to store as a single parameter or
  645. * provide a key and value as two parameters.
  646. * @param {Object} key The name of the option. Can be string,
  647. * number if providing a value as the second parameter, or a hash
  648. * of multiple options (see function description).
  649. * @param {Any} value The value to store. Not used if providing a
  650. * hash as the first argument.
  651. * @example myOptions.set('option1', true); // or
  652. * myOptions.set({'option2': false, 'option3': 'very true'});
  653. */
  654. set: function (key, value) {
  655. var j;
  656. if ((typeof key === 'string' || !isNaN(key)) && value) {
  657. this.options[key] = value;
  658. } else if (_.isObject(key)) {
  659. for (j in key) {
  660. if (key.hasOwnProperty(j)) {
  661. this.options[j] = key[j];
  662. }
  663. }
  664. }
  665. this.saveOptions();
  666. return this;
  667. },
  668. /**
  669. * Retrieves options previously stored in localStorage.
  670. * @private
  671. */
  672. retrieveOptions: function () {
  673. var options = localStorage[this.localStorageName];
  674. if (options) {
  675. this.options = options;
  676. }
  677. }
  678. });
  679.  
  680. this.Shortcut = OL.Class(this,
  681. /** @lends wLib.Interface.Shortcut.prototype */ {
  682. name: null,
  683. group: null,
  684. shortcut: {},
  685. callback: null,
  686. scope: null,
  687. groupExists: false,
  688. actionExists: false,
  689. eventExists: false,
  690. /**
  691. * Creates a new {wLib.Interface.Shortcut}.
  692. * @class
  693. * @name wLib.Interface.Shortcut
  694. * @param name {String} The name of the shortcut.
  695. * @param group {String} The name of the shortcut group.
  696. * @param shortcut {String} The shortcut key(s). The shortcut
  697. * should be of the form 'i' where i is the keyboard shortuct or
  698. * include modifier keys such as 'CSA+i', where C = the control
  699. * key, S = the shift key, A = the alt key, and i = the desired
  700. * keyboard shortcut. The modifier keys are optional.
  701. * @param callback {Function} The function to be called by the
  702. * shortcut.
  703. * @param scope {Object} The object to be used as this by the
  704. * callback.
  705. * @return {wLib.Interface.Shortcut} The new shortcut object.
  706. * @example //Creates new shortcut and adds it to the map.
  707. * shortcut = new wLib.Interface.Shortcut('myName', 'myGroup', 'C+p', callbackFunc, null).add();
  708. */
  709. initialize: function (name, group, shortcut, callback, scope) {
  710. var defaults = { group: 'default' };
  711. this.CLASS_NAME = 'wLib Shortcut';
  712. if ('string' === typeof name && name.length > 0 &&
  713. 'string' === typeof shortcut && shortcut.length > 0 &&
  714. 'function' === typeof callback) {
  715. this.name = name;
  716. this.group = group || defaults.group;
  717. this.callback = callback;
  718. this.shortcut[shortcut] = name;
  719. if ('object' !== typeof scope) {
  720. this.scope = null;
  721. } else {
  722. this.scope = scope;
  723. }
  724. return this;
  725. }
  726. },
  727. /**
  728. * Determines if the shortcut's group already exists.
  729. * @private
  730. */
  731. doesGroupExist: function () {
  732. this.groupExists = 'undefined' !== typeof W.accelerators.Groups[this.group] &&
  733. undefined !== typeof W.accelerators.Groups[this.group].members &&
  734. W.accelerators.Groups[this.group].length > 0;
  735. return this.groupExists;
  736. },
  737. /**
  738. * Determines if the shortcut's action already exists.
  739. * @private
  740. */
  741. doesActionExist: function () {
  742. this.actionExists = 'undefined' !== typeof W.accelerators.Actions[this.name];
  743. return this.actionExists;
  744. },
  745. /**
  746. * Determines if the shortcut's event already exists.
  747. * @private
  748. */
  749. doesEventExist: function () {
  750. this.eventExists = 'undefined' !== typeof W.accelerators.events.listeners[this.name] &&
  751. W.accelerators.events.listeners[this.name].length > 0 &&
  752. this.callback === W.accelerators.events.listeners[this.name][0].func &&
  753. this.scope === W.accelerators.events.listeners[this.name][0].obj;
  754. return this.eventExists;
  755. },
  756. /**
  757. * Creates the shortcut's group.
  758. * @private
  759. */
  760. createGroup: function () {
  761. W.accelerators.Groups[this.group] = [];
  762. W.accelerators.Groups[this.group].members = [];
  763. },
  764. /**
  765. * Registers the shortcut's action.
  766. * @private
  767. */
  768. addAction: function () {
  769. W.accelerators.addAction(this.name, { group: this.group });
  770. },
  771. /**
  772. * Registers the shortcut's event.
  773. * @private
  774. */
  775. addEvent: function () {
  776. W.accelerators.events.register(this.name, this.scope, this.callback);
  777. },
  778. /**
  779. * Registers the shortcut's keyboard shortcut.
  780. * @private
  781. */
  782. registerShortcut: function () {
  783. W.accelerators.registerShortcuts(this.shortcut);
  784. },
  785. /**
  786. * Adds the keyboard shortcut to the map.
  787. * @return {wLib.Interface.Shortcut} The keyboard shortcut.
  788. */
  789. add: function () {
  790. /* If the group is not already defined, initialize the group. */
  791. if (!this.doesGroupExist()) {
  792. this.createGroup();
  793. }
  794.  
  795. /* Clear existing actions with same name */
  796. if (this.doesActionExist()) {
  797. W.accelerators.Actions[this.name] = null;
  798. }
  799. this.addAction();
  800.  
  801. /* Register event only if it's not already registered */
  802. if (!this.doesEventExist()) {
  803. this.addEvent();
  804. }
  805.  
  806. /* Finally, register the shortcut. */
  807. this.registerShortcut();
  808. return this;
  809. },
  810. /**
  811. * Removes the keyboard shortcut from the map.
  812. * @return {wLib.Interface.Shortcut} The keyboard shortcut.
  813. */
  814. remove: function () {
  815. if (this.doesEventExist()) {
  816. W.accelerators.events.unregister(this.name, this.scope, this.callback);
  817. }
  818. if (this.doesActionExist()) {
  819. delete W.accelerators.Actions[this.name];
  820. }
  821. //remove shortcut?
  822. return this;
  823. },
  824. /**
  825. * Changes the keyboard shortcut and applies changes to the map.
  826. * @return {wLib.Interface.Shortcut} The keyboard shortcut.
  827. */
  828. change: function (shortcut) {
  829. if (shortcut) {
  830. this.shortcut = {};
  831. this.shortcut[shortcut] = this.name;
  832. this.registerShortcut();
  833. }
  834. return this;
  835. }
  836. }),
  837.  
  838. this.Tab = OL.Class(this, {
  839. /** @lends wLib.Interface.Tab */
  840. TAB_SELECTOR: '#user-tabs ul.nav-tabs',
  841. CONTENT_SELECTOR: '#user-info > div.tab-content',
  842. callback: null,
  843. $content: null,
  844. context: null,
  845. $tab: null,
  846. /**
  847. * Creates a new {wLib.Interface.Tab}. The tab is appended to the WME
  848. * editor sidebar and contains the passed HTML content.
  849. * @class
  850. * @name wLib.Interface.Tab
  851. * @param name {String} The name of the tab. Should not contain any
  852. * special characters.
  853. * @param content {String} The HTML content of the tab.
  854. * @param callback {Function} A function to call upon successfully
  855. * appending the tab.
  856. * @param {Object} context The context in which to call the callback
  857. * function.
  858. * @return {wLib.Interface.Tab} The new tab object.
  859. * @example //Creates new tab and adds it to the page.
  860. * new wLib.Interface.Tab('thebestscriptever', '<div>Hello World!</div>');
  861. */
  862. initialize: function (name, content, callback, context) {
  863. var idName, i = 0;
  864. if (name && 'string' === typeof name &&
  865. content && 'string' === typeof content) {
  866. if (callback && 'function' === typeof callback) {
  867. this.callback = callback;
  868. this.context = context || callback;
  869. }
  870. /* Sanitize name for html id attribute */
  871. idName = name.toLowerCase().replace(/[^a-z-_]/g, '');
  872. /* Make sure id will be unique on page */
  873. while (
  874. $('#sidepanel-' + (i ? idName + i : idName)).length > 0) {
  875. i++;
  876. }
  877. if (i) {
  878. idName = idName + i;
  879. }
  880. /* Create tab and content */
  881. this.$tab = $('<li/>')
  882. .append($('<a/>')
  883. .attr({
  884. 'href': '#sidepanel-' + idName,
  885. 'data-toggle': 'tab',
  886. })
  887. .text(name));
  888. this.$content = $('<div/>')
  889. .addClass('tab-pane')
  890. .attr('id', 'sidepanel-' + idName)
  891. .html(content);
  892.  
  893. this.appendTab();
  894. }
  895. },
  896. append: function (content) {
  897. this.$content.append(content);
  898. },
  899. appendTab: function (tries) {
  900. wLib.Util.waitForElement(
  901. this.TAB_SELECTOR + ',' + this.CONTENT_SELECTOR,
  902. function () {
  903. $(this.TAB_SELECTOR).append(this.$tab);
  904. $(this.CONTENT_SELECTOR).append(this.$content);
  905. if (this.callback) {
  906. this.callback.call(this.context);
  907. }
  908. }, this);
  909. }
  910. });
  911. }).call(wLib.Interface);
  912.  
  913. /*** Utilities ***/
  914. (function () {
  915. /**
  916. * Function to track the ready state of the map.
  917. * @memberof wLib.Util
  918. * @return {Boolean} Whether or not a map operation is pending or undefined
  919. * if the function has not yet seen a map ready event fired.
  920. */
  921. this.mapReady = function () {
  922. var mapReady = true;
  923. W.vent.on('operationPending', function () {
  924. mapReady = false;
  925. });
  926. W.vent.on('operationDone', function () {
  927. mapReady = true;
  928. });
  929. return function () {
  930. return mapReady;
  931. };
  932. } ();
  933.  
  934. /**
  935. * Function to track the ready state of the model.
  936. * @memberof wLib.Util
  937. * @return {Boolean} Whether or not the model has loaded objects or
  938. * undefined if the function has not yet seen a model ready event fired.
  939. */
  940. this.modelReady = function () {
  941. var modelReady = true;
  942. W.model.events.register('mergestart', null, function () {
  943. modelReady = false;
  944. });
  945. W.model.events.register('mergeend', null, function () {
  946. modelReady = true;
  947. });
  948. return function () {
  949. return modelReady;
  950. };
  951. } ();
  952. /**
  953. * Function to defer function execution until an element is present on the
  954. * page.
  955. * @memberof wLib.Util
  956. * @param {String} selector The CSS selector string or a jQuery object to
  957. * find before executing the callback.
  958. * @param {Function} callback The function to call when the page element is
  959. * detected.
  960. * @param {Object} context The context in which to call the callback.
  961. */
  962. this.waitForElement = function (selector, callback, context) {
  963. var jqObj;
  964.  
  965. if (!selector || typeof callback !== 'function') {
  966. return;
  967. }
  968.  
  969. jqObj = typeof selector === 'string' ?
  970. $(selector) : selector instanceof $ ? selector : null;
  971.  
  972. if (!jqObj.size()) {
  973. window.requestAnimationFrame(function () {
  974. wLib.Util.waitForElement(selector, callback, context);
  975. });
  976. } else {
  977. callback.call(context || callback);
  978. }
  979. };
  980. /**
  981. * Returns a callback function in the appropriate scope.
  982. * @memberof wLib.Util
  983. * @param {Function} func The callback function.
  984. * @param {Obj} scope The scope in which to call the callback.
  985. * @return {Function} A function that returns the callback function called
  986. * in the appropriate scope.
  987. */
  988. this.createCallback = function (func, scope) {
  989. return typeof func === 'function' && function () {
  990. return func.call(scope || func);
  991. };
  992. };
  993.  
  994. }).call(wLib.Util);
  995.  
  996. /*** API ***/
  997. (function () {
  998.  
  999. }).call(wLib.api);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址