-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathplugin.js
More file actions
101 lines (87 loc) · 2.97 KB
/
plugin.js
File metadata and controls
101 lines (87 loc) · 2.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
* Plugin: Decode S3 paths from base64 fileuri parameters
*
* When using cloud storage (S3, GCS, Azure), Label Studio generates resolve URLs
* with base64-encoded file paths. This plugin decodes those paths to show
* human-readable S3/GCS/Azure paths in Text tags.
*
* Example:
* Before: /tasks/123/resolve/?fileuri=czM6Ly9idWNrZXQvcGF0aC9maWxlLmpwZw==
* After: s3://bucket/path/file.jpg
*/
(() => {
// Cache for already processed elements (avoid re-processing)
const processedElements = new WeakSet();
// Debounce timer
let debounceTimer = null;
/**
* Decode base64 fileuri to S3/GCS/Azure path
* @param {string} text - Text containing fileuri parameter
* @returns {string|null} - Decoded path or null
*/
function decodeBase64Path(text) {
const match = text.match(/fileuri=([A-Za-z0-9+/=_-]+)/);
if (!match) return null;
try {
// Handle URL-safe base64 variants (replace - with + and _ with /)
const b64 = match[1].replace(/-/g, "+").replace(/_/g, "/");
return atob(b64);
} catch (e) {
console.warn("[S3 Path Decoder] Failed to decode:", e.message);
return null;
}
}
/**
* Process text nodes and replace encoded paths with decoded ones
*/
function processTextNodes() {
// Target elements likely containing path text
const candidates = document.querySelectorAll('[class*="text"], [class*="Text"], span, p, div');
for (const el of candidates) {
// Skip already processed elements
if (processedElements.has(el)) continue;
// Only process leaf text nodes (elements with single text child)
if (el.childNodes.length !== 1 || el.childNodes[0].nodeType !== 3) continue;
const text = el.textContent;
if (!text || !text.includes("/resolve/?fileuri=")) continue;
const decoded = decodeBase64Path(text);
if (decoded) {
el.textContent = text.replace(/\/tasks\/\d+\/resolve\/\?fileuri=[A-Za-z0-9+/=_-]+/, decoded);
processedElements.add(el);
}
}
}
/**
* Debounced processing to avoid excessive DOM operations
*/
function debouncedProcess() {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(processTextNodes, 150);
}
/**
* Initialize the plugin
*/
function init() {
// Initial processing with staggered retries for dynamic content
processTextNodes();
setTimeout(processTextNodes, 500);
setTimeout(processTextNodes, 1500);
// Watch for dynamic content changes (task navigation, etc.)
const observer = new MutationObserver(debouncedProcess);
observer.observe(document.body, {
childList: true,
subtree: true,
});
// Cleanup on page unload
window.addEventListener("beforeunload", () => {
observer.disconnect();
if (debounceTimer) clearTimeout(debounceTimer);
});
}
// Start when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();