fluxfiles 1.26.1
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 +1163 -0
- package/fluxfiles.d.ts +77 -0
- package/fluxfiles.js +231 -0
- package/package.json +31 -0
package/fluxfiles.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
interface FluxFilesOpenOptions {
|
|
2
|
+
endpoint: string;
|
|
3
|
+
token: string;
|
|
4
|
+
disk?: string;
|
|
5
|
+
/** Available disks shown in sidebar. */
|
|
6
|
+
disks?: string[];
|
|
7
|
+
mode?: 'picker' | 'browser';
|
|
8
|
+
/** Allow multi-select; when true, onSelect receives array of FluxFile */
|
|
9
|
+
multiple?: boolean;
|
|
10
|
+
/** Locale code (e.g. "en", "vi", "ar"). Default: "en". */
|
|
11
|
+
locale?: string;
|
|
12
|
+
/** Theme: "light", "dark", or "auto". */
|
|
13
|
+
theme?: string;
|
|
14
|
+
allowedTypes?: string[];
|
|
15
|
+
maxSize?: number;
|
|
16
|
+
container?: string;
|
|
17
|
+
onSelect?: (file: FluxFile | FluxFile[]) => void;
|
|
18
|
+
onClose?: () => void;
|
|
19
|
+
/** Called when the iframe needs a fresh JWT (401 received). Return new token or null. */
|
|
20
|
+
onTokenRefresh?: (context: { reason: string; disk?: string; path?: string }) => Promise<string | null>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface FluxFile {
|
|
24
|
+
path: string;
|
|
25
|
+
basename: string;
|
|
26
|
+
key?: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
url?: string;
|
|
29
|
+
size?: number;
|
|
30
|
+
mime?: string;
|
|
31
|
+
disk?: string;
|
|
32
|
+
is_dir?: boolean;
|
|
33
|
+
modified?: string;
|
|
34
|
+
meta?: {
|
|
35
|
+
title?: string | null;
|
|
36
|
+
alt_text?: string | null;
|
|
37
|
+
caption?: string | null;
|
|
38
|
+
tags?: string | null;
|
|
39
|
+
} | null;
|
|
40
|
+
/** Image variant URLs (thumb/medium/large WebP). */
|
|
41
|
+
variants?: Record<string, { url: string; key: string }> | null;
|
|
42
|
+
/** Which variant was selected ("original", "thumb", "medium", "large"). */
|
|
43
|
+
variant?: 'original' | 'thumb' | 'medium' | 'large';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface FluxEvent {
|
|
47
|
+
action: 'upload' | 'delete' | 'move' | 'copy' | 'mkdir' | 'restore' | 'purge' | 'trash' | 'crop' | 'ai_tag';
|
|
48
|
+
disk: string;
|
|
49
|
+
path: string;
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type FluxFilesEventType = 'FM_READY' | 'FM_SELECT' | 'FM_EVENT' | 'FM_CLOSE' | 'FM_TOKEN_REFRESH';
|
|
54
|
+
|
|
55
|
+
interface FluxFilesSDK {
|
|
56
|
+
open(options: FluxFilesOpenOptions): void;
|
|
57
|
+
close(): void;
|
|
58
|
+
command(action: string, data?: Record<string, unknown>): void;
|
|
59
|
+
navigate(path: string): void;
|
|
60
|
+
setDisk(disk: string): void;
|
|
61
|
+
refresh(): void;
|
|
62
|
+
search(query: string): void;
|
|
63
|
+
crossCopy(dstDisk: string, dstPath?: string): void;
|
|
64
|
+
crossMove(dstDisk: string, dstPath?: string): void;
|
|
65
|
+
aiTag(): void;
|
|
66
|
+
/** Switch locale/language at runtime. */
|
|
67
|
+
setLocale(locale: string): void;
|
|
68
|
+
/** Push a new token (e.g. after background refresh). */
|
|
69
|
+
updateToken(token: string): void;
|
|
70
|
+
on(event: FluxFilesEventType, callback: (data: unknown) => void): () => void;
|
|
71
|
+
off(event: FluxFilesEventType, callback: (data: unknown) => void): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
declare const FluxFiles: FluxFilesSDK;
|
|
75
|
+
|
|
76
|
+
export = FluxFiles;
|
|
77
|
+
export as namespace FluxFiles;
|
package/fluxfiles.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
(function(root) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var VERSION = 1;
|
|
5
|
+
var SOURCE = 'fluxfiles';
|
|
6
|
+
var iframe = null;
|
|
7
|
+
var listeners = {};
|
|
8
|
+
var config = {};
|
|
9
|
+
var ready = false;
|
|
10
|
+
var iframeOrigin = '';
|
|
11
|
+
var _tokenRefreshing = false;
|
|
12
|
+
|
|
13
|
+
function uuid() {
|
|
14
|
+
return 'ff-' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function postToIframe(type, payload) {
|
|
18
|
+
if (!iframe || !iframe.contentWindow) return;
|
|
19
|
+
iframe.contentWindow.postMessage({
|
|
20
|
+
source: SOURCE,
|
|
21
|
+
type: type,
|
|
22
|
+
v: VERSION,
|
|
23
|
+
id: uuid(),
|
|
24
|
+
payload: payload || {}
|
|
25
|
+
}, iframeOrigin || '*');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleMessage(e) {
|
|
29
|
+
if (iframeOrigin && e.origin !== iframeOrigin) return;
|
|
30
|
+
var msg = e.data;
|
|
31
|
+
if (!msg || msg.source !== SOURCE) return;
|
|
32
|
+
|
|
33
|
+
switch (msg.type) {
|
|
34
|
+
case 'FM_READY':
|
|
35
|
+
ready = true;
|
|
36
|
+
postToIframe('FM_CONFIG', {
|
|
37
|
+
disk: config.disk || 'local',
|
|
38
|
+
disks: config.disks || ['local'],
|
|
39
|
+
token: config.token || '',
|
|
40
|
+
path: config.path || '',
|
|
41
|
+
mode: config.mode || 'picker',
|
|
42
|
+
multiple: !!config.multiple,
|
|
43
|
+
allowedTypes: config.allowedTypes || null,
|
|
44
|
+
maxSize: config.maxSize || null,
|
|
45
|
+
endpoint: config.endpoint || '',
|
|
46
|
+
locale: config.locale || null,
|
|
47
|
+
theme: config.theme || null
|
|
48
|
+
});
|
|
49
|
+
emit('FM_READY', msg.payload);
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case 'FM_SELECT':
|
|
53
|
+
if (typeof config.onSelect === 'function') {
|
|
54
|
+
config.onSelect(msg.payload);
|
|
55
|
+
}
|
|
56
|
+
emit('FM_SELECT', msg.payload);
|
|
57
|
+
FluxFiles.close();
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case 'FM_TOKEN_REFRESH':
|
|
61
|
+
// Iframe is requesting a new token (401 received)
|
|
62
|
+
if (_tokenRefreshing) break; // prevent concurrent refreshes
|
|
63
|
+
_tokenRefreshing = true;
|
|
64
|
+
emit('FM_TOKEN_REFRESH', msg.payload);
|
|
65
|
+
|
|
66
|
+
if (typeof config.onTokenRefresh === 'function') {
|
|
67
|
+
// onTokenRefresh must return a Promise<string> (new JWT) or Promise<null> (failed)
|
|
68
|
+
Promise.resolve()
|
|
69
|
+
.then(function() { return config.onTokenRefresh(msg.payload); })
|
|
70
|
+
.then(function(newToken) {
|
|
71
|
+
_tokenRefreshing = false;
|
|
72
|
+
if (newToken) {
|
|
73
|
+
config.token = newToken;
|
|
74
|
+
postToIframe('FM_TOKEN_UPDATED', { token: newToken });
|
|
75
|
+
} else {
|
|
76
|
+
postToIframe('FM_TOKEN_FAILED', { reason: 'refresh_returned_null' });
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
.catch(function(err) {
|
|
80
|
+
_tokenRefreshing = false;
|
|
81
|
+
console.error('FluxFiles: onTokenRefresh failed', err);
|
|
82
|
+
postToIframe('FM_TOKEN_FAILED', { reason: err.message || 'refresh_error' });
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
// No handler — notify iframe that refresh is not supported
|
|
86
|
+
_tokenRefreshing = false;
|
|
87
|
+
postToIframe('FM_TOKEN_FAILED', { reason: 'no_handler' });
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
|
|
91
|
+
case 'FM_EVENT':
|
|
92
|
+
emit('FM_EVENT', msg.payload);
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case 'FM_CLOSE':
|
|
96
|
+
if (typeof config.onClose === 'function') {
|
|
97
|
+
config.onClose();
|
|
98
|
+
}
|
|
99
|
+
emit('FM_CLOSE', msg.payload);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function emit(type, data) {
|
|
105
|
+
var cbs = listeners[type] || [];
|
|
106
|
+
for (var i = 0; i < cbs.length; i++) {
|
|
107
|
+
try { cbs[i](data); } catch(ex) { console.error('FluxFiles listener error:', ex); }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var FluxFiles = {
|
|
112
|
+
open: function(options) {
|
|
113
|
+
config = options || {};
|
|
114
|
+
var endpoint = (config.endpoint || '').replace(/\/+$/, '');
|
|
115
|
+
var container = config.container
|
|
116
|
+
? document.querySelector(config.container)
|
|
117
|
+
: document.body;
|
|
118
|
+
|
|
119
|
+
// Derive origin from endpoint for postMessage validation
|
|
120
|
+
try {
|
|
121
|
+
var u = new URL(endpoint + '/public/index.html');
|
|
122
|
+
iframeOrigin = u.origin;
|
|
123
|
+
} catch (_) {
|
|
124
|
+
iframeOrigin = window.location.origin;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Clean up existing
|
|
128
|
+
this.close();
|
|
129
|
+
|
|
130
|
+
// Create iframe
|
|
131
|
+
iframe = document.createElement('iframe');
|
|
132
|
+
iframe.id = 'fluxfiles-iframe';
|
|
133
|
+
iframe.src = endpoint + '/public/index.html';
|
|
134
|
+
iframe.style.cssText = 'width:100%;height:100%;border:none;';
|
|
135
|
+
iframe.setAttribute('allow', 'clipboard-write');
|
|
136
|
+
|
|
137
|
+
if (!config.container) {
|
|
138
|
+
// Modal overlay — UI scaled down 5% (icons, text, spacing)
|
|
139
|
+
var overlay = document.createElement('div');
|
|
140
|
+
overlay.id = 'fluxfiles-overlay';
|
|
141
|
+
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:99999;display:flex;align-items:center;justify-content:center;';
|
|
142
|
+
|
|
143
|
+
var modal = document.createElement('div');
|
|
144
|
+
modal.id = 'fluxfiles-modal';
|
|
145
|
+
modal.style.cssText = 'width:90vw;height:85vh;max-width:1200px;background:#f5f5f7;border-radius:10px;overflow:hidden;box-shadow:0 25px 50px rgba(0,0,0,0.22);display:flex;flex-direction:column;';
|
|
146
|
+
|
|
147
|
+
// Header — macOS-style red close button (left) with × icon
|
|
148
|
+
var header = document.createElement('div');
|
|
149
|
+
header.style.cssText = 'flex-shrink:0;display:flex;align-items:center;justify-content:flex-start;padding:10px 12px;border-bottom:1px solid rgba(0,0,0,0.06);background:#f5f5f7;';
|
|
150
|
+
var closeBtn = document.createElement('button');
|
|
151
|
+
closeBtn.type = 'button';
|
|
152
|
+
closeBtn.setAttribute('aria-label', 'Close');
|
|
153
|
+
closeBtn.style.cssText = 'width:28px;height:28px;border:none;border-radius:6px;background:transparent;color:#6b7280;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:20px;line-height:1;font-weight:300;transition:color .15s,background .15s;flex-shrink:0;';
|
|
154
|
+
closeBtn.textContent = '\u00D7';
|
|
155
|
+
closeBtn.addEventListener('mouseenter', function() { closeBtn.style.background = '#e5e7eb'; closeBtn.style.color = '#374151'; });
|
|
156
|
+
closeBtn.addEventListener('mouseleave', function() { closeBtn.style.background = 'transparent'; closeBtn.style.color = '#6b7280'; });
|
|
157
|
+
closeBtn.addEventListener('click', function() { FluxFiles.close(); });
|
|
158
|
+
header.appendChild(closeBtn);
|
|
159
|
+
modal.appendChild(header);
|
|
160
|
+
|
|
161
|
+
var body = document.createElement('div');
|
|
162
|
+
body.style.cssText = 'flex:1;min-height:0;display:flex;flex-direction:column;';
|
|
163
|
+
body.appendChild(iframe);
|
|
164
|
+
modal.appendChild(body);
|
|
165
|
+
overlay.appendChild(modal);
|
|
166
|
+
document.body.appendChild(overlay);
|
|
167
|
+
|
|
168
|
+
overlay.addEventListener('click', function(e) {
|
|
169
|
+
if (e.target === overlay) FluxFiles.close();
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
container.appendChild(iframe);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
window.addEventListener('message', handleMessage);
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
close: function() {
|
|
179
|
+
ready = false;
|
|
180
|
+
window.removeEventListener('message', handleMessage);
|
|
181
|
+
|
|
182
|
+
var overlay = document.getElementById('fluxfiles-overlay');
|
|
183
|
+
if (overlay) overlay.remove();
|
|
184
|
+
|
|
185
|
+
var existing = document.getElementById('fluxfiles-iframe');
|
|
186
|
+
if (existing) existing.remove();
|
|
187
|
+
|
|
188
|
+
iframe = null;
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
command: function(action, data) {
|
|
192
|
+
postToIframe('FM_COMMAND', Object.assign({ action: action }, data || {}));
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
navigate: function(path) { this.command('navigate', { path: path }); },
|
|
196
|
+
setDisk: function(disk) { this.command('setDisk', { disk: disk }); },
|
|
197
|
+
refresh: function() { this.command('refresh'); },
|
|
198
|
+
search: function(q) { this.command('search', { q: q }); },
|
|
199
|
+
crossCopy: function(dstDisk, dstPath) { this.command('crossCopy', { dst_disk: dstDisk, dst_path: dstPath || '' }); },
|
|
200
|
+
crossMove: function(dstDisk, dstPath) { this.command('crossMove', { dst_disk: dstDisk, dst_path: dstPath || '' }); },
|
|
201
|
+
aiTag: function() { this.command('aiTag'); },
|
|
202
|
+
setLocale: function(locale) { this.command('setLocale', { locale: locale }); },
|
|
203
|
+
|
|
204
|
+
updateToken: function(newToken) {
|
|
205
|
+
config.token = newToken;
|
|
206
|
+
if (ready && iframe) {
|
|
207
|
+
postToIframe('FM_TOKEN_UPDATED', { token: newToken });
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
on: function(type, cb) {
|
|
212
|
+
if (!listeners[type]) listeners[type] = [];
|
|
213
|
+
listeners[type].push(cb);
|
|
214
|
+
return function() { FluxFiles.off(type, cb); };
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
off: function(type, cb) {
|
|
218
|
+
if (!listeners[type]) return;
|
|
219
|
+
listeners[type] = listeners[type].filter(function(fn) { return fn !== cb; });
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// UMD export
|
|
224
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
225
|
+
module.exports = FluxFiles;
|
|
226
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
227
|
+
define(function() { return FluxFiles; });
|
|
228
|
+
} else {
|
|
229
|
+
root.FluxFiles = FluxFiles;
|
|
230
|
+
}
|
|
231
|
+
})(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fluxfiles",
|
|
3
|
+
"version": "1.26.1",
|
|
4
|
+
"description": "FluxFiles JavaScript SDK — embed the file manager in any web app",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "fluxfiles.js",
|
|
7
|
+
"types": "fluxfiles.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"fluxfiles.js",
|
|
10
|
+
"fluxfiles.d.ts"
|
|
11
|
+
],
|
|
12
|
+
"author": "thai-pc",
|
|
13
|
+
"homepage": "https://github.com/thai-pc/fluxfiles",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/thai-pc/fluxfiles.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/thai-pc/fluxfiles/issues"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"fluxfiles",
|
|
23
|
+
"file-manager",
|
|
24
|
+
"sdk",
|
|
25
|
+
"iframe",
|
|
26
|
+
"s3",
|
|
27
|
+
"r2",
|
|
28
|
+
"upload",
|
|
29
|
+
"media"
|
|
30
|
+
]
|
|
31
|
+
}
|