@brftech/filex-core 0.1.56 → 0.1.61

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 (82) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +108 -108
  3. package/dist/{ArchiveViewer-CJdaw5x3.js → ArchiveViewer-CbUUEREI.js} +20 -20
  4. package/dist/ArchiveViewer-CbUUEREI.js.map +1 -0
  5. package/dist/{CsvViewer-QYx_tePQ.js → CsvViewer-72rDsQJX.js} +3 -3
  6. package/dist/CsvViewer-72rDsQJX.js.map +1 -0
  7. package/dist/{DrawioViewer-GWt0TyB7.js → DrawioViewer-DSyMwGvg.js} +7 -7
  8. package/dist/DrawioViewer-DSyMwGvg.js.map +1 -0
  9. package/dist/{EpubViewer-Je0ywklQ.js → EpubViewer-DvDa17d7.js} +3 -3
  10. package/dist/EpubViewer-DvDa17d7.js.map +1 -0
  11. package/dist/{IpynbViewer-TsB4gqP-.js → IpynbViewer-DibIkdsz.js} +3 -3
  12. package/dist/IpynbViewer-DibIkdsz.js.map +1 -0
  13. package/dist/{MermaidViewer-CXfeDS-7.js → MermaidViewer-BoFLtGvl.js} +3 -3
  14. package/dist/MermaidViewer-BoFLtGvl.js.map +1 -0
  15. package/dist/{PsdViewer-VtPpxKVe.js → PsdViewer-K2iZkwax.js} +3 -3
  16. package/dist/PsdViewer-K2iZkwax.js.map +1 -0
  17. package/dist/{TiffViewer-OINn9bbb.js → TiffViewer-JzDzbqpO.js} +3 -3
  18. package/dist/TiffViewer-JzDzbqpO.js.map +1 -0
  19. package/dist/{Viewer3D-xvvyiWHd.js → Viewer3D-WEMYCP8l.js} +7 -7
  20. package/dist/Viewer3D-WEMYCP8l.js.map +1 -0
  21. package/dist/filex-core.js +1 -1
  22. package/dist/filex-core.umd.cjs +4 -4
  23. package/dist/filex-core.umd.cjs.map +1 -1
  24. package/dist/{index-CmSYzt6L.js → index-B5YT-m5Q.js} +209 -209
  25. package/dist/index-B5YT-m5Q.js.map +1 -0
  26. package/dist/style.css +1 -1
  27. package/dist/useViewerFetch-czqbd2Lj.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/components/Breadcrumb.vue +282 -282
  30. package/src/components/ContextMenu.vue +152 -152
  31. package/src/components/GridView.vue +166 -166
  32. package/src/components/ListView.vue +212 -212
  33. package/src/components/PendingOpsTray.vue +91 -91
  34. package/src/components/RecentlyOpened.vue +155 -155
  35. package/src/components/StarButton.vue +101 -101
  36. package/src/components/TagPicker.vue +184 -184
  37. package/src/components/Toolbar.vue +220 -220
  38. package/src/components/UploadProgress.vue +78 -78
  39. package/src/composables/useFileApi.ts +692 -692
  40. package/src/composables/useKeyboardShortcuts.ts +144 -144
  41. package/src/composables/useLocale.ts +60 -60
  42. package/src/composables/useMonacoLoader.ts +98 -98
  43. package/src/composables/usePendingOps.ts +200 -200
  44. package/src/composables/useSelection.ts +81 -81
  45. package/src/composables/useUploadChunked.ts +250 -250
  46. package/src/composables/useViewerFetch.ts +58 -58
  47. package/src/index.ts +80 -80
  48. package/src/locales/en.ts +122 -122
  49. package/src/locales/index.ts +12 -12
  50. package/src/locales/tr.ts +122 -122
  51. package/src/modals/DeleteConfirmModal.vue +32 -32
  52. package/src/modals/Modal.vue +104 -104
  53. package/src/modals/NewFolderModal.vue +64 -64
  54. package/src/modals/PermissionsModal.vue +815 -815
  55. package/src/modals/PreviewModal.vue +1070 -1070
  56. package/src/modals/RenameModal.vue +58 -58
  57. package/src/modals/ShareModal.vue +135 -135
  58. package/src/styles/base.css +1224 -1224
  59. package/src/styles/variables.css +90 -90
  60. package/src/types/FileNode.ts +136 -136
  61. package/src/types/index.ts +27 -27
  62. package/src/types/peers.d.ts +45 -45
  63. package/src/viewers/ArchiveViewer.vue +186 -186
  64. package/src/viewers/CsvViewer.vue +319 -319
  65. package/src/viewers/DrawioViewer.vue +243 -243
  66. package/src/viewers/EpubViewer.vue +307 -307
  67. package/src/viewers/IpynbViewer.vue +365 -365
  68. package/src/viewers/MermaidViewer.vue +259 -259
  69. package/src/viewers/PdfViewer.vue +562 -562
  70. package/src/viewers/PsdViewer.vue +264 -264
  71. package/src/viewers/TiffViewer.vue +273 -273
  72. package/src/viewers/Viewer3D.vue +120 -120
  73. package/dist/ArchiveViewer-CJdaw5x3.js.map +0 -1
  74. package/dist/CsvViewer-QYx_tePQ.js.map +0 -1
  75. package/dist/DrawioViewer-GWt0TyB7.js.map +0 -1
  76. package/dist/EpubViewer-Je0ywklQ.js.map +0 -1
  77. package/dist/IpynbViewer-TsB4gqP-.js.map +0 -1
  78. package/dist/MermaidViewer-CXfeDS-7.js.map +0 -1
  79. package/dist/PsdViewer-VtPpxKVe.js.map +0 -1
  80. package/dist/TiffViewer-OINn9bbb.js.map +0 -1
  81. package/dist/Viewer3D-xvvyiWHd.js.map +0 -1
  82. package/dist/index-CmSYzt6L.js.map +0 -1
@@ -1,319 +1,319 @@
1
- <script setup lang="ts">
2
- /**
3
- * CsvViewer — lightweight CSV / TSV table preview.
4
- *
5
- * Lazy-imports `papaparse` when available (best-quality parser — handles
6
- * embedded quotes, multi-line cells, etc). Falls back to a simple
7
- * `split` parser when the peer is missing — workable for the trivial
8
- * "first 1000 rows" preview case but loses fidelity on complex files.
9
- *
10
- * UX:
11
- * - first-row-as-header toggle
12
- * - filter input that case-insensitively matches any cell
13
- * - 100-rows-per-page pagination
14
- * - tab vs comma auto-detection (manual override via toolbar)
15
- */
16
- import { computed, onMounted, ref, watch } from 'vue';
17
- import { fetchViewerText } from '../composables/useViewerFetch';
18
-
19
- const props = defineProps<{
20
- url: string;
21
- ext: string;
22
- t?: (key: string) => string;
23
- authHeaders?: () => Record<string, string>;
24
- authCredentials?: RequestCredentials;
25
- }>();
26
-
27
- const PAGE_SIZE = 100;
28
- const ROW_LIMIT = 1000;
29
-
30
- const rows = ref<string[][]>([]);
31
- const error = ref<string | null>(null);
32
- const loading = ref(true);
33
- const firstRowHeader = ref(true);
34
- const filter = ref('');
35
- const page = ref(1);
36
- const detectedDelim = ref<',' | '\t' | ';'>(',');
37
- const userDelim = ref<'auto' | ',' | '\t' | ';'>('auto');
38
-
39
- let renderToken = 0;
40
-
41
- function detectDelimiter(sample: string): ',' | '\t' | ';' {
42
- const sampleLines = sample.split(/\r?\n/).slice(0, 5).join('\n');
43
- const tab = (sampleLines.match(/\t/g) || []).length;
44
- const semi = (sampleLines.match(/;/g) || []).length;
45
- const comma = (sampleLines.match(/,/g) || []).length;
46
- if (tab > comma && tab > semi) return '\t';
47
- if (semi > comma && semi > tab) return ';';
48
- return ',';
49
- }
50
-
51
- async function parseWith(text: string, delim: string): Promise<string[][]> {
52
- try {
53
- const mod = await import(/* @vite-ignore */ 'papaparse');
54
- const Papa = (mod as any).default ?? mod;
55
- const result = Papa.parse(text, {
56
- delimiter: delim,
57
- skipEmptyLines: true,
58
- header: false,
59
- });
60
- return (result.data as string[][]).slice(0, ROW_LIMIT);
61
- } catch {
62
- // Fallback: naive line/column split. Loses quoted-comma support
63
- // but renders something reasonable.
64
- return text
65
- .split(/\r?\n/)
66
- .filter((l) => l.length > 0)
67
- .slice(0, ROW_LIMIT)
68
- .map((line) => line.split(delim));
69
- }
70
- }
71
-
72
- async function load(): Promise<void> {
73
- loading.value = true;
74
- error.value = null;
75
- rows.value = [];
76
- page.value = 1;
77
- const myToken = ++renderToken;
78
-
79
- let text: string;
80
- try {
81
- text = await fetchViewerText({
82
- url: props.url,
83
- headers: props.authHeaders?.() ?? {},
84
- credentials: props.authCredentials,
85
- });
86
- } catch (err) {
87
- error.value = err instanceof Error ? err.message : 'fetch failed';
88
- loading.value = false;
89
- return;
90
- }
91
-
92
- if (myToken !== renderToken) return;
93
-
94
- if (props.ext === 'tsv') {
95
- detectedDelim.value = '\t';
96
- } else {
97
- detectedDelim.value = detectDelimiter(text.slice(0, 4096));
98
- }
99
-
100
- const delim = userDelim.value === 'auto' ? detectedDelim.value : userDelim.value;
101
- rows.value = await parseWith(text, delim);
102
- loading.value = false;
103
- }
104
-
105
- onMounted(load);
106
- watch(() => props.url, load);
107
- watch(() => userDelim.value, () => {
108
- if (rows.value.length === 0) return;
109
- // Re-parse with the new delimiter using the cached source — but we
110
- // didn't keep it. Cheapest path is a refetch.
111
- load();
112
- });
113
-
114
- const headers = computed<string[]>(() => {
115
- if (!firstRowHeader.value || rows.value.length === 0) {
116
- if (rows.value.length === 0) return [];
117
- return rows.value[0].map((_, i) => `Col ${i + 1}`);
118
- }
119
- return rows.value[0];
120
- });
121
-
122
- const dataRows = computed<string[][]>(() => {
123
- return firstRowHeader.value ? rows.value.slice(1) : rows.value;
124
- });
125
-
126
- const filtered = computed<string[][]>(() => {
127
- const q = filter.value.trim().toLowerCase();
128
- if (!q) return dataRows.value;
129
- return dataRows.value.filter((r) =>
130
- r.some((c) => (c || '').toLowerCase().includes(q)),
131
- );
132
- });
133
-
134
- const totalPages = computed(() =>
135
- Math.max(1, Math.ceil(filtered.value.length / PAGE_SIZE)),
136
- );
137
-
138
- const visibleRows = computed<string[][]>(() => {
139
- const start = (page.value - 1) * PAGE_SIZE;
140
- return filtered.value.slice(start, start + PAGE_SIZE);
141
- });
142
-
143
- watch(filter, () => {
144
- page.value = 1;
145
- });
146
-
147
- function tt(key: string, fallback: string): string {
148
- return props.t ? props.t(key) : fallback;
149
- }
150
- </script>
151
-
152
- <template>
153
- <div class="filex-viewer-csv">
154
- <div class="filex-viewer-csv__pane">
155
- <div v-if="error" class="filex-viewer-fallback">
156
- <span class="filex-viewer-fallback__icon">📊</span>
157
- <p>{{ error }}</p>
158
- </div>
159
- <div v-else-if="loading" class="filex-viewer-fallback">
160
- <span class="filex-viewer-fallback__icon">⏳</span>
161
- <p>{{ tt('viewer.loading', 'Loading…') }}</p>
162
- </div>
163
- <table v-else class="filex-viewer-csv__table">
164
- <thead>
165
- <tr>
166
- <th class="filex-viewer-csv__rownum">#</th>
167
- <th v-for="(h, i) in headers" :key="i">{{ h }}</th>
168
- </tr>
169
- </thead>
170
- <tbody>
171
- <tr v-for="(row, ri) in visibleRows" :key="ri">
172
- <td class="filex-viewer-csv__rownum">{{ (page - 1) * 100 + ri + 1 }}</td>
173
- <td v-for="(c, ci) in row" :key="ci">{{ c }}</td>
174
- </tr>
175
- </tbody>
176
- </table>
177
- </div>
178
- <div v-if="!loading && !error && totalPages > 1" class="filex-viewer-csv__pager">
179
- <button
180
- type="button"
181
- class="filex-viewer-btn"
182
- :disabled="page <= 1"
183
- @click="page--"
184
- >‹</button>
185
- <span class="filex-viewer-csv__pageno">{{ page }} / {{ totalPages }} ({{ filtered.length }} satır)</span>
186
- <button
187
- type="button"
188
- class="filex-viewer-btn"
189
- :disabled="page >= totalPages"
190
- @click="page++"
191
- >›</button>
192
- </div>
193
- </div>
194
- </template>
195
-
196
- <style scoped>
197
- .filex-viewer-csv {
198
- display: flex;
199
- flex-direction: column;
200
- width: 100%;
201
- height: 100%;
202
- min-height: 70vh;
203
- background: var(--fe-bg, #fff);
204
- color: var(--fe-text, #1a1e27);
205
- }
206
- .filex-viewer-csv__bar {
207
- display: flex;
208
- align-items: center;
209
- gap: 12px;
210
- padding: 8px 12px;
211
- background: var(--fe-bg-elev, #f7f8fa);
212
- border-bottom: 1px solid var(--fe-border, #e2e6ed);
213
- font-size: 12px;
214
- }
215
- .filex-viewer-csv__check {
216
- display: flex;
217
- align-items: center;
218
- gap: 4px;
219
- font-size: 12px;
220
- }
221
- .filex-viewer-csv__select,
222
- .filex-viewer-csv__filter {
223
- border: 1px solid var(--fe-border, #e2e6ed);
224
- background: var(--fe-bg, #fff);
225
- color: inherit;
226
- padding: 4px 8px;
227
- border-radius: 4px;
228
- font: inherit;
229
- font-size: 12px;
230
- }
231
- .filex-viewer-csv__filter {
232
- flex: 0 1 240px;
233
- }
234
- .filex-viewer-spacer { flex: 1; }
235
- .filex-viewer-csv__count {
236
- font-size: 11px;
237
- color: var(--fe-text-muted, #5a6475);
238
- font-variant-numeric: tabular-nums;
239
- }
240
- .filex-viewer-csv__pane {
241
- flex: 1;
242
- overflow: auto;
243
- }
244
- .filex-viewer-csv__table {
245
- border-collapse: collapse;
246
- width: max-content;
247
- min-width: 100%;
248
- font-size: 12px;
249
- font-family: var(--fe-font-mono, monospace);
250
- }
251
- .filex-viewer-csv__table th,
252
- .filex-viewer-csv__table td {
253
- padding: 4px 10px;
254
- border: 1px solid var(--fe-border, #e2e6ed);
255
- white-space: nowrap;
256
- max-width: 320px;
257
- overflow: hidden;
258
- text-overflow: ellipsis;
259
- }
260
- .filex-viewer-csv__table th {
261
- background: var(--fe-bg-elev, #f7f8fa);
262
- position: sticky;
263
- top: 0;
264
- font-weight: 600;
265
- text-align: left;
266
- }
267
- .filex-viewer-csv__table tbody tr:nth-child(even) {
268
- background: var(--fe-bg-hover, rgba(0, 0, 0, 0.02));
269
- }
270
- .filex-viewer-csv__rownum {
271
- color: var(--fe-text-muted, #5a6475);
272
- text-align: right;
273
- background: var(--fe-bg-elev, #f7f8fa);
274
- font-variant-numeric: tabular-nums;
275
- position: sticky;
276
- left: 0;
277
- }
278
- .filex-viewer-csv__pager {
279
- display: flex;
280
- justify-content: center;
281
- align-items: center;
282
- gap: 8px;
283
- padding: 6px;
284
- background: var(--fe-bg-elev, #f7f8fa);
285
- border-top: 1px solid var(--fe-border, #e2e6ed);
286
- font-size: 12px;
287
- }
288
- .filex-viewer-csv__pageno {
289
- font-variant-numeric: tabular-nums;
290
- font-size: 12px;
291
- }
292
- .filex-viewer-fallback {
293
- text-align: center;
294
- padding: 32px;
295
- color: var(--fe-text-muted, #5a6475);
296
- }
297
- .filex-viewer-fallback__icon {
298
- font-size: 48px;
299
- display: block;
300
- margin-bottom: 12px;
301
- }
302
- .filex-viewer-btn {
303
- border: 1px solid var(--fe-border, #e2e6ed);
304
- background: var(--fe-bg, #fff);
305
- color: var(--fe-text, #1a1e27);
306
- padding: 4px 10px;
307
- border-radius: 4px;
308
- cursor: pointer;
309
- font: inherit;
310
- font-size: 12px;
311
- }
312
- .filex-viewer-btn:hover:not(:disabled) {
313
- background: var(--fe-bg-hover, #edf0f5);
314
- }
315
- .filex-viewer-btn:disabled {
316
- opacity: 0.4;
317
- cursor: not-allowed;
318
- }
319
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * CsvViewer — lightweight CSV / TSV table preview.
4
+ *
5
+ * Lazy-imports `papaparse` when available (best-quality parser — handles
6
+ * embedded quotes, multi-line cells, etc). Falls back to a simple
7
+ * `split` parser when the peer is missing — workable for the trivial
8
+ * "first 1000 rows" preview case but loses fidelity on complex files.
9
+ *
10
+ * UX:
11
+ * - first-row-as-header toggle
12
+ * - filter input that case-insensitively matches any cell
13
+ * - 100-rows-per-page pagination
14
+ * - tab vs comma auto-detection (manual override via toolbar)
15
+ */
16
+ import { computed, onMounted, ref, watch } from 'vue';
17
+ import { fetchViewerText } from '../composables/useViewerFetch';
18
+
19
+ const props = defineProps<{
20
+ url: string;
21
+ ext: string;
22
+ t?: (key: string) => string;
23
+ authHeaders?: () => Record<string, string>;
24
+ authCredentials?: RequestCredentials;
25
+ }>();
26
+
27
+ const PAGE_SIZE = 100;
28
+ const ROW_LIMIT = 1000;
29
+
30
+ const rows = ref<string[][]>([]);
31
+ const error = ref<string | null>(null);
32
+ const loading = ref(true);
33
+ const firstRowHeader = ref(true);
34
+ const filter = ref('');
35
+ const page = ref(1);
36
+ const detectedDelim = ref<',' | '\t' | ';'>(',');
37
+ const userDelim = ref<'auto' | ',' | '\t' | ';'>('auto');
38
+
39
+ let renderToken = 0;
40
+
41
+ function detectDelimiter(sample: string): ',' | '\t' | ';' {
42
+ const sampleLines = sample.split(/\r?\n/).slice(0, 5).join('\n');
43
+ const tab = (sampleLines.match(/\t/g) || []).length;
44
+ const semi = (sampleLines.match(/;/g) || []).length;
45
+ const comma = (sampleLines.match(/,/g) || []).length;
46
+ if (tab > comma && tab > semi) return '\t';
47
+ if (semi > comma && semi > tab) return ';';
48
+ return ',';
49
+ }
50
+
51
+ async function parseWith(text: string, delim: string): Promise<string[][]> {
52
+ try {
53
+ const mod = await import(/* @vite-ignore */ 'papaparse');
54
+ const Papa = (mod as any).default ?? mod;
55
+ const result = Papa.parse(text, {
56
+ delimiter: delim,
57
+ skipEmptyLines: true,
58
+ header: false,
59
+ });
60
+ return (result.data as string[][]).slice(0, ROW_LIMIT);
61
+ } catch {
62
+ // Fallback: naive line/column split. Loses quoted-comma support
63
+ // but renders something reasonable.
64
+ return text
65
+ .split(/\r?\n/)
66
+ .filter((l) => l.length > 0)
67
+ .slice(0, ROW_LIMIT)
68
+ .map((line) => line.split(delim));
69
+ }
70
+ }
71
+
72
+ async function load(): Promise<void> {
73
+ loading.value = true;
74
+ error.value = null;
75
+ rows.value = [];
76
+ page.value = 1;
77
+ const myToken = ++renderToken;
78
+
79
+ let text: string;
80
+ try {
81
+ text = await fetchViewerText({
82
+ url: props.url,
83
+ headers: props.authHeaders?.() ?? {},
84
+ credentials: props.authCredentials,
85
+ });
86
+ } catch (err) {
87
+ error.value = err instanceof Error ? err.message : 'fetch failed';
88
+ loading.value = false;
89
+ return;
90
+ }
91
+
92
+ if (myToken !== renderToken) return;
93
+
94
+ if (props.ext === 'tsv') {
95
+ detectedDelim.value = '\t';
96
+ } else {
97
+ detectedDelim.value = detectDelimiter(text.slice(0, 4096));
98
+ }
99
+
100
+ const delim = userDelim.value === 'auto' ? detectedDelim.value : userDelim.value;
101
+ rows.value = await parseWith(text, delim);
102
+ loading.value = false;
103
+ }
104
+
105
+ onMounted(load);
106
+ watch(() => props.url, load);
107
+ watch(() => userDelim.value, () => {
108
+ if (rows.value.length === 0) return;
109
+ // Re-parse with the new delimiter using the cached source — but we
110
+ // didn't keep it. Cheapest path is a refetch.
111
+ load();
112
+ });
113
+
114
+ const headers = computed<string[]>(() => {
115
+ if (!firstRowHeader.value || rows.value.length === 0) {
116
+ if (rows.value.length === 0) return [];
117
+ return rows.value[0].map((_, i) => `Col ${i + 1}`);
118
+ }
119
+ return rows.value[0];
120
+ });
121
+
122
+ const dataRows = computed<string[][]>(() => {
123
+ return firstRowHeader.value ? rows.value.slice(1) : rows.value;
124
+ });
125
+
126
+ const filtered = computed<string[][]>(() => {
127
+ const q = filter.value.trim().toLowerCase();
128
+ if (!q) return dataRows.value;
129
+ return dataRows.value.filter((r) =>
130
+ r.some((c) => (c || '').toLowerCase().includes(q)),
131
+ );
132
+ });
133
+
134
+ const totalPages = computed(() =>
135
+ Math.max(1, Math.ceil(filtered.value.length / PAGE_SIZE)),
136
+ );
137
+
138
+ const visibleRows = computed<string[][]>(() => {
139
+ const start = (page.value - 1) * PAGE_SIZE;
140
+ return filtered.value.slice(start, start + PAGE_SIZE);
141
+ });
142
+
143
+ watch(filter, () => {
144
+ page.value = 1;
145
+ });
146
+
147
+ function tt(key: string, fallback: string): string {
148
+ return props.t ? props.t(key) : fallback;
149
+ }
150
+ </script>
151
+
152
+ <template>
153
+ <div class="filex-viewer-csv">
154
+ <div class="filex-viewer-csv__pane">
155
+ <div v-if="error" class="filex-viewer-fallback">
156
+ <span class="filex-viewer-fallback__icon">📊</span>
157
+ <p>{{ error }}</p>
158
+ </div>
159
+ <div v-else-if="loading" class="filex-viewer-fallback">
160
+ <span class="filex-viewer-fallback__icon">⏳</span>
161
+ <p>{{ tt('viewer.loading', 'Loading…') }}</p>
162
+ </div>
163
+ <table v-else class="filex-viewer-csv__table">
164
+ <thead>
165
+ <tr>
166
+ <th class="filex-viewer-csv__rownum">#</th>
167
+ <th v-for="(h, i) in headers" :key="i">{{ h }}</th>
168
+ </tr>
169
+ </thead>
170
+ <tbody>
171
+ <tr v-for="(row, ri) in visibleRows" :key="ri">
172
+ <td class="filex-viewer-csv__rownum">{{ (page - 1) * 100 + ri + 1 }}</td>
173
+ <td v-for="(c, ci) in row" :key="ci">{{ c }}</td>
174
+ </tr>
175
+ </tbody>
176
+ </table>
177
+ </div>
178
+ <div v-if="!loading && !error && totalPages > 1" class="filex-viewer-csv__pager">
179
+ <button
180
+ type="button"
181
+ class="filex-viewer-btn"
182
+ :disabled="page <= 1"
183
+ @click="page--"
184
+ >‹</button>
185
+ <span class="filex-viewer-csv__pageno">{{ page }} / {{ totalPages }} ({{ filtered.length }} satır)</span>
186
+ <button
187
+ type="button"
188
+ class="filex-viewer-btn"
189
+ :disabled="page >= totalPages"
190
+ @click="page++"
191
+ >›</button>
192
+ </div>
193
+ </div>
194
+ </template>
195
+
196
+ <style scoped>
197
+ .filex-viewer-csv {
198
+ display: flex;
199
+ flex-direction: column;
200
+ width: 100%;
201
+ height: 100%;
202
+ min-height: 70vh;
203
+ background: var(--fe-bg, #fff);
204
+ color: var(--fe-text, #1a1e27);
205
+ }
206
+ .filex-viewer-csv__bar {
207
+ display: flex;
208
+ align-items: center;
209
+ gap: 12px;
210
+ padding: 8px 12px;
211
+ background: var(--fe-bg-elev, #f7f8fa);
212
+ border-bottom: 1px solid var(--fe-border, #e2e6ed);
213
+ font-size: 12px;
214
+ }
215
+ .filex-viewer-csv__check {
216
+ display: flex;
217
+ align-items: center;
218
+ gap: 4px;
219
+ font-size: 12px;
220
+ }
221
+ .filex-viewer-csv__select,
222
+ .filex-viewer-csv__filter {
223
+ border: 1px solid var(--fe-border, #e2e6ed);
224
+ background: var(--fe-bg, #fff);
225
+ color: inherit;
226
+ padding: 4px 8px;
227
+ border-radius: 4px;
228
+ font: inherit;
229
+ font-size: 12px;
230
+ }
231
+ .filex-viewer-csv__filter {
232
+ flex: 0 1 240px;
233
+ }
234
+ .filex-viewer-spacer { flex: 1; }
235
+ .filex-viewer-csv__count {
236
+ font-size: 11px;
237
+ color: var(--fe-text-muted, #5a6475);
238
+ font-variant-numeric: tabular-nums;
239
+ }
240
+ .filex-viewer-csv__pane {
241
+ flex: 1;
242
+ overflow: auto;
243
+ }
244
+ .filex-viewer-csv__table {
245
+ border-collapse: collapse;
246
+ width: max-content;
247
+ min-width: 100%;
248
+ font-size: 12px;
249
+ font-family: var(--fe-font-mono, monospace);
250
+ }
251
+ .filex-viewer-csv__table th,
252
+ .filex-viewer-csv__table td {
253
+ padding: 4px 10px;
254
+ border: 1px solid var(--fe-border, #e2e6ed);
255
+ white-space: nowrap;
256
+ max-width: 320px;
257
+ overflow: hidden;
258
+ text-overflow: ellipsis;
259
+ }
260
+ .filex-viewer-csv__table th {
261
+ background: var(--fe-bg-elev, #f7f8fa);
262
+ position: sticky;
263
+ top: 0;
264
+ font-weight: 600;
265
+ text-align: left;
266
+ }
267
+ .filex-viewer-csv__table tbody tr:nth-child(even) {
268
+ background: var(--fe-bg-hover, rgba(0, 0, 0, 0.02));
269
+ }
270
+ .filex-viewer-csv__rownum {
271
+ color: var(--fe-text-muted, #5a6475);
272
+ text-align: right;
273
+ background: var(--fe-bg-elev, #f7f8fa);
274
+ font-variant-numeric: tabular-nums;
275
+ position: sticky;
276
+ left: 0;
277
+ }
278
+ .filex-viewer-csv__pager {
279
+ display: flex;
280
+ justify-content: center;
281
+ align-items: center;
282
+ gap: 8px;
283
+ padding: 6px;
284
+ background: var(--fe-bg-elev, #f7f8fa);
285
+ border-top: 1px solid var(--fe-border, #e2e6ed);
286
+ font-size: 12px;
287
+ }
288
+ .filex-viewer-csv__pageno {
289
+ font-variant-numeric: tabular-nums;
290
+ font-size: 12px;
291
+ }
292
+ .filex-viewer-fallback {
293
+ text-align: center;
294
+ padding: 32px;
295
+ color: var(--fe-text-muted, #5a6475);
296
+ }
297
+ .filex-viewer-fallback__icon {
298
+ font-size: 48px;
299
+ display: block;
300
+ margin-bottom: 12px;
301
+ }
302
+ .filex-viewer-btn {
303
+ border: 1px solid var(--fe-border, #e2e6ed);
304
+ background: var(--fe-bg, #fff);
305
+ color: var(--fe-text, #1a1e27);
306
+ padding: 4px 10px;
307
+ border-radius: 4px;
308
+ cursor: pointer;
309
+ font: inherit;
310
+ font-size: 12px;
311
+ }
312
+ .filex-viewer-btn:hover:not(:disabled) {
313
+ background: var(--fe-bg-hover, #edf0f5);
314
+ }
315
+ .filex-viewer-btn:disabled {
316
+ opacity: 0.4;
317
+ cursor: not-allowed;
318
+ }
319
+ </style>