sourcemonkey 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. package/skills/SourceMonkey-friction/references/template.md +59 -0
@@ -0,0 +1,432 @@
1
+ // The "delete local script file" confirmation, opened from the trash
2
+ // can on a script whose file lives in the Downloads copies directory.
3
+ //
4
+ // It owns the whole action the way the copy dialog owns Copy: what will
5
+ // be deleted, the confirmation, the deletion itself, and any failure in
6
+ // its own error slot. The caller hears only about files that went.
7
+ //
8
+ // See docs/local-delete.md.
9
+ import { effectiveDisplayName } from './collection-display.js';
10
+ import { downloadsCopiesDir, readDownloadsCopiesSetting } from './local-copy.js';
11
+ import { copyDeletePlan, deletableCopyPath, deleteCopyFiles, noDownloadRecord, SCRIPT_FILE, trackedFiles, } from './local-delete.js';
12
+ import { describeError, getCollectionWithScriptMeta, } from './script-store.js';
13
+ import { dlog } from '../debug-log.js';
14
+ import { makeCopyButton } from './copy-button.js';
15
+ import { missingFiles } from './file-changes.js';
16
+ import { isFileSchemeAccessAllowed } from './file-access.js';
17
+ let elements = null;
18
+ // One at a time, claimed synchronously — the same guard the other
19
+ // dialogs use: a second `showModal` throws and would leave the first
20
+ // caller's promise unsettled.
21
+ let dialogInFlight = false;
22
+ function el(tag, className, text) {
23
+ const node = document.createElement(tag);
24
+ if (className)
25
+ node.className = className;
26
+ if (text !== undefined)
27
+ node.textContent = text;
28
+ return node;
29
+ }
30
+ function buildDialog() {
31
+ const dialog = el('dialog', 'sm-dialog delete-dialog');
32
+ dialog.appendChild(el('h2', 'dlg-title', 'Delete local script file'));
33
+ const body = el('div', 'dlg-body');
34
+ // Label and control carry the same tooltip, as in the copy dialog:
35
+ // either one is what a reader points at to ask "what is this?".
36
+ const row = (label, control, tooltip) => {
37
+ const cell = el('span', 'dlg-label', label);
38
+ cell.title = tooltip;
39
+ control.title = tooltip;
40
+ body.appendChild(cell);
41
+ body.appendChild(control);
42
+ return cell;
43
+ };
44
+ const collection = el('span', 'dlg-static');
45
+ collection.id = 'delete-collection';
46
+ row('Collection', collection, 'The collection this copy belongs to, and its directory');
47
+ const fileWrap = el('div', 'dlg-static');
48
+ const file = el('div', 'dlg-target-name');
49
+ file.id = 'delete-file';
50
+ const filePath = el('span', 'dlg-hint');
51
+ filePath.id = 'delete-file-path';
52
+ // Beside the full path, the same copy button the viewer's Location
53
+ // row has: the message below can tell the user to delete the file by
54
+ // hand, and a path they can paste is what that takes.
55
+ const pathRow = el('div', 'dlg-path-row');
56
+ pathRow.append(filePath, makeCopyButton({
57
+ label: 'Copy path to clipboard',
58
+ text: () => filePath.textContent || undefined,
59
+ }));
60
+ fileWrap.append(file, pathRow);
61
+ row('Script file', fileWrap, 'The file that will be deleted from disk');
62
+ // No choice to make here: what happens to each dependency file
63
+ // follows from facts the dialog can state — whether another script
64
+ // loads it, whether Chrome can still be asked to delete it, and
65
+ // whether it is still there. So the row is headed lists rather than
66
+ // a checkbox.
67
+ const depsWrap = el('div', 'dlg-deps');
68
+ const groups = DEP_GROUPS.map(() => {
69
+ const wrap = el('div', 'dlg-dep-group');
70
+ const heading = el('div', 'dlg-hint');
71
+ const list = el('ul', 'dlg-deps-list');
72
+ wrap.append(heading, list);
73
+ depsWrap.appendChild(wrap);
74
+ return { wrap, heading, list };
75
+ });
76
+ const depsRow = row('Dependencies', depsWrap, 'Local @require and @resource files this script loads from the collection’s directory.');
77
+ dialog.appendChild(body);
78
+ const errorRow = el('div', 'dlg-error-row');
79
+ const error = el('p', 'dlg-error');
80
+ error.setAttribute('role', 'alert');
81
+ errorRow.appendChild(error);
82
+ dialog.appendChild(errorRow);
83
+ const actions = el('div', 'dlg-actions');
84
+ const cancel = el('button', undefined, 'Cancel');
85
+ cancel.type = 'button';
86
+ const ok = el('button', 'primary danger', 'Delete');
87
+ ok.type = 'button';
88
+ ok.id = 'delete-confirm-btn';
89
+ actions.append(cancel, ok);
90
+ dialog.appendChild(actions);
91
+ document.body.appendChild(dialog);
92
+ return { dialog, collection, file, filePath, depsRow, depsWrap, groups, error, cancel, ok };
93
+ }
94
+ // Open the dialog and resolve with what was deleted, or null when it
95
+ // was dismissed or nothing was deleted.
96
+ export function openDeleteDialog(request) {
97
+ if (!elements)
98
+ elements = buildDialog();
99
+ const ui = elements;
100
+ if (dialogInFlight)
101
+ return Promise.resolve(null);
102
+ dialogInFlight = true;
103
+ return new Promise((resolve) => {
104
+ try {
105
+ runDialog(ui, request, (result) => {
106
+ dialogInFlight = false;
107
+ resolve(result);
108
+ });
109
+ }
110
+ catch (err) {
111
+ dialogInFlight = false;
112
+ resolve(null);
113
+ dlog(`[SourceMonkey] delete dialog failed to open: ${describeError(err)}`);
114
+ }
115
+ });
116
+ }
117
+ function runDialog(ui, request, resolve) {
118
+ const { script } = request;
119
+ let settled = false;
120
+ // A deletion is in flight. Esc is refused for its duration: closing
121
+ // would resolve the caller `null` while files are still going, so the
122
+ // step that removes the script's record would never run.
123
+ let submitting = false;
124
+ // The plan, what Chrome can still be asked to delete, and what is
125
+ // still on disk, once the open-time reads land. Until then there is nothing to confirm, so
126
+ // the Delete button stays off.
127
+ let plan = null;
128
+ let report = null;
129
+ // The watch for files going away while the dialog is up (`watchDisk`).
130
+ let watch = null;
131
+ let probing = false;
132
+ const finish = (result) => {
133
+ if (settled)
134
+ return;
135
+ settled = true;
136
+ stopWatching();
137
+ if (ui.dialog.open)
138
+ ui.dialog.close();
139
+ resolve(result);
140
+ };
141
+ // The dialog can be telling the user to delete files by hand — the
142
+ // script file when its record is gone, dependencies under the third
143
+ // heading — and it has copy buttons for exactly that. So it watches
144
+ // for them doing it: a file that goes drops out of the lists, and a
145
+ // script file that goes turns the blocking message into the offer
146
+ // to remove the entry, without closing and reopening the dialog.
147
+ //
148
+ // Every file in the plan is probed each tick (one directory listing
149
+ // per folder, `missingFiles`), for the whole time the dialog is up. Not
150
+ // just the ones the user was told to delete: a file with a record
151
+ // can be deleted by hand too, and Chrome's `exists` flag is only as
152
+ // fresh as its last check — attempting it would fail after the
153
+ // fact, where moving it to the last list is right. And not just the
154
+ // ones still there: a file put back (an undo in the file manager)
155
+ // moves out of that list again.
156
+ //
157
+ // The user most likely deletes in another window and comes back, so
158
+ // the window regaining focus probes at once rather than making them
159
+ // wait out the tick. `focus` on `window` fires when the page's
160
+ // window is switched back to, and `visibilitychange` when its tab
161
+ // is; either counts.
162
+ const probe = () => {
163
+ if (settled || submitting || !plan || probing)
164
+ return;
165
+ const current = plan;
166
+ probing = true;
167
+ void goneFiles(planPaths(current), current.fileAccess).then((gone) => {
168
+ probing = false;
169
+ if (settled || plan !== current || sameSet(gone, current.gone))
170
+ return;
171
+ plan = { ...current, gone };
172
+ // A failed attempt's report described the files as they were;
173
+ // the line goes back to saying what Delete would do now.
174
+ report = null;
175
+ paintDependencies(ui, plan);
176
+ sync();
177
+ });
178
+ };
179
+ const onReturn = () => {
180
+ if (document.visibilityState === 'visible')
181
+ probe();
182
+ };
183
+ const stopWatching = () => {
184
+ if (watch !== null)
185
+ clearInterval(watch);
186
+ watch = null;
187
+ window.removeEventListener('focus', onReturn);
188
+ document.removeEventListener('visibilitychange', onReturn);
189
+ };
190
+ const watchDisk = () => {
191
+ if (settled)
192
+ return;
193
+ watch = setInterval(probe, DISK_POLL_MS);
194
+ window.addEventListener('focus', onReturn);
195
+ document.addEventListener('visibilitychange', onReturn);
196
+ };
197
+ // ----- The state that is known before any read -----
198
+ ui.collection.textContent = '…';
199
+ ui.file.textContent = script.relativePath;
200
+ ui.filePath.textContent = '';
201
+ paintDependencies(ui, null);
202
+ const sync = () => {
203
+ // The script file's own record is the one thing that blocks: with
204
+ // no way to delete it there is nothing to confirm, and its message
205
+ // outranks the warning line. Unless the file has already gone —
206
+ // then Delete is what clears the entry, and the line says so.
207
+ const view = plan && planView(plan);
208
+ const blocked = view !== null && view.scriptFile === 'untracked';
209
+ ui.error.textContent = report
210
+ ?? (blocked
211
+ // Its dependencies were going with it, and are staying too.
212
+ ? noDownloadRecord(SCRIPT_FILE, view.deleting.length + view.untracked.length > 0,
213
+ // The watch can only see the file go with file access on.
214
+ plan.fileAccess)
215
+ : deleteWarning(view));
216
+ ui.error.classList.toggle('warn', !report && !blocked);
217
+ ui.ok.disabled = plan === null || blocked || submitting;
218
+ };
219
+ // Replaced on every open, so stale closures can't fire.
220
+ ui.cancel.onclick = () => finish(null);
221
+ ui.dialog.onclose = () => finish(null);
222
+ ui.dialog.oncancel = (event) => {
223
+ if (submitting)
224
+ event.preventDefault();
225
+ };
226
+ ui.ok.onclick = () => void submit();
227
+ const submit = async () => {
228
+ if (!plan)
229
+ return;
230
+ const view = planView(plan);
231
+ submitting = true;
232
+ report = null;
233
+ sync();
234
+ ui.cancel.disabled = true;
235
+ const spinner = el('span', 'busy-spinner');
236
+ ui.ok.after(spinner);
237
+ try {
238
+ const result = await deleteCopyFiles({
239
+ script: view.scriptFile === 'gone'
240
+ ? null
241
+ : { path: plan.path, relativePath: plan.relativePath },
242
+ // Only the files that are this script's alone AND that Chrome
243
+ // can still be asked about. The rest are on screen under the
244
+ // heading that says why they are staying.
245
+ dependencies: view.deleting,
246
+ });
247
+ if (result.scriptError) {
248
+ report = result.scriptError;
249
+ }
250
+ else {
251
+ finish({
252
+ scriptId: script.id,
253
+ collectionId: script.collectionId,
254
+ dependencyErrors: result.dependencyErrors,
255
+ });
256
+ return;
257
+ }
258
+ }
259
+ catch (err) {
260
+ report = describeError(err);
261
+ }
262
+ finally {
263
+ submitting = false;
264
+ spinner.remove();
265
+ ui.cancel.disabled = false;
266
+ sync();
267
+ }
268
+ };
269
+ sync();
270
+ ui.dialog.showModal();
271
+ ui.cancel.focus();
272
+ // ----- The open-time reads, after the dialog is on screen -----
273
+ void openPlan(script).then((opened) => {
274
+ if (settled)
275
+ return;
276
+ plan = opened.plan;
277
+ ui.collection.replaceChildren(el('span', 'dlg-target-name', opened.collectionName), el('span', 'dlg-hint', ` (${opened.copiesDir})`));
278
+ ui.file.textContent = opened.plan.relativePath;
279
+ ui.filePath.textContent = opened.plan.path;
280
+ paintDependencies(ui, plan);
281
+ sync();
282
+ watchDisk();
283
+ }, (err) => {
284
+ if (settled)
285
+ return;
286
+ report = describeError(err);
287
+ sync();
288
+ });
289
+ }
290
+ function planView(plan) {
291
+ const onDisk = (d) => !plan.gone.has(d.path);
292
+ const own = plan.own.filter(onDisk);
293
+ return {
294
+ scriptFile: plan.gone.has(plan.path)
295
+ ? 'gone'
296
+ : plan.tracked.has(plan.path) ? 'tracked' : 'untracked',
297
+ deleting: own.filter((d) => plan.tracked.has(d.path)),
298
+ untracked: own.filter((d) => !plan.tracked.has(d.path)),
299
+ shared: plan.shared.filter(onDisk),
300
+ gone: [...plan.own, ...plan.shared].filter((d) => !onDisk(d)),
301
+ };
302
+ }
303
+ // Every file the plan mentions — the script's, and each dependency
304
+ // under any heading.
305
+ function planPaths(plan) {
306
+ return [plan.path, ...plan.own.map((d) => d.path), ...plan.shared.map((d) => d.path)];
307
+ }
308
+ // Everything the dialog needs, read once the dialog is up.
309
+ async function openPlan(script) {
310
+ const setting = await readDownloadsCopiesSetting();
311
+ if (!setting)
312
+ throw new Error(NOT_A_LOCAL_COPY);
313
+ const copiesDir = downloadsCopiesDir(setting.downloadsDir);
314
+ const prior = await getCollectionWithScriptMeta(script.collectionId);
315
+ // The STORED row, not the snapshot the button closed over: a button
316
+ // painted once outlives any number of refreshes, so its copy of the
317
+ // row can name `@require`s the script no longer has — and can still
318
+ // say `ok` for a file that has since gone.
319
+ const row = prior?.scripts.find((s) => s.id === script.id) ?? null;
320
+ // Re-checked here, not trusted from the button: the row could have
321
+ // moved or gone missing since it was painted, and everything below
322
+ // assumes the collection at the copies directory is the whole set of
323
+ // scripts that could be reading these files.
324
+ if (!prior || !row || !deletableCopyPath(row, prior.collection, setting)) {
325
+ throw new Error(NOT_A_LOCAL_COPY);
326
+ }
327
+ const plan = copyDeletePlan(row, prior.scripts, copiesDir);
328
+ // The shared files are deliberately NOT asked about: they are never
329
+ // deleted, so whether Chrome could remove them changes nothing, and
330
+ // a shared file that is also untracked belongs under the heading
331
+ // that says another script needs it — the reason it is staying.
332
+ const fileAccess = await isFileSchemeAccessAllowed();
333
+ const [tracked, gone] = await Promise.all([
334
+ trackedFiles([plan.path, ...plan.own.map((d) => d.path)]),
335
+ goneFiles(planPaths(plan), fileAccess),
336
+ ]);
337
+ return {
338
+ plan: { ...plan, tracked, gone, fileAccess },
339
+ copiesDir,
340
+ collectionName: effectiveDisplayName(prior.collection),
341
+ };
342
+ }
343
+ // Which of `paths` are no longer on disk. Probed rather than trusted
344
+ // from the rows' status — a row says `ok` until the collection's next
345
+ // refresh, and this dialog is opened before that happens, typically
346
+ // right after the user deleted a file by hand because it said to. The
347
+ // probe is one directory listing per folder involved, so polling is
348
+ // cheap; it never reports a file gone unless its folder listed and
349
+ // the name wasn't there (`missingFiles`).
350
+ //
351
+ // With file access off, nothing is ever reported gone (see
352
+ // `DeletePlan.gone`). `missingFiles` already answers that way — the
353
+ // listing fails — but the guard keeps the promise the dialog's
354
+ // wording makes explicit rather than incidental.
355
+ async function goneFiles(paths, fileAccess) {
356
+ if (!fileAccess)
357
+ return new Set();
358
+ return missingFiles(paths);
359
+ }
360
+ function sameSet(a, b) {
361
+ return a.size === b.size && [...a].every((x) => b.has(x));
362
+ }
363
+ // How often the dialog re-probes its files (`watchDisk` in `runDialog`).
364
+ const DISK_POLL_MS = 1000;
365
+ const NOT_A_LOCAL_COPY = 'This script is not in the Downloads copies directory, so SourceMonkey cannot delete its file';
366
+ // The line in the error slot when nothing has gone wrong: what Delete
367
+ // is about to do. It is a warning, not an error — the user asked for
368
+ // it — but it is the last thing they read before the files go.
369
+ function deleteWarning(plan) {
370
+ if (!plan)
371
+ return '';
372
+ if (plan.scriptFile === 'gone') {
373
+ // The file went by hand; the entry is what is left. Any dependency
374
+ // files still going are on disk and listed above, so they are
375
+ // said once more here — this is the last thing read before they go.
376
+ const deps = plan.deleting.length;
377
+ const alsoFiles = deps > 0
378
+ ? ` and ${deps} ${deps === 1 ? 'dependency file' : 'dependency files'} from disk`
379
+ : '';
380
+ return `Script file has already been deleted.\nDelete will remove this script from SourceMonkey${alsoFiles}.`;
381
+ }
382
+ const count = 1 + plan.deleting.length;
383
+ // Once the dialog is showing lists of files, the line says how many
384
+ // of them are going, even when the answer is just the script. "This
385
+ // file" beside a list of files it isn't taking would read as though
386
+ // it meant all of them.
387
+ const listing = plan.deleting.length + plan.shared.length + plan.untracked.length > 0;
388
+ const files = listing
389
+ ? `${count} ${count === 1 ? 'file' : 'files'}`
390
+ : 'this file';
391
+ return `Delete will remove ${files} from disk.\nThis cannot be undone.`;
392
+ }
393
+ // The lists under Dependencies, in the order the user reads them:
394
+ // what is going, the two reasons a file is staying, then what has
395
+ // already gone. Each heading states a fact about the files under it,
396
+ // so there is nothing to decide — which is why this row has no
397
+ // control.
398
+ const DEP_GROUPS = [
399
+ 'Files used by this script only',
400
+ 'Not deleting files also used by other scripts',
401
+ 'Not deleting files no longer in Chrome download history',
402
+ 'Files already deleted',
403
+ ];
404
+ // Paint the row from the plan, `null` while it is still being read —
405
+ // the row says nothing rather than guessing.
406
+ function paintDependencies(ui, plan) {
407
+ const view = plan && planView(plan);
408
+ const lists = [view?.deleting ?? [], view?.shared ?? [], view?.untracked ?? [], view?.gone ?? []];
409
+ // Hidden while the plan is being read, so the row appears once with
410
+ // its real contents rather than flicking through an empty state.
411
+ ui.depsRow.hidden = plan === null || lists.every((files) => files.length === 0);
412
+ ui.depsWrap.hidden = ui.depsRow.hidden;
413
+ ui.groups.forEach((group, i) => {
414
+ group.wrap.hidden = lists[i].length === 0;
415
+ group.heading.textContent = DEP_GROUPS[i];
416
+ group.list.replaceChildren(...lists[i].map((d) => depRow(d)));
417
+ });
418
+ }
419
+ // One file in any of the lists: its path relative to the copies
420
+ // directory, with the URL it stands in for as the tooltip, and a copy
421
+ // button for its full path — a file under "no longer in Chrome
422
+ // download history" is one the user has to delete by hand.
423
+ function depRow(dep) {
424
+ const name = el('span', undefined, dep.relativePath);
425
+ name.title = dep.url;
426
+ const li = el('li', 'dlg-path-row');
427
+ li.append(name, makeCopyButton({
428
+ label: 'Copy path to clipboard',
429
+ text: () => dep.path,
430
+ }));
431
+ return li;
432
+ }
@@ -0,0 +1,40 @@
1
+ // The two trash cans.
2
+ //
3
+ // SourceMonkey has two removal actions that look alike and aren't:
4
+ //
5
+ // - `record` — drop SourceMonkey's row for a script. The bytes it came
6
+ // from are untouched (they may not even be reachable any more).
7
+ // - `file` — delete the script FILE from disk, which only a local
8
+ // copy in the copies directory qualifies for. This is the one
9
+ // action that destroys something SourceMonkey didn't create.
10
+ //
11
+ // Both used to render the same trash-can emoji and differ only in
12
+ // `title`, so the difference was invisible until you hovered. They now
13
+ // share a trash-can silhouette — same family, same action shape — and the
14
+ // file variant carries a small disk badge in its lower-right corner
15
+ // plus a red resting colour, so the destructive one is marked twice:
16
+ // by shape and by colour, since colour alone isn't a cue everyone gets.
17
+ //
18
+ // The glyphs live as `<symbol>`s in the shared sprite
19
+ // (src/glyph-sprite.svg, inlined into each page by the build) rather
20
+ // than being written out per button, so a hundred rows share one copy
21
+ // of the path data. Every call site goes through `setDeleteIcon` so
22
+ // the pair can't drift apart again.
23
+ import { renderGlyph } from './glyph.js';
24
+ const SYMBOL_ID = {
25
+ record: '#sm-trash',
26
+ file: '#sm-trash-file',
27
+ };
28
+ // Replace the button's contents with the glyph for `kind`, and mark
29
+ // the file-deleting variant with `.deletes-file` so the stylesheet can
30
+ // give it its own resting colour. Callers keep owning `title` /
31
+ // `aria-label`: the icon narrows what the tooltip has to explain, it
32
+ // doesn't replace it.
33
+ export function setDeleteIcon(btn, kind) {
34
+ btn.textContent = '';
35
+ btn.classList.add('icon-btn');
36
+ // Its own glyph class so the cans can be found and styled as a pair,
37
+ // but the same `.icon-btn` box as every other glyph button.
38
+ btn.appendChild(renderGlyph(btn.ownerDocument, SYMBOL_ID[kind], 'delete-icon'));
39
+ btn.classList.toggle('deletes-file', kind === 'file');
40
+ }
@@ -0,0 +1,66 @@
1
+ // Tooltip text for the details panel's row labels.
2
+ //
3
+ // Why the rows carry tooltips at all: the panel is a curated set, not a
4
+ // dump of the metadata block, so several rows report something the
5
+ // label alone can't convey — what we checked but deliberately didn't
6
+ // act on (`Version at update URL`), what a value is NOT (`Homepage` is
7
+ // an unverified claim), or where a name shows up outside this page
8
+ // (`Log ID`). The tooltip's job is to correct the likely misreading,
9
+ // not to restate the label.
10
+ //
11
+ // Wording rules, matching the rest of the UI:
12
+ // - Capitalized, and no trailing period on a ONE-sentence tooltip.
13
+ // - A two-sentence tooltip ends BOTH sentences with a period and puts
14
+ // a newline between them, so the second reads as a separate remark
15
+ // rather than a run-on. `title` renders `\n` as a line break.
16
+ //
17
+ // Kept in one table rather than beside each `appendRow` call so the
18
+ // whole set can be read — and copy-edited — as a unit.
19
+ // Row labels whose text is fixed. `Version` is absent on purpose: it
20
+ // depends on which mode is rendering (see `rowTooltip`), because a
21
+ // preview has nothing installed to describe.
22
+ const FIXED = {
23
+ Name: "The script's @name",
24
+ Description: "The script's @description",
25
+ 'Version at update URL': "The version published at the script's @updateURL",
26
+ 'Version at download URL': "The version published at the script's @downloadURL",
27
+ Collection: 'The collection this script was added from.\n'
28
+ + 'Click through to manage it on the Collections page.',
29
+ Location: 'Where this script file lives, and where a refresh re-reads it from',
30
+ Homepage: "The script's @homepage",
31
+ 'Log ID': 'How SourceMonkey labels this script in the Chrome console and in stack traces.\n'
32
+ + 'The #E number distinguishes it from other scripts with the same filename.',
33
+ Match: "Pages the script runs on, from its @match rules",
34
+ Include: "Pages the script runs on, from its @include rules",
35
+ Exclude: "Pages the script is blocked from, from its @exclude rules",
36
+ // Spell out the "as you" part: the grant is closer to "may act as
37
+ // you there" than to "may fetch a URL".
38
+ 'Network access': 'Other sites the script may contact.\n'
39
+ + 'Warning: The requests carry your cookies, so the script can act as you on these sites.',
40
+ 'Local overrides': '@require / @resource files being read from your disk '
41
+ + 'instead of the URL the script declares',
42
+ Size: 'Size of the script, plus any libraries or resources it loads',
43
+ Status: 'Status from loading and executing this script.\n'
44
+ + 'Click to open the Log.',
45
+ };
46
+ // The `Version` row describes different things in the two modes: an
47
+ // installed script has a copy that is actually running, a preview is
48
+ // just a file making a claim. One string for both would have to be
49
+ // vague enough to be true of a file on disk, which throws away the more
50
+ // useful reading on the page people spend their time on.
51
+ const VERSION_INSTALLED = 'The @version of the script that is installed and running';
52
+ const VERSION_PREVIEW = "The script's @version";
53
+ // The hosting-site row's label is the site's own name — `Greasy Fork
54
+ // page`, `OpenUserJS page`, `GitHub Gist page` — so it can't be a key.
55
+ // Matched on the shape those labels share; `Homepage` is a separate
56
+ // entry above and doesn't end in ` page`.
57
+ const SITE_PAGE_SUFFIX = ' page';
58
+ const SITE_PAGE_TOOLTIP = "The hosting site's page where this script is published";
59
+ export function rowTooltip(label, mode) {
60
+ if (label === 'Version') {
61
+ return mode === 'preview' ? VERSION_PREVIEW : VERSION_INSTALLED;
62
+ }
63
+ if (label.endsWith(SITE_PAGE_SUFFIX))
64
+ return SITE_PAGE_TOOLTIP;
65
+ return FIXED[label];
66
+ }