WME Wide-Angle Lens Map Comments

Find map comments that match filter criteria

目前為 2023-09-25 提交的版本,檢視 最新版本

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

QingJ © 2025

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