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.
- package/LICENSE +21 -0
- package/README.md +87 -2
- package/dist/bytes.js +37 -0
- package/dist/channel-placeholder.js +5 -0
- package/dist/collection-scan.js +17 -0
- package/dist/collections.html +353 -0
- package/dist/compare.html +358 -0
- package/dist/composed-source.js +205 -0
- package/dist/connect-prompt.html +47 -0
- package/dist/debug/icons.html +211 -0
- package/dist/debug/storage.html +163 -0
- package/dist/debug-log.js +75 -0
- package/dist/diagnostic-location.js +64 -0
- package/dist/eval-hook.js +308 -0
- package/dist/extension/background.js +5263 -0
- package/dist/extension/channel.js +133 -0
- package/dist/extension/code-links.js +187 -0
- package/dist/extension/code-tabs.js +460 -0
- package/dist/extension/collection-dialog.js +619 -0
- package/dist/extension/collection-display.js +322 -0
- package/dist/extension/collections.js +2905 -0
- package/dist/extension/compare.js +867 -0
- package/dist/extension/concurrency.js +186 -0
- package/dist/extension/connect-grants.js +109 -0
- package/dist/extension/connect-prompt-page.js +70 -0
- package/dist/extension/connect-prompt.js +266 -0
- package/dist/extension/copy-button.js +63 -0
- package/dist/extension/copy-dialog.js +502 -0
- package/dist/extension/copy-preference.js +66 -0
- package/dist/extension/debug/icons.js +203 -0
- package/dist/extension/debug/storage.js +365 -0
- package/dist/extension/deep-link.js +77 -0
- package/dist/extension/delete-dialog.js +432 -0
- package/dist/extension/delete-icon.js +40 -0
- package/dist/extension/details-tooltips.js +66 -0
- package/dist/extension/directories.js +679 -0
- package/dist/extension/export-backup.js +407 -0
- package/dist/extension/export-dialog.js +142 -0
- package/dist/extension/file-access.js +74 -0
- package/dist/extension/file-changes.js +346 -0
- package/dist/extension/frame-info.js +85 -0
- package/dist/extension/github-provider.js +336 -0
- package/dist/extension/glyph.js +84 -0
- package/dist/extension/gm-bindings.js +601 -0
- package/dist/extension/gm-download.js +419 -0
- package/dist/extension/gm-menu.js +228 -0
- package/dist/extension/gm-notification.js +141 -0
- package/dist/extension/gm-originator-map.js +23 -0
- package/dist/extension/gm-tab-data.js +121 -0
- package/dist/extension/gm-tab.js +160 -0
- package/dist/extension/gm-url-change.js +111 -0
- package/dist/extension/gm-url-scheme.js +27 -0
- package/dist/extension/gm-values.js +176 -0
- package/dist/extension/gm-window-control.js +104 -0
- package/dist/extension/gm-xhr-headers.js +333 -0
- package/dist/extension/gm-xhr.js +584 -0
- package/dist/extension/injector.js +3145 -0
- package/dist/extension/install-capture.js +277 -0
- package/dist/extension/line-diff.js +233 -0
- package/dist/extension/local-copy.js +560 -0
- package/dist/extension/local-delete.js +276 -0
- package/dist/extension/location-kinds.js +108 -0
- package/dist/extension/log-format.js +158 -0
- package/dist/extension/managed-changes.js +361 -0
- package/dist/extension/managed-items.js +57 -0
- package/dist/extension/match-index.js +375 -0
- package/dist/extension/missing-scripts.js +66 -0
- package/dist/extension/more-menu.js +109 -0
- package/dist/extension/move-dialog.js +197 -0
- package/dist/extension/offscreen.js +37 -0
- package/dist/extension/options.js +504 -0
- package/dist/extension/page-heading.js +170 -0
- package/dist/extension/page-links.js +199 -0
- package/dist/extension/parse-check-client.js +70 -0
- package/dist/extension/parse-check.js +161 -0
- package/dist/extension/parse-sandbox-host.js +172 -0
- package/dist/extension/parse-sandbox.js +175 -0
- package/dist/extension/popup-commands-open.js +41 -0
- package/dist/extension/popup-view.js +387 -0
- package/dist/extension/popup.js +703 -0
- package/dist/extension/prism-userscript.js +53 -0
- package/dist/extension/refresh-error.js +136 -0
- package/dist/extension/refresh-targets.js +160 -0
- package/dist/extension/related-versions.js +1144 -0
- package/dist/extension/resize-observer-loop.js +19 -0
- package/dist/extension/row-cache.js +387 -0
- package/dist/extension/script-blobs.js +413 -0
- package/dist/extension/script-details.js +658 -0
- package/dist/extension/script-enablement.js +285 -0
- package/dist/extension/script-identity.js +310 -0
- package/dist/extension/script-keys.js +54 -0
- package/dist/extension/script-log-id.js +155 -0
- package/dist/extension/script-log.js +290 -0
- package/dist/extension/script-rejection.js +104 -0
- package/dist/extension/script-sites.js +158 -0
- package/dist/extension/script-store.js +3507 -0
- package/dist/extension/script-updater.js +991 -0
- package/dist/extension/script-url-hash.js +61 -0
- package/dist/extension/select-all.js +37 -0
- package/dist/extension/select-open-guard.js +90 -0
- package/dist/extension/site-icon.js +1261 -0
- package/dist/extension/sm-dev-link.js +302 -0
- package/dist/extension/solo-click.js +58 -0
- package/dist/extension/sticky-numbers.js +188 -0
- package/dist/extension/storage-breakdown.js +253 -0
- package/dist/extension/storage-scan.js +86 -0
- package/dist/extension/storage-usage.js +194 -0
- package/dist/extension/structural-keys.js +29 -0
- package/dist/extension/sw-teardown.js +36 -0
- package/dist/extension/tab-status.js +403 -0
- package/dist/extension/toolbar-summary.js +105 -0
- package/dist/extension/user-scripts-access.js +43 -0
- package/dist/extension/userscript.js +3672 -0
- package/dist/extension/value-store.js +204 -0
- package/dist/extension/version-probe-fetch.js +68 -0
- package/dist/extension/worker-client.js +109 -0
- package/dist/extension/worker-ops.js +171 -0
- package/dist/extension/zip-writer.js +24 -0
- package/dist/external-files.js +878 -0
- package/dist/file-loader.js +436 -0
- package/dist/file-locations.js +439 -0
- package/dist/generated-wrappers.js +5 -0
- package/dist/github-auth.js +148 -0
- package/dist/github-urls.js +46 -0
- package/dist/gm-api-catalog.js +217 -0
- package/dist/gm-connect.js +273 -0
- package/dist/gm-xhr-types.js +5 -0
- package/dist/http-read.js +142 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/local-file-overrides.js +558 -0
- package/dist/manifest.json +38 -0
- package/dist/markup-response.js +126 -0
- package/dist/match-targets.js +305 -0
- package/dist/menu-captions.js +22 -0
- package/dist/offscreen.html +39 -0
- package/dist/options.html +197 -0
- package/dist/parse-check-protocol.js +51 -0
- package/dist/parse-location.js +171 -0
- package/dist/parse-sandbox.html +56 -0
- package/dist/phase-timing.js +155 -0
- package/dist/popup.html +197 -0
- package/dist/public-suffixes.js +6957 -0
- package/dist/run-context.js +31 -0
- package/dist/runtime-messages.js +34 -0
- package/dist/sandboxed-page.js +35 -0
- package/dist/script-build.js +787 -0
- package/dist/script-display.js +383 -0
- package/dist/script-icon.js +43 -0
- package/dist/script-links.js +204 -0
- package/dist/script-source-url.js +214 -0
- package/dist/script-world.js +65 -0
- package/dist/sm-dev-protocol.js +84 -0
- package/dist/source-map.js +177 -0
- package/dist/stack-link.js +138 -0
- package/dist/styles.css +825 -0
- package/dist/unsafe-window-use.js +229 -0
- package/dist/url-match.js +734 -0
- package/dist/userscript-header.js +248 -0
- package/dist/userscript.html +1165 -0
- package/dist/vendor/fflate.LICENSE +21 -0
- package/dist/vendor/fflate.js +2692 -0
- package/dist/vendor/prism/prism-line-numbers.css +1 -0
- package/dist/vendor/prism/prism-line-numbers.js +1 -0
- package/dist/vendor/prism/prism.css +1 -0
- package/dist/vendor/prism/prism.js +1946 -0
- package/dist/version-compare.js +141 -0
- package/dist/version-probe.js +120 -0
- package/lib/build-info.json +1 -0
- package/lib/bytes.d.ts +3 -0
- package/lib/bytes.js +37 -0
- package/lib/channel-placeholder.d.ts +1 -0
- package/lib/channel-placeholder.js +5 -0
- package/lib/collection-scan.d.ts +8 -0
- package/lib/collection-scan.js +17 -0
- package/lib/composed-source.d.ts +28 -0
- package/lib/composed-source.js +205 -0
- package/lib/debug-log.d.ts +9 -0
- package/lib/debug-log.js +75 -0
- package/lib/diagnostic-location.d.ts +8 -0
- package/lib/diagnostic-location.js +64 -0
- package/lib/eval-hook.d.ts +34 -0
- package/lib/eval-hook.js +308 -0
- package/lib/external-files.d.ts +111 -0
- package/lib/external-files.js +878 -0
- package/lib/file-loader.d.ts +48 -0
- package/lib/file-loader.js +436 -0
- package/lib/file-locations.d.ts +44 -0
- package/lib/file-locations.js +439 -0
- package/lib/generated-wrappers.d.ts +11 -0
- package/lib/generated-wrappers.js +5 -0
- package/lib/github-auth.d.ts +12 -0
- package/lib/github-auth.js +148 -0
- package/lib/github-urls.d.ts +1 -0
- package/lib/github-urls.js +46 -0
- package/lib/gm-api-catalog.d.ts +24 -0
- package/lib/gm-api-catalog.js +217 -0
- package/lib/gm-connect.d.ts +34 -0
- package/lib/gm-connect.js +273 -0
- package/lib/gm-xhr-types.d.ts +68 -0
- package/lib/gm-xhr-types.js +5 -0
- package/lib/harness/browser-extensions.d.ts +73 -0
- package/lib/harness/browser-extensions.js +285 -0
- package/lib/harness/browser-lang.d.ts +20 -0
- package/lib/harness/browser-lang.js +86 -0
- package/lib/harness/browser-launch.d.ts +14 -0
- package/lib/harness/browser-launch.js +184 -0
- package/lib/harness/checks.d.ts +110 -0
- package/lib/harness/checks.js +321 -0
- package/lib/harness/cli.d.ts +2 -0
- package/lib/harness/cli.js +2259 -0
- package/lib/harness/command-args.d.ts +91 -0
- package/lib/harness/command-args.js +369 -0
- package/lib/harness/command-lines.d.ts +80 -0
- package/lib/harness/command-lines.js +180 -0
- package/lib/harness/command-list.d.ts +206 -0
- package/lib/harness/command-list.js +1475 -0
- package/lib/harness/console-commands.d.ts +463 -0
- package/lib/harness/console-commands.js +3216 -0
- package/lib/harness/download-watch.d.ts +21 -0
- package/lib/harness/download-watch.js +156 -0
- package/lib/harness/extension-backend.d.ts +41 -0
- package/lib/harness/extension-backend.js +100 -0
- package/lib/harness/extension-host.d.ts +112 -0
- package/lib/harness/extension-host.js +552 -0
- package/lib/harness/extension-launch.d.ts +67 -0
- package/lib/harness/extension-launch.js +285 -0
- package/lib/harness/extension-link.d.ts +315 -0
- package/lib/harness/extension-link.js +607 -0
- package/lib/harness/external-manager.d.ts +146 -0
- package/lib/harness/external-manager.js +414 -0
- package/lib/harness/fixture.d.ts +165 -0
- package/lib/harness/fixture.js +461 -0
- package/lib/harness/frame-sessions.d.ts +71 -0
- package/lib/harness/frame-sessions.js +248 -0
- package/lib/harness/frames.d.ts +68 -0
- package/lib/harness/frames.js +216 -0
- package/lib/harness/help-format.d.ts +17 -0
- package/lib/harness/help-format.js +127 -0
- package/lib/harness/host.d.ts +352 -0
- package/lib/harness/host.js +423 -0
- package/lib/harness/index.d.ts +8 -0
- package/lib/harness/index.js +10 -0
- package/lib/harness/input-file.d.ts +18 -0
- package/lib/harness/input-file.js +61 -0
- package/lib/harness/json-output.d.ts +565 -0
- package/lib/harness/json-output.js +71 -0
- package/lib/harness/manager-actions.d.ts +21 -0
- package/lib/harness/manager-actions.js +129 -0
- package/lib/harness/manager-commands.d.ts +142 -0
- package/lib/harness/manager-commands.js +1196 -0
- package/lib/harness/manager-fetch.d.ts +33 -0
- package/lib/harness/manager-fetch.js +239 -0
- package/lib/harness/managers.d.ts +24 -0
- package/lib/harness/managers.js +27 -0
- package/lib/harness/output-style.d.ts +125 -0
- package/lib/harness/output-style.js +322 -0
- package/lib/harness/page-commands.d.ts +134 -0
- package/lib/harness/page-commands.js +1141 -0
- package/lib/harness/page-output.d.ts +67 -0
- package/lib/harness/page-output.js +139 -0
- package/lib/harness/page-watch.d.ts +171 -0
- package/lib/harness/page-watch.js +607 -0
- package/lib/harness/parse-check-node.d.ts +3 -0
- package/lib/harness/parse-check-node.js +132 -0
- package/lib/harness/path-args.d.ts +22 -0
- package/lib/harness/path-args.js +148 -0
- package/lib/harness/playwright-chromium.d.ts +16 -0
- package/lib/harness/playwright-chromium.js +86 -0
- package/lib/harness/prepare.d.ts +168 -0
- package/lib/harness/prepare.js +364 -0
- package/lib/harness/prompt-editor.d.ts +17 -0
- package/lib/harness/prompt-editor.js +354 -0
- package/lib/harness/prompt-history.d.ts +13 -0
- package/lib/harness/prompt-history.js +89 -0
- package/lib/harness/rebuild-advice.d.ts +6 -0
- package/lib/harness/rebuild-advice.js +39 -0
- package/lib/harness/run-log.d.ts +47 -0
- package/lib/harness/run-log.js +140 -0
- package/lib/harness/run-logs.d.ts +132 -0
- package/lib/harness/run-logs.js +241 -0
- package/lib/harness/run.d.ts +194 -0
- package/lib/harness/run.js +1973 -0
- package/lib/harness/runs.d.ts +229 -0
- package/lib/harness/runs.js +726 -0
- package/lib/harness/save-html.d.ts +39 -0
- package/lib/harness/save-html.js +89 -0
- package/lib/harness/saved-pages.d.ts +120 -0
- package/lib/harness/saved-pages.js +275 -0
- package/lib/harness/script-mark.d.ts +17 -0
- package/lib/harness/script-mark.js +56 -0
- package/lib/harness/snapshot.d.ts +143 -0
- package/lib/harness/snapshot.js +474 -0
- package/lib/harness/stuck-tabs.d.ts +19 -0
- package/lib/harness/stuck-tabs.js +131 -0
- package/lib/harness/tab-refs.d.ts +20 -0
- package/lib/harness/tab-refs.js +80 -0
- package/lib/harness/tampermonkey.d.ts +71 -0
- package/lib/harness/tampermonkey.js +356 -0
- package/lib/harness/tm-ids.d.ts +18 -0
- package/lib/harness/tm-ids.js +88 -0
- package/lib/harness/violentmonkey.d.ts +34 -0
- package/lib/harness/violentmonkey.js +162 -0
- package/lib/harness/wait.d.ts +77 -0
- package/lib/harness/wait.js +458 -0
- package/lib/http-read.d.ts +30 -0
- package/lib/http-read.js +142 -0
- package/lib/local-file-overrides.d.ts +35 -0
- package/lib/local-file-overrides.js +558 -0
- package/lib/manifest.json +38 -0
- package/lib/markup-response.d.ts +11 -0
- package/lib/markup-response.js +126 -0
- package/lib/match-targets.d.ts +18 -0
- package/lib/match-targets.js +305 -0
- package/lib/menu-captions.d.ts +1 -0
- package/lib/menu-captions.js +22 -0
- package/lib/parse-check-protocol.d.ts +27 -0
- package/lib/parse-check-protocol.js +51 -0
- package/lib/parse-location.d.ts +12 -0
- package/lib/parse-location.js +171 -0
- package/lib/phase-timing.d.ts +12 -0
- package/lib/phase-timing.js +155 -0
- package/lib/public-suffixes.d.ts +1 -0
- package/lib/public-suffixes.js +6957 -0
- package/lib/run-context.d.ts +2 -0
- package/lib/run-context.js +31 -0
- package/lib/runtime-messages.d.ts +10 -0
- package/lib/runtime-messages.js +34 -0
- package/lib/sandboxed-page.d.ts +5 -0
- package/lib/sandboxed-page.js +35 -0
- package/lib/script-build.d.ts +111 -0
- package/lib/script-build.js +787 -0
- package/lib/script-display.d.ts +51 -0
- package/lib/script-display.js +383 -0
- package/lib/script-icon.d.ts +16 -0
- package/lib/script-icon.js +43 -0
- package/lib/script-links.d.ts +24 -0
- package/lib/script-links.js +204 -0
- package/lib/script-source-url.d.ts +8 -0
- package/lib/script-source-url.js +214 -0
- package/lib/script-world.d.ts +6 -0
- package/lib/script-world.js +65 -0
- package/lib/sm-dev-protocol.d.ts +251 -0
- package/lib/sm-dev-protocol.js +84 -0
- package/lib/source-map.d.ts +32 -0
- package/lib/source-map.js +177 -0
- package/lib/stack-link.d.ts +15 -0
- package/lib/stack-link.js +138 -0
- package/lib/unsafe-window-use.d.ts +1 -0
- package/lib/unsafe-window-use.js +229 -0
- package/lib/url-match.d.ts +40 -0
- package/lib/url-match.js +734 -0
- package/lib/userscript-header.d.ts +33 -0
- package/lib/userscript-header.js +248 -0
- package/lib/version-compare.d.ts +6 -0
- package/lib/version-compare.js +141 -0
- package/lib/version-probe.d.ts +17 -0
- package/lib/version-probe.js +120 -0
- package/package.json +55 -3
- package/skills/SourceMonkey-dev/SKILL.md +868 -0
- package/skills/SourceMonkey-dev/references/cdp.md +293 -0
- package/skills/SourceMonkey-dev/references/commands.md +297 -0
- package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
- package/skills/SourceMonkey-dev/references/specs.md +46 -0
- package/skills/SourceMonkey-friction/SKILL.md +103 -0
- package/skills/SourceMonkey-friction/references/template.md +59 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
// The details panel — the `<dl>` of parsed-header fields plus the
|
|
2
|
+
// where-did-this-come-from rows — shared by the installed-script
|
|
3
|
+
// viewer and the not-yet-installed preview.
|
|
4
|
+
//
|
|
5
|
+
// The two modes render the same rows from the same code; rows whose
|
|
6
|
+
// data a mode doesn't have simply aren't passed in (a preview has no
|
|
7
|
+
// Collection or Status; an unparseable file has no header). Row order
|
|
8
|
+
// is fixed here so the panel doesn't reshuffle between modes.
|
|
9
|
+
//
|
|
10
|
+
// What earns a row: the panel is NOT a mirror of the header block.
|
|
11
|
+
// The header block is on screen already — the first code tab opens on
|
|
12
|
+
// it — so a row that only repeats a tag verbatim is asking the reader
|
|
13
|
+
// to look at the same string twice. A row earns its place when it is
|
|
14
|
+
// - identity a reader scans for (Name, Description, Version),
|
|
15
|
+
// - something we resolved or computed that the file doesn't say
|
|
16
|
+
// (Collection, Location, hosting-site page, Size, Status, the
|
|
17
|
+
// upstream version probes), or
|
|
18
|
+
// - a claim worth acting on (Homepage, Match / Include / Exclude,
|
|
19
|
+
// Network access).
|
|
20
|
+
// Tags that fail all three — `@author`, `@namespace`, `@run-at`,
|
|
21
|
+
// `@icon`, … — stay in the code view where they were written.
|
|
22
|
+
//
|
|
23
|
+
// `@grant` is the deliberate omission, and the reasoning is specific
|
|
24
|
+
// to what our GM surface can actually do:
|
|
25
|
+
// - `GM_xmlhttpRequest` is the API that reaches other origins, and
|
|
26
|
+
// only the hosts its `@connect` lines name, or that the user allowed
|
|
27
|
+
// when asked (`connect-prompt.ts`), get past the page's own origin.
|
|
28
|
+
// The Network access row lists both, so it tells that story more
|
|
29
|
+
// precisely than a grant name would.
|
|
30
|
+
// - Most of the rest act within reach the page already has: styles,
|
|
31
|
+
// elements, menu commands, resources, per-script storage.
|
|
32
|
+
// - `GM_download` and `GM_openInTab` genuinely do reach outside the
|
|
33
|
+
// page and are NOT `@connect`-gated. They are the case this
|
|
34
|
+
// omission trades away; see the TODO note in docs/script-viewer.md.
|
|
35
|
+
import { openInNewTabAnchor } from './code-links.js';
|
|
36
|
+
import { makeCopyButton } from './copy-button.js';
|
|
37
|
+
import { standInTag, tagLocalFile } from './file-changes.js';
|
|
38
|
+
import { rowTooltip } from './details-tooltips.js';
|
|
39
|
+
import { matchTargetInfo, sameDirLinkUrl } from '../match-targets.js';
|
|
40
|
+
import { connectLinkUrl } from '../script-links.js';
|
|
41
|
+
import { renderPatternIconInto } from './site-icon.js';
|
|
42
|
+
import { isContainerCollection, locationFsPath } from '../file-locations.js';
|
|
43
|
+
import { appendWithSlashBreaks, displayLocation, formatBytes, logIdScriptNumber, stripSriFragment } from '../script-display.js';
|
|
44
|
+
import { sitePageRow } from './script-sites.js';
|
|
45
|
+
import { effectiveDisplayName, makeCollectionTypeBubble, collectionAnchorId, } from './collection-display.js';
|
|
46
|
+
import { wireNamedTabLink } from './page-links.js';
|
|
47
|
+
import { getValue, getValues } from '../userscript-header.js';
|
|
48
|
+
import { versionRelation } from '../version-compare.js';
|
|
49
|
+
import { probeLabel } from '../version-probe.js';
|
|
50
|
+
export function renderDetails(container, model) {
|
|
51
|
+
// Set before any row is built and read only by `appendRow`. Threading
|
|
52
|
+
// it through every add* helper would touch a dozen signatures to
|
|
53
|
+
// deliver one value that can't vary within a single render.
|
|
54
|
+
currentMode = model.mode ?? 'installed';
|
|
55
|
+
container.replaceChildren();
|
|
56
|
+
const dl = document.createElement('dl');
|
|
57
|
+
const h = model.header;
|
|
58
|
+
if (h) {
|
|
59
|
+
addText(dl, 'Name', getValue(h, 'name'));
|
|
60
|
+
addText(dl, 'Description', getValue(h, 'description'));
|
|
61
|
+
const version = getValue(h, 'version');
|
|
62
|
+
const probes = probesWorthShowing(version, model.versionProbes);
|
|
63
|
+
// A script with no `@version` normally gets no Version row at all.
|
|
64
|
+
// It gets one reading `<none>` when a probe DID find a version
|
|
65
|
+
// upstream: that absence is one half of the comparison, and the
|
|
66
|
+
// probe row directly below is meaningless without it.
|
|
67
|
+
if (version)
|
|
68
|
+
addText(dl, 'Version', version);
|
|
69
|
+
else if (probes.length > 0)
|
|
70
|
+
appendRow(dl, 'Version', document.createTextNode(NO_VERSION));
|
|
71
|
+
addVersionProbes(dl, version, probes);
|
|
72
|
+
}
|
|
73
|
+
// Collection row: the colored bubble + display name combo the
|
|
74
|
+
// collections page uses, with the display name doubling as a
|
|
75
|
+
// back-link to the collections page (anchored to this collection's
|
|
76
|
+
// section). Above Location so the "where did this come from" rows are
|
|
77
|
+
// grouped.
|
|
78
|
+
if (model.collection) {
|
|
79
|
+
appendRow(dl, 'Collection', collectionCell(model.collection, model.collectionTrailing));
|
|
80
|
+
}
|
|
81
|
+
if (model.location) {
|
|
82
|
+
appendRow(dl, 'Location', locationCell(model.location));
|
|
83
|
+
addSitePage(dl, model.location.href, h);
|
|
84
|
+
}
|
|
85
|
+
// Straight after the hosting-site row: both name a page about this
|
|
86
|
+
// script, one derived from where it lives and one claimed by the file
|
|
87
|
+
// itself, and reading them together is what shows when they disagree.
|
|
88
|
+
//
|
|
89
|
+
// Outside the `location` block on purpose, even though it reads as
|
|
90
|
+
// part of that group: `@homepage` comes from the header alone, so a
|
|
91
|
+
// script whose parent collection row has vanished (no Location row,
|
|
92
|
+
// see `currentFilenamePath`) must not lose it too.
|
|
93
|
+
if (h)
|
|
94
|
+
addLink(dl, 'Homepage', declaredHomepage(h));
|
|
95
|
+
// Always, for an installed script: its `#E4` is written nowhere in
|
|
96
|
+
// the file, and it is what a console line, a stack trace's
|
|
97
|
+
// `//# sourceURL=` and sm-dev (`E4`) name the script by.
|
|
98
|
+
if (model.logId) {
|
|
99
|
+
const dd = appendRow(dl, 'Log ID', logIdCell(model.logId));
|
|
100
|
+
// The tooltip's third line names this script's own number.
|
|
101
|
+
const number = logIdScriptNumber(model.logId);
|
|
102
|
+
const dt = dd.previousElementSibling;
|
|
103
|
+
if (number !== null && dt)
|
|
104
|
+
dt.title = `${dt.title ? `${dt.title}\n` : ''}E${number} is the script's ID in sm-dev.`;
|
|
105
|
+
}
|
|
106
|
+
if (h) {
|
|
107
|
+
addPatterns(dl, 'Match', getValues(h, 'match'), model.scriptUrl);
|
|
108
|
+
addPatterns(dl, 'Include', getValues(h, 'include'), model.scriptUrl);
|
|
109
|
+
addPatterns(dl, 'Exclude', getValues(h, 'exclude'), model.scriptUrl);
|
|
110
|
+
// Directly under the match targets, because it is the same
|
|
111
|
+
// question asked outward: those rows say which pages the script
|
|
112
|
+
// reads, this one says where it may send what it read. `@grant`
|
|
113
|
+
// gets no row beside it — the rest of the GM surface acts within
|
|
114
|
+
// the reach the page already has, so `@connect` is where the
|
|
115
|
+
// interesting part of an install decision lives. See
|
|
116
|
+
// `docs/script-viewer.md`.
|
|
117
|
+
addConnect(dl, getValues(h, 'connect'), model.connect);
|
|
118
|
+
}
|
|
119
|
+
// Last of the "what does this actually pull in" group, under the
|
|
120
|
+
// reach rows and above the byte total those files contribute to.
|
|
121
|
+
addLocalFiles(dl, model.files);
|
|
122
|
+
if (model.size) {
|
|
123
|
+
appendRow(dl, 'Size', document.createTextNode(model.size + dependencySizeSuffix(model.files)));
|
|
124
|
+
}
|
|
125
|
+
if (model.status) {
|
|
126
|
+
appendRow(dl, 'Status', model.status);
|
|
127
|
+
}
|
|
128
|
+
container.appendChild(dl);
|
|
129
|
+
}
|
|
130
|
+
// What the Version row shows when the script declares no `@version`.
|
|
131
|
+
// Only rendered when a probe DID find one upstream — see
|
|
132
|
+
// `renderDetails`. Angle brackets mark it as our word for an absence
|
|
133
|
+
// rather than a version string a file could actually carry.
|
|
134
|
+
export const NO_VERSION = '<none>';
|
|
135
|
+
// Which probes get a row. A probe earns one when it has something the
|
|
136
|
+
// reader can act on:
|
|
137
|
+
//
|
|
138
|
+
// - its version differs from ours (`newer` / `older`);
|
|
139
|
+
// - exactly one side declares a `@version` at all (`unknown`) — we
|
|
140
|
+
// can't say which is ahead, but "they publish 2.0 and we declare
|
|
141
|
+
// nothing" is still a difference worth seeing;
|
|
142
|
+
// - the latest check FAILED, whatever the version says. This row is
|
|
143
|
+
// the only surface a probe failure has: probes run downstream of
|
|
144
|
+
// the refresh and deliberately can't set a status or a
|
|
145
|
+
// `refreshError` (see `runVersionProbes` in `script-updater.ts`),
|
|
146
|
+
// and nothing logs them. Dropping the row would let a permanently
|
|
147
|
+
// broken `@updateURL` go silent, and the script would read as up to
|
|
148
|
+
// date when it had never once been checked.
|
|
149
|
+
//
|
|
150
|
+
// Only one case is dropped: the version matches ours AND the check
|
|
151
|
+
// succeeded — the expected state of an up-to-date script, and a line
|
|
152
|
+
// the reader would learn nothing from.
|
|
153
|
+
//
|
|
154
|
+
// Exported so the caller can ask "will any probe render?" before
|
|
155
|
+
// deciding how to draw the Version row above them.
|
|
156
|
+
export function probesWorthShowing(loadedVersion, probes) {
|
|
157
|
+
return (probes ?? []).filter((probe) => {
|
|
158
|
+
if (probe.error)
|
|
159
|
+
return true;
|
|
160
|
+
if (probe.version === undefined)
|
|
161
|
+
return true;
|
|
162
|
+
return versionRelation(loadedVersion, probe.version) !== 'same';
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// One row per probe worth showing, labeled by the header that named it
|
|
166
|
+
// — "Version at update URL" or "Version at download URL".
|
|
167
|
+
//
|
|
168
|
+
// The value says how that copy relates to ours ("2.1 (Newer)") because
|
|
169
|
+
// the relation is the part a user acts on; the bare number alone would
|
|
170
|
+
// make them do the comparison themselves. Where there is no relation to
|
|
171
|
+
// state, the bare number is all we can honestly report. The URL rides
|
|
172
|
+
// along as the row's tooltip rather than a third column of text.
|
|
173
|
+
function addVersionProbes(dl, loadedVersion, probes) {
|
|
174
|
+
for (const probe of probes) {
|
|
175
|
+
const relation = versionRelation(loadedVersion, probe.version);
|
|
176
|
+
const dd = appendRow(dl, `Version at ${probeLabel(probe.kinds)}`, document.createTextNode(probeValueText(probe, relation)));
|
|
177
|
+
dd.title = probeTitleText(probe, relation);
|
|
178
|
+
if (relation === 'newer')
|
|
179
|
+
dd.className = 'version-newer';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// What the row reads as. Whichever of the version and the error is the
|
|
183
|
+
// news goes in the value; the other one goes in the tooltip.
|
|
184
|
+
function probeValueText(probe, relation) {
|
|
185
|
+
// No version ever resolved, so the error IS the finding. The `unknown`
|
|
186
|
+
// fallback covers a probe recorded before any attempt completed —
|
|
187
|
+
// shouldn't outlive a check, but must not render blank if it does.
|
|
188
|
+
if (probe.version === undefined)
|
|
189
|
+
return probe.error ?? 'unknown';
|
|
190
|
+
if (relation === 'newer' || relation === 'older') {
|
|
191
|
+
return `${probe.version} (${relation === 'newer' ? 'Newer' : 'Older'})`;
|
|
192
|
+
}
|
|
193
|
+
// Our `@version` is theirs plus a `-local` tail — a copy made for
|
|
194
|
+
// editing. Neither newer nor older: the release we started from.
|
|
195
|
+
if (relation === 'edited')
|
|
196
|
+
return `${probe.version} (Base of this local edit)`;
|
|
197
|
+
// `unknown`: we declare no `@version` and they do. No qualifier — the
|
|
198
|
+
// number is all we can say, and the Version row above reads `<none>`,
|
|
199
|
+
// so the contrast is on screen without us guessing at a direction.
|
|
200
|
+
if (relation === 'unknown')
|
|
201
|
+
return probe.version;
|
|
202
|
+
// `same` — reachable only when the check errored (see
|
|
203
|
+
// `probesWorthShowing`). The version is old news and matches ours
|
|
204
|
+
// anyway, so the failed re-check is what the row is here to say.
|
|
205
|
+
return probe.error ?? probe.version;
|
|
206
|
+
}
|
|
207
|
+
function probeTitleText(probe, relation) {
|
|
208
|
+
if (!probe.error)
|
|
209
|
+
return probe.url;
|
|
210
|
+
// The value is showing a version, so the tooltip carries the failure
|
|
211
|
+
// — otherwise the row reads as freshly confirmed when it isn't.
|
|
212
|
+
if (relation !== 'same') {
|
|
213
|
+
return `${probe.url} (last check failed: ${probe.error})`;
|
|
214
|
+
}
|
|
215
|
+
// The value is showing the error, so the tooltip carries the version
|
|
216
|
+
// we last saw there, which is the part it displaced.
|
|
217
|
+
return probe.version !== undefined
|
|
218
|
+
? `${probe.url} (last known version ${probe.version})`
|
|
219
|
+
: probe.url;
|
|
220
|
+
}
|
|
221
|
+
// The publishing site's page for the script, derived from the Location
|
|
222
|
+
// URL — the location is a raw-code URL, this is the page a person can
|
|
223
|
+
// read. `sitePageRow` owns which sites are recognised and when the row
|
|
224
|
+
// would only repeat something already on screen; see
|
|
225
|
+
// `script-sites.ts`.
|
|
226
|
+
//
|
|
227
|
+
// Labeled with the site's own name ("Greasy Fork page") rather than a
|
|
228
|
+
// generic "Home page": the panel already has a Homepage row fed by the
|
|
229
|
+
// script's `@homepage`, and two rows with the same label saying
|
|
230
|
+
// different things would be worse than either alone.
|
|
231
|
+
function addSitePage(dl, locationUrl, h) {
|
|
232
|
+
const row = sitePageRow(locationUrl, h ? declaredHomepage(h) : undefined);
|
|
233
|
+
if (!row)
|
|
234
|
+
return;
|
|
235
|
+
appendRow(dl, row.label, openInNewTabAnchor(row.url, row.url));
|
|
236
|
+
}
|
|
237
|
+
// The header's own claim about where the script lives: the two
|
|
238
|
+
// spellings of the tag, first one wins. Read in three places — the
|
|
239
|
+
// Homepage row here and on the compare page, and the hosting-site
|
|
240
|
+
// row's duplicate check — which have to agree on what counts, or the
|
|
241
|
+
// check silently stops matching when this grows a tag.
|
|
242
|
+
export function declaredHomepage(h) {
|
|
243
|
+
return getValue(h, 'homepage') ?? getValue(h, 'homepageURL');
|
|
244
|
+
}
|
|
245
|
+
function addText(dl, label, value) {
|
|
246
|
+
if (!value)
|
|
247
|
+
return;
|
|
248
|
+
appendRow(dl, label, document.createTextNode(value));
|
|
249
|
+
}
|
|
250
|
+
// `@connect` row: hosts and the wildcard/`self` forms, rendered like
|
|
251
|
+
// the Match patterns above it (always bullets, each with its site's
|
|
252
|
+
// icon and a link to it; see `connectValueNode`).
|
|
253
|
+
//
|
|
254
|
+
// The user's prompt answers follow in their own list, each with a
|
|
255
|
+
// Reset button: a grant made in a hurry has to be as easy to find and
|
|
256
|
+
// undo as the header lines are.
|
|
257
|
+
function addConnect(dl, values, answers) {
|
|
258
|
+
const items = [];
|
|
259
|
+
if (answers) {
|
|
260
|
+
if (answers.grants.allHosts)
|
|
261
|
+
items.push({ host: null, allowed: true, reset: () => answers.revoke(null) });
|
|
262
|
+
for (const host of answers.grants.hosts)
|
|
263
|
+
items.push({ host, allowed: true, reset: () => answers.revoke(host) });
|
|
264
|
+
for (const host of answers.grants.denied ?? [])
|
|
265
|
+
items.push({ host, allowed: false, reset: () => answers.forgetDenied(host) });
|
|
266
|
+
}
|
|
267
|
+
if (values.length === 0 && items.length === 0)
|
|
268
|
+
return;
|
|
269
|
+
// Two lists, each under a heading and present only when non-empty:
|
|
270
|
+
// what the header declares, then what the user answered at the
|
|
271
|
+
// prompt (which only asks about hosts `@connect` doesn't cover).
|
|
272
|
+
const cell = document.createElement('div');
|
|
273
|
+
const addGroup = (heading, list, make) => {
|
|
274
|
+
if (list.length === 0)
|
|
275
|
+
return;
|
|
276
|
+
const h = document.createElement('div');
|
|
277
|
+
h.className = 'list-heading';
|
|
278
|
+
h.textContent = heading;
|
|
279
|
+
cell.append(h, bulletList(list, make));
|
|
280
|
+
};
|
|
281
|
+
addGroup('Declared in @connect:', values, (v) => {
|
|
282
|
+
if (v.trim() !== '*')
|
|
283
|
+
return connectValueNode(v);
|
|
284
|
+
// Worded like the "All sites" prompt answer below, rather than a
|
|
285
|
+
// bare `*` beside a globe.
|
|
286
|
+
return allSitesNode();
|
|
287
|
+
});
|
|
288
|
+
addGroup('Undeclared in @connect:', items, (item) => {
|
|
289
|
+
const span = document.createElement('span');
|
|
290
|
+
span.className = 'connect-answer';
|
|
291
|
+
// One element for the label and its colon: the span is a flex row,
|
|
292
|
+
// so separate pieces would each get the gap, putting a space before
|
|
293
|
+
// the colon.
|
|
294
|
+
const label = document.createElement('span');
|
|
295
|
+
if (item.host === null) {
|
|
296
|
+
label.append(allSitesNode(), ':');
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// A denied host gets no favicon fetch: opening this page must not
|
|
300
|
+
// contact a site the user refused.
|
|
301
|
+
label.append(connectValueNode(item.host, item.allowed), ':');
|
|
302
|
+
}
|
|
303
|
+
span.append(label);
|
|
304
|
+
const verdict = document.createElement('b');
|
|
305
|
+
verdict.textContent = item.allowed ? 'Allowed' : 'Denied';
|
|
306
|
+
const btn = document.createElement('button');
|
|
307
|
+
btn.type = 'button';
|
|
308
|
+
btn.textContent = 'Reset';
|
|
309
|
+
btn.title = `Clear this ${item.allowed ? 'approval' : 'denial'}. The next request will prompt again.`;
|
|
310
|
+
const reset = item.reset;
|
|
311
|
+
btn.addEventListener('click', () => {
|
|
312
|
+
btn.disabled = true;
|
|
313
|
+
reset();
|
|
314
|
+
});
|
|
315
|
+
span.append(verdict, btn);
|
|
316
|
+
return span;
|
|
317
|
+
});
|
|
318
|
+
appendRow(dl, 'Network access', cell);
|
|
319
|
+
}
|
|
320
|
+
// The bold "All sites" both lists use: for `@connect *`, and for the
|
|
321
|
+
// "Allow all sites" prompt answer.
|
|
322
|
+
function allSitesNode() {
|
|
323
|
+
const all = document.createElement('b');
|
|
324
|
+
all.textContent = 'All sites';
|
|
325
|
+
all.title = 'The script can access any site and act as you.';
|
|
326
|
+
return all;
|
|
327
|
+
}
|
|
328
|
+
function addLink(dl, label, value) {
|
|
329
|
+
if (!value)
|
|
330
|
+
return;
|
|
331
|
+
const info = safeUrl(value);
|
|
332
|
+
appendRow(dl, label, info ? openInNewTabAnchor(info, value) : document.createTextNode(value));
|
|
333
|
+
}
|
|
334
|
+
// Every `@require` / `@resource` whose body was read from a local file
|
|
335
|
+
// instead of the URL it declares, with the file we actually read.
|
|
336
|
+
//
|
|
337
|
+
// This row is a disclosure, not a listing: it exists because running
|
|
338
|
+
// something other than what a declaration names must never be silent
|
|
339
|
+
// (see docs/local-file-overrides.md). So it is absent for the
|
|
340
|
+
// overwhelming majority of scripts, and its presence is the whole
|
|
341
|
+
// signal. That also keeps it inside the panel's "earns a row" rule —
|
|
342
|
+
// it reports what we resolved, never what the header said.
|
|
343
|
+
//
|
|
344
|
+
// `@require` also wears a `local` chip on its own code tab, but
|
|
345
|
+
// `@resource` gets no tab at all, and the install preview has no Log
|
|
346
|
+
// tab to fall back on — so for a resource stand-in before install this
|
|
347
|
+
// row is the only surface there is.
|
|
348
|
+
function addLocalFiles(dl, files) {
|
|
349
|
+
const entries = [];
|
|
350
|
+
const groups = [
|
|
351
|
+
['@require', files?.requires],
|
|
352
|
+
['@resource', files?.resources],
|
|
353
|
+
];
|
|
354
|
+
for (const [kind, list] of groups) {
|
|
355
|
+
for (const f of list ?? []) {
|
|
356
|
+
if (f.localUrl)
|
|
357
|
+
entries.push({ kind, declared: f.url, file: { ...f, localUrl: f.localUrl } });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (entries.length === 0)
|
|
361
|
+
return;
|
|
362
|
+
appendRow(dl, 'Local overrides', listNode(entries, (e) => {
|
|
363
|
+
const span = document.createElement('span');
|
|
364
|
+
span.className = 'local-override-cell';
|
|
365
|
+
const kind = document.createElement('span');
|
|
366
|
+
kind.className = 'local-override-kind';
|
|
367
|
+
kind.textContent = e.kind;
|
|
368
|
+
span.appendChild(kind);
|
|
369
|
+
// The local path is the value, because it's what the row promises
|
|
370
|
+
// and what the reader needs in order to go open the file.
|
|
371
|
+
//
|
|
372
|
+
// Rendered as a BARE filesystem path, not the `file://` URL we
|
|
373
|
+
// hold: the Location row above resolves its own `file://` location
|
|
374
|
+
// the same way (`locationFsPath`, then `displayLocation`), and two
|
|
375
|
+
// path styles in one panel would read as two different kinds of
|
|
376
|
+
// thing. The code tab's tooltip keeps the URL form deliberately —
|
|
377
|
+
// there it sits directly under the declared URL, and the pair reads
|
|
378
|
+
// as "asked for this, got that".
|
|
379
|
+
const localPath = locationFsPath(e.file.localUrl);
|
|
380
|
+
const path = document.createElement('span');
|
|
381
|
+
path.className = 'local-override-path';
|
|
382
|
+
appendWithSlashBreaks(path, displayLocation(localPath));
|
|
383
|
+
// Which declaration it stood in for rides along as the tooltip,
|
|
384
|
+
// in the URL display form every other declared ref uses.
|
|
385
|
+
path.title = `Stands in for ${displayLocation(stripSriFragment(e.declared))}`
|
|
386
|
+
+ (files?.pushed ? '\n\nSnapshot of this file, pushed by sm-dev' : '');
|
|
387
|
+
// A stand-in the user has since deleted or edited gets a chip
|
|
388
|
+
// saying so: this row still claims to load the bytes it holds,
|
|
389
|
+
// until the next refresh (or, for a pushed snapshot, the next push).
|
|
390
|
+
tagLocalFile(path, standInTag(localPath, e.file, files?.pushed));
|
|
391
|
+
span.appendChild(path);
|
|
392
|
+
// Copies the same path the row shows — for a local file that IS the
|
|
393
|
+
// useful thing to paste, and it matches what the Location row's
|
|
394
|
+
// button gives you for a `file://` script.
|
|
395
|
+
span.appendChild(makeCopyButton({
|
|
396
|
+
label: 'Copy local file path to clipboard',
|
|
397
|
+
text: () => localPath,
|
|
398
|
+
}));
|
|
399
|
+
return span;
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
// Tail on the Size row accounting for what the script loads alongside
|
|
403
|
+
// itself: ` (plus 2 @require files: 125 KB, 1 @resource file: 4 KB)`,
|
|
404
|
+
// carrying only the halves that exist and '' when neither does.
|
|
405
|
+
//
|
|
406
|
+
// Counts entries that actually loaded and reported a size, NOT the
|
|
407
|
+
// declarations in the header. A `@require` that failed to fetch
|
|
408
|
+
// contributes no bytes, so counting it would promise a total the
|
|
409
|
+
// number beside it doesn't include.
|
|
410
|
+
//
|
|
411
|
+
// A failure therefore shows up as the tail going missing entirely, not
|
|
412
|
+
// as a short count: `attachFiles` reports a failure with EMPTY lists,
|
|
413
|
+
// so one broken dependency drops the whole suffix. The declarations
|
|
414
|
+
// themselves stay visible in the metadata block either way, and the
|
|
415
|
+
// error itself is what the message bar above the panel is for.
|
|
416
|
+
function dependencySizeSuffix(files) {
|
|
417
|
+
const parts = [];
|
|
418
|
+
const groups = [
|
|
419
|
+
['@require', files?.requires],
|
|
420
|
+
['@resource', files?.resources],
|
|
421
|
+
];
|
|
422
|
+
for (const [tag, entries] of groups) {
|
|
423
|
+
const sizes = (entries ?? []).map((e) => e.size).filter((s) => s != null);
|
|
424
|
+
if (sizes.length === 0)
|
|
425
|
+
continue;
|
|
426
|
+
const total = sizes.reduce((a, b) => a + b, 0);
|
|
427
|
+
const noun = sizes.length === 1 ? 'file' : 'files';
|
|
428
|
+
parts.push(`${sizes.length} ${tag} ${noun}: ${formatBytes(total)}`);
|
|
429
|
+
}
|
|
430
|
+
return parts.length === 0 ? '' : ` (plus ${parts.join(', ')})`;
|
|
431
|
+
}
|
|
432
|
+
// `@match` / `@include` / `@exclude` row: each pattern that resolves to
|
|
433
|
+
// a usable linkUrl via the same wildcard-trim rule as the site favicon
|
|
434
|
+
// becomes a clickable open-in-new-tab anchor, led by that site's
|
|
435
|
+
// favicon (the globe when the pattern names no site). A pattern naming
|
|
436
|
+
// no site gets a second chance against the script's own directory.
|
|
437
|
+
// Patterns we can't linkify either way (`<all_urls>`, regex literals,
|
|
438
|
+
// a host wildcard with nothing to anchor on) render as plain text so
|
|
439
|
+
// the row still shows every entry.
|
|
440
|
+
//
|
|
441
|
+
// Always a bulleted list, even for one pattern: these rows sit
|
|
442
|
+
// together and an icon-led inline value beside an icon-led list read
|
|
443
|
+
// as two different things.
|
|
444
|
+
function addPatterns(dl, label, values, scriptUrl) {
|
|
445
|
+
if (values.length === 0)
|
|
446
|
+
return;
|
|
447
|
+
appendRow(dl, label, bulletList(values, (p) => patternValueNode(p, scriptUrl)));
|
|
448
|
+
}
|
|
449
|
+
// One value renders inline; several render as a `<ul>` (folded when
|
|
450
|
+
// long — see `bulletList`). Generic in the item type so the rows whose
|
|
451
|
+
// items aren't plain strings (Local overrides) share the same
|
|
452
|
+
// one-vs-many rule rather than reimplementing it.
|
|
453
|
+
function listNode(values, make) {
|
|
454
|
+
if (values.length === 1)
|
|
455
|
+
return make(values[0]);
|
|
456
|
+
return bulletList(values, make);
|
|
457
|
+
}
|
|
458
|
+
// How many items a details-panel list shows before folding the rest
|
|
459
|
+
// behind a "(N more)" link. Long enough that ordinary scripts never
|
|
460
|
+
// fold; short enough that a fifty-site `@match` block doesn't push the
|
|
461
|
+
// code box off the screen.
|
|
462
|
+
const LIST_FOLD_AT = 10;
|
|
463
|
+
// A `<ul>` of `values`, folded past `LIST_FOLD_AT`: the tail is hidden
|
|
464
|
+
// behind a last bullet reading "(N more)", which swaps the whole list
|
|
465
|
+
// in and turns into "(show fewer)". All items are rendered up front —
|
|
466
|
+
// the fold is display only — so a per-item side effect (a favicon
|
|
467
|
+
// fetch) happens once, whether or not the reader ever expands.
|
|
468
|
+
function bulletList(values, make) {
|
|
469
|
+
const ul = document.createElement('ul');
|
|
470
|
+
const items = [];
|
|
471
|
+
for (const v of values) {
|
|
472
|
+
const li = document.createElement('li');
|
|
473
|
+
li.appendChild(make(v));
|
|
474
|
+
ul.appendChild(li);
|
|
475
|
+
items.push(li);
|
|
476
|
+
}
|
|
477
|
+
// A "(1 more)" bullet takes the line it would save; show the item.
|
|
478
|
+
const hidden = items.slice(LIST_FOLD_AT);
|
|
479
|
+
if (hidden.length < 2)
|
|
480
|
+
return ul;
|
|
481
|
+
const toggleLi = document.createElement('li');
|
|
482
|
+
toggleLi.className = 'list-fold';
|
|
483
|
+
const toggle = document.createElement('button');
|
|
484
|
+
toggle.type = 'button';
|
|
485
|
+
toggle.className = 'list-fold-toggle';
|
|
486
|
+
toggleLi.appendChild(toggle);
|
|
487
|
+
ul.appendChild(toggleLi);
|
|
488
|
+
const setFolded = (folded) => {
|
|
489
|
+
for (const li of hidden)
|
|
490
|
+
li.hidden = folded;
|
|
491
|
+
toggle.textContent = folded ? `(${hidden.length} more)` : '(show fewer)';
|
|
492
|
+
toggle.setAttribute('aria-expanded', String(!folded));
|
|
493
|
+
};
|
|
494
|
+
toggle.addEventListener('click', () => setFolded(!hidden[0].hidden));
|
|
495
|
+
setFolded(true);
|
|
496
|
+
return ul;
|
|
497
|
+
}
|
|
498
|
+
function patternValueNode(pattern, scriptUrl) {
|
|
499
|
+
const target = matchTargetInfo(pattern);
|
|
500
|
+
return siteCell(pattern, target, target?.linkUrl ?? sameDirLinkUrl(pattern, scriptUrl));
|
|
501
|
+
}
|
|
502
|
+
// A `@connect` value (or a prompt-answer host) in the same icon + link
|
|
503
|
+
// cell as a Match pattern. `connectLinkUrl` picks the site: a host
|
|
504
|
+
// links to its https root, `*.host` to the bare host, and `self` / `*`
|
|
505
|
+
// name no single site, so they stay plain text beside the globe.
|
|
506
|
+
// `fetchIcon` false keeps the globe (still linked) instead of looking
|
|
507
|
+
// up the site's favicon.
|
|
508
|
+
function connectValueNode(value, fetchIcon = true) {
|
|
509
|
+
const linkUrl = connectLinkUrl(value);
|
|
510
|
+
const target = linkUrl && fetchIcon ? { origin: new URL(linkUrl).origin, linkUrl } : null;
|
|
511
|
+
return siteCell(value, target, linkUrl, connectNoLinkTitle(value));
|
|
512
|
+
}
|
|
513
|
+
// The globe's tooltip for `self`, which names no single site to link
|
|
514
|
+
// to. The icon's generic "Unable to extract link" would read as a
|
|
515
|
+
// fault for it, when it is just a broader rule. Any other unlinked
|
|
516
|
+
// value (`foo*.com`) is read as a literal host that matches nothing, so
|
|
517
|
+
// it keeps the generic tooltip. (`*` shows as "All sites", with no
|
|
518
|
+
// icon; see `addConnect`.)
|
|
519
|
+
function connectNoLinkTitle(value) {
|
|
520
|
+
const v = value.trim().toLowerCase();
|
|
521
|
+
if (v === 'self')
|
|
522
|
+
return 'The sites in this script\'s @match and @include rules, and the page it is running on';
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
// One value with its site icon: a Match pattern or a `@connect` value.
|
|
526
|
+
// `noLinkTitle` replaces the icon's default tooltip when there's no
|
|
527
|
+
// link.
|
|
528
|
+
function siteCell(value, target, linkUrl, noLinkTitle) {
|
|
529
|
+
let text;
|
|
530
|
+
if (!linkUrl) {
|
|
531
|
+
// Non-linkable value (`<all_urls>`, regex literal, `*`, etc.);
|
|
532
|
+
// render as plain text but still allow slash-aware wrapping in case
|
|
533
|
+
// it is long.
|
|
534
|
+
const span = document.createElement('span');
|
|
535
|
+
appendWithSlashBreaks(span, value);
|
|
536
|
+
text = span;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
text = openInNewTabAnchor(linkUrl, value);
|
|
540
|
+
}
|
|
541
|
+
// Icon first, then the value; both open the same URL.
|
|
542
|
+
const cell = document.createElement('span');
|
|
543
|
+
cell.className = 'pattern-cell';
|
|
544
|
+
const slot = document.createElement('span');
|
|
545
|
+
slot.className = 'site-icon pattern-icon';
|
|
546
|
+
renderPatternIconInto(slot, target, linkUrl, noLinkTitle);
|
|
547
|
+
cell.append(slot, text);
|
|
548
|
+
return cell;
|
|
549
|
+
}
|
|
550
|
+
function logIdCell(logId) {
|
|
551
|
+
const span = document.createElement('span');
|
|
552
|
+
span.className = 'log-id-cell';
|
|
553
|
+
span.textContent = logId;
|
|
554
|
+
return span;
|
|
555
|
+
}
|
|
556
|
+
// Collection-row cell: colored type bubble on the left (linking out to
|
|
557
|
+
// the underlying file:// / http(s):// URL), followed by the
|
|
558
|
+
// collection's display name as a back-link to the collections page
|
|
559
|
+
// anchored at this collection's section. Also each side's Collection
|
|
560
|
+
// cell on the compare page.
|
|
561
|
+
export function collectionCell(collection, trailing) {
|
|
562
|
+
const cell = document.createElement('span');
|
|
563
|
+
cell.className = 'collection-cell';
|
|
564
|
+
cell.appendChild(makeCollectionTypeBubble(collection.path));
|
|
565
|
+
const nameLink = document.createElement('a');
|
|
566
|
+
nameLink.className = 'collection-display-name';
|
|
567
|
+
appendWithSlashBreaks(nameLink, effectiveDisplayName(collection));
|
|
568
|
+
// The collections page is a named tab: this opens it beside the
|
|
569
|
+
// viewer rather than replacing it, reusing the one that is already
|
|
570
|
+
// open and scrolling it to this collection. See src/extension/page-links.ts.
|
|
571
|
+
wireNamedTabLink(nameLink, 'collections', collectionAnchorId(collection.id));
|
|
572
|
+
// Tooltip surfaces the raw stored path; the anchor itself jumps to
|
|
573
|
+
// the section on the collections page. Container collections have a
|
|
574
|
+
// synthetic `container:…` path which would leak — skip then.
|
|
575
|
+
if (!isContainerCollection(collection.path))
|
|
576
|
+
nameLink.title = collection.path;
|
|
577
|
+
cell.appendChild(nameLink);
|
|
578
|
+
if (trailing)
|
|
579
|
+
cell.appendChild(trailing);
|
|
580
|
+
return cell;
|
|
581
|
+
}
|
|
582
|
+
function locationCell(row) {
|
|
583
|
+
const cell = document.createElement('span');
|
|
584
|
+
cell.className = 'filename-cell';
|
|
585
|
+
if (row.unopenable) {
|
|
586
|
+
const textEl = document.createElement('span');
|
|
587
|
+
textEl.className = 'filename-text';
|
|
588
|
+
textEl.title = row.text;
|
|
589
|
+
appendWithSlashBreaks(textEl, row.text);
|
|
590
|
+
cell.appendChild(textEl);
|
|
591
|
+
if (row.trailing)
|
|
592
|
+
cell.appendChild(row.trailing);
|
|
593
|
+
return cell;
|
|
594
|
+
}
|
|
595
|
+
// Wrap the path in an anchor so it doubles as an open-in-new-tab
|
|
596
|
+
// link. http(s) URLs navigate normally; file:// links are accepted
|
|
597
|
+
// by the anchor but Chromium typically refuses to navigate to them
|
|
598
|
+
// from a chrome-extension page — the path is still readable, still
|
|
599
|
+
// selectable, and the Copy button beside it is the reliable
|
|
600
|
+
// "send this to another app" affordance.
|
|
601
|
+
const pathEl = document.createElement('a');
|
|
602
|
+
pathEl.className = 'filename-text';
|
|
603
|
+
pathEl.href = row.href;
|
|
604
|
+
pathEl.target = '_blank';
|
|
605
|
+
pathEl.rel = 'noopener noreferrer';
|
|
606
|
+
// Display text is the bare fs path for file:// scripts, and the
|
|
607
|
+
// percent-decoded URL for the rest; the tooltip surfaces the actual
|
|
608
|
+
// href, so the encoded form the browser will request is one hover
|
|
609
|
+
// away and the user can see what would open (file://... vs
|
|
610
|
+
// http://...) without clicking through. Slash-aware appender lets
|
|
611
|
+
// long paths wrap at `/` boundaries before breaking mid-segment.
|
|
612
|
+
pathEl.title = row.href;
|
|
613
|
+
appendWithSlashBreaks(pathEl, row.text);
|
|
614
|
+
if (row.onActivate) {
|
|
615
|
+
const handler = row.onActivate;
|
|
616
|
+
for (const type of ['click', 'auxclick']) {
|
|
617
|
+
pathEl.addEventListener(type, (ev) => {
|
|
618
|
+
ev.preventDefault();
|
|
619
|
+
handler();
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (row.localFile)
|
|
624
|
+
tagLocalFile(pathEl, row.localFile);
|
|
625
|
+
cell.appendChild(pathEl);
|
|
626
|
+
// The caller's button is a single long-lived element; appendChild
|
|
627
|
+
// moves it into the new cell on every render, so any in-flight
|
|
628
|
+
// "Copied" flash carries across.
|
|
629
|
+
if (row.trailing)
|
|
630
|
+
cell.appendChild(row.trailing);
|
|
631
|
+
return cell;
|
|
632
|
+
}
|
|
633
|
+
// See `renderDetails` — the mode the rows currently being built belong
|
|
634
|
+
// to, for their label tooltips.
|
|
635
|
+
let currentMode = 'installed';
|
|
636
|
+
function appendRow(dl, label, value) {
|
|
637
|
+
const dt = document.createElement('dt');
|
|
638
|
+
dt.textContent = label;
|
|
639
|
+
const tip = rowTooltip(label, currentMode);
|
|
640
|
+
if (tip)
|
|
641
|
+
dt.title = tip;
|
|
642
|
+
const dd = document.createElement('dd');
|
|
643
|
+
dd.appendChild(value);
|
|
644
|
+
dl.append(dt, dd);
|
|
645
|
+
return dd;
|
|
646
|
+
}
|
|
647
|
+
// Header values are file content, so a `@homepage` can be anything.
|
|
648
|
+
// Only linkify what parses as an absolute http(s) URL — a relative or
|
|
649
|
+
// javascript: value stays plain text.
|
|
650
|
+
function safeUrl(value) {
|
|
651
|
+
try {
|
|
652
|
+
const u = new URL(value);
|
|
653
|
+
return u.protocol === 'http:' || u.protocol === 'https:' ? u.href : null;
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
}
|