webspresso 0.0.107 β 0.0.109
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 +3 -3
- package/bin/commands/add-esbuild.js +7 -3
- package/bin/commands/add-tailwind.js +9 -5
- package/bin/commands/build.js +20 -4
- package/bin/commands/dev.js +57 -7
- package/bin/commands/doctor.js +42 -1
- package/bin/commands/favicon-generate.js +10 -3
- package/bin/commands/new.js +8 -3
- package/bin/commands/start.js +3 -1
- package/bin/utils/add-command.js +16 -0
- package/core/app.js +52 -8
- package/core/applySchema.js +3 -72
- package/core/auth/authenticate.js +3 -3
- package/core/auth/hash.js +167 -43
- package/core/auth/index.js +13 -1
- package/core/auth/jwt.js +21 -2
- package/core/auth/manager.js +63 -14
- package/core/auth/middleware.js +4 -2
- package/core/auth/oauth.js +5 -1
- package/core/auth/tokens.js +7 -2
- package/core/auth/totp.js +42 -6
- package/core/auth/user-agent.js +2 -0
- package/core/cache/lru.js +10 -0
- package/core/cache/tagged-lru.js +4 -0
- package/core/client/adapter/datastar.js +1254 -0
- package/core/client/api.js +1033 -0
- package/core/client/controller.js +1081 -0
- package/core/client/debug.js +367 -0
- package/core/client/forms.js +1021 -0
- package/core/client/fragments.js +738 -0
- package/core/client/index.d.ts +40 -0
- package/core/client/index.js +347 -0
- package/core/client/lite.js +164 -0
- package/core/client/server-fragments.js +859 -0
- package/core/client/utils.js +144 -0
- package/core/compileSchema.js +3 -68
- package/core/compression/compressible.js +53 -6
- package/core/compression/middleware.js +277 -26
- package/core/compression/negotiate.js +31 -12
- package/core/context/app-context.js +223 -0
- package/core/context/index.js +55 -0
- package/core/context/request.js +269 -0
- package/core/errors/middleware.js +14 -0
- package/core/events/dispatcher.js +177 -15
- package/core/events/signal.js +9 -0
- package/core/forms/flash.js +136 -102
- package/core/health/middleware.js +36 -6
- package/core/health/probes.js +12 -2
- package/core/http/index.js +10 -1
- package/core/http/pipeline.js +558 -368
- package/core/index.js +45 -8
- package/core/kernel/view.js +41 -10
- package/core/metrics/middleware.js +30 -6
- package/core/metrics/registry.js +13 -5
- package/core/orm/cache/fingerprint.js +32 -11
- package/core/orm/cache/layer.js +19 -2
- package/core/orm/eager-loader.js +90 -55
- package/core/orm/hidden.js +145 -0
- package/core/orm/index.js +3 -39
- package/core/orm/model.js +54 -3
- package/core/orm/query-builder.js +343 -6
- package/core/orm/query-filters.js +14 -2
- package/core/orm/repository.js +64 -13
- package/core/orm/schema-helpers.js +68 -6
- package/core/orm/transaction.js +157 -18
- package/core/orm/types.js +4 -0
- package/core/plugins/index.js +8 -0
- package/core/plugins/plugin-manager.js +1098 -0
- package/core/queue/adapters/database.js +184 -2
- package/core/queue/adapters/memory.js +153 -5
- package/core/queue/adapters/redis.js +59 -4
- package/core/queue/job.js +14 -1
- package/core/queue/manager.js +170 -27
- package/core/rate-limit/stores/memory.js +23 -10
- package/core/resilience/circuit-breaker.js +61 -22
- package/core/schema-cache.js +2 -64
- package/core/services/builtins/auth.js +273 -0
- package/core/services/builtins/exchange.js +396 -0
- package/core/services/builtins/file-manager.js +214 -0
- package/core/services/builtins/mail.js +161 -0
- package/core/services/builtins/media.js +196 -0
- package/core/services/builtins/queue.js +144 -0
- package/core/services/builtins/security.js +118 -0
- package/core/services/builtins/system.js +219 -0
- package/core/services/builtins/webhook.js +110 -0
- package/core/services/discovery.js +133 -0
- package/core/services/executor.js +651 -0
- package/core/services/index.js +113 -5
- package/core/services/loader.js +161 -0
- package/core/services/memoize.js +408 -0
- package/core/services/registry.js +731 -0
- package/core/services/validator.js +424 -0
- package/core/shutdown/shutdown-manager.js +146 -85
- package/core/sse/stream.js +28 -5
- package/core/ssr/stream.js +109 -16
- package/core/stream/text-stream.js +45 -4
- package/core/utils/index.js +110 -0
- package/core/utils/timeout.js +12 -1
- package/core/validation/apply.js +74 -0
- package/core/validation/cache.js +66 -0
- package/core/validation/compiler.js +80 -0
- package/core/validation/index.js +22 -1
- package/index.d.ts +1201 -72
- package/index.js +188 -28
- package/package.json +75 -26
- package/plugins/admin-panel/client/parts/08-record-list.js +717 -70
- package/plugins/admin-panel/index.js +19 -8
- package/plugins/csrf/index.js +3 -3
- package/plugins/data-exchange/export.js +214 -0
- package/plugins/data-exchange/import.js +48 -5
- package/plugins/data-exchange/index.js +70 -28
- package/plugins/data-exchange/preview.js +266 -0
- package/plugins/data-exchange/template.js +242 -0
- package/plugins/email/admin-component.js +783 -141
- package/plugins/email/api-handlers.js +51 -1
- package/plugins/email/index.js +5 -0
- package/plugins/email/service.js +48 -0
- package/plugins/file-manager/admin-component.js +1546 -70
- package/plugins/file-manager/api-handlers.js +166 -1
- package/plugins/file-manager/core.js +598 -8
- package/plugins/file-manager/index.js +8 -0
- package/plugins/index.js +7 -0
- package/plugins/mcp/auth.js +3 -3
- package/plugins/oauth/index.js +36 -3
- package/plugins/queue/admin-component.js +1092 -0
- package/plugins/queue/api-handlers.js +248 -0
- package/plugins/queue/index.js +69 -0
- package/plugins/rest-resources/index.js +7 -2
- package/plugins/security/admin-component.js +689 -0
- package/plugins/security/api-handlers.js +169 -0
- package/plugins/security/audit.js +153 -0
- package/plugins/security/bruteforce.js +244 -0
- package/plugins/security/firewall.js +459 -0
- package/plugins/security/headers.js +214 -0
- package/plugins/security/honeypot.js +234 -0
- package/plugins/security/incident-recorder.js +137 -0
- package/plugins/security/index.js +211 -0
- package/plugins/seo-checker/analyzer.js +20 -7
- package/plugins/upload/index.js +36 -0
- package/plugins/webhooks/admin-component.js +702 -175
- package/plugins/webhooks/api-handlers.js +75 -1
- package/plugins/webhooks/dispatcher.js +290 -11
- package/plugins/webhooks/index.js +37 -2
- package/plugins/webhooks/sender.js +56 -0
- package/plugins/xlsx/engine.js +19 -4
- package/src/api/api-loader.js +19 -1
- package/src/app-context.js +4 -196
- package/src/bundle/index.js +443 -17
- package/src/client-runtime/mount.js +9 -2
- package/src/dev/api-typegen.js +1300 -0
- package/src/dev/banner.js +10 -1
- package/src/dev/colors.js +1 -1
- package/src/dev/index.js +7 -1
- package/src/dev/typegen.js +174 -18
- package/src/dev-reload/index.js +1 -1
- package/src/discovery/file-route-parser.js +17 -3
- package/src/discovery/scan-routes.js +4 -1
- package/src/file-router.js +249 -69
- package/src/helpers.js +46 -26
- package/src/njk-frontmatter.js +30 -4
- package/src/plugin-manager.js +5 -1086
- package/src/routing/cascading.js +26 -8
- package/src/routing/mount-discovered-routes.js +69 -0
- package/src/server.js +238 -64
- package/src/services/builtins/auth.js +2 -273
- package/src/services/builtins/exchange.js +2 -229
- package/src/services/builtins/file-manager.js +2 -178
- package/src/services/builtins/mail.js +2 -134
- package/src/services/builtins/media.js +2 -196
- package/src/services/builtins/system.js +2 -219
- package/src/services/discovery.js +2 -133
- package/src/services/executor.js +2 -575
- package/src/services/index.js +4 -109
- package/src/services/loader.js +1 -119
- package/src/services/memoize.js +2 -408
- package/src/services/registry.js +1 -711
- package/src/services/validator.js +2 -424
- package/.agents/ADMIN.md +0 -426
- package/.agents/AGENTS.md +0 -93
- package/.agents/ARCHITECTURE.md +0 -163
- package/.agents/AUTH.md +0 -581
- package/.agents/CACHE.md +0 -147
- package/.agents/CLI.md +0 -649
- package/.agents/COMPRESSION.md +0 -59
- package/.agents/CONVENTIONS.md +0 -50
- package/.agents/EARLY_HINTS.md +0 -120
- package/.agents/EMAIL.md +0 -223
- package/.agents/ERRORS.md +0 -260
- package/.agents/EVENTS.md +0 -179
- package/.agents/FLAGS.md +0 -118
- package/.agents/HEALTH.md +0 -118
- package/.agents/LIMITS.md +0 -109
- package/.agents/LOGGING.md +0 -93
- package/.agents/MCP.md +0 -103
- package/.agents/METRICS.md +0 -127
- package/.agents/MIGRATION_JOBS.md +0 -95
- package/.agents/ORM.md +0 -626
- package/.agents/PLUGINS.md +0 -729
- package/.agents/QUEUE.md +0 -290
- package/.agents/RATE_LIMIT.md +0 -147
- package/.agents/REALTIME.md +0 -213
- package/.agents/RESILIENCE.md +0 -235
- package/.agents/ROUTING.md +0 -605
- package/.agents/SCALE_OUT.md +0 -109
- package/.agents/SECRETS.md +0 -117
- package/.agents/SERVICES.md +0 -341
- package/.agents/SHUTDOWN.md +0 -67
- package/.agents/STREAMING.md +0 -108
- package/.agents/WORKFLOW.md +0 -27
- package/.agents/internal/DOCUMENTATION_REVIEW.md +0 -485
- package/.agents/internal/TECHNICAL_REVIEW.md +0 -428
- package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +0 -122
- package/.agents/skills/webspresso-framework/SKILL.md +0 -88
- package/.agents/skills/webspresso-orm-and-database/SKILL.md +0 -118
- package/.agents/skills/webspresso-routing-and-api/SKILL.md +0 -114
- package/.agents/skills/webspresso-services/SKILL.md +0 -101
- package/.cursorrules +0 -14
- package/AGENTS.md +0 -200
- package/CLAUDE.md +0 -54
- package/core/kernel/run-demo.js +0 -58
- package/core/utils/array.js +0 -12
- package/core/utils/base64url.js +0 -8
- package/core/utils/ensure-leading-slash.js +0 -6
- package/core/utils/escape-html.js +0 -7
- package/core/utils/escape-regexp.js +0 -7
- package/core/utils/get-client-ip.js +0 -6
- package/core/utils/hmac.js +0 -6
- package/core/utils/random.js +0 -9
- package/core/utils/safe-compare.js +0 -3
- package/core/utils/sha256.js +0 -6
- package/core/utils/strip-trailing-slash.js +0 -6
- package/core/utils/walk.js +0 -7
- package/plugins/admin-panel/styles.js +0 -9
- package/plugins/data-exchange/export-xlsx.js +0 -119
|
@@ -85,6 +85,40 @@ function generateFileManagerComponent(options = {}) {
|
|
|
85
85
|
]);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function renderShortcutItem(keys, desc) {
|
|
89
|
+
return m('div.flex.items-center.justify-between.p-2.rounded-lg.bg-gray-50.dark:bg-slate-800/70.border.border-gray-100.dark:border-slate-700/50', [
|
|
90
|
+
m('span.text-xs.text-gray-600.dark:text-slate-300', desc),
|
|
91
|
+
m('kbd.px-2.py-0.5.text-[11px].font-mono.font-semibold.rounded.bg-white.dark:bg-slate-700.text-gray-800.dark:text-slate-200.border.border-gray-200.dark:border-slate-600.shadow-xs', keys)
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function renderCategoryCard(title, icon, cat, textColor, dotBg) {
|
|
96
|
+
var c = cat || { count: 0, bytes: 0, percent: 0, formattedBytes: '0 B' };
|
|
97
|
+
return m('div.p-2.rounded-lg.bg-gray-50.dark:bg-slate-900/60.border.border-gray-100.dark:border-slate-700/50.flex.flex-col.gap-0.5', [
|
|
98
|
+
m('div.flex.items-center.gap-1.5', [
|
|
99
|
+
m('span.w-2.h-2.rounded-full.' + dotBg),
|
|
100
|
+
m('span.font-semibold.text-gray-800.dark:text-slate-200.truncate', title)
|
|
101
|
+
]),
|
|
102
|
+
m('div.flex.items-baseline.justify-between.mt-1', [
|
|
103
|
+
m('span.font-medium.' + textColor, c.formattedBytes),
|
|
104
|
+
m('span.text-[10px].text-gray-400', c.count + ' files')
|
|
105
|
+
])
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
var TEXT_EXTS = [
|
|
110
|
+
'txt', 'csv', 'md', 'markdown', 'json', 'xml', 'html', 'htm',
|
|
111
|
+
'css', 'scss', 'sass', 'less', 'js', 'mjs', 'cjs', 'ts', 'tsx', 'jsx',
|
|
112
|
+
'sql', 'yaml', 'yml', 'env', 'sh', 'bash', 'log', 'ini', 'toml',
|
|
113
|
+
'svg', 'gitignore', 'conf', 'config'
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
function isTextFile(item) {
|
|
117
|
+
if (!item || item.isDir) return false;
|
|
118
|
+
var ext = (item.ext || '').toLowerCase();
|
|
119
|
+
return item.type === 'text' || item.type === 'code' || TEXT_EXTS.indexOf(ext) !== -1;
|
|
120
|
+
}
|
|
121
|
+
|
|
88
122
|
function FileManagerView(isPickerMode, onSelectCallback, pickerAccept) {
|
|
89
123
|
var currentPath = '';
|
|
90
124
|
var breadcrumbs = [{ name: 'Root', path: '' }];
|
|
@@ -103,9 +137,235 @@ function generateFileManagerComponent(options = {}) {
|
|
|
103
137
|
var copyingFeedback = null;
|
|
104
138
|
var uploading = false;
|
|
105
139
|
var uploadStatus = '';
|
|
140
|
+
var isDraggingOver = false;
|
|
141
|
+
var dragCounter = 0;
|
|
142
|
+
var activeXhr = null;
|
|
143
|
+
var uploadProgress = 0;
|
|
144
|
+
var uploadLoadedBytes = 0;
|
|
145
|
+
var uploadTotalBytes = 0;
|
|
146
|
+
var uploadFileCount = 0;
|
|
147
|
+
var uploadSuccessMessage = '';
|
|
148
|
+
var uploadSuccessTimer = null;
|
|
149
|
+
var selectedPaths = {};
|
|
150
|
+
var batchActionLoading = false;
|
|
151
|
+
var focusedIndex = 0;
|
|
152
|
+
var showShortcutsModal = false;
|
|
153
|
+
var storageStats = null;
|
|
154
|
+
var showStorageDetails = false;
|
|
155
|
+
var editMode = false;
|
|
156
|
+
var editorState = {
|
|
157
|
+
loaded: false,
|
|
158
|
+
rotation: 0,
|
|
159
|
+
flipH: false,
|
|
160
|
+
flipV: false,
|
|
161
|
+
aspectRatio: 'free',
|
|
162
|
+
naturalWidth: 0,
|
|
163
|
+
naturalHeight: 0,
|
|
164
|
+
cropX: 0,
|
|
165
|
+
cropY: 0,
|
|
166
|
+
cropW: 100,
|
|
167
|
+
cropH: 100,
|
|
168
|
+
targetWidth: 0,
|
|
169
|
+
targetHeight: 0,
|
|
170
|
+
lockRatio: true,
|
|
171
|
+
format: 'original',
|
|
172
|
+
saving: false,
|
|
173
|
+
saveStatus: '',
|
|
174
|
+
};
|
|
175
|
+
var loadedImageMeta = {};
|
|
176
|
+
|
|
177
|
+
var textEditMode = false;
|
|
178
|
+
var textEditorState = {
|
|
179
|
+
loading: false,
|
|
180
|
+
saving: false,
|
|
181
|
+
saveStatus: '',
|
|
182
|
+
error: null,
|
|
183
|
+
file: null,
|
|
184
|
+
content: '',
|
|
185
|
+
initialContent: '',
|
|
186
|
+
lineCount: 1,
|
|
187
|
+
charCount: 0,
|
|
188
|
+
wordWrap: false,
|
|
189
|
+
fontSize: 13,
|
|
190
|
+
isJson: false,
|
|
191
|
+
jsonError: null,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
function openTextEditor(item) {
|
|
195
|
+
if (!item || item.isDir) return;
|
|
196
|
+
textEditMode = true;
|
|
197
|
+
modalState = null;
|
|
198
|
+
textEditorState.loading = true;
|
|
199
|
+
textEditorState.saving = false;
|
|
200
|
+
textEditorState.saveStatus = '';
|
|
201
|
+
textEditorState.error = null;
|
|
202
|
+
textEditorState.file = item;
|
|
203
|
+
textEditorState.content = '';
|
|
204
|
+
textEditorState.initialContent = '';
|
|
205
|
+
textEditorState.isJson = (item.ext || '').toLowerCase() === 'json';
|
|
206
|
+
textEditorState.jsonError = null;
|
|
207
|
+
|
|
208
|
+
api.get(API + '/content?path=' + encodeURIComponent(item.path)).then(function(res) {
|
|
209
|
+
textEditorState.loading = false;
|
|
210
|
+
textEditorState.content = res.content || '';
|
|
211
|
+
textEditorState.initialContent = res.content || '';
|
|
212
|
+
updateTextStats();
|
|
213
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
214
|
+
}).catch(function(err) {
|
|
215
|
+
textEditorState.loading = false;
|
|
216
|
+
textEditorState.error = err.message || String(err);
|
|
217
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function updateTextStats() {
|
|
222
|
+
var lines = textEditorState.content.split(String.fromCharCode(10));
|
|
223
|
+
textEditorState.lineCount = lines.length;
|
|
224
|
+
textEditorState.charCount = textEditorState.content.length;
|
|
225
|
+
if (textEditorState.isJson) {
|
|
226
|
+
try {
|
|
227
|
+
if (textEditorState.content.trim()) {
|
|
228
|
+
JSON.parse(textEditorState.content);
|
|
229
|
+
}
|
|
230
|
+
textEditorState.jsonError = null;
|
|
231
|
+
} catch (e) {
|
|
232
|
+
textEditorState.jsonError = e.message;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function formatJsonContent() {
|
|
238
|
+
try {
|
|
239
|
+
var parsed = JSON.parse(textEditorState.content);
|
|
240
|
+
textEditorState.content = JSON.stringify(parsed, null, 2);
|
|
241
|
+
textEditorState.jsonError = null;
|
|
242
|
+
updateTextStats();
|
|
243
|
+
} catch (e) {
|
|
244
|
+
alert('Invalid JSON: ' + e.message);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function saveTextContent(overwrite) {
|
|
249
|
+
if (!textEditorState.file || textEditorState.saving) return;
|
|
250
|
+
textEditorState.saving = true;
|
|
251
|
+
textEditorState.saveStatus = 'Savingβ¦';
|
|
252
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
253
|
+
|
|
254
|
+
api.post(API + '/save-content', {
|
|
255
|
+
path: textEditorState.file.path,
|
|
256
|
+
content: textEditorState.content,
|
|
257
|
+
overwrite: !!overwrite
|
|
258
|
+
}).then(function(res) {
|
|
259
|
+
textEditorState.saving = false;
|
|
260
|
+
textEditorState.saveStatus = 'Saved successfully!';
|
|
261
|
+
textEditorState.initialContent = textEditorState.content;
|
|
262
|
+
setTimeout(function() {
|
|
263
|
+
textEditorState.saveStatus = '';
|
|
264
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
265
|
+
}, 2500);
|
|
266
|
+
if (res && res.file) {
|
|
267
|
+
textEditorState.file = res.file;
|
|
268
|
+
selectedItem = res.file;
|
|
269
|
+
}
|
|
270
|
+
loadDirectory();
|
|
271
|
+
}).catch(function(err) {
|
|
272
|
+
textEditorState.saving = false;
|
|
273
|
+
textEditorState.saveStatus = '';
|
|
274
|
+
alert('Failed to save file: ' + (err.message || String(err)));
|
|
275
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function getSelectedPathsList() {
|
|
280
|
+
return Object.keys(selectedPaths).filter(function(k) { return !!selectedPaths[k]; });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isSelected(item) {
|
|
284
|
+
return !!selectedPaths[item.path];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function toggleSelect(item, e) {
|
|
288
|
+
if (e && e.stopPropagation) e.stopPropagation();
|
|
289
|
+
if (selectedPaths[item.path]) {
|
|
290
|
+
delete selectedPaths[item.path];
|
|
291
|
+
} else {
|
|
292
|
+
selectedPaths[item.path] = item;
|
|
293
|
+
}
|
|
294
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isAllSelected() {
|
|
298
|
+
if (!items || items.length === 0) return false;
|
|
299
|
+
for (var i = 0; i < items.length; i++) {
|
|
300
|
+
if (!selectedPaths[items[i].path]) return false;
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function toggleSelectAll() {
|
|
306
|
+
if (isAllSelected()) {
|
|
307
|
+
clearSelection();
|
|
308
|
+
} else {
|
|
309
|
+
for (var i = 0; i < items.length; i++) {
|
|
310
|
+
selectedPaths[items[i].path] = items[i];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function clearSelection() {
|
|
317
|
+
selectedPaths = {};
|
|
318
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function downloadBatchZip() {
|
|
322
|
+
var paths = getSelectedPathsList();
|
|
323
|
+
if (paths.length === 0) return;
|
|
324
|
+
var query = paths.map(function(p) { return 'paths=' + encodeURIComponent(p); }).join('&');
|
|
325
|
+
var downloadUrl = (window.__ADMIN_PATH__ || '/_admin') + '/api' + API + '/download-zip?' + query;
|
|
326
|
+
window.open(downloadUrl, '_blank');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function deleteBatchSelected() {
|
|
330
|
+
var paths = getSelectedPathsList();
|
|
331
|
+
if (paths.length === 0) return;
|
|
332
|
+
if (!confirm('Are you sure you want to delete ' + paths.length + ' selected item(s)?')) return;
|
|
333
|
+
batchActionLoading = true;
|
|
334
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
335
|
+
|
|
336
|
+
api.post(API + '/batch-delete', { paths: paths }).then(function(res) {
|
|
337
|
+
batchActionLoading = false;
|
|
338
|
+
clearSelection();
|
|
339
|
+
loadDirectory();
|
|
340
|
+
}).catch(function(err) {
|
|
341
|
+
batchActionLoading = false;
|
|
342
|
+
alert('Failed to delete selected items: ' + (err.message || String(err)));
|
|
343
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function moveBatchSelected() {
|
|
348
|
+
var paths = getSelectedPathsList();
|
|
349
|
+
if (paths.length === 0) return;
|
|
350
|
+
var target = prompt('Enter target relative directory path to move selected items into:', currentPath);
|
|
351
|
+
if (target === null) return;
|
|
352
|
+
batchActionLoading = true;
|
|
353
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
354
|
+
|
|
355
|
+
api.post(API + '/batch-move', { paths: paths, destination: target }).then(function(res) {
|
|
356
|
+
batchActionLoading = false;
|
|
357
|
+
clearSelection();
|
|
358
|
+
loadDirectory();
|
|
359
|
+
}).catch(function(err) {
|
|
360
|
+
batchActionLoading = false;
|
|
361
|
+
alert('Failed to move selected items: ' + (err.message || String(err)));
|
|
362
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
363
|
+
});
|
|
364
|
+
}
|
|
106
365
|
|
|
107
366
|
function loadDirectory(pathParam) {
|
|
108
367
|
if (pathParam !== undefined) currentPath = pathParam;
|
|
368
|
+
selectedPaths = {};
|
|
109
369
|
loading = true;
|
|
110
370
|
error = null;
|
|
111
371
|
|
|
@@ -117,7 +377,11 @@ function generateFileManagerComponent(options = {}) {
|
|
|
117
377
|
api.get(API + q).then(function(res) {
|
|
118
378
|
items = res.items || [];
|
|
119
379
|
breadcrumbs = res.breadcrumbs || [{ name: 'Root', path: '' }];
|
|
380
|
+
if (res.storage) {
|
|
381
|
+
storageStats = res.storage;
|
|
382
|
+
}
|
|
120
383
|
loading = false;
|
|
384
|
+
focusedIndex = Math.max(0, Math.min(focusedIndex, Math.max(0, items.length - 1)));
|
|
121
385
|
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
122
386
|
}).catch(function(err) {
|
|
123
387
|
error = err.message || String(err);
|
|
@@ -126,6 +390,15 @@ function generateFileManagerComponent(options = {}) {
|
|
|
126
390
|
});
|
|
127
391
|
}
|
|
128
392
|
|
|
393
|
+
function loadStorageStats(force) {
|
|
394
|
+
api.get(API + '/storage-stats' + (force ? '?refresh=true' : '')).then(function(res) {
|
|
395
|
+
if (res && res.stats) {
|
|
396
|
+
storageStats = res.stats;
|
|
397
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
398
|
+
}
|
|
399
|
+
}).catch(function() {});
|
|
400
|
+
}
|
|
401
|
+
|
|
129
402
|
function createFolder() {
|
|
130
403
|
if (!inputFolderValue.trim()) return;
|
|
131
404
|
api.post(API + '/mkdir', { path: currentPath, name: inputFolderValue.trim() }).then(function() {
|
|
@@ -160,10 +433,27 @@ function generateFileManagerComponent(options = {}) {
|
|
|
160
433
|
});
|
|
161
434
|
}
|
|
162
435
|
|
|
436
|
+
function cancelUpload() {
|
|
437
|
+
if (activeXhr) {
|
|
438
|
+
try { activeXhr.abort(); } catch (_) {}
|
|
439
|
+
activeXhr = null;
|
|
440
|
+
}
|
|
441
|
+
uploading = false;
|
|
442
|
+
uploadProgress = 0;
|
|
443
|
+
uploadStatus = 'Upload cancelled';
|
|
444
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
445
|
+
}
|
|
446
|
+
|
|
163
447
|
function handleFileUpload(fileList) {
|
|
164
448
|
if (!fileList || fileList.length === 0) return;
|
|
165
449
|
uploading = true;
|
|
450
|
+
uploadProgress = 0;
|
|
451
|
+
uploadLoadedBytes = 0;
|
|
452
|
+
uploadTotalBytes = 0;
|
|
453
|
+
uploadFileCount = fileList.length;
|
|
166
454
|
uploadStatus = 'Uploading ' + fileList.length + ' file(s)β¦';
|
|
455
|
+
uploadSuccessMessage = '';
|
|
456
|
+
if (uploadSuccessTimer) clearTimeout(uploadSuccessTimer);
|
|
167
457
|
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
168
458
|
|
|
169
459
|
var fd = new FormData();
|
|
@@ -173,31 +463,515 @@ function generateFileManagerComponent(options = {}) {
|
|
|
173
463
|
|
|
174
464
|
var uploadUrl = (window.__ADMIN_PATH__ || '/_admin') + '/api' + API + '/upload?path=' + encodeURIComponent(currentPath);
|
|
175
465
|
|
|
176
|
-
var
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
466
|
+
var xhr = new XMLHttpRequest();
|
|
467
|
+
activeXhr = xhr;
|
|
468
|
+
|
|
469
|
+
xhr.upload.onprogress = function(evt) {
|
|
470
|
+
if (evt.lengthComputable) {
|
|
471
|
+
uploadLoadedBytes = evt.loaded;
|
|
472
|
+
uploadTotalBytes = evt.total;
|
|
473
|
+
uploadProgress = Math.min(100, Math.round((evt.loaded / evt.total) * 100));
|
|
474
|
+
uploadStatus = 'Uploading ' + uploadFileCount + ' file(s) (' + formatBytes(evt.loaded) + ' / ' + formatBytes(evt.total) + ')';
|
|
475
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
xhr.onload = function() {
|
|
480
|
+
activeXhr = null;
|
|
183
481
|
uploading = false;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
482
|
+
uploadProgress = 100;
|
|
483
|
+
try {
|
|
484
|
+
var res = JSON.parse(xhr.responseText || '{}');
|
|
485
|
+
if (xhr.status >= 200 && xhr.status < 300 && res.success) {
|
|
486
|
+
uploadSuccessMessage = (res.files && res.files.length ? res.files.length : uploadFileCount) + ' file(s) successfully uploaded!';
|
|
487
|
+
uploadSuccessTimer = setTimeout(function() {
|
|
488
|
+
uploadSuccessMessage = '';
|
|
489
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
490
|
+
}, 4000);
|
|
491
|
+
loadDirectory();
|
|
492
|
+
} else {
|
|
493
|
+
alert('Upload error: ' + (res.error || ('HTTP ' + xhr.status)));
|
|
494
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
495
|
+
}
|
|
496
|
+
} catch (e) {
|
|
497
|
+
alert('Upload failed: ' + (e.message || String(e)));
|
|
189
498
|
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
190
499
|
}
|
|
191
|
-
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
xhr.onerror = function() {
|
|
503
|
+
activeXhr = null;
|
|
192
504
|
uploading = false;
|
|
193
|
-
|
|
194
|
-
alert('
|
|
505
|
+
uploadProgress = 0;
|
|
506
|
+
alert('Network error while uploading');
|
|
195
507
|
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
196
|
-
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
xhr.onabort = function() {
|
|
511
|
+
activeXhr = null;
|
|
512
|
+
uploading = false;
|
|
513
|
+
uploadProgress = 0;
|
|
514
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
xhr.open('POST', uploadUrl, true);
|
|
518
|
+
xhr.withCredentials = true;
|
|
519
|
+
xhr.send(fd);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (typeof window !== 'undefined') {
|
|
523
|
+
window.__fmDropzoneTest = {
|
|
524
|
+
setDrag: function(active) {
|
|
525
|
+
isDraggingOver = !!active;
|
|
526
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
527
|
+
},
|
|
528
|
+
setProgress: function(pct, loaded, total, status) {
|
|
529
|
+
uploading = true;
|
|
530
|
+
uploadProgress = pct;
|
|
531
|
+
uploadLoadedBytes = loaded || 2450000;
|
|
532
|
+
uploadTotalBytes = total || 5000000;
|
|
533
|
+
uploadStatus = status || ('Uploading 3 file(s) (' + formatBytes(uploadLoadedBytes) + ' / ' + formatBytes(uploadTotalBytes) + ')');
|
|
534
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
535
|
+
},
|
|
536
|
+
setSuccess: function(msg) {
|
|
537
|
+
uploading = false;
|
|
538
|
+
uploadSuccessMessage = msg || '3 file(s) successfully uploaded!';
|
|
539
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
540
|
+
},
|
|
541
|
+
selectFirst: function(n) {
|
|
542
|
+
selectedPaths = {};
|
|
543
|
+
for (var i = 0; i < Math.min(n || 2, items.length); i++) {
|
|
544
|
+
selectedPaths[items[i].path] = items[i];
|
|
545
|
+
}
|
|
546
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
547
|
+
},
|
|
548
|
+
reset: function() {
|
|
549
|
+
uploading = false;
|
|
550
|
+
isDraggingOver = false;
|
|
551
|
+
uploadSuccessMessage = '';
|
|
552
|
+
selectedPaths = {};
|
|
553
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
554
|
+
},
|
|
555
|
+
setFocusedIndex: function(idx) {
|
|
556
|
+
focusedIndex = idx;
|
|
557
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
558
|
+
},
|
|
559
|
+
getFocusedIndex: function() { return focusedIndex; },
|
|
560
|
+
toggleShortcutsModal: function(show) {
|
|
561
|
+
showShortcutsModal = typeof show === 'boolean' ? show : !showShortcutsModal;
|
|
562
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
563
|
+
},
|
|
564
|
+
isShortcutsModalOpen: function() { return showShortcutsModal; },
|
|
565
|
+
setStorageStats: function(stats) {
|
|
566
|
+
storageStats = stats;
|
|
567
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
568
|
+
},
|
|
569
|
+
getStorageStats: function() { return storageStats; },
|
|
570
|
+
toggleStorageDetails: function(show) {
|
|
571
|
+
showStorageDetails = typeof show === 'boolean' ? show : !showStorageDetails;
|
|
572
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
573
|
+
},
|
|
574
|
+
isStorageDetailsOpen: function() { return showStorageDetails; }
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function isEditableActive() {
|
|
579
|
+
var el = document.activeElement;
|
|
580
|
+
if (!el) return false;
|
|
581
|
+
var tag = el.tagName ? el.tagName.toLowerCase() : '';
|
|
582
|
+
return tag === 'input' || tag === 'textarea' || tag === 'select' || el.isContentEditable;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function handleGlobalKeydown(e) {
|
|
586
|
+
if (!e) return;
|
|
587
|
+
|
|
588
|
+
// Ignore when typing inside editable inputs
|
|
589
|
+
if (isEditableActive()) {
|
|
590
|
+
if (e.key === 'Escape') {
|
|
591
|
+
if (document.activeElement && document.activeElement.blur) document.activeElement.blur();
|
|
592
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// If text editor or image editor is active
|
|
598
|
+
if (textEditMode) {
|
|
599
|
+
if (e.key === 'Escape') {
|
|
600
|
+
if (textEditorState.content !== textEditorState.initialContent) {
|
|
601
|
+
if (!confirm('You have unsaved changes. Discard them?')) return;
|
|
602
|
+
}
|
|
603
|
+
textEditMode = false;
|
|
604
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
605
|
+
}
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (editMode) {
|
|
609
|
+
if (e.key === 'Escape') {
|
|
610
|
+
editMode = false;
|
|
611
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
612
|
+
}
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Close shortcuts modal if open
|
|
617
|
+
if (showShortcutsModal) {
|
|
618
|
+
if (e.key === 'Escape' || e.key === '?' || e.key === 'h' || e.key === 'H') {
|
|
619
|
+
e.preventDefault();
|
|
620
|
+
showShortcutsModal = false;
|
|
621
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
622
|
+
}
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Handle Escape: close open modal or clear selection
|
|
627
|
+
if (e.key === 'Escape') {
|
|
628
|
+
if (modalState) {
|
|
629
|
+
e.preventDefault();
|
|
630
|
+
modalState = null;
|
|
631
|
+
selectedItem = null;
|
|
632
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
var curSelected = getSelectedPathsList();
|
|
636
|
+
if (curSelected.length > 0) {
|
|
637
|
+
e.preventDefault();
|
|
638
|
+
selectedPaths = {};
|
|
639
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// If other dialogs (rename, delete, newFolder) are open, let modal handle it
|
|
645
|
+
if (modalState && modalState !== 'preview') {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Toggle shortcuts cheatsheet with '?' or 'h'
|
|
650
|
+
if (e.key === '?' || ((e.key === 'h' || e.key === 'H') && !e.ctrlKey && !e.metaKey)) {
|
|
651
|
+
e.preventDefault();
|
|
652
|
+
showShortcutsModal = true;
|
|
653
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Focus search input with '/'
|
|
658
|
+
if (e.key === '/' && !e.ctrlKey && !e.metaKey) {
|
|
659
|
+
e.preventDefault();
|
|
660
|
+
var searchEl = document.getElementById('fm-search-input');
|
|
661
|
+
if (searchEl) {
|
|
662
|
+
searchEl.focus();
|
|
663
|
+
searchEl.select();
|
|
664
|
+
}
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Select All with Cmd+A or Ctrl+A
|
|
669
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === 'a' || e.key === 'A')) {
|
|
670
|
+
e.preventDefault();
|
|
671
|
+
toggleSelectAll();
|
|
672
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Space: Finder-style Quick Look
|
|
677
|
+
if (e.key === ' ' || e.code === 'Space') {
|
|
678
|
+
if (items.length > 0 && focusedIndex < items.length) {
|
|
679
|
+
e.preventDefault();
|
|
680
|
+
if (modalState === 'preview') {
|
|
681
|
+
modalState = null;
|
|
682
|
+
selectedItem = null;
|
|
683
|
+
} else {
|
|
684
|
+
selectedItem = items[focusedIndex];
|
|
685
|
+
modalState = 'preview';
|
|
686
|
+
}
|
|
687
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
688
|
+
}
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// If preview modal is open, arrows cycle through items
|
|
693
|
+
if (modalState === 'preview') {
|
|
694
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
695
|
+
e.preventDefault();
|
|
696
|
+
if (items.length > 0) {
|
|
697
|
+
focusedIndex = (focusedIndex + 1) % items.length;
|
|
698
|
+
selectedItem = items[focusedIndex];
|
|
699
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
700
|
+
}
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
704
|
+
e.preventDefault();
|
|
705
|
+
if (items.length > 0) {
|
|
706
|
+
focusedIndex = (focusedIndex - 1 + items.length) % items.length;
|
|
707
|
+
selectedItem = items[focusedIndex];
|
|
708
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
709
|
+
}
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Arrow navigation in items list
|
|
716
|
+
if (e.key === 'ArrowRight') {
|
|
717
|
+
if (items.length > 0) {
|
|
718
|
+
e.preventDefault();
|
|
719
|
+
focusedIndex = (focusedIndex + 1) % items.length;
|
|
720
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (e.key === 'ArrowLeft') {
|
|
725
|
+
if (items.length > 0) {
|
|
726
|
+
e.preventDefault();
|
|
727
|
+
focusedIndex = (focusedIndex - 1 + items.length) % items.length;
|
|
728
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (e.key === 'ArrowDown') {
|
|
733
|
+
if (items.length > 0) {
|
|
734
|
+
e.preventDefault();
|
|
735
|
+
var step = viewMode === 'grid' ? 4 : 1;
|
|
736
|
+
focusedIndex = Math.min(items.length - 1, focusedIndex + step);
|
|
737
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
738
|
+
}
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (e.key === 'ArrowUp') {
|
|
742
|
+
if (items.length > 0) {
|
|
743
|
+
e.preventDefault();
|
|
744
|
+
var stepUp = viewMode === 'grid' ? 4 : 1;
|
|
745
|
+
focusedIndex = Math.max(0, focusedIndex - stepUp);
|
|
746
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
747
|
+
}
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Enter: Open folder or view file
|
|
752
|
+
if (e.key === 'Enter') {
|
|
753
|
+
if (items.length > 0 && focusedIndex < items.length) {
|
|
754
|
+
e.preventDefault();
|
|
755
|
+
var itm = items[focusedIndex];
|
|
756
|
+
if (itm.isDir) {
|
|
757
|
+
loadDirectory(itm.path);
|
|
758
|
+
focusedIndex = 0;
|
|
759
|
+
} else if (isPickerMode && onSelectCallback) {
|
|
760
|
+
onSelectCallback(itm.publicUrl, itm);
|
|
761
|
+
} else {
|
|
762
|
+
selectedItem = itm;
|
|
763
|
+
modalState = 'preview';
|
|
764
|
+
}
|
|
765
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
766
|
+
}
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Parent folder: Alt+Up or Cmd/Ctrl+Up
|
|
771
|
+
if ((e.altKey && e.key === 'ArrowUp') || ((e.metaKey || e.ctrlKey) && e.key === 'ArrowUp')) {
|
|
772
|
+
if (breadcrumbs.length > 1) {
|
|
773
|
+
e.preventDefault();
|
|
774
|
+
var parentBc = breadcrumbs[breadcrumbs.length - 2];
|
|
775
|
+
loadDirectory(parentBc.path);
|
|
776
|
+
focusedIndex = 0;
|
|
777
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
778
|
+
}
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Toggle selection with 'x'
|
|
783
|
+
if ((e.key === 'x' || e.key === 'X') && !e.ctrlKey && !e.metaKey) {
|
|
784
|
+
if (items.length > 0 && focusedIndex < items.length) {
|
|
785
|
+
e.preventDefault();
|
|
786
|
+
var toggleItem = items[focusedIndex];
|
|
787
|
+
if (selectedPaths[toggleItem.path]) {
|
|
788
|
+
delete selectedPaths[toggleItem.path];
|
|
789
|
+
} else {
|
|
790
|
+
selectedPaths[toggleItem.path] = toggleItem;
|
|
791
|
+
}
|
|
792
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
793
|
+
}
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Quick edit with 'e'
|
|
798
|
+
if ((e.key === 'e' || e.key === 'E') && !e.ctrlKey && !e.metaKey) {
|
|
799
|
+
if (items.length > 0 && focusedIndex < items.length) {
|
|
800
|
+
var editItm = items[focusedIndex];
|
|
801
|
+
if (isTextFile(editItm)) {
|
|
802
|
+
e.preventDefault();
|
|
803
|
+
openTextEditor(editItm);
|
|
804
|
+
} else if (editItm.type === 'image' && editItm.publicUrl && !editItm.name.endsWith('.svg')) {
|
|
805
|
+
e.preventDefault();
|
|
806
|
+
selectedItem = editItm;
|
|
807
|
+
openImageEditor(editItm);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Rename with 'r'
|
|
814
|
+
if ((e.key === 'r' || e.key === 'R') && !e.ctrlKey && !e.metaKey) {
|
|
815
|
+
if (items.length > 0 && focusedIndex < items.length) {
|
|
816
|
+
e.preventDefault();
|
|
817
|
+
selectedItem = items[focusedIndex];
|
|
818
|
+
inputRenameValue = selectedItem.name;
|
|
819
|
+
modalState = 'rename';
|
|
820
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
821
|
+
}
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Delete with Delete or Cmd+Backspace
|
|
826
|
+
if (e.key === 'Delete' || (e.key === 'Backspace' && (e.metaKey || e.ctrlKey))) {
|
|
827
|
+
e.preventDefault();
|
|
828
|
+
var selPaths = getSelectedPathsList();
|
|
829
|
+
if (selPaths.length > 0) {
|
|
830
|
+
deleteBatchSelected();
|
|
831
|
+
} else if (items.length > 0 && focusedIndex < items.length) {
|
|
832
|
+
selectedItem = items[focusedIndex];
|
|
833
|
+
modalState = 'delete';
|
|
834
|
+
}
|
|
835
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function openImageEditor(item) {
|
|
841
|
+
if (!item || item.type !== 'image' || !item.publicUrl) return;
|
|
842
|
+
editMode = true;
|
|
843
|
+
editorState.loaded = false;
|
|
844
|
+
editorState.rotation = 0;
|
|
845
|
+
editorState.flipH = false;
|
|
846
|
+
editorState.flipV = false;
|
|
847
|
+
editorState.aspectRatio = 'free';
|
|
848
|
+
editorState.cropX = 0;
|
|
849
|
+
editorState.cropY = 0;
|
|
850
|
+
editorState.cropW = 100;
|
|
851
|
+
editorState.cropH = 100;
|
|
852
|
+
editorState.saving = false;
|
|
853
|
+
editorState.saveStatus = '';
|
|
854
|
+
editorState.format = 'original';
|
|
855
|
+
|
|
856
|
+
var img = new Image();
|
|
857
|
+
img.crossOrigin = 'anonymous';
|
|
858
|
+
img.onload = function() {
|
|
859
|
+
editorState.loaded = true;
|
|
860
|
+
editorState.naturalWidth = img.naturalWidth;
|
|
861
|
+
editorState.naturalHeight = img.naturalHeight;
|
|
862
|
+
editorState.targetWidth = img.naturalWidth;
|
|
863
|
+
editorState.targetHeight = img.naturalHeight;
|
|
864
|
+
loadedImageMeta[item.path] = { width: img.naturalWidth, height: img.naturalHeight };
|
|
865
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
866
|
+
};
|
|
867
|
+
img.onerror = function() {
|
|
868
|
+
alert('Could not load image for editing.');
|
|
869
|
+
editMode = false;
|
|
870
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
871
|
+
};
|
|
872
|
+
img.src = item.publicUrl;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function setAspectRatio(ratio) {
|
|
876
|
+
editorState.aspectRatio = ratio;
|
|
877
|
+
if (ratio === '1:1') {
|
|
878
|
+
var minSide = Math.min(editorState.cropW, editorState.cropH);
|
|
879
|
+
editorState.cropW = minSide;
|
|
880
|
+
editorState.cropH = minSide;
|
|
881
|
+
} else if (ratio === '16:9') {
|
|
882
|
+
editorState.cropW = 100;
|
|
883
|
+
editorState.cropH = Math.min(100, Math.round((100 * (editorState.naturalWidth * 9)) / (editorState.naturalHeight * 16)));
|
|
884
|
+
} else if (ratio === '4:3') {
|
|
885
|
+
editorState.cropW = 100;
|
|
886
|
+
editorState.cropH = Math.min(100, Math.round((100 * (editorState.naturalWidth * 3)) / (editorState.naturalHeight * 4)));
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function saveTransformedImage(overwrite) {
|
|
891
|
+
if (!selectedItem || editorState.saving) return;
|
|
892
|
+
editorState.saving = true;
|
|
893
|
+
editorState.saveStatus = 'Applying transformationsβ¦';
|
|
894
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
895
|
+
|
|
896
|
+
var img = new Image();
|
|
897
|
+
img.crossOrigin = 'anonymous';
|
|
898
|
+
img.onload = function() {
|
|
899
|
+
try {
|
|
900
|
+
var canvas = document.createElement('canvas');
|
|
901
|
+
var ctx = canvas.getContext('2d');
|
|
902
|
+
|
|
903
|
+
var sx = (editorState.cropX / 100) * img.naturalWidth;
|
|
904
|
+
var sy = (editorState.cropY / 100) * img.naturalHeight;
|
|
905
|
+
var sw = (editorState.cropW / 100) * img.naturalWidth;
|
|
906
|
+
var sh = (editorState.cropH / 100) * img.naturalHeight;
|
|
907
|
+
|
|
908
|
+
var isRotated90 = (editorState.rotation % 180 !== 0);
|
|
909
|
+
var outW = editorState.targetWidth || Math.round(sw);
|
|
910
|
+
var outH = editorState.targetHeight || Math.round(sh);
|
|
911
|
+
|
|
912
|
+
canvas.width = isRotated90 ? outH : outW;
|
|
913
|
+
canvas.height = isRotated90 ? outW : outH;
|
|
914
|
+
|
|
915
|
+
ctx.save();
|
|
916
|
+
ctx.translate(canvas.width / 2, canvas.height / 2);
|
|
917
|
+
ctx.rotate((editorState.rotation * Math.PI) / 180);
|
|
918
|
+
ctx.scale(editorState.flipH ? -1 : 1, editorState.flipV ? -1 : 1);
|
|
919
|
+
|
|
920
|
+
var drawW = isRotated90 ? canvas.height : canvas.width;
|
|
921
|
+
var drawH = isRotated90 ? canvas.width : canvas.height;
|
|
922
|
+
ctx.drawImage(img, sx, sy, sw, sh, -drawW / 2, -drawH / 2, drawW, drawH);
|
|
923
|
+
ctx.restore();
|
|
924
|
+
|
|
925
|
+
var mime = 'image/png';
|
|
926
|
+
if (editorState.format && editorState.format !== 'original') {
|
|
927
|
+
mime = editorState.format;
|
|
928
|
+
} else if (selectedItem.ext === 'jpg' || selectedItem.ext === 'jpeg') {
|
|
929
|
+
mime = 'image/jpeg';
|
|
930
|
+
} else if (selectedItem.ext === 'webp') {
|
|
931
|
+
mime = 'image/webp';
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
var dataUrl = canvas.toDataURL(mime, 0.92);
|
|
935
|
+
|
|
936
|
+
api.post(API + '/save-image', {
|
|
937
|
+
path: selectedItem.path,
|
|
938
|
+
dataUrl: dataUrl,
|
|
939
|
+
overwrite: !!overwrite
|
|
940
|
+
}).then(function(res) {
|
|
941
|
+
editorState.saving = false;
|
|
942
|
+
editorState.saveStatus = '';
|
|
943
|
+
editMode = false;
|
|
944
|
+
if (res && res.file) {
|
|
945
|
+
selectedItem = res.file;
|
|
946
|
+
}
|
|
947
|
+
loadDirectory();
|
|
948
|
+
}).catch(function(err) {
|
|
949
|
+
editorState.saving = false;
|
|
950
|
+
editorState.saveStatus = '';
|
|
951
|
+
alert('Failed to save image: ' + (err.message || String(err)));
|
|
952
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
953
|
+
});
|
|
954
|
+
} catch (e) {
|
|
955
|
+
editorState.saving = false;
|
|
956
|
+
editorState.saveStatus = '';
|
|
957
|
+
alert('Canvas processing error: ' + (e.message || String(e)));
|
|
958
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
img.onerror = function() {
|
|
962
|
+
editorState.saving = false;
|
|
963
|
+
editorState.saveStatus = '';
|
|
964
|
+
alert('Could not reload image for canvas export.');
|
|
965
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
966
|
+
};
|
|
967
|
+
img.src = selectedItem.publicUrl + (selectedItem.publicUrl.indexOf('?') === -1 ? '?t=' : '&t=') + Date.now();
|
|
197
968
|
}
|
|
198
969
|
|
|
199
970
|
return {
|
|
200
971
|
oninit: function() {
|
|
972
|
+
if (typeof window !== 'undefined') {
|
|
973
|
+
window.addEventListener('keydown', handleGlobalKeydown);
|
|
974
|
+
}
|
|
201
975
|
if (isPickerMode && pickerAccept) {
|
|
202
976
|
if (pickerAccept.indexOf('image') !== -1) typeFilter = 'image';
|
|
203
977
|
else if (pickerAccept.indexOf('video') !== -1) typeFilter = 'video';
|
|
@@ -207,33 +981,49 @@ function generateFileManagerComponent(options = {}) {
|
|
|
207
981
|
loadDirectory('');
|
|
208
982
|
},
|
|
209
983
|
|
|
984
|
+
onremove: function() {
|
|
985
|
+
if (typeof window !== 'undefined') {
|
|
986
|
+
window.removeEventListener('keydown', handleGlobalKeydown);
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
|
|
210
990
|
view: function() {
|
|
211
991
|
return m('div.space-y-4', [
|
|
212
992
|
// Toolbar
|
|
213
993
|
m('div.flex.flex-col.md:flex-row.md:items-center.justify-between.gap-3.bg-white.dark:bg-slate-800.p-4.rounded-xl.border.border-gray-200.dark:border-slate-700.shadow-sm', [
|
|
214
|
-
// Left: Breadcrumbs
|
|
215
|
-
m('div.flex.items-center.gap-
|
|
216
|
-
m('
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
994
|
+
// Left: Breadcrumbs & Select All
|
|
995
|
+
m('div.flex.items-center.gap-3.flex-wrap.text-sm', [
|
|
996
|
+
!loading && !error && items.length > 0 ? m('label.flex.items-center.gap-1.5.cursor-pointer.px-2.5.py-1.rounded-lg.border.border-gray-200.dark:border-slate-700.bg-gray-50.dark:bg-slate-900.text-xs.font-medium.text-gray-700.dark:text-slate-300.hover:bg-gray-100.dark:hover:bg-slate-800.transition.select-none', [
|
|
997
|
+
m('input[type=checkbox].rounded.border-gray-300.text-indigo-600.focus:ring-indigo-500.cursor-pointer', {
|
|
998
|
+
checked: isAllSelected(),
|
|
999
|
+
onchange: toggleSelectAll
|
|
1000
|
+
}),
|
|
1001
|
+
m('span', isAllSelected() ? 'Unselect All' : 'Select All')
|
|
1002
|
+
]) : null,
|
|
1003
|
+
m('div.flex.items-center.gap-2.flex-wrap', [
|
|
1004
|
+
m('span.text-gray-400.dark:text-slate-500', 'π'),
|
|
1005
|
+
breadcrumbs.map(function(bc, idx) {
|
|
1006
|
+
var isLast = idx === breadcrumbs.length - 1;
|
|
1007
|
+
return m('span.flex.items-center.gap-2', [
|
|
1008
|
+
isLast
|
|
1009
|
+
? m('span.font-semibold.text-gray-900.dark:text-slate-100', bc.name)
|
|
1010
|
+
: m('button.text-indigo-600.dark:text-indigo-400.hover:underline', {
|
|
1011
|
+
onclick: function() { loadDirectory(bc.path); }
|
|
1012
|
+
}, bc.name),
|
|
1013
|
+
!isLast ? m('span.text-gray-400.dark:text-slate-600', '/') : null
|
|
1014
|
+
]);
|
|
1015
|
+
})
|
|
1016
|
+
])
|
|
228
1017
|
]),
|
|
229
1018
|
|
|
230
1019
|
// Right: Actions & Tools
|
|
231
1020
|
m('div.flex.items-center.gap-2.flex-wrap', [
|
|
232
1021
|
// Search input
|
|
233
1022
|
m('div.relative', [
|
|
234
|
-
m('input.text-xs.pl-8.pr-3.py-1.5.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-gray-50.dark:bg-slate-900.text-gray-900.dark:text-slate-100.placeholder-gray-400', {
|
|
1023
|
+
m('input#fm-search-input.text-xs.pl-8.pr-3.py-1.5.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-gray-50.dark:bg-slate-900.text-gray-900.dark:text-slate-100.placeholder-gray-400', {
|
|
1024
|
+
id: 'fm-search-input',
|
|
235
1025
|
type: 'text',
|
|
236
|
-
placeholder: 'Search filesβ¦',
|
|
1026
|
+
placeholder: 'Search files⦠(/ to focus)',
|
|
237
1027
|
value: searchQuery,
|
|
238
1028
|
oninput: function(e) { searchQuery = e.target.value; },
|
|
239
1029
|
onkeydown: function(e) { if (e.key === 'Enter') loadDirectory(); }
|
|
@@ -285,6 +1075,15 @@ function generateFileManagerComponent(options = {}) {
|
|
|
285
1075
|
})
|
|
286
1076
|
]),
|
|
287
1077
|
|
|
1078
|
+
// Shortcuts Cheatsheet
|
|
1079
|
+
m('button.px-2.5.py-1.5.text-xs.font-medium.bg-gray-100.dark:bg-slate-700.hover:bg-gray-200.dark:hover:bg-slate-600.text-gray-700.dark:text-slate-300.rounded-lg.flex.items-center.gap-1.transition', {
|
|
1080
|
+
title: 'Keyboard Shortcuts (Press ? or h)',
|
|
1081
|
+
onclick: function() { showShortcutsModal = true; }
|
|
1082
|
+
}, [
|
|
1083
|
+
m('span', 'β¨οΈ'),
|
|
1084
|
+
m('span.hidden.sm:inline', 'Shortcuts')
|
|
1085
|
+
]),
|
|
1086
|
+
|
|
288
1087
|
// Refresh
|
|
289
1088
|
m('button.p-1.5.text-xs.text-gray-500.hover:text-gray-900.dark:hover:text-slate-100.rounded-lg', {
|
|
290
1089
|
title: 'Refresh',
|
|
@@ -293,23 +1092,163 @@ function generateFileManagerComponent(options = {}) {
|
|
|
293
1092
|
])
|
|
294
1093
|
]),
|
|
295
1094
|
|
|
296
|
-
//
|
|
297
|
-
uploading ? m('div.p-
|
|
298
|
-
m('
|
|
299
|
-
|
|
1095
|
+
// Upload Progress Banner / Card
|
|
1096
|
+
uploading ? m('div.p-4.bg-white.dark:bg-slate-800.border.border-indigo-200.dark:border-indigo-800.rounded-xl.shadow-sm.flex.flex-col.gap-2.animate-in.fade-in', [
|
|
1097
|
+
m('div.flex.items-center.justify-between.text-xs', [
|
|
1098
|
+
m('div.flex.items-center.gap-2', [
|
|
1099
|
+
m('span.w-2.h-2.rounded-full.bg-indigo-600.animate-ping'),
|
|
1100
|
+
m('span.font-semibold.text-gray-900.dark:text-slate-100', uploadStatus || 'Uploading filesβ¦'),
|
|
1101
|
+
]),
|
|
1102
|
+
m('div.flex.items-center.gap-3', [
|
|
1103
|
+
m('span.font-mono.font-bold.text-indigo-600.dark:text-indigo-400', uploadProgress + '%'),
|
|
1104
|
+
m('button.text-xs.text-red-500.hover:text-red-700.dark:hover:text-red-400.font-medium.px-2.py-0.5.rounded.border.border-red-200.dark:border-red-900/40.hover:bg-red-50.dark:hover:bg-red-950/30.transition', {
|
|
1105
|
+
onclick: cancelUpload
|
|
1106
|
+
}, 'Cancel')
|
|
1107
|
+
])
|
|
1108
|
+
]),
|
|
1109
|
+
m('div.w-full.h-2.bg-gray-100.dark:bg-slate-700.rounded-full.overflow-hidden', [
|
|
1110
|
+
m('div.h-full.bg-gradient-to-r.from-indigo-500.to-violet-600.rounded-full.transition-all.duration-200', {
|
|
1111
|
+
style: { width: uploadProgress + '%' }
|
|
1112
|
+
})
|
|
1113
|
+
]),
|
|
1114
|
+
uploadTotalBytes > 0 ? m('div.flex.items-center.justify-between.text-[11px].text-gray-400.dark:text-slate-400', [
|
|
1115
|
+
m('span', formatBytes(uploadLoadedBytes) + ' uploaded'),
|
|
1116
|
+
m('span', 'Total ' + formatBytes(uploadTotalBytes))
|
|
1117
|
+
]) : null
|
|
1118
|
+
]) : null,
|
|
1119
|
+
|
|
1120
|
+
// Upload Success Banner
|
|
1121
|
+
uploadSuccessMessage ? m('div.p-3.bg-emerald-50.dark:bg-emerald-950/40.border.border-emerald-200.dark:border-emerald-800.rounded-xl.flex.items-center.justify-between.text-xs.text-emerald-800.dark:text-emerald-200.shadow-sm.animate-in.fade-in', [
|
|
1122
|
+
m('div.flex.items-center.gap-2', [
|
|
1123
|
+
m('span.text-base', 'β
'),
|
|
1124
|
+
m('span.font-medium', uploadSuccessMessage)
|
|
1125
|
+
]),
|
|
1126
|
+
m('button.text-emerald-600.dark:text-emerald-400.hover:text-emerald-900.font-bold.px-1', {
|
|
1127
|
+
onclick: function() { uploadSuccessMessage = ''; }
|
|
1128
|
+
}, 'β')
|
|
1129
|
+
]) : null,
|
|
1130
|
+
|
|
1131
|
+
// STORAGE USAGE & QUOTA VISUALIZER (OPTION 6)
|
|
1132
|
+
storageStats ? m('div.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.rounded-xl.p-3.5.shadow-sm.space-y-2.5', [
|
|
1133
|
+
// Top row: Usage stats & Controls
|
|
1134
|
+
m('div.flex.flex-col.sm:flex-row.sm:items-center.justify-between.gap-2.text-xs', [
|
|
1135
|
+
m('div.flex.items-center.gap-2.flex-wrap', [
|
|
1136
|
+
m('span.text-base', 'πΎ'),
|
|
1137
|
+
m('span.font-semibold.text-gray-900.dark:text-slate-100', 'Storage:'),
|
|
1138
|
+
m('span.font-medium.text-gray-700.dark:text-slate-300', storageStats.formattedTotal + ' of ' + storageStats.formattedQuota),
|
|
1139
|
+
m('span.px-2.py-0.5.rounded-full.text-[11px].font-semibold', {
|
|
1140
|
+
class: storageStats.isOverLimit
|
|
1141
|
+
? 'bg-red-100 dark:bg-red-950/60 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-800'
|
|
1142
|
+
: storageStats.isNearLimit
|
|
1143
|
+
? 'bg-amber-100 dark:bg-amber-950/60 text-amber-800 dark:text-amber-300 border border-amber-200 dark:border-amber-800'
|
|
1144
|
+
: 'bg-emerald-50 dark:bg-emerald-950/50 text-emerald-700 dark:text-emerald-300 border border-emerald-200 dark:border-emerald-800'
|
|
1145
|
+
}, storageStats.usagePercent + '% used' + (storageStats.isOverLimit ? ' (Over Quota!)' : storageStats.isNearLimit ? ' (Near Quota)' : '')),
|
|
1146
|
+
]),
|
|
1147
|
+
|
|
1148
|
+
m('div.flex.items-center.gap-3.text-gray-500.dark:text-slate-400', [
|
|
1149
|
+
m('span', storageStats.fileCount + ' files Β· ' + storageStats.folderCount + ' folders'),
|
|
1150
|
+
m('button.text-indigo-600.dark:text-indigo-400.hover:underline.font-medium.flex.items-center.gap-1', {
|
|
1151
|
+
onclick: function() { showStorageDetails = !showStorageDetails; }
|
|
1152
|
+
}, [
|
|
1153
|
+
m('span', showStorageDetails ? 'Hide Breakdown' : 'Breakdown'),
|
|
1154
|
+
m('span.text-[10px]', showStorageDetails ? 'β²' : 'βΌ')
|
|
1155
|
+
]),
|
|
1156
|
+
m('button.p-1.text-gray-400.hover:text-gray-700.dark:hover:text-slate-200.rounded', {
|
|
1157
|
+
title: 'Refresh Storage Stats',
|
|
1158
|
+
onclick: function() { loadStorageStats(true); }
|
|
1159
|
+
}, 'π')
|
|
1160
|
+
])
|
|
1161
|
+
]),
|
|
1162
|
+
|
|
1163
|
+
// Segmented Progress Bar
|
|
1164
|
+
m('div.w-full.h-2.5.bg-gray-100.dark:bg-slate-700/60.rounded-full.overflow-hidden.flex.shadow-inner', [
|
|
1165
|
+
storageStats.categories.images && storageStats.categories.images.bytes > 0
|
|
1166
|
+
? m('div.h-full.bg-emerald-500.transition-all', {
|
|
1167
|
+
style: { width: Math.max(1, storageStats.categories.images.percent) + '%' },
|
|
1168
|
+
title: 'Images: ' + storageStats.categories.images.formattedBytes + ' (' + storageStats.categories.images.percent + '%)'
|
|
1169
|
+
}) : null,
|
|
1170
|
+
storageStats.categories.documents && storageStats.categories.documents.bytes > 0
|
|
1171
|
+
? m('div.h-full.bg-indigo-500.transition-all', {
|
|
1172
|
+
style: { width: Math.max(1, storageStats.categories.documents.percent) + '%' },
|
|
1173
|
+
title: 'Documents: ' + storageStats.categories.documents.formattedBytes + ' (' + storageStats.categories.documents.percent + '%)'
|
|
1174
|
+
}) : null,
|
|
1175
|
+
storageStats.categories.code && storageStats.categories.code.bytes > 0
|
|
1176
|
+
? m('div.h-full.bg-amber-500.transition-all', {
|
|
1177
|
+
style: { width: Math.max(1, storageStats.categories.code.percent) + '%' },
|
|
1178
|
+
title: 'Code & Text: ' + storageStats.categories.code.formattedBytes + ' (' + storageStats.categories.code.percent + '%)'
|
|
1179
|
+
}) : null,
|
|
1180
|
+
storageStats.categories.media && storageStats.categories.media.bytes > 0
|
|
1181
|
+
? m('div.h-full.bg-pink-500.transition-all', {
|
|
1182
|
+
style: { width: Math.max(1, storageStats.categories.media.percent) + '%' },
|
|
1183
|
+
title: 'Media: ' + storageStats.categories.media.formattedBytes + ' (' + storageStats.categories.media.percent + '%)'
|
|
1184
|
+
}) : null,
|
|
1185
|
+
storageStats.categories.archives && storageStats.categories.archives.bytes > 0
|
|
1186
|
+
? m('div.h-full.bg-purple-500.transition-all', {
|
|
1187
|
+
style: { width: Math.max(1, storageStats.categories.archives.percent) + '%' },
|
|
1188
|
+
title: 'Archives: ' + storageStats.categories.archives.formattedBytes + ' (' + storageStats.categories.archives.percent + '%)'
|
|
1189
|
+
}) : null,
|
|
1190
|
+
storageStats.categories.other && storageStats.categories.other.bytes > 0
|
|
1191
|
+
? m('div.h-full.bg-slate-400.transition-all', {
|
|
1192
|
+
style: { width: Math.max(1, storageStats.categories.other.percent) + '%' },
|
|
1193
|
+
title: 'Other: ' + storageStats.categories.other.formattedBytes + ' (' + storageStats.categories.other.percent + '%)'
|
|
1194
|
+
}) : null
|
|
1195
|
+
]),
|
|
1196
|
+
|
|
1197
|
+
// Expanded Breakdown Details
|
|
1198
|
+
showStorageDetails ? m('div.pt-2.border-t.border-gray-100.dark:border-slate-700/60.grid.grid-cols-2.sm:grid-cols-3.md:grid-cols-6.gap-2.text-xs.animate-in.fade-in', [
|
|
1199
|
+
renderCategoryCard('Images', 'πΌοΈ', storageStats.categories.images, 'text-emerald-600 dark:text-emerald-400', 'bg-emerald-500'),
|
|
1200
|
+
renderCategoryCard('Documents', 'π', storageStats.categories.documents, 'text-indigo-600 dark:text-indigo-400', 'bg-indigo-500'),
|
|
1201
|
+
renderCategoryCard('Code & Text', 'π', storageStats.categories.code, 'text-amber-600 dark:text-amber-400', 'bg-amber-500'),
|
|
1202
|
+
renderCategoryCard('Media', 'π¬', storageStats.categories.media, 'text-pink-600 dark:text-pink-400', 'bg-pink-500'),
|
|
1203
|
+
renderCategoryCard('Archives', 'π¦', storageStats.categories.archives, 'text-purple-600 dark:text-purple-400', 'bg-purple-500'),
|
|
1204
|
+
renderCategoryCard('Other', 'ποΈ', storageStats.categories.other, 'text-slate-600 dark:text-slate-400', 'bg-slate-400'),
|
|
1205
|
+
]) : null
|
|
300
1206
|
]) : null,
|
|
301
1207
|
|
|
302
1208
|
// Main Content / Drop Zone
|
|
303
|
-
m('div.min-h-[
|
|
304
|
-
|
|
1209
|
+
m('div.min-h-[420px].relative', {
|
|
1210
|
+
ondragenter: function(e) {
|
|
1211
|
+
e.preventDefault();
|
|
1212
|
+
e.stopPropagation();
|
|
1213
|
+
dragCounter++;
|
|
1214
|
+
isDraggingOver = true;
|
|
1215
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
1216
|
+
},
|
|
1217
|
+
ondragleave: function(e) {
|
|
1218
|
+
e.preventDefault();
|
|
1219
|
+
e.stopPropagation();
|
|
1220
|
+
dragCounter--;
|
|
1221
|
+
if (dragCounter <= 0) {
|
|
1222
|
+
dragCounter = 0;
|
|
1223
|
+
isDraggingOver = false;
|
|
1224
|
+
}
|
|
1225
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
1226
|
+
},
|
|
1227
|
+
ondragover: function(e) {
|
|
1228
|
+
e.preventDefault();
|
|
1229
|
+
e.stopPropagation();
|
|
1230
|
+
if (e.dataTransfer) {
|
|
1231
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
1232
|
+
}
|
|
1233
|
+
},
|
|
305
1234
|
ondrop: function(e) {
|
|
306
1235
|
e.preventDefault();
|
|
307
1236
|
e.stopPropagation();
|
|
308
|
-
|
|
1237
|
+
dragCounter = 0;
|
|
1238
|
+
isDraggingOver = false;
|
|
1239
|
+
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
309
1240
|
handleFileUpload(e.dataTransfer.files);
|
|
310
1241
|
}
|
|
1242
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
311
1243
|
}
|
|
312
1244
|
}, [
|
|
1245
|
+
// FULL SURFACE DROPZONE OVERLAY
|
|
1246
|
+
isDraggingOver ? m('div.absolute.inset-0.z-30.bg-indigo-600/15.dark:bg-indigo-500/25.backdrop-blur-sm.border-2.border-dashed.border-indigo-500.rounded-2xl.flex.flex-col.items-center.justify-center.p-8.pointer-events-none.transition-all', [
|
|
1247
|
+
m('div.w-20.h-20.rounded-2xl.bg-indigo-600.text-white.flex.items-center.justify-center.text-3xl.shadow-xl.shadow-indigo-500/30.mb-4.animate-bounce', 'π₯'),
|
|
1248
|
+
m('h3.text-lg.font-bold.text-indigo-950.dark:text-indigo-100', 'Drop files here to upload'),
|
|
1249
|
+
m('p.text-sm.text-indigo-700.dark:text-indigo-300.mt-1', 'Target Directory: ' + (currentPath ? '/' + currentPath : '/ (Root)')),
|
|
1250
|
+
m('div.mt-4.px-3.py-1.rounded-full.bg-white/80.dark:bg-slate-900/80.text-xs.font-semibold.text-indigo-600.dark:text-indigo-400.shadow-sm', 'Release to automatically upload')
|
|
1251
|
+
]) : null,
|
|
313
1252
|
loading ? m('div.flex.flex-col.items-center.justify-center.py-20.text-gray-400', [
|
|
314
1253
|
m('div.w-8.h-8.border-2.border-indigo-600.border-t-transparent.rounded-full.animate-spin.mb-2'),
|
|
315
1254
|
m('p.text-xs', 'Loading filesβ¦')
|
|
@@ -324,10 +1263,14 @@ function generateFileManagerComponent(options = {}) {
|
|
|
324
1263
|
]) : null,
|
|
325
1264
|
|
|
326
1265
|
// GRID VIEW
|
|
327
|
-
!loading && !error && items.length > 0 && viewMode === 'grid' ? m('div.grid.grid-cols-2.sm:grid-cols-3.md:grid-cols-4.lg:grid-cols-6.gap-4', items.map(function(item) {
|
|
1266
|
+
!loading && !error && items.length > 0 && viewMode === 'grid' ? m('div.grid.grid-cols-2.sm:grid-cols-3.md:grid-cols-4.lg:grid-cols-6.gap-4', items.map(function(item, idx) {
|
|
328
1267
|
var isImg = item.type === 'image' && item.publicUrl;
|
|
329
|
-
|
|
1268
|
+
var selected = isSelected(item);
|
|
1269
|
+
var isFocused = (focusedIndex === idx);
|
|
1270
|
+
return m('div.group.relative.flex.flex-col.bg-white.dark:bg-slate-800.border.rounded-xl.p-3.hover:shadow-md.transition.cursor-pointer', {
|
|
1271
|
+
class: (selected ? 'border-indigo-500 ring-2 ring-indigo-500/50 bg-indigo-50/40 dark:bg-indigo-950/20 ' : 'border-gray-200 dark:border-slate-700 ') + (isFocused ? 'ring-2 ring-offset-2 ring-indigo-600 dark:ring-offset-slate-900 shadow-md ' : ''),
|
|
330
1272
|
onclick: function() {
|
|
1273
|
+
focusedIndex = idx;
|
|
331
1274
|
if (item.isDir) {
|
|
332
1275
|
loadDirectory(item.path);
|
|
333
1276
|
} else if (isPickerMode && onSelectCallback) {
|
|
@@ -338,6 +1281,15 @@ function generateFileManagerComponent(options = {}) {
|
|
|
338
1281
|
}
|
|
339
1282
|
}
|
|
340
1283
|
}, [
|
|
1284
|
+
// Selection Checkbox
|
|
1285
|
+
m('div.absolute.top-2.left-2.z-10', [
|
|
1286
|
+
m('input[type=checkbox].rounded.border-gray-300.text-indigo-600.focus:ring-indigo-500.cursor-pointer.w-4.h-4.shadow-sm', {
|
|
1287
|
+
checked: selected,
|
|
1288
|
+
onclick: function(e) { if (e && e.stopPropagation) e.stopPropagation(); },
|
|
1289
|
+
onchange: function(e) { toggleSelect(item, e); }
|
|
1290
|
+
})
|
|
1291
|
+
]),
|
|
1292
|
+
|
|
341
1293
|
// Thumbnail / Icon
|
|
342
1294
|
m('div.aspect-square.w-full.rounded-lg.bg-gray-50.dark:bg-slate-900.flex.items-center.justify-center.overflow-hidden.mb-2.relative', [
|
|
343
1295
|
isImg
|
|
@@ -352,6 +1304,21 @@ function generateFileManagerComponent(options = {}) {
|
|
|
352
1304
|
m('span', item.isDir ? 'Folder' : formatBytes(item.size)),
|
|
353
1305
|
// Menu dots
|
|
354
1306
|
m('div.relative.opacity-0.group-hover:opacity-100.transition', [
|
|
1307
|
+
isTextFile(item) ? m('button.hover:text-indigo-600.px-1', {
|
|
1308
|
+
title: 'Edit Code / Text',
|
|
1309
|
+
onclick: function(e) {
|
|
1310
|
+
e.stopPropagation();
|
|
1311
|
+
openTextEditor(item);
|
|
1312
|
+
}
|
|
1313
|
+
}, 'π') : null,
|
|
1314
|
+
item.type === 'image' && item.publicUrl && !item.name.endsWith('.svg') ? m('button.hover:text-indigo-600.px-1', {
|
|
1315
|
+
title: 'Edit Image',
|
|
1316
|
+
onclick: function(e) {
|
|
1317
|
+
e.stopPropagation();
|
|
1318
|
+
selectedItem = item;
|
|
1319
|
+
openImageEditor(item);
|
|
1320
|
+
}
|
|
1321
|
+
}, 'βοΈ') : null,
|
|
355
1322
|
m('button.hover:text-gray-700.dark:hover:text-slate-200.px-1', {
|
|
356
1323
|
onclick: function(e) {
|
|
357
1324
|
e.stopPropagation();
|
|
@@ -377,6 +1344,12 @@ function generateFileManagerComponent(options = {}) {
|
|
|
377
1344
|
m('table.min-w-full.text-left.text-xs', [
|
|
378
1345
|
m('thead.bg-gray-50.dark:bg-slate-900.border-b.border-gray-200.dark:border-slate-700.text-gray-500', [
|
|
379
1346
|
m('tr', [
|
|
1347
|
+
m('th.p-3.w-8', [
|
|
1348
|
+
m('input[type=checkbox].rounded.border-gray-300.text-indigo-600.focus:ring-indigo-500.cursor-pointer', {
|
|
1349
|
+
checked: isAllSelected(),
|
|
1350
|
+
onchange: toggleSelectAll
|
|
1351
|
+
})
|
|
1352
|
+
]),
|
|
380
1353
|
m('th.p-3', 'Name'),
|
|
381
1354
|
m('th.p-3', 'Type'),
|
|
382
1355
|
m('th.p-3', 'Size'),
|
|
@@ -384,9 +1357,13 @@ function generateFileManagerComponent(options = {}) {
|
|
|
384
1357
|
m('th.p-3.text-right', 'Actions'),
|
|
385
1358
|
])
|
|
386
1359
|
]),
|
|
387
|
-
m('tbody.divide-y.divide-gray-100.dark:divide-slate-700/50', items.map(function(item) {
|
|
388
|
-
|
|
1360
|
+
m('tbody.divide-y.divide-gray-100.dark:divide-slate-700/50', items.map(function(item, idx) {
|
|
1361
|
+
var selected = isSelected(item);
|
|
1362
|
+
var isFocused = (focusedIndex === idx);
|
|
1363
|
+
return m('tr.transition.cursor-pointer', {
|
|
1364
|
+
class: (selected ? 'bg-indigo-50/70 dark:bg-indigo-950/40 ' : 'hover:bg-gray-50/80 dark:hover:bg-slate-700/30 ') + (isFocused ? 'ring-2 ring-inset ring-indigo-500 font-semibold bg-indigo-50/40 dark:bg-indigo-950/30 ' : ''),
|
|
389
1365
|
onclick: function() {
|
|
1366
|
+
focusedIndex = idx;
|
|
390
1367
|
if (item.isDir) {
|
|
391
1368
|
loadDirectory(item.path);
|
|
392
1369
|
} else if (isPickerMode && onSelectCallback) {
|
|
@@ -397,6 +1374,13 @@ function generateFileManagerComponent(options = {}) {
|
|
|
397
1374
|
}
|
|
398
1375
|
}
|
|
399
1376
|
}, [
|
|
1377
|
+
m('td.p-3.w-8', [
|
|
1378
|
+
m('input[type=checkbox].rounded.border-gray-300.text-indigo-600.focus:ring-indigo-500.cursor-pointer', {
|
|
1379
|
+
checked: selected,
|
|
1380
|
+
onclick: function(e) { if (e && e.stopPropagation) e.stopPropagation(); },
|
|
1381
|
+
onchange: function(e) { toggleSelect(item, e); }
|
|
1382
|
+
})
|
|
1383
|
+
]),
|
|
400
1384
|
m('td.p-3.flex.items-center.gap-2.font-medium.text-gray-900.dark:text-slate-100', [
|
|
401
1385
|
m('span', renderFileIcon(item)),
|
|
402
1386
|
m('span.truncate.max-w-xs.md:max-w-md', item.name)
|
|
@@ -405,6 +1389,19 @@ function generateFileManagerComponent(options = {}) {
|
|
|
405
1389
|
m('td.p-3.text-gray-500.dark:text-slate-400', item.isDir ? 'β' : formatBytes(item.size)),
|
|
406
1390
|
m('td.p-3.text-gray-500.dark:text-slate-400', formatDate(item.mtime)),
|
|
407
1391
|
m('td.p-3.text-right.space-x-2', [
|
|
1392
|
+
isTextFile(item) ? m('button.text-indigo-600.dark:text-indigo-400.hover:underline.font-medium', {
|
|
1393
|
+
onclick: function(e) {
|
|
1394
|
+
e.stopPropagation();
|
|
1395
|
+
openTextEditor(item);
|
|
1396
|
+
}
|
|
1397
|
+
}, 'Edit') : null,
|
|
1398
|
+
item.type === 'image' && item.publicUrl && !item.name.endsWith('.svg') ? m('button.text-indigo-600.dark:text-indigo-400.hover:underline.font-medium', {
|
|
1399
|
+
onclick: function(e) {
|
|
1400
|
+
e.stopPropagation();
|
|
1401
|
+
selectedItem = item;
|
|
1402
|
+
openImageEditor(item);
|
|
1403
|
+
}
|
|
1404
|
+
}, 'Edit') : null,
|
|
408
1405
|
!item.isDir && item.publicUrl ? m('button.text-indigo-600.dark:text-indigo-400.hover:underline', {
|
|
409
1406
|
onclick: function(e) {
|
|
410
1407
|
e.stopPropagation();
|
|
@@ -434,6 +1431,50 @@ function generateFileManagerComponent(options = {}) {
|
|
|
434
1431
|
]) : null
|
|
435
1432
|
]),
|
|
436
1433
|
|
|
1434
|
+
// STICKY FLOATING BULK ACTION BAR
|
|
1435
|
+
getSelectedPathsList().length > 0 ? m('div.fixed.bottom-6.left-1/2.-translate-x-1/2.z-50.bg-slate-900/95.dark:bg-slate-800/95.text-white.px-5.py-3.rounded-2xl.shadow-2xl.backdrop-blur-md.border.border-slate-700.flex.items-center.gap-4.animate-in.fade-in', [
|
|
1436
|
+
m('div.flex.items-center.gap-2.border-r.border-slate-700.pr-4', [
|
|
1437
|
+
m('span.w-2.h-2.rounded-full.bg-indigo-400.animate-ping'),
|
|
1438
|
+
m('span.text-xs.font-semibold', getSelectedPathsList().length + ' selected')
|
|
1439
|
+
]),
|
|
1440
|
+
|
|
1441
|
+
// Action Buttons
|
|
1442
|
+
m('div.flex.items-center.gap-2', [
|
|
1443
|
+
// Download Zip
|
|
1444
|
+
m('button.px-3.py-1.5.bg-indigo-600.hover:bg-indigo-500.text-white.text-xs.font-medium.rounded-lg.flex.items-center.gap-1.5.transition.shadow-sm', {
|
|
1445
|
+
disabled: batchActionLoading,
|
|
1446
|
+
onclick: downloadBatchZip
|
|
1447
|
+
}, [
|
|
1448
|
+
m('span', 'π¦'),
|
|
1449
|
+
m('span', 'Download ZIP')
|
|
1450
|
+
]),
|
|
1451
|
+
|
|
1452
|
+
// Move
|
|
1453
|
+
m('button.px-3.py-1.5.bg-slate-800.dark:bg-slate-700.hover:bg-slate-700.dark:hover:bg-slate-600.text-white.text-xs.font-medium.rounded-lg.flex.items-center.gap-1.5.transition', {
|
|
1454
|
+
disabled: batchActionLoading,
|
|
1455
|
+
onclick: moveBatchSelected
|
|
1456
|
+
}, [
|
|
1457
|
+
m('span', 'π'),
|
|
1458
|
+
m('span', 'Moveβ¦')
|
|
1459
|
+
]),
|
|
1460
|
+
|
|
1461
|
+
// Delete
|
|
1462
|
+
m('button.px-3.py-1.5.bg-red-600/90.hover:bg-red-600.text-white.text-xs.font-medium.rounded-lg.flex.items-center.gap-1.5.transition.shadow-sm', {
|
|
1463
|
+
disabled: batchActionLoading,
|
|
1464
|
+
onclick: deleteBatchSelected
|
|
1465
|
+
}, [
|
|
1466
|
+
m('span', 'ποΈ'),
|
|
1467
|
+
m('span', 'Delete (' + getSelectedPathsList().length + ')')
|
|
1468
|
+
]),
|
|
1469
|
+
|
|
1470
|
+
// Deselect
|
|
1471
|
+
m('button.p-1.5.text-slate-400.hover:text-white.rounded-lg.text-xs.transition.ml-1', {
|
|
1472
|
+
title: 'Clear Selection',
|
|
1473
|
+
onclick: clearSelection
|
|
1474
|
+
}, 'β')
|
|
1475
|
+
])
|
|
1476
|
+
]) : null,
|
|
1477
|
+
|
|
437
1478
|
// MODALS
|
|
438
1479
|
// 1. New Folder Modal
|
|
439
1480
|
modalState === 'newFolder' ? m('div.fixed.inset-0.bg-black/50.flex.items-center.justify-center.z-50.p-4', [
|
|
@@ -498,52 +1539,101 @@ function generateFileManagerComponent(options = {}) {
|
|
|
498
1539
|
]) : null,
|
|
499
1540
|
|
|
500
1541
|
// 4. Preview Modal
|
|
501
|
-
modalState === 'preview' && selectedItem ? m('div.fixed.inset-0.bg-black/60.flex.items-center.justify-center.z-50.p-4.backdrop-blur-sm', {
|
|
1542
|
+
modalState === 'preview' && selectedItem && !editMode ? m('div.fixed.inset-0.bg-black/60.flex.items-center.justify-center.z-50.p-4.backdrop-blur-sm', {
|
|
502
1543
|
onclick: function() { modalState = null; }
|
|
503
1544
|
}, [
|
|
504
|
-
m('div.bg-white.dark:bg-slate-800.rounded-2xl.shadow-2xl.max-w-
|
|
1545
|
+
m('div.bg-white.dark:bg-slate-800.rounded-2xl.shadow-2xl.w-full.max-w-3xl.max-h-[85vh].flex.flex-col.overflow-hidden.border.border-gray-200.dark:border-slate-700.transition-all', {
|
|
505
1546
|
onclick: function(e) { e.stopPropagation(); }
|
|
506
1547
|
}, [
|
|
507
1548
|
// Header
|
|
508
|
-
m('div.flex.items-center.justify-between.
|
|
509
|
-
m('
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
1549
|
+
m('div.flex.items-center.justify-between.px-5.py-3.5.border-b.border-gray-200.dark:border-slate-700.bg-gray-50/70.dark:bg-slate-800/80', [
|
|
1550
|
+
m('div.flex.items-center.gap-2.truncate.max-w-md', [
|
|
1551
|
+
m('span.text-xs.font-semibold.uppercase.px-2.py-0.5.rounded.bg-indigo-100.dark:bg-indigo-900/50.text-indigo-700.dark:text-indigo-300', selectedItem.ext || selectedItem.type),
|
|
1552
|
+
m('h3.text-sm.font-bold.text-gray-900.dark:text-slate-100.truncate', selectedItem.name),
|
|
1553
|
+
]),
|
|
1554
|
+
m('div.flex.items-center.gap-2', [
|
|
1555
|
+
isTextFile(selectedItem) ? m('button.px-3.py-1.5.text-xs.font-medium.rounded-lg.bg-indigo-50.dark:bg-indigo-950/40.text-indigo-600.dark:text-indigo-400.hover:bg-indigo-100.dark:hover:bg-indigo-900/60.border.border-indigo-200.dark:border-indigo-800.flex.items-center.gap-1.5.transition', {
|
|
1556
|
+
onclick: function() { openTextEditor(selectedItem); }
|
|
1557
|
+
}, [
|
|
1558
|
+
m('span', 'π'),
|
|
1559
|
+
m('span', 'Edit Text / Code')
|
|
1560
|
+
]) : null,
|
|
1561
|
+
selectedItem.type === 'image' && selectedItem.publicUrl && !selectedItem.name.endsWith('.svg') ? m('button.px-3.py-1.5.text-xs.font-medium.rounded-lg.bg-indigo-50.dark:bg-indigo-950/40.text-indigo-600.dark:text-indigo-400.hover:bg-indigo-100.dark:hover:bg-indigo-900/60.border.border-indigo-200.dark:border-indigo-800.flex.items-center.gap-1.5.transition', {
|
|
1562
|
+
onclick: function() { openImageEditor(selectedItem); }
|
|
1563
|
+
}, [
|
|
1564
|
+
m('span', 'βοΈ'),
|
|
1565
|
+
m('span', 'Edit Image')
|
|
1566
|
+
]) : null,
|
|
1567
|
+
m('button.text-gray-400.hover:text-gray-600.dark:hover:text-slate-200.text-lg.p-1.rounded-md', {
|
|
1568
|
+
onclick: function() { modalState = null; }
|
|
1569
|
+
}, 'β')
|
|
1570
|
+
])
|
|
513
1571
|
]),
|
|
514
1572
|
// Media Viewer Area
|
|
515
|
-
m('div.p-6.
|
|
1573
|
+
m('div.flex-1.p-6.flex.items-center.justify-center.min-h-[220px].max-h-[420px].overflow-hidden', {
|
|
1574
|
+
style: selectedItem.type === 'image' ? 'background-image: radial-gradient(rgba(148, 163, 184, 0.25) 1px, transparent 1px); background-size: 16px 16px; background-color: rgba(248, 250, 252, 0.6);' : ''
|
|
1575
|
+
}, [
|
|
516
1576
|
selectedItem.type === 'image' && selectedItem.publicUrl
|
|
517
|
-
? m('img.max-h-
|
|
1577
|
+
? m('img.max-h-[360px].max-w-full.w-auto.h-auto.rounded-lg.object-contain.shadow-sm.border.border-gray-200/50.dark:border-slate-700/50', {
|
|
1578
|
+
src: selectedItem.publicUrl,
|
|
1579
|
+
onload: function(e) {
|
|
1580
|
+
loadedImageMeta[selectedItem.path] = { width: e.target.naturalWidth, height: e.target.naturalHeight };
|
|
1581
|
+
if (typeof m !== 'undefined' && m.redraw) m.redraw();
|
|
1582
|
+
}
|
|
1583
|
+
})
|
|
518
1584
|
: (selectedItem.type === 'video' && selectedItem.publicUrl
|
|
519
|
-
? m('video.max-h-
|
|
1585
|
+
? m('video.max-h-[360px].max-w-full.rounded-lg', { src: selectedItem.publicUrl, controls: true })
|
|
520
1586
|
: (selectedItem.type === 'audio' && selectedItem.publicUrl
|
|
521
|
-
? m('audio.w-full', { src: selectedItem.publicUrl, controls: true })
|
|
522
|
-
:
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
1587
|
+
? m('audio.w-full.max-w-md', { src: selectedItem.publicUrl, controls: true })
|
|
1588
|
+
: (isTextFile(selectedItem)
|
|
1589
|
+
? m('div.flex.flex-col.items-center.justify-center.p-8.text-center', [
|
|
1590
|
+
m('div.w-16.h-16.rounded-2xl.bg-indigo-50.dark:bg-slate-700/60.flex.items-center.justify-center.mb-3', renderFileIcon(selectedItem)),
|
|
1591
|
+
m('p.text-sm.font-bold.text-gray-900.dark:text-slate-100', selectedItem.name),
|
|
1592
|
+
m('p.text-xs.text-gray-500.dark:text-slate-400.mt-1', 'Text / Configuration / Code File (' + (selectedItem.ext || 'text').toUpperCase() + ')'),
|
|
1593
|
+
m('button.mt-4.px-4.py-2.bg-indigo-600.hover:bg-indigo-700.text-white.text-xs.font-semibold.rounded-xl.shadow-sm.flex.items-center.gap-2.transition', {
|
|
1594
|
+
onclick: function() { openTextEditor(selectedItem); }
|
|
1595
|
+
}, [
|
|
1596
|
+
m('span', 'π'),
|
|
1597
|
+
m('span', 'Open in Code Editor')
|
|
1598
|
+
])
|
|
1599
|
+
])
|
|
1600
|
+
: m('div.text-center.p-8', [
|
|
1601
|
+
renderFileIcon(selectedItem),
|
|
1602
|
+
m('p.text-xs.text-gray-500.mt-2', 'Preview not available')
|
|
1603
|
+
]))))
|
|
526
1604
|
]),
|
|
527
1605
|
// File Info Pane
|
|
528
|
-
m('div.p-4.space-y-
|
|
529
|
-
m('div.grid.grid-cols-2.gap-2', [
|
|
530
|
-
m('p
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
1606
|
+
m('div.p-4.space-y-3.text-xs.text-gray-600.dark:text-slate-300.border-t.border-gray-200.dark:border-slate-700.bg-white.dark:bg-slate-800', [
|
|
1607
|
+
m('div.grid.grid-cols-2.md:grid-cols-4.gap-2.text-[11px]', [
|
|
1608
|
+
m('div.bg-gray-50.dark:bg-slate-900.p-2.rounded-lg.border.border-gray-100.dark:border-slate-700', [
|
|
1609
|
+
m('span.text-gray-400.block', 'File Size'),
|
|
1610
|
+
m('strong.text-gray-800.dark:text-slate-200', formatBytes(selectedItem.size))
|
|
1611
|
+
]),
|
|
1612
|
+
m('div.bg-gray-50.dark:bg-slate-900.p-2.rounded-lg.border.border-gray-100.dark:border-slate-700', [
|
|
1613
|
+
m('span.text-gray-400.block', 'Dimensions'),
|
|
1614
|
+
m('strong.text-gray-800.dark:text-slate-200', loadedImageMeta[selectedItem.path] ? loadedImageMeta[selectedItem.path].width + ' Γ ' + loadedImageMeta[selectedItem.path].height + ' px' : 'β')
|
|
1615
|
+
]),
|
|
1616
|
+
m('div.bg-gray-50.dark:bg-slate-900.p-2.rounded-lg.border.border-gray-100.dark:border-slate-700', [
|
|
1617
|
+
m('span.text-gray-400.block', 'Format'),
|
|
1618
|
+
m('strong.text-gray-800.dark:text-slate-200', selectedItem.mimeType || selectedItem.ext.toUpperCase())
|
|
1619
|
+
]),
|
|
1620
|
+
m('div.bg-gray-50.dark:bg-slate-900.p-2.rounded-lg.border.border-gray-100.dark:border-slate-700', [
|
|
1621
|
+
m('span.text-gray-400.block', 'Last Modified'),
|
|
1622
|
+
m('strong.text-gray-800.dark:text-slate-200', formatDate(selectedItem.mtime))
|
|
1623
|
+
]),
|
|
534
1624
|
]),
|
|
535
|
-
selectedItem.publicUrl ? m('div.flex.items-center.gap-2.pt-
|
|
536
|
-
m('input.flex-1.px-2.py-1.text-xs.border.rounded.bg-gray-50.dark:bg-slate-900.text-gray-700.dark:text-slate-300', {
|
|
1625
|
+
selectedItem.publicUrl ? m('div.flex.items-center.gap-2.pt-1', [
|
|
1626
|
+
m('input.flex-1.px-2.5.py-1.5.text-xs.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-gray-50.dark:bg-slate-900.text-gray-700.dark:text-slate-300', {
|
|
537
1627
|
readonly: true,
|
|
538
1628
|
value: selectedItem.publicUrl
|
|
539
1629
|
}),
|
|
540
|
-
m('button.px-3.py-1.bg-indigo-600.text-white.rounded.font-medium.hover:bg-indigo-700', {
|
|
1630
|
+
m('button.px-3.5.py-1.5.bg-indigo-600.text-white.rounded-lg.font-medium.hover:bg-indigo-700.transition', {
|
|
541
1631
|
onclick: function() {
|
|
542
1632
|
copyToClipboard(selectedItem.publicUrl);
|
|
543
|
-
alert('URL copied!');
|
|
1633
|
+
alert('URL copied to clipboard!');
|
|
544
1634
|
}
|
|
545
|
-
}, 'Copy'),
|
|
546
|
-
isPickerMode && onSelectCallback ? m('button.px-3.py-1.bg-emerald-600.text-white.rounded.font-medium.hover:bg-emerald-700', {
|
|
1635
|
+
}, 'Copy URL'),
|
|
1636
|
+
isPickerMode && onSelectCallback ? m('button.px-3.5.py-1.5.bg-emerald-600.text-white.rounded-lg.font-medium.hover:bg-emerald-700.transition', {
|
|
547
1637
|
onclick: function() {
|
|
548
1638
|
onSelectCallback(selectedItem.publicUrl);
|
|
549
1639
|
modalState = null;
|
|
@@ -552,6 +1642,392 @@ function generateFileManagerComponent(options = {}) {
|
|
|
552
1642
|
]) : null
|
|
553
1643
|
])
|
|
554
1644
|
])
|
|
1645
|
+
]) : null,
|
|
1646
|
+
|
|
1647
|
+
// 5. Image Editor Modal
|
|
1648
|
+
editMode && selectedItem ? m('div.fixed.inset-0.bg-black/75.flex.items-center.justify-center.z-50.p-4.backdrop-blur-sm', {
|
|
1649
|
+
onclick: function() { if (!editorState.saving) editMode = false; }
|
|
1650
|
+
}, [
|
|
1651
|
+
m('div.bg-white.dark:bg-slate-800.rounded-2xl.shadow-2xl.w-full.max-w-5xl.max-h-[90vh].flex.flex-col.overflow-hidden.border.border-gray-200.dark:border-slate-700', {
|
|
1652
|
+
onclick: function(e) { e.stopPropagation(); }
|
|
1653
|
+
}, [
|
|
1654
|
+
// Header
|
|
1655
|
+
m('div.flex.items-center.justify-between.px-6.py-3.5.border-b.border-gray-200.dark:border-slate-700.bg-gray-50/70.dark:bg-slate-800', [
|
|
1656
|
+
m('div.flex.items-center.gap-2.truncate', [
|
|
1657
|
+
m('span.text-base', 'π¨'),
|
|
1658
|
+
m('h3.text-sm.font-bold.text-gray-900.dark:text-slate-100.truncate', 'Edit Image: ' + selectedItem.name),
|
|
1659
|
+
editorState.naturalWidth ? m('span.text-xs.text-gray-500.dark:text-slate-400', '(' + editorState.naturalWidth + ' Γ ' + editorState.naturalHeight + ' px)') : null,
|
|
1660
|
+
]),
|
|
1661
|
+
m('button.text-gray-400.hover:text-gray-600.dark:hover:text-slate-200.text-lg', {
|
|
1662
|
+
disabled: editorState.saving,
|
|
1663
|
+
onclick: function() { editMode = false; }
|
|
1664
|
+
}, 'β')
|
|
1665
|
+
]),
|
|
1666
|
+
|
|
1667
|
+
// Body: 2 Columns (Canvas/Preview on Left, Controls on Right)
|
|
1668
|
+
m('div.flex-1.grid.grid-cols-1.lg:grid-cols-3.overflow-y-auto', [
|
|
1669
|
+
// Left Column: Interactive Canvas & Preview
|
|
1670
|
+
m('div.lg:col-span-2.p-6.flex.flex-col.items-center.justify-center.border-b.lg:border-b-0.lg:border-r.border-gray-200.dark:border-slate-700', {
|
|
1671
|
+
style: 'background-image: radial-gradient(rgba(148, 163, 184, 0.3) 1px, transparent 1px); background-size: 16px 16px; background-color: rgba(248, 250, 252, 0.5);'
|
|
1672
|
+
}, [
|
|
1673
|
+
m('div.relative.max-w-full.flex.items-center.justify-center.overflow-hidden.rounded-xl.shadow-md.border.border-gray-200.dark:border-slate-700.bg-white.dark:bg-slate-900', {
|
|
1674
|
+
style: 'max-height: 420px;'
|
|
1675
|
+
}, [
|
|
1676
|
+
m('img.max-h-[380px].max-w-full.w-auto.h-auto.object-contain.transition-all.duration-200', {
|
|
1677
|
+
src: selectedItem.publicUrl,
|
|
1678
|
+
style: 'transform: rotate(' + editorState.rotation + 'deg) scale(' + (editorState.flipH ? -1 : 1) + ', ' + (editorState.flipV ? -1 : 1) + ');'
|
|
1679
|
+
}),
|
|
1680
|
+
// Crop frame overlay if aspect ratio active
|
|
1681
|
+
editorState.aspectRatio !== 'free' ? m('div.absolute.inset-4.border-2.border-indigo-500.border-dashed.rounded-lg.pointer-events-none.flex.items-center.justify-center.bg-indigo-500/10', [
|
|
1682
|
+
m('span.text-xs.font-bold.px-2.py-1.rounded.bg-indigo-600.text-white.shadow', editorState.aspectRatio)
|
|
1683
|
+
]) : null
|
|
1684
|
+
]),
|
|
1685
|
+
m('div.mt-3.text-xs.text-gray-500.dark:text-slate-400.text-center', [
|
|
1686
|
+
m('span', 'Rotation: ' + editorState.rotation + 'Β° | Flip: ' + (editorState.flipH ? 'H ' : '') + (editorState.flipV ? 'V' : (editorState.flipH ? '' : 'None'))),
|
|
1687
|
+
editorState.aspectRatio !== 'free' ? m('span.ml-2.font-semibold.text-indigo-600', '| Crop: ' + editorState.aspectRatio) : null
|
|
1688
|
+
])
|
|
1689
|
+
]),
|
|
1690
|
+
|
|
1691
|
+
// Right Column: Tools & Actions
|
|
1692
|
+
m('div.p-6.space-y-5.bg-white.dark:bg-slate-800/80', [
|
|
1693
|
+
// 1. Aspect Ratio Presets
|
|
1694
|
+
m('div.space-y-2', [
|
|
1695
|
+
m('label.text-xs.font-bold.uppercase.tracking-wider.text-gray-500.dark:text-slate-400.block', 'Aspect Ratio (KΔ±rpma)'),
|
|
1696
|
+
m('div.grid.grid-cols-2.gap-1.5', [
|
|
1697
|
+
['free', 'Free / Serbest'],
|
|
1698
|
+
['1:1', '1:1 Square'],
|
|
1699
|
+
['16:9', '16:9 Banner'],
|
|
1700
|
+
['4:3', '4:3 Standard']
|
|
1701
|
+
].map(function(item) {
|
|
1702
|
+
var active = editorState.aspectRatio === item[0];
|
|
1703
|
+
return m('button.px-2.py-1.5.text-xs.rounded-lg.border.font-medium.transition', {
|
|
1704
|
+
class: active
|
|
1705
|
+
? 'bg-indigo-600 text-white border-indigo-600'
|
|
1706
|
+
: 'bg-gray-50 dark:bg-slate-900 text-gray-700 dark:text-slate-300 border-gray-200 dark:border-slate-700 hover:bg-gray-100',
|
|
1707
|
+
onclick: function() { setAspectRatio(item[0]); }
|
|
1708
|
+
}, item[1]);
|
|
1709
|
+
}))
|
|
1710
|
+
]),
|
|
1711
|
+
|
|
1712
|
+
// 2. Rotate & Flip Controls
|
|
1713
|
+
m('div.space-y-2', [
|
|
1714
|
+
m('label.text-xs.font-bold.uppercase.tracking-wider.text-gray-500.dark:text-slate-400.block', 'Transform (DΓΆndΓΌr & Γevir)'),
|
|
1715
|
+
m('div.grid.grid-cols-2.gap-2', [
|
|
1716
|
+
m('button.px-3.py-2.text-xs.rounded-lg.border.border-gray-200.dark:border-slate-700.bg-gray-50.dark:bg-slate-900.hover:bg-gray-100.dark:hover:bg-slate-800.text-gray-800.dark:text-slate-200.flex.items-center.justify-center.gap-1.5', {
|
|
1717
|
+
onclick: function() { editorState.rotation = (editorState.rotation - 90 + 360) % 360; }
|
|
1718
|
+
}, [m('span', 'βΊ'), m('span', '-90Β°')]),
|
|
1719
|
+
m('button.px-3.py-2.text-xs.rounded-lg.border.border-gray-200.dark:border-slate-700.bg-gray-50.dark:bg-slate-900.hover:bg-gray-100.dark:hover:bg-slate-800.text-gray-800.dark:text-slate-200.flex.items-center.justify-center.gap-1.5', {
|
|
1720
|
+
onclick: function() { editorState.rotation = (editorState.rotation + 90) % 360; }
|
|
1721
|
+
}, [m('span', 'β»'), m('span', '+90Β°')]),
|
|
1722
|
+
m('button.px-3.py-2.text-xs.rounded-lg.border.border-gray-200.dark:border-slate-700.bg-gray-50.dark:bg-slate-900.hover:bg-gray-100.dark:hover:bg-slate-800.text-gray-800.dark:text-slate-200.flex.items-center.justify-center.gap-1.5', {
|
|
1723
|
+
class: editorState.flipH ? 'border-indigo-500 text-indigo-600 bg-indigo-50/50' : '',
|
|
1724
|
+
onclick: function() { editorState.flipH = !editorState.flipH; }
|
|
1725
|
+
}, [m('span', 'β'), m('span', 'Flip Horiz')]),
|
|
1726
|
+
m('button.px-3.py-2.text-xs.rounded-lg.border.border-gray-200.dark:border-slate-700.bg-gray-50.dark:bg-slate-900.hover:bg-gray-100.dark:hover:bg-slate-800.text-gray-800.dark:text-slate-200.flex.items-center.justify-center.gap-1.5', {
|
|
1727
|
+
class: editorState.flipV ? 'border-indigo-500 text-indigo-600 bg-indigo-50/50' : '',
|
|
1728
|
+
onclick: function() { editorState.flipV = !editorState.flipV; }
|
|
1729
|
+
}, [m('span', 'β
'), m('span', 'Flip Vert')])
|
|
1730
|
+
])
|
|
1731
|
+
]),
|
|
1732
|
+
|
|
1733
|
+
// 3. Resize Controls
|
|
1734
|
+
m('div.space-y-2', [
|
|
1735
|
+
m('label.text-xs.font-bold.uppercase.tracking-wider.text-gray-500.dark:text-slate-400.block', 'Resize (Yeniden BoyutlandΔ±r)'),
|
|
1736
|
+
m('div.grid.grid-cols-2.gap-2', [
|
|
1737
|
+
m('div', [
|
|
1738
|
+
m('span.text-[10px].text-gray-400.block.mb-1', 'Width (px)'),
|
|
1739
|
+
m('input.w-full.px-2.5.py-1.5.text-xs.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-gray-50.dark:bg-slate-900.text-gray-800.dark:text-slate-200', {
|
|
1740
|
+
type: 'number',
|
|
1741
|
+
value: editorState.targetWidth,
|
|
1742
|
+
oninput: function(e) {
|
|
1743
|
+
var w = parseInt(e.target.value, 10) || 1;
|
|
1744
|
+
editorState.targetWidth = w;
|
|
1745
|
+
if (editorState.lockRatio && editorState.naturalWidth) {
|
|
1746
|
+
editorState.targetHeight = Math.round((w * editorState.naturalHeight) / editorState.naturalWidth);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
})
|
|
1750
|
+
]),
|
|
1751
|
+
m('div', [
|
|
1752
|
+
m('span.text-[10px].text-gray-400.block.mb-1', 'Height (px)'),
|
|
1753
|
+
m('input.w-full.px-2.5.py-1.5.text-xs.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-gray-50.dark:bg-slate-900.text-gray-800.dark:text-slate-200', {
|
|
1754
|
+
type: 'number',
|
|
1755
|
+
value: editorState.targetHeight,
|
|
1756
|
+
oninput: function(e) {
|
|
1757
|
+
var h = parseInt(e.target.value, 10) || 1;
|
|
1758
|
+
editorState.targetHeight = h;
|
|
1759
|
+
if (editorState.lockRatio && editorState.naturalHeight) {
|
|
1760
|
+
editorState.targetWidth = Math.round((h * editorState.naturalWidth) / editorState.naturalHeight);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
})
|
|
1764
|
+
])
|
|
1765
|
+
])
|
|
1766
|
+
]),
|
|
1767
|
+
|
|
1768
|
+
// 4. Output Format
|
|
1769
|
+
m('div.space-y-2', [
|
|
1770
|
+
m('label.text-xs.font-bold.uppercase.tracking-wider.text-gray-500.dark:text-slate-400.block', 'Output Format'),
|
|
1771
|
+
m('select.w-full.px-2.5.py-1.5.text-xs.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-gray-50.dark:bg-slate-900.text-gray-800.dark:text-slate-200', {
|
|
1772
|
+
value: editorState.format,
|
|
1773
|
+
onchange: function(e) { editorState.format = e.target.value; }
|
|
1774
|
+
}, [
|
|
1775
|
+
m('option', { value: 'original' }, 'Keep Original (' + selectedItem.ext.toUpperCase() + ')'),
|
|
1776
|
+
m('option', { value: 'image/webp' }, 'Convert to WebP (Recommended)'),
|
|
1777
|
+
m('option', { value: 'image/png' }, 'PNG'),
|
|
1778
|
+
m('option', { value: 'image/jpeg' }, 'JPEG')
|
|
1779
|
+
])
|
|
1780
|
+
]),
|
|
1781
|
+
|
|
1782
|
+
// 5. Actions: Save as Copy vs Overwrite
|
|
1783
|
+
m('div.pt-3.border-t.border-gray-200.dark:border-slate-700.space-y-2', [
|
|
1784
|
+
editorState.saveStatus ? m('p.text-xs.text-indigo-600.font-medium.animate-pulse', editorState.saveStatus) : null,
|
|
1785
|
+
m('div.flex.flex-col.gap-2', [
|
|
1786
|
+
m('button.w-full.py-2.px-4.bg-indigo-600.hover:bg-indigo-700.text-white.text-xs.font-semibold.rounded-xl.shadow-sm.transition.flex.items-center.justify-center.gap-1.5', {
|
|
1787
|
+
disabled: editorState.saving,
|
|
1788
|
+
onclick: function() { saveTransformedImage(false); }
|
|
1789
|
+
}, [
|
|
1790
|
+
m('span', 'πΎ'),
|
|
1791
|
+
m('span', 'Save as New File')
|
|
1792
|
+
]),
|
|
1793
|
+
m('button.w-full.py-2.px-4.bg-amber-600.hover:bg-amber-700.text-white.text-xs.font-semibold.rounded-xl.shadow-sm.transition.flex.items-center.justify-center.gap-1.5', {
|
|
1794
|
+
disabled: editorState.saving,
|
|
1795
|
+
onclick: function() {
|
|
1796
|
+
if (confirm('Are you sure you want to overwrite "' + selectedItem.name + '"?')) {
|
|
1797
|
+
saveTransformedImage(true);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}, [
|
|
1801
|
+
m('span', 'β‘'),
|
|
1802
|
+
m('span', 'Overwrite Original')
|
|
1803
|
+
]),
|
|
1804
|
+
m('button.w-full.py-1.5.px-4.border.border-gray-300.dark:border-slate-600.text-gray-700.dark:text-slate-300.text-xs.rounded-xl.hover:bg-gray-100.dark:hover:bg-slate-700.transition', {
|
|
1805
|
+
disabled: editorState.saving,
|
|
1806
|
+
onclick: function() { editMode = false; }
|
|
1807
|
+
}, 'Cancel')
|
|
1808
|
+
])
|
|
1809
|
+
])
|
|
1810
|
+
])
|
|
1811
|
+
])
|
|
1812
|
+
])
|
|
1813
|
+
]) : null,
|
|
1814
|
+
|
|
1815
|
+
// 6. Text / Code Editor Modal
|
|
1816
|
+
textEditMode && textEditorState.file ? m('div.fixed.inset-0.bg-black/75.flex.items-center.justify-center.z-50.p-4.backdrop-blur-sm', {
|
|
1817
|
+
onclick: function() {
|
|
1818
|
+
if (textEditorState.saving) return;
|
|
1819
|
+
if (textEditorState.content !== textEditorState.initialContent) {
|
|
1820
|
+
if (!confirm('You have unsaved changes. Discard them?')) return;
|
|
1821
|
+
}
|
|
1822
|
+
textEditMode = false;
|
|
1823
|
+
}
|
|
1824
|
+
}, [
|
|
1825
|
+
m('div.bg-slate-900.text-slate-100.rounded-2xl.shadow-2xl.w-full.max-w-5xl.max-h-[90vh].flex.flex-col.overflow-hidden.border.border-slate-700', {
|
|
1826
|
+
onclick: function(e) { e.stopPropagation(); }
|
|
1827
|
+
}, [
|
|
1828
|
+
// Header
|
|
1829
|
+
m('div.flex.items-center.justify-between.px-6.py-3.border-b.border-slate-800.bg-slate-950/80', [
|
|
1830
|
+
m('div.flex.items-center.gap-2.5.truncate', [
|
|
1831
|
+
m('span.text-base', textEditorState.isJson ? 'βοΈ' : 'π'),
|
|
1832
|
+
m('h3.text-sm.font-bold.text-slate-100.truncate', textEditorState.file.name),
|
|
1833
|
+
m('span.text-[10px].uppercase.font-bold.px-2.py-0.5.rounded.bg-indigo-900/60.text-indigo-300.border.border-indigo-700/50', textEditorState.file.ext || 'TEXT'),
|
|
1834
|
+
textEditorState.content !== textEditorState.initialContent
|
|
1835
|
+
? m('span.text-[10px].font-semibold.px-2.py-0.5.rounded-full.bg-amber-500/20.text-amber-400.border.border-amber-500/40.animate-pulse', 'β Unsaved')
|
|
1836
|
+
: m('span.text-[10px].font-semibold.px-2.py-0.5.rounded-full.bg-emerald-500/10.text-emerald-400', 'Saved'),
|
|
1837
|
+
textEditorState.isJson
|
|
1838
|
+
? (textEditorState.jsonError
|
|
1839
|
+
? m('span.text-[10px].font-semibold.px-2.py-0.5.rounded.bg-red-500/20.text-red-400.truncate.max-w-xs', { title: textEditorState.jsonError }, 'β οΈ Invalid JSON')
|
|
1840
|
+
: m('span.text-[10px].font-semibold.px-2.py-0.5.rounded.bg-emerald-500/20.text-emerald-400', 'β Valid JSON'))
|
|
1841
|
+
: null
|
|
1842
|
+
]),
|
|
1843
|
+
// Header Actions
|
|
1844
|
+
m('div.flex.items-center.gap-2', [
|
|
1845
|
+
textEditorState.isJson ? m('button.px-2.5.py-1.text-xs.rounded-lg.bg-indigo-600/30.hover:bg-indigo-600/50.text-indigo-200.border.border-indigo-500/40.flex.items-center.gap-1.transition', {
|
|
1846
|
+
onclick: formatJsonContent,
|
|
1847
|
+
title: 'Beautify / Format JSON with 2-space indentation'
|
|
1848
|
+
}, [
|
|
1849
|
+
m('span', 'β¨'),
|
|
1850
|
+
m('span', 'Format JSON')
|
|
1851
|
+
]) : null,
|
|
1852
|
+
m('button.px-2.py-1.text-xs.rounded-lg.bg-slate-800.hover:bg-slate-700.text-slate-300.border.border-slate-700.transition', {
|
|
1853
|
+
onclick: function() { textEditorState.wordWrap = !textEditorState.wordWrap; },
|
|
1854
|
+
title: 'Toggle Word Wrap'
|
|
1855
|
+
}, textEditorState.wordWrap ? 'Wrap: On' : 'Wrap: Off'),
|
|
1856
|
+
m('div.flex.items-center.border.border-slate-700.rounded-lg.overflow-hidden', [
|
|
1857
|
+
m('button.px-2.py-1.text-xs.bg-slate-800.hover:bg-slate-700.text-slate-300', {
|
|
1858
|
+
onclick: function() { textEditorState.fontSize = Math.max(10, textEditorState.fontSize - 1); },
|
|
1859
|
+
title: 'Decrease font size'
|
|
1860
|
+
}, 'A-'),
|
|
1861
|
+
m('span.px-2.py-1.text-[10px].bg-slate-900.text-slate-400', textEditorState.fontSize + 'px'),
|
|
1862
|
+
m('button.px-2.py-1.text-xs.bg-slate-800.hover:bg-slate-700.text-slate-300', {
|
|
1863
|
+
onclick: function() { textEditorState.fontSize = Math.min(22, textEditorState.fontSize + 1); },
|
|
1864
|
+
title: 'Increase font size'
|
|
1865
|
+
}, 'A+'),
|
|
1866
|
+
]),
|
|
1867
|
+
m('button.text-slate-400.hover:text-slate-100.text-lg.p-1.rounded-md', {
|
|
1868
|
+
disabled: textEditorState.saving,
|
|
1869
|
+
onclick: function() {
|
|
1870
|
+
if (textEditorState.content !== textEditorState.initialContent) {
|
|
1871
|
+
if (!confirm('You have unsaved changes. Discard them?')) return;
|
|
1872
|
+
}
|
|
1873
|
+
textEditMode = false;
|
|
1874
|
+
}
|
|
1875
|
+
}, 'β')
|
|
1876
|
+
])
|
|
1877
|
+
]),
|
|
1878
|
+
|
|
1879
|
+
// Editor Body: Loading / Error / Textarea
|
|
1880
|
+
m('div.flex-1.relative.flex.overflow-hidden.bg-slate-950', [
|
|
1881
|
+
textEditorState.loading ? m('div.w-full.flex.flex-col.items-center.justify-center.py-24.text-slate-400', [
|
|
1882
|
+
m('div.w-8.h-8.border-2.border-indigo-500.border-t-transparent.rounded-full.animate-spin.mb-2'),
|
|
1883
|
+
m('p.text-xs', 'Loading file contentβ¦')
|
|
1884
|
+
]) : (textEditorState.error ? m('div.w-full.p-8.text-center.text-red-400.text-sm', textEditorState.error) : [
|
|
1885
|
+
// Textarea with line numbers gutter
|
|
1886
|
+
m('div.flex.w-full.h-full.min-h-[420px]', [
|
|
1887
|
+
// Line numbers gutter
|
|
1888
|
+
m('div.py-3.px-3.bg-slate-900/60.text-slate-500.text-right.select-none.font-mono.border-r.border-slate-800/80', {
|
|
1889
|
+
style: 'font-size: ' + textEditorState.fontSize + 'px; line-height: 1.5; min-width: 44px;'
|
|
1890
|
+
}, (function() {
|
|
1891
|
+
var lines = [];
|
|
1892
|
+
var count = Math.max(1, textEditorState.lineCount);
|
|
1893
|
+
for (var i = 1; i <= count; i++) {
|
|
1894
|
+
lines.push(m('div', String(i)));
|
|
1895
|
+
}
|
|
1896
|
+
return lines;
|
|
1897
|
+
})()),
|
|
1898
|
+
// Real textarea
|
|
1899
|
+
m('textarea.w-full.h-full.p-3.bg-transparent.text-slate-100.font-mono.resize-none.outline-none.focus:ring-0.border-none', {
|
|
1900
|
+
value: textEditorState.content,
|
|
1901
|
+
spellcheck: false,
|
|
1902
|
+
style: 'font-size: ' + textEditorState.fontSize + 'px; line-height: 1.5; white-space: ' + (textEditorState.wordWrap ? 'pre-wrap' : 'pre') + '; tab-size: 2;',
|
|
1903
|
+
oninput: function(e) {
|
|
1904
|
+
textEditorState.content = e.target.value;
|
|
1905
|
+
updateTextStats();
|
|
1906
|
+
},
|
|
1907
|
+
onkeydown: function(e) {
|
|
1908
|
+
if (e.key === 'Tab') {
|
|
1909
|
+
e.preventDefault();
|
|
1910
|
+
var start = e.target.selectionStart;
|
|
1911
|
+
var end = e.target.selectionEnd;
|
|
1912
|
+
var val = e.target.value;
|
|
1913
|
+
e.target.value = val.substring(0, start) + ' ' + val.substring(end);
|
|
1914
|
+
e.target.selectionStart = e.target.selectionEnd = start + 2;
|
|
1915
|
+
textEditorState.content = e.target.value;
|
|
1916
|
+
updateTextStats();
|
|
1917
|
+
} else if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
1918
|
+
e.preventDefault();
|
|
1919
|
+
saveTextContent(true);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
})
|
|
1923
|
+
])
|
|
1924
|
+
])
|
|
1925
|
+
]),
|
|
1926
|
+
|
|
1927
|
+
// Footer: Status Bar & Save Actions
|
|
1928
|
+
m('div.flex.items-center.justify-between.px-6.py-3.border-t.border-slate-800.bg-slate-900', [
|
|
1929
|
+
// Left stats
|
|
1930
|
+
m('div.flex.items-center.gap-4.text-xs.text-slate-400', [
|
|
1931
|
+
m('span', 'Lines: ' + textEditorState.lineCount),
|
|
1932
|
+
m('span', 'Chars: ' + textEditorState.charCount),
|
|
1933
|
+
m('span', 'Encoding: UTF-8'),
|
|
1934
|
+
textEditorState.saveStatus ? m('span.text-indigo-400.font-medium.animate-pulse', textEditorState.saveStatus) : null
|
|
1935
|
+
]),
|
|
1936
|
+
// Right buttons
|
|
1937
|
+
m('div.flex.items-center.gap-2', [
|
|
1938
|
+
m('button.px-3.py-1.5.text-xs.font-medium.rounded-xl.border.border-slate-700.hover:bg-slate-800.text-slate-300.transition', {
|
|
1939
|
+
disabled: textEditorState.saving,
|
|
1940
|
+
onclick: function() {
|
|
1941
|
+
if (textEditorState.content !== textEditorState.initialContent) {
|
|
1942
|
+
if (!confirm('You have unsaved changes. Discard them?')) return;
|
|
1943
|
+
}
|
|
1944
|
+
textEditMode = false;
|
|
1945
|
+
}
|
|
1946
|
+
}, 'Close'),
|
|
1947
|
+
m('button.px-3.py-1.5.text-xs.font-semibold.rounded-xl.bg-slate-800.hover:bg-slate-700.text-slate-200.border.border-slate-600.flex.items-center.gap-1.5.transition', {
|
|
1948
|
+
disabled: textEditorState.saving || textEditorState.loading,
|
|
1949
|
+
onclick: function() { saveTextContent(false); }
|
|
1950
|
+
}, [
|
|
1951
|
+
m('span', 'π'),
|
|
1952
|
+
m('span', 'Save as Copy')
|
|
1953
|
+
]),
|
|
1954
|
+
m('button.px-4.py-1.5.text-xs.font-semibold.rounded-xl.bg-indigo-600.hover:bg-indigo-500.text-white.shadow-sm.flex.items-center.gap-1.5.transition', {
|
|
1955
|
+
disabled: textEditorState.saving || textEditorState.loading,
|
|
1956
|
+
onclick: function() { saveTextContent(true); }
|
|
1957
|
+
}, [
|
|
1958
|
+
m('span', textEditorState.saving ? 'β³' : 'πΎ'),
|
|
1959
|
+
m('span', textEditorState.saving ? 'Savingβ¦' : 'Save Changes')
|
|
1960
|
+
])
|
|
1961
|
+
])
|
|
1962
|
+
])
|
|
1963
|
+
])
|
|
1964
|
+
]) : null,
|
|
1965
|
+
|
|
1966
|
+
// KEYBOARD SHORTCUTS CHEATSHEET MODAL
|
|
1967
|
+
showShortcutsModal ? m('div.fixed.inset-0.bg-black/60.flex.items-center.justify-center.z-50.p-4.backdrop-blur-sm', {
|
|
1968
|
+
onclick: function() { showShortcutsModal = false; }
|
|
1969
|
+
}, [
|
|
1970
|
+
m('div.bg-white.dark:bg-slate-900.rounded-2xl.shadow-2xl.w-full.max-w-xl.overflow-hidden.border.border-gray-200.dark:border-slate-800.transition-all', {
|
|
1971
|
+
onclick: function(e) { e.stopPropagation(); }
|
|
1972
|
+
}, [
|
|
1973
|
+
// Header
|
|
1974
|
+
m('div.flex.items-center.justify-between.px-6.py-4.border-b.border-gray-100.dark:border-slate-800.bg-gray-50/60.dark:bg-slate-800/50', [
|
|
1975
|
+
m('div.flex.items-center.gap-2.5', [
|
|
1976
|
+
m('span.text-xl', 'β¨οΈ'),
|
|
1977
|
+
m('div', [
|
|
1978
|
+
m('h3.text-base.font-bold.text-gray-900.dark:text-slate-100', 'Keyboard Shortcuts'),
|
|
1979
|
+
m('p.text-xs.text-gray-500.dark:text-slate-400', 'Navigate and manage files without touching your mouse')
|
|
1980
|
+
])
|
|
1981
|
+
]),
|
|
1982
|
+
m('button.text-gray-400.hover:text-gray-600.dark:hover:text-slate-200.p-1.5.rounded-lg.hover:bg-gray-100.dark:hover:bg-slate-800', {
|
|
1983
|
+
onclick: function() { showShortcutsModal = false; }
|
|
1984
|
+
}, 'β')
|
|
1985
|
+
]),
|
|
1986
|
+
|
|
1987
|
+
// Body: Shortcut Categories
|
|
1988
|
+
m('div.p-6.space-y-5.max-h-[70vh].overflow-y-auto', [
|
|
1989
|
+
// Group 1: Navigation
|
|
1990
|
+
m('div.space-y-2.5', [
|
|
1991
|
+
m('h4.text-xs.font-bold.text-indigo-600.dark:text-indigo-400.uppercase.tracking-wider', 'Navigation & Browsing'),
|
|
1992
|
+
m('div.grid.grid-cols-1.sm:grid-cols-2.gap-2', [
|
|
1993
|
+
renderShortcutItem('β β β β', 'Move focus between items'),
|
|
1994
|
+
renderShortcutItem('Enter', 'Open folder / preview file'),
|
|
1995
|
+
renderShortcutItem('Space', 'Finder Quick Look (preview)'),
|
|
1996
|
+
renderShortcutItem('Alt + β', 'Enclosing parent folder')
|
|
1997
|
+
])
|
|
1998
|
+
]),
|
|
1999
|
+
|
|
2000
|
+
// Group 2: Selection & Batch
|
|
2001
|
+
m('div.space-y-2.5', [
|
|
2002
|
+
m('h4.text-xs.font-bold.text-indigo-600.dark:text-indigo-400.uppercase.tracking-wider', 'Selection & Batch Actions'),
|
|
2003
|
+
m('div.grid.grid-cols-1.sm:grid-cols-2.gap-2', [
|
|
2004
|
+
renderShortcutItem('Cmd / Ctrl + A', 'Select all files/folders'),
|
|
2005
|
+
renderShortcutItem('X', 'Toggle select focused item'),
|
|
2006
|
+
renderShortcutItem('Esc', 'Clear selection / close modal'),
|
|
2007
|
+
renderShortcutItem('Delete', 'Delete selected / focused item')
|
|
2008
|
+
])
|
|
2009
|
+
]),
|
|
2010
|
+
|
|
2011
|
+
// Group 3: Quick Tools & Editors
|
|
2012
|
+
m('div.space-y-2.5', [
|
|
2013
|
+
m('h4.text-xs.font-bold.text-indigo-600.dark:text-indigo-400.uppercase.tracking-wider', 'Quick Tools & Actions'),
|
|
2014
|
+
m('div.grid.grid-cols-1.sm:grid-cols-2.gap-2', [
|
|
2015
|
+
renderShortcutItem('E', 'Edit code/text or image'),
|
|
2016
|
+
renderShortcutItem('R', 'Rename focused item'),
|
|
2017
|
+
renderShortcutItem('/', 'Focus search input'),
|
|
2018
|
+
renderShortcutItem('? or H', 'Toggle shortcuts help')
|
|
2019
|
+
])
|
|
2020
|
+
])
|
|
2021
|
+
]),
|
|
2022
|
+
|
|
2023
|
+
// Footer
|
|
2024
|
+
m('div.flex.items-center.justify-between.px-6.py-3.border-t.border-gray-100.dark:border-slate-800.bg-gray-50.dark:bg-slate-800/70', [
|
|
2025
|
+
m('span.text-xs.text-gray-400.dark:text-slate-500', 'Press Esc to close at any time'),
|
|
2026
|
+
m('button.px-4.py-1.5.text-xs.font-semibold.rounded-xl.bg-indigo-600.hover:bg-indigo-500.text-white.shadow-sm.transition', {
|
|
2027
|
+
onclick: function() { showShortcutsModal = false; }
|
|
2028
|
+
}, 'Got It')
|
|
2029
|
+
])
|
|
2030
|
+
])
|
|
555
2031
|
]) : null
|
|
556
2032
|
]);
|
|
557
2033
|
}
|