WME Wide-Angle Lens Map Comments

Find map comments that match filter criteria

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