webspresso 0.0.95 → 0.0.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.agents/ADMIN.md +225 -0
  2. package/.agents/AGENTS.md +72 -0
  3. package/.agents/ARCHITECTURE.md +119 -0
  4. package/.agents/AUTH.md +103 -0
  5. package/.agents/CLI.md +115 -0
  6. package/.agents/COMPRESSION.md +59 -0
  7. package/.agents/CONVENTIONS.md +46 -0
  8. package/.agents/EMAIL.md +141 -0
  9. package/.agents/ERRORS.md +201 -0
  10. package/.agents/MCP.md +103 -0
  11. package/.agents/ORM.md +230 -0
  12. package/.agents/PLUGINS.md +419 -0
  13. package/.agents/QUEUE.md +113 -0
  14. package/.agents/REALTIME.md +213 -0
  15. package/.agents/ROUTING.md +292 -0
  16. package/.agents/SERVICES.md +341 -0
  17. package/.agents/SHUTDOWN.md +67 -0
  18. package/.agents/STREAMING.md +73 -0
  19. package/.agents/WORKFLOW.md +27 -0
  20. package/.agents/internal/DOCUMENTATION_REVIEW.md +485 -0
  21. package/.agents/internal/TECHNICAL_REVIEW.md +428 -0
  22. package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +122 -0
  23. package/.agents/skills/webspresso-framework/SKILL.md +56 -0
  24. package/.agents/skills/webspresso-orm-and-database/SKILL.md +119 -0
  25. package/.agents/skills/webspresso-routing-and-api/SKILL.md +114 -0
  26. package/.agents/skills/webspresso-services/SKILL.md +101 -0
  27. package/.cursorrules +14 -0
  28. package/AGENTS.md +175 -0
  29. package/CLAUDE.md +54 -0
  30. package/README.md +18 -14
  31. package/bin/commands/admin-password.js +2 -1
  32. package/bin/commands/admin-setup.js +1 -1
  33. package/bin/commands/agents-init.js +209 -0
  34. package/bin/commands/api.js +114 -48
  35. package/bin/commands/audit-prune.js +1 -1
  36. package/bin/commands/doctor.js +35 -0
  37. package/bin/commands/favicon-generate.js +1 -1
  38. package/bin/commands/new.js +13 -2
  39. package/bin/commands/orm-map.js +5 -4
  40. package/bin/commands/page.js +75 -89
  41. package/bin/commands/polar-migrate.js +1 -1
  42. package/bin/commands/seed.js +1 -1
  43. package/bin/commands/service.js +77 -0
  44. package/bin/webspresso.js +2 -2
  45. package/core/auth/hash.js +10 -5
  46. package/core/compileSchema.js +1 -1
  47. package/core/errors/middleware.js +22 -2
  48. package/core/errors/normalize.js +29 -2
  49. package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
  50. package/index.d.ts +306 -0
  51. package/index.js +35 -1
  52. package/package.json +36 -14
  53. package/plugins/admin-panel/app.js +10 -9
  54. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
  55. package/plugins/admin-panel/core/api-extensions.js +4 -0
  56. package/plugins/basic-auth/index.js +3 -1
  57. package/plugins/content/client/inline-edit.js +12 -12
  58. package/plugins/cors.js +6 -7
  59. package/plugins/csrf/index.js +3 -2
  60. package/plugins/csv/engine.js +447 -0
  61. package/plugins/csv/index.js +94 -0
  62. package/plugins/csv/middleware.js +80 -0
  63. package/plugins/csv/services.js +138 -0
  64. package/plugins/file-manager/admin-component.js +613 -0
  65. package/plugins/file-manager/api-handlers.js +261 -0
  66. package/plugins/file-manager/core.js +601 -0
  67. package/plugins/file-manager/index.js +139 -0
  68. package/plugins/file-manager/security.js +217 -0
  69. package/plugins/index.js +45 -79
  70. package/plugins/mcp/transports/sse.js +4 -2
  71. package/plugins/site-analytics/admin-component.js +7 -8
  72. package/plugins/xlsx/engine.js +397 -0
  73. package/plugins/xlsx/index.js +91 -0
  74. package/plugins/xlsx/middleware.js +87 -0
  75. package/plugins/xlsx/services.js +118 -0
  76. package/src/api/api-loader.js +233 -0
  77. package/src/api/define-api.js +35 -0
  78. package/src/discovery/file-route-parser.js +201 -0
  79. package/src/discovery/scan-routes.js +265 -0
  80. package/src/file-router.js +9 -1
  81. package/src/modules/define-module.js +99 -0
  82. package/src/modules/module-discovery.js +117 -0
  83. package/src/pages/define-page.js +36 -0
  84. package/src/pages/page-loader.js +214 -0
  85. package/src/routing/mount-discovered-routes.js +78 -0
  86. package/src/routing/route-table.js +191 -0
  87. package/src/server.js +112 -15
  88. package/src/services/builtins/file-manager.js +178 -0
  89. package/src/services/discovery.js +6 -1
  90. package/src/services/index.js +6 -11
  91. package/bin/commands/skill.js +0 -187
  92. package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
  93. package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
  94. package/templates/skills/webspresso-usage/SKILL.md +0 -48
@@ -0,0 +1,613 @@
1
+ /**
2
+ * File Manager Admin Page & Modal Picker — Mithril.js component source (string)
3
+ * @module plugins/file-manager/admin-component
4
+ */
5
+
6
+ /**
7
+ * @param {Object} options
8
+ * @param {string} [options.apiPrefix='/files']
9
+ * @param {string} [options.publicBasePath='/uploads']
10
+ */
11
+ function generateFileManagerComponent(options = {}) {
12
+ const apiPrefix = options.apiPrefix || '/files';
13
+ const publicBasePath = options.publicBasePath || '/uploads';
14
+
15
+ return `
16
+ (function() {
17
+ var API = '${apiPrefix}';
18
+ var PUBLIC_BASE = '${publicBasePath}';
19
+
20
+ function formatBytes(bytes) {
21
+ if (!bytes || bytes === 0) return '0 B';
22
+ var k = 1024;
23
+ var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
24
+ var i = Math.floor(Math.log(bytes) / Math.log(k));
25
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
26
+ }
27
+
28
+ function formatDate(d) {
29
+ if (!d) return '—';
30
+ try {
31
+ var date = new Date(d);
32
+ return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
33
+ } catch(e) {
34
+ return String(d);
35
+ }
36
+ }
37
+
38
+ function copyToClipboard(text) {
39
+ if (navigator.clipboard && navigator.clipboard.writeText) {
40
+ return navigator.clipboard.writeText(text);
41
+ }
42
+ var ta = document.createElement('textarea');
43
+ ta.value = text;
44
+ document.body.appendChild(ta);
45
+ ta.select();
46
+ document.execCommand('copy');
47
+ document.body.removeChild(ta);
48
+ return Promise.resolve();
49
+ }
50
+
51
+ function renderFileIcon(item) {
52
+ if (item.isDir) {
53
+ return m('svg.w-8.h-8.text-amber-500', { fill: 'currentColor', viewBox: '0 0 20 20' }, [
54
+ m('path', { d: 'M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z' })
55
+ ]);
56
+ }
57
+ var type = item.type || 'other';
58
+ if (type === 'image') {
59
+ return m('svg.w-8.h-8.text-emerald-500', { fill: 'none', stroke: 'currentColor', strokeWidth: 2, viewBox: '0 0 24 24' }, [
60
+ m('path', { strokeLinecap: 'round', strokeLinejoin: 'round', d: 'M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z' })
61
+ ]);
62
+ }
63
+ if (type === 'document' || type === 'text') {
64
+ return m('svg.w-8.h-8.text-blue-500', { fill: 'none', stroke: 'currentColor', strokeWidth: 2, viewBox: '0 0 24 24' }, [
65
+ m('path', { strokeLinecap: 'round', strokeLinejoin: 'round', d: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' })
66
+ ]);
67
+ }
68
+ if (type === 'video') {
69
+ return m('svg.w-8.h-8.text-purple-500', { fill: 'none', stroke: 'currentColor', strokeWidth: 2, viewBox: '0 0 24 24' }, [
70
+ m('path', { strokeLinecap: 'round', strokeLinejoin: 'round', d: 'M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z' })
71
+ ]);
72
+ }
73
+ if (type === 'audio') {
74
+ return m('svg.w-8.h-8.text-pink-500', { fill: 'none', stroke: 'currentColor', strokeWidth: 2, viewBox: '0 0 24 24' }, [
75
+ m('path', { strokeLinecap: 'round', strokeLinejoin: 'round', d: 'M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3' })
76
+ ]);
77
+ }
78
+ if (type === 'archive') {
79
+ return m('svg.w-8.h-8.text-amber-600', { fill: 'none', stroke: 'currentColor', strokeWidth: 2, viewBox: '0 0 24 24' }, [
80
+ m('path', { strokeLinecap: 'round', strokeLinejoin: 'round', d: 'M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4' })
81
+ ]);
82
+ }
83
+ return m('svg.w-8.h-8.text-gray-400', { fill: 'none', stroke: 'currentColor', strokeWidth: 2, viewBox: '0 0 24 24' }, [
84
+ m('path', { strokeLinecap: 'round', strokeLinejoin: 'round', d: 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z' })
85
+ ]);
86
+ }
87
+
88
+ function FileManagerView(isPickerMode, onSelectCallback, pickerAccept) {
89
+ var currentPath = '';
90
+ var breadcrumbs = [{ name: 'Root', path: '' }];
91
+ var items = [];
92
+ var loading = true;
93
+ var error = null;
94
+ var searchQuery = '';
95
+ var typeFilter = 'all';
96
+ var viewMode = 'grid'; // 'grid' | 'list'
97
+ var sort = 'name';
98
+ var order = 'asc';
99
+ var selectedItem = null;
100
+ var modalState = null; // 'newFolder' | 'rename' | 'delete' | 'preview'
101
+ var inputFolderValue = '';
102
+ var inputRenameValue = '';
103
+ var copyingFeedback = null;
104
+ var uploading = false;
105
+ var uploadStatus = '';
106
+
107
+ function loadDirectory(pathParam) {
108
+ if (pathParam !== undefined) currentPath = pathParam;
109
+ loading = true;
110
+ error = null;
111
+
112
+ var q = '?path=' + encodeURIComponent(currentPath);
113
+ if (searchQuery) q += '&search=' + encodeURIComponent(searchQuery);
114
+ if (typeFilter && typeFilter !== 'all') q += '&type=' + encodeURIComponent(typeFilter);
115
+ if (sort) q += '&sort=' + encodeURIComponent(sort) + '&order=' + encodeURIComponent(order);
116
+
117
+ api.get(API + q).then(function(res) {
118
+ items = res.items || [];
119
+ breadcrumbs = res.breadcrumbs || [{ name: 'Root', path: '' }];
120
+ loading = false;
121
+ if (typeof m !== 'undefined' && m.redraw) m.redraw();
122
+ }).catch(function(err) {
123
+ error = err.message || String(err);
124
+ loading = false;
125
+ if (typeof m !== 'undefined' && m.redraw) m.redraw();
126
+ });
127
+ }
128
+
129
+ function createFolder() {
130
+ if (!inputFolderValue.trim()) return;
131
+ api.post(API + '/mkdir', { path: currentPath, name: inputFolderValue.trim() }).then(function() {
132
+ modalState = null;
133
+ inputFolderValue = '';
134
+ loadDirectory();
135
+ }).catch(function(err) {
136
+ alert('Klasör oluşturulamadı: ' + (err.message || String(err)));
137
+ });
138
+ }
139
+
140
+ function renameSelected() {
141
+ if (!selectedItem || !inputRenameValue.trim()) return;
142
+ api.post(API + '/rename', { path: selectedItem.path, newName: inputRenameValue.trim() }).then(function() {
143
+ modalState = null;
144
+ inputRenameValue = '';
145
+ selectedItem = null;
146
+ loadDirectory();
147
+ }).catch(function(err) {
148
+ alert('Yeniden adlandırılamadı: ' + (err.message || String(err)));
149
+ });
150
+ }
151
+
152
+ function deleteSelected() {
153
+ if (!selectedItem) return;
154
+ api.post(API + '/delete', { path: selectedItem.path }).then(function() {
155
+ modalState = null;
156
+ selectedItem = null;
157
+ loadDirectory();
158
+ }).catch(function(err) {
159
+ alert('Silinemedi: ' + (err.message || String(err)));
160
+ });
161
+ }
162
+
163
+ function handleFileUpload(fileList) {
164
+ if (!fileList || fileList.length === 0) return;
165
+ uploading = true;
166
+ uploadStatus = fileList.length + ' dosya yükleniyor…';
167
+ if (typeof m !== 'undefined' && m.redraw) m.redraw();
168
+
169
+ var fd = new FormData();
170
+ for (var i = 0; i < fileList.length; i++) {
171
+ fd.append('files', fileList[i]);
172
+ }
173
+
174
+ var uploadUrl = (window.__ADMIN_PATH__ || '/_admin') + '/api' + API + '/upload?path=' + encodeURIComponent(currentPath);
175
+
176
+ fetch(uploadUrl, {
177
+ method: 'POST',
178
+ body: fd,
179
+ credentials: 'include',
180
+ }).then(function(r) { return r.json(); }).then(function(res) {
181
+ uploading = false;
182
+ uploadStatus = '';
183
+ if (res.success) {
184
+ loadDirectory();
185
+ } else {
186
+ alert('Yükleme hatası: ' + (res.error || 'Bilinmeyen hata'));
187
+ if (typeof m !== 'undefined' && m.redraw) m.redraw();
188
+ }
189
+ }).catch(function(err) {
190
+ uploading = false;
191
+ uploadStatus = '';
192
+ alert('Yükleme hatası: ' + (err.message || String(err)));
193
+ if (typeof m !== 'undefined' && m.redraw) m.redraw();
194
+ });
195
+ }
196
+
197
+ return {
198
+ oninit: function() {
199
+ if (isPickerMode && pickerAccept) {
200
+ if (pickerAccept.indexOf('image') !== -1) typeFilter = 'image';
201
+ else if (pickerAccept.indexOf('video') !== -1) typeFilter = 'video';
202
+ else if (pickerAccept.indexOf('audio') !== -1) typeFilter = 'audio';
203
+ else if (pickerAccept.indexOf('pdf') !== -1) typeFilter = 'document';
204
+ }
205
+ loadDirectory('');
206
+ },
207
+
208
+ view: function() {
209
+ return m('div.space-y-4', [
210
+ // Toolbar
211
+ 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', [
212
+ // Left: Breadcrumbs
213
+ m('div.flex.items-center.gap-2.flex-wrap.text-sm', [
214
+ m('span.text-gray-400.dark:text-slate-500', '📁'),
215
+ breadcrumbs.map(function(bc, idx) {
216
+ var isLast = idx === breadcrumbs.length - 1;
217
+ return m('span.flex.items-center.gap-2', [
218
+ isLast
219
+ ? m('span.font-semibold.text-gray-900.dark:text-slate-100', bc.name)
220
+ : m('button.text-indigo-600.dark:text-indigo-400.hover:underline', {
221
+ onclick: function() { loadDirectory(bc.path); }
222
+ }, bc.name),
223
+ !isLast ? m('span.text-gray-400.dark:text-slate-600', '/') : null
224
+ ]);
225
+ })
226
+ ]),
227
+
228
+ // Right: Actions & Tools
229
+ m('div.flex.items-center.gap-2.flex-wrap', [
230
+ // Search input
231
+ m('div.relative', [
232
+ 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', {
233
+ type: 'text',
234
+ placeholder: 'Dosyalarda ara…',
235
+ value: searchQuery,
236
+ oninput: function(e) { searchQuery = e.target.value; },
237
+ onkeydown: function(e) { if (e.key === 'Enter') loadDirectory(); }
238
+ }),
239
+ m('span.absolute.left-2.5.top-2.text-gray-400', '🔍')
240
+ ]),
241
+
242
+ // Type filter
243
+ m('select.text-xs.px-2.5.py-1.5.border.border-gray-200.dark:border-slate-700.rounded-lg.bg-gray-50.dark:bg-slate-900.text-gray-800.dark:text-slate-200', {
244
+ value: typeFilter,
245
+ onchange: function(e) { typeFilter = e.target.value; loadDirectory(); }
246
+ }, [
247
+ m('option', { value: 'all' }, 'Tüm Dosyalar'),
248
+ m('option', { value: 'image' }, 'Resimler'),
249
+ m('option', { value: 'document' }, 'Belgeler'),
250
+ m('option', { value: 'video' }, 'Videolar'),
251
+ m('option', { value: 'audio' }, 'Sesler'),
252
+ m('option', { value: 'archive' }, 'Arşivler'),
253
+ m('option', { value: 'code' }, 'Kod & Metin'),
254
+ ]),
255
+
256
+ // Grid / List View switch
257
+ m('div.flex.border.border-gray-200.dark:border-slate-700.rounded-lg.overflow-hidden', [
258
+ m('button.px-2.5.py-1.text-xs.font-medium', {
259
+ class: viewMode === 'grid' ? 'bg-indigo-600 text-white' : 'bg-gray-50 dark:bg-slate-900 text-gray-700 dark:text-slate-300',
260
+ onclick: function() { viewMode = 'grid'; }
261
+ }, 'Grid'),
262
+ m('button.px-2.5.py-1.text-xs.font-medium', {
263
+ class: viewMode === 'list' ? 'bg-indigo-600 text-white' : 'bg-gray-50 dark:bg-slate-900 text-gray-700 dark:text-slate-300',
264
+ onclick: function() { viewMode = 'list'; }
265
+ }, 'Liste'),
266
+ ]),
267
+
268
+ // New Folder Button
269
+ m('button.px-3.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-800.dark:text-slate-200.rounded-lg.flex.items-center.gap-1.transition', {
270
+ onclick: function() { inputFolderValue = ''; modalState = 'newFolder'; }
271
+ }, [
272
+ m('span', '+'),
273
+ m('span', 'Yeni Klasör')
274
+ ]),
275
+
276
+ // Upload Button
277
+ m('label.px-3.py-1.5.text-xs.font-medium.bg-indigo-600.hover:bg-indigo-700.text-white.rounded-lg.cursor-pointer.flex.items-center.gap-1.transition', [
278
+ m('span', '↑'),
279
+ m('span', 'Yükle'),
280
+ m('input[type=file].hidden', {
281
+ multiple: true,
282
+ onchange: function(e) { handleFileUpload(e.target.files); e.target.value = ''; }
283
+ })
284
+ ]),
285
+
286
+ // Refresh
287
+ m('button.p-1.5.text-xs.text-gray-500.hover:text-gray-900.dark:hover:text-slate-100.rounded-lg', {
288
+ title: 'Yenile',
289
+ onclick: function() { loadDirectory(); }
290
+ }, '🔄')
291
+ ])
292
+ ]),
293
+
294
+ // Uploading Banner
295
+ uploading ? m('div.p-3.bg-indigo-50.dark:bg-indigo-950/40.border.border-indigo-200.dark:border-indigo-800.rounded-lg.flex.items-center.gap-3.text-xs.text-indigo-700.dark:text-indigo-300.animate-pulse', [
296
+ m('span.animate-spin', '⏳'),
297
+ m('span.font-medium', uploadStatus)
298
+ ]) : null,
299
+
300
+ // Main Content / Drop Zone
301
+ m('div.min-h-[360px].relative', {
302
+ ondragover: function(e) { e.preventDefault(); e.stopPropagation(); },
303
+ ondrop: function(e) {
304
+ e.preventDefault();
305
+ e.stopPropagation();
306
+ if (e.dataTransfer && e.dataTransfer.files) {
307
+ handleFileUpload(e.dataTransfer.files);
308
+ }
309
+ }
310
+ }, [
311
+ loading ? m('div.flex.flex-col.items-center.justify-center.py-20.text-gray-400', [
312
+ m('div.w-8.h-8.border-2.border-indigo-600.border-t-transparent.rounded-full.animate-spin.mb-2'),
313
+ m('p.text-xs', 'Dosyalar yükleniyor…')
314
+ ]) : null,
315
+
316
+ !loading && error ? m('div.p-6.text-center.text-red-600.dark:text-red-400.text-sm', error) : null,
317
+
318
+ !loading && !error && items.length === 0 ? m('div.flex.flex-col.items-center.justify-center.py-20.text-gray-400.border-2.border-dashed.border-gray-200.dark:border-slate-800.rounded-xl', [
319
+ m('span.text-4xl.mb-2', '📂'),
320
+ m('p.text-sm.font-medium.text-gray-600.dark:text-slate-400', 'Bu klasör boş'),
321
+ m('p.text-xs.text-gray-400.mt-1', 'Dosyaları buraya sürükleyip bırakabilir veya "Yükle" butonunu kullanabilirsiniz.')
322
+ ]) : null,
323
+
324
+ // GRID VIEW
325
+ !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) {
326
+ var isImg = item.type === 'image' && item.publicUrl;
327
+ return m('div.group.relative.flex.flex-col.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.rounded-xl.p-3.hover:shadow-md.transition.cursor-pointer', {
328
+ onclick: function() {
329
+ if (item.isDir) {
330
+ loadDirectory(item.path);
331
+ } else if (isPickerMode && onSelectCallback) {
332
+ onSelectCallback(item.publicUrl);
333
+ } else {
334
+ selectedItem = item;
335
+ modalState = 'preview';
336
+ }
337
+ }
338
+ }, [
339
+ // Thumbnail / Icon
340
+ 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', [
341
+ isImg
342
+ ? m('img.w-full.h-full.object-cover.group-hover:scale-105.transition.duration-300', { src: item.publicUrl, loading: 'lazy' })
343
+ : renderFileIcon(item),
344
+ item.isDir ? null : m('span.absolute.top-1.right-1.px-1.5.py-0.5.text-[10px].uppercase.font-bold.rounded.bg-black/40.text-white.backdrop-blur-sm', item.ext || item.type)
345
+ ]),
346
+
347
+ // Info
348
+ m('p.text-xs.font-medium.text-gray-900.dark:text-slate-100.truncate', { title: item.name }, item.name),
349
+ m('div.flex.items-center.justify-between.text-[11px].text-gray-400.dark:text-slate-500.mt-1', [
350
+ m('span', item.isDir ? 'Klasör' : formatBytes(item.size)),
351
+ // Menu dots
352
+ m('div.relative.opacity-0.group-hover:opacity-100.transition', [
353
+ m('button.hover:text-gray-700.dark:hover:text-slate-200.px-1', {
354
+ onclick: function(e) {
355
+ e.stopPropagation();
356
+ selectedItem = item;
357
+ inputRenameValue = item.name;
358
+ modalState = 'rename';
359
+ }
360
+ }, '✏️'),
361
+ m('button.hover:text-red-600.px-1', {
362
+ onclick: function(e) {
363
+ e.stopPropagation();
364
+ selectedItem = item;
365
+ modalState = 'delete';
366
+ }
367
+ }, '🗑️'),
368
+ ])
369
+ ])
370
+ ]);
371
+ })) : null,
372
+
373
+ // LIST VIEW
374
+ !loading && !error && items.length > 0 && viewMode === 'list' ? m('div.overflow-x-auto.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.rounded-xl', [
375
+ m('table.min-w-full.text-left.text-xs', [
376
+ m('thead.bg-gray-50.dark:bg-slate-900.border-b.border-gray-200.dark:border-slate-700.text-gray-500', [
377
+ m('tr', [
378
+ m('th.p-3', 'Ad'),
379
+ m('th.p-3', 'Tür'),
380
+ m('th.p-3', 'Boyut'),
381
+ m('th.p-3', 'Tarih'),
382
+ m('th.p-3.text-right', 'İşlemler'),
383
+ ])
384
+ ]),
385
+ m('tbody.divide-y.divide-gray-100.dark:divide-slate-700/50', items.map(function(item) {
386
+ return m('tr.hover:bg-gray-50/80.dark:hover:bg-slate-700/30.transition.cursor-pointer', {
387
+ onclick: function() {
388
+ if (item.isDir) {
389
+ loadDirectory(item.path);
390
+ } else if (isPickerMode && onSelectCallback) {
391
+ onSelectCallback(item.publicUrl);
392
+ } else {
393
+ selectedItem = item;
394
+ modalState = 'preview';
395
+ }
396
+ }
397
+ }, [
398
+ m('td.p-3.flex.items-center.gap-2.font-medium.text-gray-900.dark:text-slate-100', [
399
+ m('span', renderFileIcon(item)),
400
+ m('span.truncate.max-w-xs.md:max-w-md', item.name)
401
+ ]),
402
+ m('td.p-3.text-gray-500.dark:text-slate-400', item.isDir ? 'Klasör' : (item.ext || item.type).toUpperCase()),
403
+ m('td.p-3.text-gray-500.dark:text-slate-400', item.isDir ? '—' : formatBytes(item.size)),
404
+ m('td.p-3.text-gray-500.dark:text-slate-400', formatDate(item.mtime)),
405
+ m('td.p-3.text-right.space-x-2', [
406
+ !item.isDir && item.publicUrl ? m('button.text-indigo-600.dark:text-indigo-400.hover:underline', {
407
+ onclick: function(e) {
408
+ e.stopPropagation();
409
+ copyToClipboard(item.publicUrl);
410
+ alert('URL kopyalandı: ' + item.publicUrl);
411
+ }
412
+ }, 'URL') : null,
413
+ m('button.text-gray-600.dark:text-slate-400.hover:text-gray-900', {
414
+ onclick: function(e) {
415
+ e.stopPropagation();
416
+ selectedItem = item;
417
+ inputRenameValue = item.name;
418
+ modalState = 'rename';
419
+ }
420
+ }, 'Ad Değiştir'),
421
+ m('button.text-red-600.dark:text-red-400.hover:underline', {
422
+ onclick: function(e) {
423
+ e.stopPropagation();
424
+ selectedItem = item;
425
+ modalState = 'delete';
426
+ }
427
+ }, 'Sil'),
428
+ ])
429
+ ]);
430
+ }))
431
+ ])
432
+ ]) : null
433
+ ]),
434
+
435
+ // MODALS
436
+ // 1. New Folder Modal
437
+ modalState === 'newFolder' ? m('div.fixed.inset-0.bg-black/50.flex.items-center.justify-center.z-50.p-4', [
438
+ m('div.bg-white.dark:bg-slate-800.rounded-xl.shadow-xl.max-w-md.w-full.p-6.space-y-4', [
439
+ m('h3.text-base.font-bold.text-gray-900.dark:text-slate-100', 'Yeni Klasör Oluştur'),
440
+ m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-lg.bg-white.dark:bg-slate-900.text-sm', {
441
+ type: 'text',
442
+ placeholder: 'Klasör adı…',
443
+ value: inputFolderValue,
444
+ oninput: function(e) { inputFolderValue = e.target.value; },
445
+ onkeydown: function(e) { if (e.key === 'Enter') createFolder(); }
446
+ }),
447
+ m('div.flex.justify-end.gap-2', [
448
+ m('button.px-4.py-2.text-xs.rounded-lg.border.border-gray-300.dark:border-slate-600.text-gray-700.dark:text-slate-300', {
449
+ onclick: function() { modalState = null; }
450
+ }, 'İptal'),
451
+ m('button.px-4.py-2.text-xs.rounded-lg.bg-indigo-600.text-white.font-medium', {
452
+ onclick: createFolder
453
+ }, 'Oluştur')
454
+ ])
455
+ ])
456
+ ]) : null,
457
+
458
+ // 2. Rename Modal
459
+ modalState === 'rename' && selectedItem ? m('div.fixed.inset-0.bg-black/50.flex.items-center.justify-center.z-50.p-4', [
460
+ m('div.bg-white.dark:bg-slate-800.rounded-xl.shadow-xl.max-w-md.w-full.p-6.space-y-4', [
461
+ m('h3.text-base.font-bold.text-gray-900.dark:text-slate-100', 'Yeniden Adlandır'),
462
+ m('input.w-full.px-3.py-2.border.border-gray-300.dark:border-slate-600.rounded-lg.bg-white.dark:bg-slate-900.text-sm', {
463
+ type: 'text',
464
+ value: inputRenameValue,
465
+ oninput: function(e) { inputRenameValue = e.target.value; },
466
+ onkeydown: function(e) { if (e.key === 'Enter') renameSelected(); }
467
+ }),
468
+ m('div.flex.justify-end.gap-2', [
469
+ m('button.px-4.py-2.text-xs.rounded-lg.border.border-gray-300.dark:border-slate-600.text-gray-700.dark:text-slate-300', {
470
+ onclick: function() { modalState = null; }
471
+ }, 'İptal'),
472
+ m('button.px-4.py-2.text-xs.rounded-lg.bg-indigo-600.text-white.font-medium', {
473
+ onclick: renameSelected
474
+ }, 'Kaydet')
475
+ ])
476
+ ])
477
+ ]) : null,
478
+
479
+ // 3. Delete Confirmation Modal
480
+ modalState === 'delete' && selectedItem ? m('div.fixed.inset-0.bg-black/50.flex.items-center.justify-center.z-50.p-4', [
481
+ m('div.bg-white.dark:bg-slate-800.rounded-xl.shadow-xl.max-w-md.w-full.p-6.space-y-4', [
482
+ m('h3.text-base.font-bold.text-red-600.dark:text-red-400', 'Silmeyi Onayla'),
483
+ m('p.text-sm.text-gray-600.dark:text-slate-300', [
484
+ m('strong', selectedItem.name),
485
+ ' kalıcı olarak silinecek. Emin misiniz?'
486
+ ]),
487
+ m('div.flex.justify-end.gap-2', [
488
+ m('button.px-4.py-2.text-xs.rounded-lg.border.border-gray-300.dark:border-slate-600.text-gray-700.dark:text-slate-300', {
489
+ onclick: function() { modalState = null; }
490
+ }, 'Vazgeç'),
491
+ m('button.px-4.py-2.text-xs.rounded-lg.bg-red-600.text-white.font-medium', {
492
+ onclick: deleteSelected
493
+ }, 'Sil')
494
+ ])
495
+ ])
496
+ ]) : null,
497
+
498
+ // 4. Preview Modal
499
+ modalState === 'preview' && selectedItem ? m('div.fixed.inset-0.bg-black/60.flex.items-center.justify-center.z-50.p-4.backdrop-blur-sm', {
500
+ onclick: function() { modalState = null; }
501
+ }, [
502
+ m('div.bg-white.dark:bg-slate-800.rounded-2xl.shadow-2xl.max-w-2xl.w-full.overflow-hidden.border.border-gray-200.dark:border-slate-700', {
503
+ onclick: function(e) { e.stopPropagation(); }
504
+ }, [
505
+ // Header
506
+ m('div.flex.items-center.justify-between.p-4.border-b.border-gray-200.dark:border-slate-700', [
507
+ m('h3.text-sm.font-bold.text-gray-900.dark:text-slate-100.truncate.max-w-lg', selectedItem.name),
508
+ m('button.text-gray-400.hover:text-gray-600.dark:hover:text-slate-200.text-lg', {
509
+ onclick: function() { modalState = null; }
510
+ }, '✕')
511
+ ]),
512
+ // Media Viewer Area
513
+ m('div.p-6.bg-gray-50.dark:bg-slate-900/50.flex.items-center.justify-center.min-h-[240px]', [
514
+ selectedItem.type === 'image' && selectedItem.publicUrl
515
+ ? m('img.max-h-96.rounded-lg.object-contain.shadow-sm', { src: selectedItem.publicUrl })
516
+ : (selectedItem.type === 'video' && selectedItem.publicUrl
517
+ ? m('video.max-h-96.rounded-lg', { src: selectedItem.publicUrl, controls: true })
518
+ : (selectedItem.type === 'audio' && selectedItem.publicUrl
519
+ ? m('audio.w-full', { src: selectedItem.publicUrl, controls: true })
520
+ : m('div.text-center.p-8', [
521
+ renderFileIcon(selectedItem),
522
+ m('p.text-xs.text-gray-500.mt-2', 'Önizleme mevcut değil')
523
+ ])))
524
+ ]),
525
+ // File Info Pane
526
+ m('div.p-4.space-y-2.text-xs.text-gray-600.dark:text-slate-300.border-t.border-gray-200.dark:border-slate-700', [
527
+ m('div.grid.grid-cols-2.gap-2', [
528
+ m('p', [m('span.text-gray-400', 'Boyut: '), formatBytes(selectedItem.size)]),
529
+ m('p', [m('span.text-gray-400', 'Tür: '), selectedItem.mimeType || selectedItem.ext]),
530
+ m('p', [m('span.text-gray-400', 'Tarih: '), formatDate(selectedItem.mtime)]),
531
+ m('p.truncate', [m('span.text-gray-400', 'Yol: '), selectedItem.path]),
532
+ ]),
533
+ selectedItem.publicUrl ? m('div.flex.items-center.gap-2.pt-2', [
534
+ 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', {
535
+ readonly: true,
536
+ value: selectedItem.publicUrl
537
+ }),
538
+ m('button.px-3.py-1.bg-indigo-600.text-white.rounded.font-medium.hover:bg-indigo-700', {
539
+ onclick: function() {
540
+ copyToClipboard(selectedItem.publicUrl);
541
+ alert('URL kopyalandı!');
542
+ }
543
+ }, 'Kopyala'),
544
+ isPickerMode && onSelectCallback ? m('button.px-3.py-1.bg-emerald-600.text-white.rounded.font-medium.hover:bg-emerald-700', {
545
+ onclick: function() {
546
+ onSelectCallback(selectedItem.publicUrl);
547
+ modalState = null;
548
+ }
549
+ }, 'Bu Dosyayı Seç') : null
550
+ ]) : null
551
+ ])
552
+ ])
553
+ ]) : null
554
+ ]);
555
+ }
556
+ };
557
+ }
558
+
559
+ function FileManagerPage() {
560
+ return FileManagerView(false, null, null);
561
+ }
562
+
563
+ window.__customPages = window.__customPages || {};
564
+ window.__customPages['files'] = FileManagerPage;
565
+
566
+ // Global Picker Modal helper
567
+ window.__openFileManagerPicker = function(options) {
568
+ var opts = options || {};
569
+ var onSelect = opts.onSelect;
570
+ var accept = opts.accept || '';
571
+
572
+ var modalContainer = document.createElement('div');
573
+ modalContainer.id = 'file-manager-picker-modal-root';
574
+ document.body.appendChild(modalContainer);
575
+
576
+ function cleanup() {
577
+ if (modalContainer && modalContainer.parentNode) {
578
+ m.mount(modalContainer, null);
579
+ modalContainer.parentNode.removeChild(modalContainer);
580
+ }
581
+ }
582
+
583
+ var PickerModalComponent = {
584
+ view: function() {
585
+ return m('div.fixed.inset-0.bg-black/60.flex.items-center.justify-center.z-[9999].p-4.backdrop-blur-sm', [
586
+ m('div.bg-white.dark:bg-slate-900.rounded-2xl.shadow-2xl.max-w-4xl.w-full.max-h-[90vh].flex.flex-col.overflow-hidden.border.border-gray-200.dark:border-slate-700', [
587
+ m('div.flex.items-center.justify-between.p-4.border-b.border-gray-200.dark:border-slate-700.bg-gray-50.dark:bg-slate-800', [
588
+ m('h2.text-base.font-bold.text-gray-900.dark:text-slate-100', opts.title || '📁 Ortam Kütüphanesinden Dosya Seç'),
589
+ m('button.text-gray-400.hover:text-gray-600.dark:hover:text-slate-200.text-xl', {
590
+ onclick: cleanup
591
+ }, '✕')
592
+ ]),
593
+ m('div.p-4.overflow-y-auto.flex-1', [
594
+ m(FileManagerView(true, function(selectedUrl) {
595
+ if (onSelect) onSelect(selectedUrl);
596
+ cleanup();
597
+ }, accept))
598
+ ])
599
+ ])
600
+ ]);
601
+ }
602
+ };
603
+
604
+ m.mount(modalContainer, PickerModalComponent);
605
+ };
606
+
607
+ })();
608
+ `;
609
+ }
610
+
611
+ module.exports = {
612
+ generateFileManagerComponent,
613
+ };