WME Wide-Angle Lens Map Comments

Find map comments that match filter criteria

目前为 2022-10-03 提交的版本。查看 最新版本

  1. /// <reference path="../typescript-typings/globals/openlayers/index.d.ts" />
  2. /// <reference path="../typescript-typings/I18n.d.ts" />
  3. /// <reference path="../typescript-typings/waze.d.ts" />
  4. /// <reference path="../typescript-typings/globals/jquery/index.d.ts" />
  5. /// <reference path="WME Wide-Angle Lens.user.ts" />
  6. /// <reference path="../typescript-typings/greasyfork.d.ts" />
  7. // ==UserScript==
  8. // @name WME Wide-Angle Lens Map Comments
  9. // @namespace https://gf.qytechs.cn/en/users/19861-vtpearce
  10. // @description Find map comments that match filter criteria
  11. // @author vtpearce and crazycaveman
  12. // @include https://www.waze.com/editor
  13. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor.*$/
  14. // @version 1.2.0
  15. // @grant none
  16. // @copyright 2020 vtpearce
  17. // @license CC BY-SA 4.0
  18. // @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
  19. // ==/UserScript==
  20. // @updateURL https://gf.qytechs.cn/scripts/418294-wme-wide-angle-lens-map-comments-beta/code/WME%20Wide-Angle%20Lens%20Map%20Comments.meta.js
  21. // @downloadURL https://gf.qytechs.cn/scripts/418294-wme-wide-angle-lens-map-comments-beta/code/WME%20Wide-Angle%20Lens%20Map%20Comments.user.js
  22. /*global W, OL, $, WazeWrap, WMEWAL, OpenLayers, I18n */
  23. var WMEWAL_MapComments;
  24. (function (WMEWAL_MapComments) {
  25. const scrName = GM_info.script.name;
  26. const Version = GM_info.script.version;
  27. const updateText = '<ul>' +
  28. '<li>Support variable output fields</li>' +
  29. '</ul>';
  30. const greasyForkPage = 'https://gf.qytechs.cn/scripts/40644';
  31. const wazeForumThread = 'https://www.waze.com/forum/viewtopic.php?t=206376';
  32. const ctlPrefix = "_wmewalMapComments";
  33. const minimumWALVersionRequired = "1.5.3";
  34. let Operation;
  35. (function (Operation) {
  36. Operation[Operation["Equal"] = 1] = "Equal";
  37. Operation[Operation["NotEqual"] = 2] = "NotEqual";
  38. Operation[Operation["LessThan"] = 3] = "LessThan";
  39. Operation[Operation["LessThanOrEqual"] = 4] = "LessThanOrEqual";
  40. Operation[Operation["GreaterThan"] = 5] = "GreaterThan";
  41. Operation[Operation["GreaterThanOrEqual"] = 6] = "GreaterThanOrEqual";
  42. })(Operation || (Operation = {}));
  43. const pluginName = "WMEWAL-MapComments";
  44. WMEWAL_MapComments.Title = "Map Comments";
  45. WMEWAL_MapComments.MinimumZoomLevel = 12;
  46. WMEWAL_MapComments.SupportsSegments = false;
  47. WMEWAL_MapComments.SupportsVenues = false;
  48. const settingsKey = "WMEWALMapCommentsSettings";
  49. const savedSettingsKey = "WMEWALMapCommentsSavedSettings";
  50. let settings = null;
  51. let savedSettings = [];
  52. let mapComments;
  53. let titleRegex = null;
  54. let commentRegex = null;
  55. let lastModifiedBy;
  56. let lastModifiedByName;
  57. let createdBy;
  58. let createdByName;
  59. let mc = null;
  60. let initCount = 0;
  61. function GetTab() {
  62. let html = "<table style='border-collapse: separate; border-spacing:0px 1px;'>";
  63. html += "<tbody>";
  64. // html += "<tr><td class='wal-heading'>Output To:</td></tr>";
  65. // html += "<tr><td style='padding-left:20px'>" +
  66. // `<select id='${ctlPrefix}OutputTo'>` +
  67. // "<option value='csv'>CSV File</option>" +
  68. // "<option value='tab'>Browser Tab</option>" +
  69. // "<option value='both'>Both CSV File and Browser Tab</option></select></td></tr>";
  70. html += "<tr><td class='wal-heading'>Saved Filters</td></tr>";
  71. html += "<tr><td class='wal-indent' style='padding-bottom: 8px'>" +
  72. `<select id='${ctlPrefix}SavedSettings'></select><br/>` +
  73. `<button class='btn btn-primary' id='${ctlPrefix}LoadSetting' title='Load'>Load</button>` +
  74. `<button class='btn btn-primary' style='margin-left: 4px;' id='${ctlPrefix}SaveSetting' title='Save'>Save</button>` +
  75. `<button class='btn btn-primary' style='margin-left: 4px;' id='${ctlPrefix}DeleteSetting' title='Delete'>Delete</button></td></tr>`;
  76. html += "<tr><td class='wal-heading' style='border-top: 1px solid; padding-top: 4px'>Filters (All Of These)</td></tr>";
  77. html += "<tr><td><b>Lock Level:</b></td></tr>";
  78. html += "<tr><td class='wal-indent'>" +
  79. `<select id='${ctlPrefix}LockLevelOp'>` +
  80. "<option value='" + Operation.Equal.toString() + "' selected='selected'>=</option>" +
  81. "<option value='" + Operation.NotEqual.toString() + "'>&lt;&gt;</option></select>" +
  82. `<select id='${ctlPrefix}LockLevel'>` +
  83. "<option value=''></option>" +
  84. "<option value='1'>1</option>" +
  85. "<option value='2'>2</option>" +
  86. "<option value='3'>3</option>" +
  87. "<option value='4'>4</option>" +
  88. "<option value='5'>5</option>" +
  89. "<option value='6'>6</option></select></td></tr>";
  90. html += "<tr><td><b>Title RegEx:</b></td></tr>";
  91. html += `<tr><td class='wal-indent'><input type='text' id='${ctlPrefix}Title' class='wal-textbox'/><br/>` +
  92. `<input id='${ctlPrefix}TitleIgnoreCase' type='checkbox' class='wal-check'/>` +
  93. `<label for='${ctlPrefix}TitleIgnoreCase' class='wal-label'>Ignore case</label></td></tr>`;
  94. html += "<tr><td><b>Comments RegEx:</b></td></tr>";
  95. html += `<tr><td class='wal-indent'><input type='text' id='${ctlPrefix}Comments' class='wal-textbox'/><br/>` +
  96. `<input id='${ctlPrefix}CommentsIgnoreCase' type='checkbox' class='wal-check'/>` +
  97. `<label for='${ctlPrefix}CommentsIgnoreCase' class='wal-label'>Ignore case</label></td></tr>`;
  98. html += "<tr><td><b>Created By:</b></td></tr>";
  99. html += "<tr><td class='wal-indent'>" +
  100. `<select id='${ctlPrefix}CreatedBy'></select></td></tr>`;
  101. html += "<tr><td><b>Last Updated By:</b></td></tr>";
  102. html += "<tr><td class='wal-indent'>" +
  103. `<select id='${ctlPrefix}LastModifiedBy'></select></td></tr>`;
  104. html += "<tr><td><b>Geometry Type:</b></td></tr>" +
  105. `<tr><td class='wal-indent'><select id='${ctlPrefix}GeometryType'>` +
  106. "<option value=''></option>" +
  107. "<option value='area'>" + I18n.t("edit.venue.type.area") + "</option>" +
  108. "<option value='point'>" + I18n.t("edit.venue.type.point") + "</option>" +
  109. "</select></td></tr>";
  110. html += `<tr><td><input id='${ctlPrefix}Expiration' type='checkbox' class='wal-check'/>` +
  111. `<label for='${ctlPrefix}Expiration' class='wal-label'>Expires:</label> ` +
  112. `<select id='${ctlPrefix}ExpirationOp'>` +
  113. `<option value='${Operation.LessThan}'>&lt;</option>` +
  114. `<option value='${Operation.LessThanOrEqual}'>&lt;=</option>` +
  115. `<option value='${Operation.GreaterThanOrEqual}'>&gt;=</option>` +
  116. `<option value='${Operation.GreaterThan}'>&gt;</option></select></td></tr>`;
  117. html += `<tr><td class='wal-indent'><input type='date' id='${ctlPrefix}ExpirationDate' class='wal-textbox'/></td></tr>`;
  118. html += `<tr><td><input id='${ctlPrefix}Editable' type='checkbox' class='wal-check'/>` +
  119. `<label for='${ctlPrefix}Editable' class='wal-label'>Editable by me</label></td></tr>`;
  120. html += "</tbody></table>";
  121. return html;
  122. }
  123. WMEWAL_MapComments.GetTab = GetTab;
  124. function TabLoaded() {
  125. updateUsers($(`#${ctlPrefix}LastModifiedBy`));
  126. updateUsers($(`#${ctlPrefix}CreatedBy`));
  127. updateUI();
  128. updateSavedSettingsList();
  129. $(`#${ctlPrefix}LastModifiedBy`).on("focus", function () {
  130. updateUsers($(`#${ctlPrefix}LastModifiedBy`));
  131. });
  132. $(`#${ctlPrefix}CreatedBy`).on("focus", function () {
  133. updateUsers($(`#${ctlPrefix}CreatedBy`));
  134. });
  135. $(`#${ctlPrefix}LoadSetting`).on("click", loadSetting);
  136. $(`#${ctlPrefix}SaveSetting`).on("click", saveSetting);
  137. $(`#${ctlPrefix}DeleteSetting`).on("click", deleteSetting);
  138. }
  139. WMEWAL_MapComments.TabLoaded = TabLoaded;
  140. function updateUsers(selectUsernameList) {
  141. // Preserve current selection
  142. const currentId = parseInt(selectUsernameList.val());
  143. selectUsernameList.empty();
  144. const userObjs = [];
  145. userObjs.push({ id: null, name: "" });
  146. for (let uo in W.model.users.objects) {
  147. if (W.model.users.objects.hasOwnProperty(uo)) {
  148. const u = W.model.users.getObjectById(parseInt(uo));
  149. if (u.type === "user" && u.userName !== null && typeof u.userName !== "undefined") {
  150. userObjs.push({ id: u.id, name: u.userName });
  151. }
  152. }
  153. }
  154. userObjs.sort(function (a, b) {
  155. if (a.id == null) {
  156. return -1;
  157. }
  158. else {
  159. return a.name.localeCompare(b.name);
  160. }
  161. });
  162. for (let ix = 0; ix < userObjs.length; ix++) {
  163. const o = userObjs[ix];
  164. const userOption = $("<option/>").text(o.name).attr("value", o.id);
  165. if (currentId != null && o.id == null) {
  166. userOption.attr("selected", "selected");
  167. }
  168. selectUsernameList.append(userOption);
  169. }
  170. }
  171. function updateSavedSettingsList() {
  172. const s = $(`#${ctlPrefix}SavedSettings`);
  173. s.empty();
  174. for (let ixSaved = 0; ixSaved < savedSettings.length; ixSaved++) {
  175. const opt = $("<option/>").attr("value", ixSaved).text(savedSettings[ixSaved].Name);
  176. s.append(opt);
  177. }
  178. }
  179. function updateUI() {
  180. // $(`#${ctlPrefix}OutputTo`).val(settings.OutputTo);
  181. $(`#${ctlPrefix}LockLevel`).val(settings.LockLevel);
  182. $(`#${ctlPrefix}LockLevelOp`).val(settings.LockLevelOperation || Operation.Equal.toString());
  183. $(`#${ctlPrefix}Title`).val(settings.TitleRegex || "");
  184. $(`#${ctlPrefix}TitleIgnoreCase`).prop("checked", settings.TitleRegexIgnoreCase);
  185. $(`#${ctlPrefix}Comments`).val(settings.CommentRegex || "");
  186. $(`#${ctlPrefix}CommentsIgnoreCase`).prop("checked", settings.CommentRegexIgnoreCase);
  187. $(`#${ctlPrefix}Editable`).prop("checked", settings.EditableByMe);
  188. $(`#${ctlPrefix}LastModifiedBy`).val(settings.LastModifiedBy);
  189. $(`#${ctlPrefix}CreatedBy`).val(settings.CreatedBy);
  190. $(`#${ctlPrefix}Expiration`).prop("checked", settings.Expiration);
  191. $(`#${ctlPrefix}ExpirationOp`).val(settings.ExpirationOperation);
  192. if (settings.ExpirationDate != null) {
  193. const expirationDate = new Date(settings.ExpirationDate);
  194. $(`#${ctlPrefix}ExpirationDate`).val(expirationDate.getFullYear().toString().padStart(4, "0") + "-" +
  195. (expirationDate.getMonth() + 1).toString().padStart(2, "0") + "-" + expirationDate.getDate().toString().padStart(2, "0"));
  196. }
  197. else {
  198. $(`#${ctlPrefix}ExpirationDate`).val("");
  199. }
  200. $(`#${ctlPrefix}GeometryType`).val(settings.GeometryType);
  201. }
  202. function loadSetting() {
  203. const selectedSetting = parseInt($(`#${ctlPrefix}SavedSettings`).val());
  204. if (selectedSetting == null || isNaN(selectedSetting) || selectedSetting < 0 || selectedSetting > savedSettings.length) {
  205. return;
  206. }
  207. const savedSetting = savedSettings[selectedSetting].Setting;
  208. for (let name in savedSetting) {
  209. if (settings.hasOwnProperty(name)) {
  210. settings[name] = savedSetting[name];
  211. }
  212. }
  213. updateUI();
  214. }
  215. function validateSettings() {
  216. function addMessage(error) {
  217. message += ((message.length > 0 ? "\n" : "") + error);
  218. }
  219. let message = "";
  220. const s = getSettings();
  221. const selectedUpdateUser = $(`#${ctlPrefix}LastModifiedBy`).val();
  222. if (nullif(selectedUpdateUser, "") !== null && s.LastModifiedBy === null) {
  223. addMessage("Invalid last updated user");
  224. }
  225. const selectedCreateUser = $(`#${ctlPrefix}CreatedBy`).val();
  226. if (nullif(selectedCreateUser, "") !== null && s.CreatedBy === null) {
  227. addMessage("Invalid created by user");
  228. }
  229. let r;
  230. if (nullif(s.TitleRegex, "") !== null) {
  231. try {
  232. r = (s.TitleRegexIgnoreCase ? new RegExp(s.TitleRegex, "i") : new RegExp(s.TitleRegex));
  233. }
  234. catch (error) {
  235. addMessage("Title RegEx is invalid");
  236. }
  237. }
  238. if (nullif(s.CommentRegex, "") !== null) {
  239. try {
  240. r = (s.CommentRegexIgnoreCase ? new RegExp(s.CommentRegex, "i") : new RegExp(s.CommentRegex));
  241. }
  242. catch (error) {
  243. addMessage("Comments RegEx is invalid");
  244. }
  245. }
  246. if (s.Expiration && s.ExpirationDate === null) {
  247. addMessage("Select an expiration date on which to filter");
  248. }
  249. if (message.length > 0) {
  250. alert(pluginName + ": " + message);
  251. return false;
  252. }
  253. return true;
  254. }
  255. function saveSetting() {
  256. if (validateSettings()) {
  257. const s = getSettings();
  258. const sName = prompt("Enter a name for this setting");
  259. if (sName == null) {
  260. return;
  261. }
  262. // Check to see if there is already a name that matches this
  263. for (let ixSetting = 0; ixSetting < savedSettings.length; ixSetting++) {
  264. if (savedSettings[ixSetting].Name === sName) {
  265. if (confirm("A setting with this name already exists. Overwrite?")) {
  266. savedSettings[ixSetting].Setting = s;
  267. updateSavedSettings();
  268. }
  269. else {
  270. alert("Please pick a new name.");
  271. }
  272. return;
  273. }
  274. }
  275. const savedSetting = {
  276. Name: sName,
  277. Setting: s
  278. };
  279. savedSettings.push(savedSetting);
  280. updateSavedSettings();
  281. }
  282. }
  283. function getSettings() {
  284. const s = {
  285. LockLevel: null,
  286. LockLevelOperation: parseInt($(`#${ctlPrefix}LockLevelOp`).val()),
  287. TitleRegex: null,
  288. TitleRegexIgnoreCase: $(`#${ctlPrefix}TitleIgnoreCase`).prop("checked"),
  289. CommentRegex: null,
  290. CommentRegexIgnoreCase: $(`#${ctlPrefix}CommentsIgnoreCase`).prop("checked"),
  291. EditableByMe: $(`#${ctlPrefix}Editable`).prop("checked"),
  292. LastModifiedBy: null,
  293. GeometryType: nullif($(`#${ctlPrefix}GeometryType`).val(), ""),
  294. Expiration: $(`#${ctlPrefix}Expiration`).prop("checked"),
  295. ExpirationOperation: parseInt($(`#${ctlPrefix}ExpirationOp`).val()),
  296. ExpirationDate: null,
  297. CreatedBy: null
  298. };
  299. const selectedUpdateUser = $(`#${ctlPrefix}LastModifiedBy`).val();
  300. if (nullif(selectedUpdateUser, "") !== null) {
  301. s.LastModifiedBy = W.model.users.getObjectById(selectedUpdateUser).id;
  302. }
  303. const selectedCreateUser = $(`#${ctlPrefix}CreatedBy`).val();
  304. if (nullif(selectedCreateUser, "") !== null) {
  305. s.CreatedBy = W.model.users.getObjectById(selectedCreateUser).id;
  306. }
  307. let pattern = $(`#${ctlPrefix}Title`).val();
  308. if (nullif(pattern, "") !== null) {
  309. s.TitleRegex = pattern;
  310. }
  311. pattern = $(`#${ctlPrefix}Comments`).val();
  312. if (nullif(pattern, "") !== null) {
  313. s.CommentRegex = pattern;
  314. }
  315. const selectedLockLevel = $(`#${ctlPrefix}LockLevel`).val();
  316. if (nullif(selectedLockLevel, "") !== null) {
  317. s.LockLevel = parseInt(selectedLockLevel);
  318. }
  319. let expirationDate = $(`#${ctlPrefix}ExpirationDate`).val();
  320. if (nullif(expirationDate, "") !== null) {
  321. switch (s.ExpirationOperation) {
  322. case Operation.LessThan:
  323. case Operation.GreaterThanOrEqual:
  324. expirationDate += " 00:00";
  325. break;
  326. case Operation.LessThanOrEqual:
  327. case Operation.GreaterThan:
  328. expirationDate += " 23:59:59";
  329. break;
  330. }
  331. s.ExpirationDate = new Date(expirationDate).getTime();
  332. }
  333. return s;
  334. }
  335. function deleteSetting() {
  336. const selectedSetting = parseInt($(`#${ctlPrefix}SavedSettings`).val());
  337. if (selectedSetting == null || isNaN(selectedSetting) || selectedSetting < 0 || selectedSetting > savedSettings.length) {
  338. return;
  339. }
  340. if (confirm("Are you sure you want to delete this saved setting?")) {
  341. savedSettings.splice(selectedSetting, 1);
  342. updateSavedSettings();
  343. }
  344. }
  345. function ScanStarted() {
  346. let allOk = validateSettings();
  347. if (allOk) {
  348. mapComments = [];
  349. mc = [];
  350. settings = getSettings();
  351. if (settings.LastModifiedBy !== null) {
  352. lastModifiedBy = W.model.users.getObjectById(settings.LastModifiedBy);
  353. lastModifiedByName = lastModifiedBy.userName;
  354. }
  355. else {
  356. lastModifiedBy = null;
  357. lastModifiedByName = null;
  358. }
  359. if (settings.CreatedBy !== null) {
  360. createdBy = W.model.users.getObjectById(settings.CreatedBy);
  361. createdByName = createdBy.userName;
  362. }
  363. else {
  364. createdBy = null;
  365. createdByName = null;
  366. }
  367. if (settings.TitleRegex !== null) {
  368. titleRegex = (settings.TitleRegexIgnoreCase ? new RegExp(settings.TitleRegex, "i") : new RegExp(settings.TitleRegex));
  369. }
  370. else {
  371. titleRegex = null;
  372. }
  373. if (settings.CommentRegex !== null) {
  374. commentRegex = (settings.CommentRegexIgnoreCase ? new RegExp(settings.CommentRegex, "i") : new RegExp(settings.CommentRegex));
  375. }
  376. else {
  377. commentRegex = null;
  378. }
  379. updateSettings();
  380. }
  381. return allOk;
  382. }
  383. WMEWAL_MapComments.ScanStarted = ScanStarted;
  384. function updateSavedSettings() {
  385. if (typeof Storage !== "undefined") {
  386. localStorage[savedSettingsKey] = WMEWAL.LZString.compressToUTF16(JSON.stringify(savedSettings));
  387. }
  388. updateSavedSettingsList();
  389. }
  390. function updateSettings() {
  391. if (typeof Storage !== "undefined") {
  392. localStorage[settingsKey] = JSON.stringify(settings);
  393. }
  394. }
  395. function getPL(mapComment, lonlat) {
  396. return WMEWAL.GenerateBasePL(lonlat.lat, lonlat.lon, 5) + "&mode=0&mapComments=" + mapComment.id;
  397. }
  398. function ScanExtent(segments, venues) {
  399. return new Promise(resolve => {
  400. setTimeout(function () {
  401. let count = scan(segments, venues);
  402. resolve({ Streets: null, Places: null, MapComments: count });
  403. }, 0);
  404. });
  405. }
  406. WMEWAL_MapComments.ScanExtent = ScanExtent;
  407. function scan(segments, venues) {
  408. for (let c in W.model.mapComments.objects) {
  409. if (mc.indexOf(c) === -1) {
  410. const mapComment = W.model.mapComments.getObjectById(c);
  411. if (mapComment != null) {
  412. mc.push(c);
  413. if ((settings.LockLevel == null ||
  414. (settings.LockLevelOperation === Operation.Equal && (mapComment.attributes.lockRank || 0) + 1 === settings.LockLevel) ||
  415. (settings.LockLevelOperation === Operation.NotEqual && (mapComment.attributes.lockRank || 0) + 1 !== settings.LockLevel)) &&
  416. (!settings.EditableByMe || mapComment.arePropertiesEditable()) &&
  417. (settings.GeometryType == null || (settings.GeometryType === "point" && mapComment.isPoint()) || (settings.GeometryType === "area" && !mapComment.isPoint())) &&
  418. (titleRegex == null || titleRegex.test(mapComment.attributes.subject)) &&
  419. ((settings.LastModifiedBy === null) ||
  420. ((mapComment.getUpdatedBy() ?? mapComment.getCreatedBy()) === settings.LastModifiedBy)) &&
  421. ((settings.CreatedBy === null) ||
  422. (mapComment.getCreatedBy() === settings.CreatedBy))) {
  423. if (settings.Expiration) {
  424. if (mapComment.attributes.endDate === null) {
  425. // If map comment doesn't have an end date, it automatically matches any greater than (or equal) filter
  426. // and automatically fails any less than (or equal) filter
  427. if (settings.ExpirationOperation === Operation.LessThan || settings.ExpirationOperation === Operation.LessThanOrEqual) {
  428. continue;
  429. }
  430. }
  431. else {
  432. const endDateNumber = Date.parse(mapComment.attributes.endDate);
  433. if (isNaN(endDateNumber)) {
  434. continue;
  435. }
  436. let expirationMatches;
  437. switch (settings.ExpirationOperation) {
  438. case Operation.LessThan:
  439. expirationMatches = (endDateNumber < settings.ExpirationDate);
  440. break;
  441. case Operation.LessThanOrEqual:
  442. expirationMatches = (endDateNumber <= settings.ExpirationDate);
  443. break;
  444. case Operation.GreaterThanOrEqual:
  445. expirationMatches = (endDateNumber >= settings.ExpirationDate);
  446. break;
  447. case Operation.GreaterThan:
  448. expirationMatches = (endDateNumber > settings.ExpirationDate);
  449. break;
  450. default:
  451. expirationMatches = false;
  452. break;
  453. }
  454. if (!expirationMatches) {
  455. continue;
  456. }
  457. }
  458. }
  459. // if (settings.LastModifiedBy != null) {
  460. // if (mapComment.attributes.updatedBy != null) {
  461. // if (mapComment.attributes.updatedBy !== settings.LastModifiedBy) {
  462. // continue;
  463. // }
  464. // } else if (mapComment.attributes.createdBy !== settings.LastModifiedBy) {
  465. // continue;
  466. // }
  467. // }
  468. if (settings.CommentRegex != null) {
  469. let match = commentRegex.test(mapComment.attributes.body);
  470. const comments = mapComment.getComments();
  471. for (let ixComment = 0; ixComment < comments.length; ixComment++ && !match) {
  472. match = commentRegex.test(comments.models[ixComment].attributes.text);
  473. }
  474. if (!match) {
  475. continue;
  476. }
  477. }
  478. if (!WMEWAL.IsMapCommentInArea(mapComment)) {
  479. continue;
  480. }
  481. const lastEditorID = mapComment.getUpdatedBy() ?? mapComment.getCreatedBy();
  482. const lastEditor = W.model.users.getObjectById(lastEditorID) ?? { userName: 'Not found' };
  483. let endDate = null;
  484. const expirationDate = mapComment.attributes.endDate;
  485. if (expirationDate != null) {
  486. endDate = Date.parse(expirationDate);
  487. if (isNaN(endDate)) {
  488. endDate = null;
  489. }
  490. }
  491. const mComment = {
  492. id: mapComment.attributes.id,
  493. geometryType: ((mapComment.isPoint()) ? I18n.t("edit.venue.type.point") : I18n.t("edit.venue.type.area")),
  494. lastEditor: (lastEditor && lastEditor.userName) || "",
  495. title: mapComment.attributes.subject,
  496. lockLevel: mapComment.attributes.lockRank + 1,
  497. expirationDate: endDate,
  498. center: mapComment.attributes.geometry.getCentroid(),
  499. createdOn: mapComment.attributes.createdOn,
  500. updatedOn: mapComment.attributes.updatedOn
  501. };
  502. mapComments.push(mComment);
  503. }
  504. }
  505. }
  506. }
  507. return mapComments.length;
  508. }
  509. function ScanComplete() {
  510. if (mapComments.length === 0) {
  511. alert(pluginName + ": No map comments found.");
  512. }
  513. else {
  514. mapComments.sort(function (a, b) {
  515. return a.title.localeCompare(b.title);
  516. });
  517. const isCSV = (WMEWAL.outputTo & WMEWAL.OutputTo.CSV);
  518. const isTab = (WMEWAL.outputTo & WMEWAL.OutputTo.Tab);
  519. const addBOM = WMEWAL.addBOM ?? false;
  520. const outputFields = WMEWAL.outputFields ?? ['CreatedEditor', 'LastEditor', 'LockLevel', 'Lat', 'Lon'];
  521. const includeLockLevel = outputFields.indexOf('LockLevel') > -1 || settings.LockLevel !== null;
  522. const includeLastEditor = outputFields.indexOf('Last Editor') > -1 || settings.LastModifiedBy !== null;
  523. const includeLat = outputFields.indexOf('Lat') > -1;
  524. const includeLon = outputFields.indexOf('Lon') > -1;
  525. let lineArray;
  526. let columnArray;
  527. let w;
  528. let fileName;
  529. if (isCSV) {
  530. lineArray = [];
  531. columnArray = ["Title"];
  532. if (includeLockLevel) {
  533. columnArray.push('Lock Level');
  534. }
  535. columnArray.push("Geometry Type", 'Expiration Date');
  536. if (includeLastEditor) {
  537. columnArray.push('Last Editor');
  538. }
  539. columnArray.push('Created On', 'Updated On');
  540. if (includeLat) {
  541. columnArray.push('Latitude');
  542. }
  543. if (includeLon) {
  544. columnArray.push('Longitude');
  545. }
  546. columnArray.push('Permalink');
  547. lineArray.push(columnArray);
  548. fileName = "MapComments" + WMEWAL.areaName;
  549. fileName += ".csv";
  550. }
  551. if (isTab) {
  552. w = window.open();
  553. w.document.write("<html><head><title>Map Comments</title></head><body>");
  554. w.document.write("<h2>Area: " + WMEWAL.areaName + "</h2>");
  555. w.document.write("<b>Filters</b>");
  556. if (settings.LockLevel != null) {
  557. w.document.write("<br/>Lock Level " + (settings.LockLevelOperation === Operation.NotEqual ? "does not equal " : "equals ") + settings.LockLevel.toString());
  558. }
  559. if (settings.TitleRegex != null) {
  560. w.document.write("<br/>Title matches " + settings.TitleRegex);
  561. if (settings.TitleRegexIgnoreCase) {
  562. w.document.write(" (ignoring case)");
  563. }
  564. }
  565. if (settings.CommentRegex != null) {
  566. w.document.write("<br/>Comment matches " + settings.CommentRegex);
  567. if (settings.CommentRegexIgnoreCase) {
  568. w.document.write(" (ignoring case)");
  569. }
  570. }
  571. if (settings.GeometryType != null) {
  572. w.document.write("<br/>Geometry type is " + I18n.t("edit.landmark.type." + settings.GeometryType));
  573. }
  574. if (settings.Expiration) {
  575. w.document.write("Expires ");
  576. switch (settings.ExpirationOperation) {
  577. case Operation.LessThan:
  578. w.document.write("before");
  579. break;
  580. case Operation.LessThanOrEqual:
  581. w.document.write("on or before");
  582. break;
  583. case Operation.GreaterThanOrEqual:
  584. w.document.write("on or after");
  585. break;
  586. case Operation.GreaterThan:
  587. w.document.write("after");
  588. break;
  589. }
  590. w.document.write(` ${new Date(settings.ExpirationDate).toString()}`);
  591. }
  592. if (settings.CreatedBy != null) {
  593. w.document.write("<br/>Created by " + createdByName);
  594. }
  595. if (settings.LastModifiedBy != null) {
  596. w.document.write("<br/>Last updated by " + lastModifiedByName);
  597. }
  598. if (settings.EditableByMe) {
  599. w.document.write("<br/>Editable by me");
  600. }
  601. w.document.write("<table style='border-collapse: separate; border-spacing: 8px 0px'><thead><tr><th>Title</th>");
  602. if (includeLockLevel) {
  603. w.document.write("<th>Lock Level</th>");
  604. }
  605. w.document.write("<th>Geometry Type</th><th>Expiration Date</th>");
  606. if (includeLastEditor) {
  607. w.document.write("<th>Last Editor</th>");
  608. }
  609. w.document.write("<th>Created On</th><th>Updated On</th>");
  610. if (includeLat) {
  611. w.document.write("<th>Latitude</th>");
  612. }
  613. if (includeLon) {
  614. w.document.write("<th>Longitude</th>");
  615. }
  616. w.document.write("<th>Permalink</th></tr><thead><tbody>");
  617. }
  618. for (let ixmc = 0; ixmc < mapComments.length; ixmc++) {
  619. const mapComment = mapComments[ixmc];
  620. const lonlat = OpenLayers.Layer.SphericalMercator.inverseMercator(mapComment.center.x, mapComment.center.y);
  621. const pl = getPL(mapComment, lonlat);
  622. let expirationDate = "";
  623. if (mapComment.expirationDate != null) {
  624. expirationDate = new Date(mapComment.expirationDate).toLocaleString();
  625. }
  626. if (isCSV) {
  627. columnArray = [`"${mapComment.title}"`];
  628. if (includeLockLevel) {
  629. columnArray.push(mapComment.lockLevel.toString());
  630. }
  631. columnArray.push(mapComment.geometryType, `"${expirationDate}"`);
  632. if (includeLastEditor) {
  633. columnArray.push(`"${mapComment.lastEditor}"`);
  634. }
  635. columnArray.push(mapComment.createdOn ? new Date(mapComment.createdOn).toLocaleString() : "", mapComment.updatedOn ? new Date(mapComment.updatedOn).toLocaleString() : "");
  636. if (includeLat) {
  637. columnArray.push(lonlat.lat.toString());
  638. }
  639. if (includeLon) {
  640. columnArray.push(lonlat.lon.toString());
  641. }
  642. columnArray.push(`"${pl}"`);
  643. lineArray.push(columnArray);
  644. }
  645. if (isTab) {
  646. w.document.write(`<tr><td>${mapComment.title}</td>`);
  647. if (includeLockLevel) {
  648. w.document.write(`<td>${mapComment.lockLevel.toString()}</td>`);
  649. }
  650. w.document.write("<td>" + mapComment.geometryType + "</td>");
  651. w.document.write("<td>" + expirationDate + "</td>");
  652. if (includeLastEditor) {
  653. w.document.write("<td>" + mapComment.lastEditor + "</td>");
  654. }
  655. w.document.write("<td>" + (mapComment.createdOn ? new Date(mapComment.createdOn).toLocaleString() : "&nbsp;") + "</td>");
  656. w.document.write("<td>" + (mapComment.updatedOn ? new Date(mapComment.updatedOn).toLocaleString() : "&nbsp;") + "</td>");
  657. if (includeLat) {
  658. w.document.write("<td>" + lonlat.lat.toString() + "</td>");
  659. }
  660. if (includeLon) {
  661. w.document.write("<td>" + lonlat.lon.toString() + "</td>");
  662. }
  663. w.document.write("<td><a href=\'" + pl + "\' target=\'_blank\'>Permalink</a></td></tr>");
  664. }
  665. }
  666. if (isCSV) {
  667. const csvContent = lineArray.join("\n");
  668. const blobContent = [];
  669. if (addBOM) {
  670. blobContent.push('\uFEFF');
  671. }
  672. blobContent.push(csvContent);
  673. const blob = new Blob(blobContent, { type: "data:text/csv;charset=utf-8" });
  674. const link = document.createElement("a");
  675. const url = URL.createObjectURL(blob);
  676. link.setAttribute("href", url);
  677. link.setAttribute("download", fileName);
  678. const node = document.body.appendChild(link);
  679. link.click();
  680. document.body.removeChild(node);
  681. }
  682. if (isTab) {
  683. w.document.write("</tbody></table></body></html>");
  684. w.document.close();
  685. w = null;
  686. }
  687. }
  688. mapComments = null;
  689. mc = null;
  690. }
  691. WMEWAL_MapComments.ScanComplete = ScanComplete;
  692. function ScanCancelled() {
  693. ScanComplete();
  694. }
  695. WMEWAL_MapComments.ScanCancelled = ScanCancelled;
  696. function Init() {
  697. console.group(pluginName + ": Initializing");
  698. initCount++;
  699. let allOK = true;
  700. const objectToCheck = ["OpenLayers",
  701. "W.app",
  702. "WMEWAL.RegisterPlugIn",
  703. "WazeWrap.Ready"];
  704. for (let i = 0; i < objectToCheck.length; i++) {
  705. const path = objectToCheck[i].split(".");
  706. let object = window;
  707. let ok = true;
  708. for (let j = 0; j < path.length; j++) {
  709. object = object[path[j]];
  710. if (typeof object === "undefined" || object == null) {
  711. console.warn(objectToCheck[i] + " NOT OK");
  712. ok = false;
  713. break;
  714. }
  715. }
  716. if (ok) {
  717. console.log(objectToCheck[i] + " OK");
  718. }
  719. else {
  720. allOK = false;
  721. }
  722. }
  723. if (!allOK) {
  724. if (initCount < 60) {
  725. window.setTimeout(Init, 1000);
  726. }
  727. else {
  728. console.error("Giving up on initialization");
  729. }
  730. console.groupEnd();
  731. return;
  732. }
  733. // Check to see if WAL is at the minimum verson needed
  734. if (!(typeof WMEWAL.IsAtMinimumVersion === "function" && WMEWAL.IsAtMinimumVersion(minimumWALVersionRequired))) {
  735. log("log", "WAL not at required minimum version.");
  736. console.groupEnd();
  737. WazeWrap.Alerts.info(GM_info.script.name, "Cannot load plugin because WAL is not at the required minimum version.&nbsp;" +
  738. "You might need to manually update it from <a href='https://gf.qytechs.cn/scripts/40641' target='_blank'>Greasy Fork镜像</a>.", true, false);
  739. return;
  740. }
  741. if (typeof Storage !== "undefined") {
  742. if (localStorage[settingsKey]) {
  743. settings = JSON.parse(localStorage[settingsKey]);
  744. }
  745. if (localStorage[savedSettingsKey]) {
  746. try {
  747. savedSettings = JSON.parse(WMEWAL.LZString.decompressFromUTF16(localStorage[savedSettingsKey]));
  748. }
  749. catch (e) { }
  750. if (typeof savedSettings === "undefined" || savedSettings === null || savedSettings.length === 0) {
  751. log("debug", "decompressFromUTF16 failed, attempting decompress");
  752. localStorage[savedSettingsKey + "Backup"] = localStorage[savedSettingsKey];
  753. try {
  754. savedSettings = JSON.parse(WMEWAL.LZString.decompress(localStorage[savedSettingsKey]));
  755. }
  756. catch (e) { }
  757. if (typeof savedSettings === "undefined" || savedSettings === null || savedSettings.length === 0) {
  758. log("debug", "decompress failed, savedSettings unrecoverable. Using blank");
  759. savedSettings = [];
  760. }
  761. updateSavedSettings();
  762. }
  763. }
  764. }
  765. if (settings == null) {
  766. settings = {
  767. TitleRegex: null,
  768. TitleRegexIgnoreCase: true,
  769. CommentRegex: null,
  770. CommentRegexIgnoreCase: true,
  771. GeometryType: null,
  772. ExpirationDate: null,
  773. LockLevel: null,
  774. LockLevelOperation: Operation.Equal,
  775. LastModifiedBy: null,
  776. EditableByMe: true,
  777. Expiration: false,
  778. ExpirationOperation: Operation.GreaterThanOrEqual,
  779. CreatedBy: null
  780. };
  781. }
  782. else {
  783. if (updateProperties()) {
  784. updateSettings();
  785. }
  786. }
  787. console.log("Initialized");
  788. console.groupEnd();
  789. WazeWrap.Interface.ShowScriptUpdate(scrName, Version, updateText, greasyForkPage, wazeForumThread);
  790. WMEWAL.RegisterPlugIn(WMEWAL_MapComments);
  791. }
  792. function updateProperties() {
  793. let upd = false;
  794. if (settings !== null) {
  795. if (!settings.hasOwnProperty("CreatedBy")) {
  796. settings.CreatedBy = null;
  797. upd = true;
  798. }
  799. if (!settings.hasOwnProperty("ExpirationOperation")) {
  800. settings.ExpirationOperation = Operation.GreaterThanOrEqual;
  801. upd = true;
  802. }
  803. if (!settings.hasOwnProperty("Expiration")) {
  804. settings.Expiration = (settings.ExpirationDate !== null);
  805. upd = true;
  806. }
  807. if (settings.hasOwnProperty("OutputTo")) {
  808. delete settings["OutputTo"];
  809. upd = true;
  810. }
  811. if (settings.hasOwnProperty("Version")) {
  812. delete settings["Version"];
  813. upd = true;
  814. }
  815. }
  816. return upd;
  817. }
  818. function nullif(s, nullVal) {
  819. if (s !== null && s === nullVal) {
  820. return null;
  821. }
  822. return s;
  823. }
  824. function log(level, message) {
  825. const t = new Date();
  826. switch (level.toLocaleLowerCase()) {
  827. case "debug":
  828. case "verbose":
  829. console.debug(`${scrName} ${t.toISOString()}: ${message}`);
  830. break;
  831. case "info":
  832. case "information":
  833. console.info(`${scrName} ${t.toISOString()}: ${message}`);
  834. break;
  835. case "warning":
  836. case "warn":
  837. console.warn(`${scrName} ${t.toISOString()}: ${message}`);
  838. break;
  839. case "error":
  840. console.error(`${scrName} ${t.toISOString()}: ${message}`);
  841. break;
  842. case "log":
  843. console.log(`${scrName} ${t.toISOString()}: ${message}`);
  844. break;
  845. default:
  846. break;
  847. }
  848. }
  849. Init();
  850. })(WMEWAL_MapComments || (WMEWAL_MapComments = {}));

QingJ © 2025

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