stat2025 commited on
Commit
b2c2cf1
·
verified ·
1 Parent(s): a4935fc

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +304 -292
index.html CHANGED
@@ -2,11 +2,12 @@
2
  <html lang="ar" dir="rtl">
3
  <head>
4
  <meta charset="UTF-8" />
5
- <title>أدمج ملفاتك بكل سهولة</title>
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <!-- خط Tajawal -->
8
  <link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;600;700&display=swap" rel="stylesheet">
9
  <link rel="stylesheet" href="style.css" />
 
10
  <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
11
  </head>
12
  <body>
@@ -20,14 +21,18 @@
20
  <!-- المحتوى الرئيسي -->
21
  <main class="main">
22
 
23
- <!-- هيرو -->
24
  <section class="hero">
25
  <div class="logo-mark">PDF</div>
26
- <h1>أدمج ملفاتك بكل سهولة</h1>
27
-
 
 
 
 
28
  </section>
29
 
30
- <!-- خطوات -->
31
  <section class="steps">
32
  <div class="step">
33
  <span class="step-number">1</span>
@@ -35,21 +40,28 @@
35
  </div>
36
  <div class="step">
37
  <span class="step-number">2</span>
38
- <span class="step-text">رتّب الملفات أو احذف ما لا تحتاجه قبل الدمج.</span>
39
  </div>
40
  <div class="step">
41
  <span class="step-number">3</span>
42
- <span class="step-text">اضغط دمج وتابع شريط التقدم حتى يتم إنشاء ملف PDF.</span>
43
  </div>
44
  </section>
45
 
46
  <!-- الكارد الرئيسي -->
47
  <section class="card main-card">
48
 
49
- <!-- اختيار الملفات -->
50
  <div class="card-section card-select">
51
- <h2 class="card-title">اختيار الملفات</h2>
52
-
 
 
 
 
 
 
 
53
 
54
  <label class="file-picker">
55
  <span class="file-picker-icon">📂</span>
@@ -58,20 +70,26 @@
58
  </label>
59
  </div>
60
 
61
- <!-- قائمة الملفات -->
62
  <div class="card-section">
63
  <div id="fileList" class="file-list hidden"></div>
64
  </div>
65
 
66
- <!-- الإخراج والأزرار -->
67
  <div class="card-section card-output">
68
  <div class="card-row inline">
69
- <label for="outputName" class="card-label">اسم ملف الإخراج (اختياري)</label>
 
 
 
 
 
 
70
  <input
71
  id="outputName"
72
  type="text"
73
  class="output-input"
74
- placeholder="مثال: تاريخ اليوم"
75
  />
76
  </div>
77
 
@@ -94,310 +112,304 @@
94
  </main>
95
  </div>
96
 
97
- <script>
98
- const filesInput = document.getElementById("files");
99
- const mergeBtn = document.getElementById("mergeBtn");
100
- const clearBtn = document.getElementById("clearBtn");
101
- const statusDiv = document.getElementById("status");
102
- const fileListDiv = document.getElementById("fileList");
103
- const outputNameInput = document.getElementById("outputName");
104
- const progressDiv = document.getElementById("progress");
105
- const progressText = document.getElementById("progressText");
106
- const progressFill = document.getElementById("progressFill");
107
-
108
- let selectedFiles = [];
109
- const MAX_RECOMMENDED_FILES = 200;
110
-
111
- function getTodayDateString() {
112
- const d = new Date();
113
- const year = d.getFullYear();
114
- const month = String(d.getMonth() + 1).padStart(2, "0");
115
- const day = String(d.getDate()).padStart(2, "0");
116
- return `${year}-${month}-${day}`;
117
- }
118
-
119
- function setStatus(msg, type = "") {
120
- statusDiv.textContent = msg;
121
- statusDiv.className = "status" + (type ? " " + type : "");
122
- if (!msg) statusDiv.className = "status";
123
- }
124
-
125
- function showProgress(show) {
126
- if (show) {
127
- progressDiv.classList.remove("hidden");
128
- progressFill.style.width = "0%";
129
- progressText.textContent = "جاري المعالجة...";
130
- } else {
131
- progressDiv.classList.add("hidden");
132
  }
133
- }
134
-
135
- function setProgress(current, total, label = "معالجة الملفات") {
136
- if (!total || total < 1) total = 1;
137
- const percent = Math.floor((current / total) * 100);
138
- progressFill.style.width = percent + "%";
139
- progressText.textContent = `${label} (${current} من ${total}) - ${percent}%`;
140
- }
141
-
142
- function isImage(file) {
143
- const name = file.name.toLowerCase();
144
- return (
145
- file.type.startsWith("image/") ||
146
- name.endsWith(".jpg") ||
147
- name.endsWith(".jpeg") ||
148
- name.endsWith(".png")
149
- );
150
- }
151
-
152
- function isPDF(file) {
153
- const name = file.name.toLowerCase();
154
- return (
155
- file.type === "application/pdf" ||
156
- name.endsWith(".pdf")
157
- );
158
- }
159
-
160
- function getFilesInfo(files) {
161
- let hasImages = false;
162
- let hasPDFs = false;
163
- files.forEach((f) => {
164
- if (isImage(f)) hasImages = true;
165
- else if (isPDF(f)) hasPDFs = true;
166
- });
167
- return { hasImages, hasPDFs };
168
- }
169
-
170
- function renderFileList(files) {
171
- if (!files.length) {
172
- fileListDiv.classList.add("hidden");
173
- fileListDiv.innerHTML = "";
174
- return;
175
  }
176
 
177
- fileListDiv.classList.remove("hidden");
178
- const { hasImages, hasPDFs } = getFilesInfo(files);
179
- let modeText = "";
180
- if (hasImages && hasPDFs) {
181
- modeText = "الوضع الحالي: دمج صور + ملفات PDF في ملف واحد، مع صفحات A4 ثابتة.";
182
- } else if (hasPDFs) {
183
- modeText = "الوضع الحالي: دمج ملفات PDF في ملف واحد (بدون تعديل محتوى الصفحات).";
184
- } else if (hasImages) {
185
- modeText = "الوضع الحالي: تحويل صور إلى ملف PDF واحد بحجم A4 لكل صفحة.";
186
- } else {
187
- modeText = "لا توجد ملفات مدعومة في القائمة.";
188
  }
189
 
190
- fileListDiv.innerHTML = `
191
- <div class="file-list-header">
192
- <span>الملفات المختارة: ${files.length}</span>
193
- <span class="file-note">رتّب الملفات، أو احذف أي ملف قبل الدمج.</span>
194
- </div>
195
- <div class="mode-label">${modeText}</div>
196
- <ul class="file-list-ul">
197
- ${files
198
- .map(
199
- (f, i) => `
200
- <li>
201
- <span class="index">${i + 1}</span>
202
- <span class="name" title="${f.name}">${f.name}</span>
203
- <span class="size">${(f.size / 1024).toFixed(1)} كيلوبايت</span>
204
- <div class="row-actions">
205
- <button class="move-btn" data-index="${i}" data-dir="up" title="نقل لأعلى">↑</button>
206
- <button class="move-btn" data-index="${i}" data-dir="down" title="نقل لأسفل">↓</button>
207
- <button class="delete-btn" data-index="${i}" title="حذف الملف">×</button>
208
- </div>
209
- </li>`
210
- )
211
- .join("")}
212
- </ul>
213
- `;
214
-
215
- if (files.length > MAX_RECOMMENDED_FILES) {
216
- setStatus(
217
- "تنبيه: عدد الملفات كبير، قد تستغرق عملية الدمج وقتًا أطول على بعض الأجهزة.",
218
- "warning"
219
  );
220
  }
221
 
222
- // حذف
223
- fileListDiv.querySelectorAll(".delete-btn").forEach((btn) => {
224
- btn.addEventListener("click", (e) => {
225
- const index = parseInt(e.currentTarget.dataset.index, 10);
226
- if (!isNaN(index)) {
227
- selectedFiles.splice(index, 1);
228
- renderFileList(selectedFiles);
229
- if (!selectedFiles.length) {
230
- setStatus("");
231
- showProgress(false);
232
- }
233
- }
234
- });
235
- });
236
 
237
- // ترتيب
238
- fileListDiv.querySelectorAll(".move-btn").forEach((btn) => {
239
- btn.addEventListener("click", (e) => {
240
- const index = parseInt(e.currentTarget.dataset.index, 10);
241
- const dir = e.currentTarget.dataset.dir;
242
- if (isNaN(index)) return;
243
-
244
- if (dir === "up" && index > 0) {
245
- [selectedFiles[index - 1], selectedFiles[index]] =
246
- [selectedFiles[index], selectedFiles[index - 1]];
247
- } else if (dir === "down" && index < selectedFiles.length - 1) {
248
- [selectedFiles[index + 1], selectedFiles[index]] =
249
- [selectedFiles[index], selectedFiles[index + 1]];
250
- }
251
- renderFileList(selectedFiles);
252
  });
253
- });
254
- }
255
-
256
- function downloadPdf(bytes, filename) {
257
- const blob = new Blob([bytes], { type: "application/pdf" });
258
- const url = URL.createObjectURL(blob);
259
- const a = document.createElement("a");
260
- a.href = url;
261
- a.download = filename;
262
- document.body.appendChild(a);
263
- a.click();
264
- a.remove();
265
- URL.revokeObjectURL(url);
266
- }
267
-
268
- // إضافة ملفات على دفعات
269
- filesInput.addEventListener("change", () => {
270
- const newFilesRaw = Array.from(filesInput.files || []);
271
- if (!newFilesRaw.length) return;
272
-
273
- const newFiles = newFilesRaw.filter((f) => isImage(f) || isPDF(f));
274
- const map = new Map();
275
-
276
- [...selectedFiles, ...newFiles].forEach((f) => {
277
- const key = `${f.name}|${f.size}|${f.lastModified}`;
278
- if (!map.has(key)) map.set(key, f);
279
- });
280
-
281
- selectedFiles = Array.from(map.values());
282
- renderFileList(selectedFiles);
283
- setStatus("");
284
- filesInput.value = "";
285
- });
286
-
287
- // مسح الكل
288
- clearBtn.addEventListener("click", () => {
289
- selectedFiles = [];
290
- renderFileList([]);
291
- setStatus("تم مسح جميع الملفات المختارة.", "ok");
292
- filesInput.value = "";
293
- outputNameInput.value = "";
294
- showProgress(false);
295
- });
296
-
297
- // الدمج
298
- mergeBtn.addEventListener("click", async () => {
299
- const files = [...selectedFiles];
300
-
301
- if (!files.length) {
302
- setStatus("الرجاء اختيار الملفات أولاً.", "error");
303
- return;
304
  }
305
 
306
- const unsupported = files.filter((f) => !isImage(f) && !isPDF(f));
307
- if (unsupported.length) {
308
- setStatus("يوجد ملفات غير مدعومة. يرجى حذفها من القائمة.", "error");
309
- return;
310
- }
 
311
 
312
- const { hasImages, hasPDFs } = getFilesInfo(files);
313
- if (!hasImages && !hasPDFs) {
314
- setStatus("لا توجد ملفات مدعومة للدمج.", "error");
315
- return;
316
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
- renderFileList(files);
319
-
320
- try {
321
- setStatus("جاري معالجة الملفات...", "loading");
322
- showProgress(true);
323
- mergeBtn.disabled = true;
324
- mergeBtn.classList.add("disabled");
325
- filesInput.disabled = true;
326
- clearBtn.disabled = true;
327
-
328
- const pdfDoc = await PDFLib.PDFDocument.create();
329
- const totalSteps = files.length;
330
- let currentStep = 0;
331
-
332
- const pageWidth = 595.28; // A4
333
- const pageHeight = 841.89; // A4
334
-
335
- for (const file of files) {
336
- const bytes = await file.arrayBuffer();
337
-
338
- if (isPDF(file)) {
339
- const donorPdf = await PDFLib.PDFDocument.load(bytes);
340
- const pages = await pdfDoc.copyPages(
341
- donorPdf,
342
- donorPdf.getPageIndices()
343
- );
344
- pages.forEach((p) => pdfDoc.addPage(p));
345
- } else if (isImage(file)) {
346
- const lower = file.name.toLowerCase();
347
- let image;
348
- if (
349
- file.type === "image/jpeg" ||
350
- file.type === "image/jpg" ||
351
- lower.endsWith(".jpg") ||
352
- lower.endsWith(".jpeg")
353
- ) {
354
- image = await pdfDoc.embedJpg(bytes);
355
- } else {
356
- image = await pdfDoc.embedPng(bytes);
357
  }
 
 
358
 
359
- const imgWidth = image.width;
360
- const imgHeight = image.height;
361
- const scale = Math.min(pageWidth / imgWidth, pageHeight / imgHeight);
362
- const drawWidth = imgWidth * scale;
363
- const drawHeight = imgHeight * scale;
364
- const x = (pageWidth - drawWidth) / 2;
365
- const y = (pageHeight - drawHeight) / 2;
 
 
 
 
 
 
 
 
 
 
 
366
 
367
- const page = pdfDoc.addPage([pageWidth, pageHeight]);
368
- page.drawImage(image, { x, y, width: drawWidth, height: drawHeight });
369
- }
 
 
 
 
 
 
 
 
370
 
371
- currentStep += 1;
372
- setProgress(currentStep, totalSteps);
373
- }
 
374
 
375
- const pdfBytes = await pdfDoc.save();
 
376
 
377
- // 👇 هنا الاسم الافتراضي = تاريخ اليوم
378
- const defaultName = getTodayDateString() + ".pdf";
379
- const outName =
380
- (outputNameInput.value || defaultName).trim() || defaultName;
381
 
382
- downloadPdf(pdfBytes, outName);
383
- setStatus("تم إنشاء ملف PDF النهائي بنجاح.", "ok");
384
- showProgress(false);
 
 
385
 
 
 
386
  selectedFiles = [];
387
  renderFileList([]);
 
388
  filesInput.value = "";
389
- } catch (err) {
390
- console.error(err);
391
- setStatus("حدث خطأ أثناء المعالجة. تأكد من الملفات وحاول مرة أخرى.", "error");
392
  showProgress(false);
393
- } finally {
394
- mergeBtn.disabled = false;
395
- mergeBtn.classList.remove("disabled");
396
- filesInput.disabled = false;
397
- clearBtn.disabled = false;
398
- }
399
- });
400
- </script>
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  </body>
403
  </html>
 
2
  <html lang="ar" dir="rtl">
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <title>أداة دمج ملفات المجموعة | PDF بدون تغيير المقاس</title>
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <!-- خط Tajawal -->
8
  <link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;600;700&display=swap" rel="stylesheet">
9
  <link rel="stylesheet" href="style.css" />
10
+ <!-- مكتبة pdf-lib للدمج داخل المتصفح -->
11
  <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
12
  </head>
13
  <body>
 
21
  <!-- المحتوى الرئيسي -->
22
  <main class="main">
23
 
24
+ <!-- هيرو تعريفي -->
25
  <section class="hero">
26
  <div class="logo-mark">PDF</div>
27
+ <h1>ادمج ملفاتك في PDF واحد بدون تغيير المقاس.</h1>
28
+ <p>
29
+ اختر صورك وملفات الـ PDF، رتّبها كما تشاء، ثم أنشئ ملف PDF واحد.
30
+ لن نغيّر مقاس الصفحات: الصور تُحفَظ في صفحات بأبعادها الأصلية،
31
+ وصفحات PDF تُنسَخ كما هي — بدون قص أو إعادة تحجيم.
32
+ </p>
33
  </section>
34
 
35
+ <!-- خطوات سريعة -->
36
  <section class="steps">
37
  <div class="step">
38
  <span class="step-number">1</span>
 
40
  </div>
41
  <div class="step">
42
  <span class="step-number">2</span>
43
+ <span class="step-text">رتّب الملفات باستخدام الأسهم أو احذف ما لا تحتاجه.</span>
44
  </div>
45
  <div class="step">
46
  <span class="step-number">3</span>
47
+ <span class="step-text">اضغط دمج، وانتظر حتى يكتمل الشريط لتحميل ملف PDF النهائي.</span>
48
  </div>
49
  </section>
50
 
51
  <!-- الكارد الرئيسي -->
52
  <section class="card main-card">
53
 
54
+ <!-- القسم 1: اختيار الملفات -->
55
  <div class="card-section card-select">
56
+ <h2 class="card-title">اختيار الملفات المراد دمجها</h2>
57
+ <p class="hint">
58
+ يدعم:
59
+ <strong>ملفات PDF</strong>،
60
+ <strong>صور JPG / PNG</strong>،
61
+ أو
62
+ <strong>الدمج بينهما معًا</strong> في ملف واحد.
63
+ الدمج يتم وفق الترتيب الظاهر في القائمة، مع الحفاظ على المقاس الأصلي لكل صفحة/صورة.
64
+ </p>
65
 
66
  <label class="file-picker">
67
  <span class="file-picker-icon">📂</span>
 
70
  </label>
71
  </div>
72
 
73
+ <!-- القسم 2: قائمة الملفات -->
74
  <div class="card-section">
75
  <div id="fileList" class="file-list hidden"></div>
76
  </div>
77
 
78
+ <!-- القسم 3: الإخراج والأزرار -->
79
  <div class="card-section card-output">
80
  <div class="card-row inline">
81
+ <label for="outputName" class="card-label">
82
+ اسم ملف الإخراج (اختياري)
83
+ <span class="label-note">
84
+ في حال تركه فارغًا سيتم استخدام تاريخ اليوم تلقائيًا،
85
+ <strong>مثال: 2025-11-12.pdf</strong>
86
+ </span>
87
+ </label>
88
  <input
89
  id="outputName"
90
  type="text"
91
  class="output-input"
92
+ placeholder="اكتب اسم الملف هنا أو اتركه فارغًا لاستخدام تاريخ اليوم"
93
  />
94
  </div>
95
 
 
112
  </main>
113
  </div>
114
 
115
+ <script>
116
+ const filesInput = document.getElementById("files");
117
+ const mergeBtn = document.getElementById("mergeBtn");
118
+ const clearBtn = document.getElementById("clearBtn");
119
+ const statusDiv = document.getElementById("status");
120
+ const fileListDiv = document.getElementById("fileList");
121
+ const outputNameInput = document.getElementById("outputName");
122
+ const progressDiv = document.getElementById("progress");
123
+ const progressText = document.getElementById("progressText");
124
+ const progressFill = document.getElementById("progressFill");
125
+
126
+ let selectedFiles = [];
127
+ const MAX_RECOMMENDED_FILES = 200;
128
+
129
+ // تاريخ اليوم بصيغة YYYY-MM-DD
130
+ function getTodayDateString() {
131
+ const d = new Date();
132
+ const year = d.getFullYear();
133
+ const month = String(d.getMonth() + 1).padStart(2, "0");
134
+ const day = String(d.getDate()).padStart(2, "0");
135
+ return `${year}-${month}-${day}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  }
137
+
138
+ function setStatus(msg, type = "") {
139
+ statusDiv.textContent = msg;
140
+ statusDiv.className = "status" + (type ? " " + type : "");
141
+ if (!msg) statusDiv.className = "status";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  }
143
 
144
+ function showProgress(show) {
145
+ if (show) {
146
+ progressDiv.classList.remove("hidden");
147
+ progressFill.style.width = "0%";
148
+ progressText.textContent = "جاري المعالجة...";
149
+ } else {
150
+ progressDiv.classList.add("hidden");
151
+ }
 
 
 
152
  }
153
 
154
+ function setProgress(current, total, label = "معالجة الملفات") {
155
+ if (!total || total < 1) total = 1;
156
+ const percent = Math.floor((current / total) * 100);
157
+ progressFill.style.width = percent + "%";
158
+ progressText.textContent = `${label} (${current} من ${total}) - ${percent}%`;
159
+ }
160
+
161
+ function isImage(file) {
162
+ const name = file.name.toLowerCase();
163
+ return (
164
+ file.type.startsWith("image/") ||
165
+ name.endsWith(".jpg") ||
166
+ name.endsWith(".jpeg") ||
167
+ name.endsWith(".png")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  );
169
  }
170
 
171
+ function isPDF(file) {
172
+ const name = file.name.toLowerCase();
173
+ return (
174
+ file.type === "application/pdf" ||
175
+ name.endsWith(".pdf")
176
+ );
177
+ }
 
 
 
 
 
 
 
178
 
179
+ function getFilesInfo(files) {
180
+ let hasImages = false;
181
+ let hasPDFs = false;
182
+ files.forEach((f) => {
183
+ if (isImage(f)) hasImages = true;
184
+ else if (isPDF(f)) hasPDFs = true;
 
 
 
 
 
 
 
 
 
185
  });
186
+ return { hasImages, hasPDFs };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
 
189
+ function renderFileList(files) {
190
+ if (!files.length) {
191
+ fileListDiv.classList.add("hidden");
192
+ fileListDiv.innerHTML = "";
193
+ return;
194
+ }
195
 
196
+ fileListDiv.classList.remove("hidden");
197
+ const { hasImages, hasPDFs } = getFilesInfo(files);
198
+ let modeText = "";
199
+ if (hasImages && hasPDFs) {
200
+ modeText = "الوضع الحالي: دمج صور + ملفات PDF في ملف واحد، مع الحفاظ على المقاس الأصلي لكل صفحة.";
201
+ } else if (hasPDFs) {
202
+ modeText = "الوضع الحالي: دمج ملفات PDF في ملف واحد (نسخ الصفحات كما هي).";
203
+ } else if (hasImages) {
204
+ modeText = "الوضع الحالي: تحويل صور إلى PDF مع إنشاء صفحة بكل صورة وبنفس أبعادها الأصلية.";
205
+ } else {
206
+ modeText = "لا توجد ملفات مدعومة في القائمة.";
207
+ }
208
+
209
+ fileListDiv.innerHTML = `
210
+ <div class="file-list-header">
211
+ <span>الملفات المختارة: ${files.length}</span>
212
+ <span class="file-note">يمكنك إعادة الترتيب أو حذف أي ملف قبل الدمج.</span>
213
+ </div>
214
+ <div class="mode-label">${modeText}</div>
215
+ <ul class="file-list-ul">
216
+ ${files
217
+ .map(
218
+ (f, i) => `
219
+ <li>
220
+ <span class="index">${i + 1}</span>
221
+ <span class="name" title="${f.name}">${f.name}</span>
222
+ <span class="size">${(f.size / 1024).toFixed(1)} كيلوبايت</span>
223
+ <div class="row-actions">
224
+ <button class="move-btn" data-index="${i}" data-dir="up" title="نقل لأعلى">↑</button>
225
+ <button class="move-btn" data-index="${i}" data-dir="down" title="نقل لأسفل">↓</button>
226
+ <button class="delete-btn" data-index="${i}" title="حذف الملف">×</button>
227
+ </div>
228
+ </li>`
229
+ )
230
+ .join("")}
231
+ </ul>
232
+ `;
233
+
234
+ if (files.length > MAX_RECOMMENDED_FILES) {
235
+ setStatus(
236
+ "تنبيه: عدد الملفات كبير، قد تستغرق عملية الدمج وقتًا أطول على بعض الأجهزة.",
237
+ "warning"
238
+ );
239
+ }
240
 
241
+ // حذف ملف
242
+ fileListDiv.querySelectorAll(".delete-btn").forEach((btn) => {
243
+ btn.addEventListener("click", (e) => {
244
+ const index = parseInt(e.currentTarget.dataset.index, 10);
245
+ if (!isNaN(index)) {
246
+ selectedFiles.splice(index, 1);
247
+ renderFileList(selectedFiles);
248
+ if (!selectedFiles.length) {
249
+ setStatus("");
250
+ showProgress(false);
251
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  }
253
+ });
254
+ });
255
 
256
+ // إعادة الترتيب
257
+ fileListDiv.querySelectorAll(".move-btn").forEach((btn) => {
258
+ btn.addEventListener("click", (e) => {
259
+ const index = parseInt(e.currentTarget.dataset.index, 10);
260
+ const dir = e.currentTarget.dataset.dir;
261
+ if (isNaN(index)) return;
262
+
263
+ if (dir === "up" && index > 0) {
264
+ [selectedFiles[index - 1], selectedFiles[index]] =
265
+ [selectedFiles[index], selectedFiles[index - 1]];
266
+ } else if (dir === "down" && index < selectedFiles.length - 1) {
267
+ [selectedFiles[index + 1], selectedFiles[index]] =
268
+ [selectedFiles[index], selectedFiles[index + 1]];
269
+ }
270
+ renderFileList(selectedFiles);
271
+ });
272
+ });
273
+ }
274
 
275
+ function downloadPdf(bytes, filename) {
276
+ const blob = new Blob([bytes], { type: "application/pdf" });
277
+ const url = URL.createObjectURL(blob);
278
+ const a = document.createElement("a");
279
+ a.href = url;
280
+ a.download = filename;
281
+ document.body.appendChild(a);
282
+ a.click();
283
+ a.remove();
284
+ URL.revokeObjectURL(url);
285
+ }
286
 
287
+ // إضافة ملفات على دفعات
288
+ filesInput.addEventListener("change", () => {
289
+ const newFilesRaw = Array.from(filesInput.files || []);
290
+ if (!newFilesRaw.length) return;
291
 
292
+ const newFiles = newFilesRaw.filter((f) => isImage(f) || isPDF(f));
293
+ const map = new Map();
294
 
295
+ [...selectedFiles, ...newFiles].forEach((f) => {
296
+ const key = `${f.name}|${f.size}|${f.lastModified}`;
297
+ if (!map.has(key)) map.set(key, f);
298
+ });
299
 
300
+ selectedFiles = Array.from(map.values());
301
+ renderFileList(selectedFiles);
302
+ setStatus("");
303
+ filesInput.value = "";
304
+ });
305
 
306
+ // مسح الكل
307
+ clearBtn.addEventListener("click", () => {
308
  selectedFiles = [];
309
  renderFileList([]);
310
+ setStatus("تم مسح جميع الملفات المختارة.", "ok");
311
  filesInput.value = "";
 
 
 
312
  showProgress(false);
313
+ });
314
+
315
+ // عملية الدمج
316
+ mergeBtn.addEventListener("click", async () => {
317
+ const files = [...selectedFiles];
 
 
 
318
 
319
+ if (!files.length) {
320
+ setStatus("الرجاء اختيار الملفات أولاً.", "error");
321
+ return;
322
+ }
323
+
324
+ const unsupported = files.filter((f) => !isImage(f) && !isPDF(f));
325
+ if (unsupported.length) {
326
+ setStatus("يوجد ملفات غير مدعومة. يرجى حذفها من القائمة.", "error");
327
+ return;
328
+ }
329
+
330
+ const { hasImages, hasPDFs } = getFilesInfo(files);
331
+ if (!hasImages && !hasPDFs) {
332
+ setStatus("لا توجد ملفات مدعومة للدمج.", "error");
333
+ return;
334
+ }
335
+
336
+ renderFileList(files);
337
+
338
+ try {
339
+ setStatus("جاري معالجة الملفات...", "loading");
340
+ showProgress(true);
341
+ mergeBtn.disabled = true;
342
+ mergeBtn.classList.add("disabled");
343
+ filesInput.disabled = true;
344
+ clearBtn.disabled = true;
345
+
346
+ const pdfDoc = await PDFLib.PDFDocument.create();
347
+ const totalSteps = files.length;
348
+ let currentStep = 0;
349
+
350
+ for (const file of files) {
351
+ const bytes = await file.arrayBuffer();
352
+
353
+ if (isPDF(file)) {
354
+ // نسخ صفحات PDF كما هي
355
+ const donorPdf = await PDFLib.PDFDocument.load(bytes);
356
+ const pages = await pdfDoc.copyPages(
357
+ donorPdf,
358
+ donorPdf.getPageIndices()
359
+ );
360
+ pages.forEach((p) => pdfDoc.addPage(p));
361
+ } else if (isImage(file)) {
362
+ // إدراج الصورة في صفحة جديدة بنفس أبعاد الصورة (بدون تغيير المقاس)
363
+ const lower = file.name.toLowerCase();
364
+ let image;
365
+ if (
366
+ file.type === "image/jpeg" ||
367
+ file.type === "image/jpg" ||
368
+ lower.endsWith(".jpg") ||
369
+ lower.endsWith(".jpeg")
370
+ ) {
371
+ image = await pdfDoc.embedJpg(bytes);
372
+ } else {
373
+ image = await pdfDoc.embedPng(bytes);
374
+ }
375
+
376
+ const imgW = image.width;
377
+ const imgH = image.height;
378
+
379
+ // صفحة بنفس أبعاد الصورة
380
+ const page = pdfDoc.addPage([imgW, imgH]);
381
+ page.drawImage(image, { x: 0, y: 0, width: imgW, height: imgH });
382
+ }
383
+
384
+ currentStep += 1;
385
+ setProgress(currentStep, totalSteps);
386
+ }
387
+
388
+ const pdfBytes = await pdfDoc.save();
389
+
390
+ // الاسم الافتراضي: تاريخ اليوم إذا لم يُكتب اسم
391
+ const defaultName = getTodayDateString() + ".pdf";
392
+ const outName =
393
+ (outputNameInput.value || defaultName).trim() || defaultName;
394
+
395
+ downloadPdf(pdfBytes, outName);
396
+ setStatus("تم إنشاء ملف PDF النهائي بنجاح.", "ok");
397
+ showProgress(false);
398
+
399
+ selectedFiles = [];
400
+ renderFileList([]);
401
+ filesInput.value = "";
402
+ } catch (err) {
403
+ console.error(err);
404
+ setStatus("حدث خطأ أثناء المعالجة. تأكد من الملفات وحاول مرة أخرى.", "error");
405
+ showProgress(false);
406
+ } finally {
407
+ mergeBtn.disabled = false;
408
+ mergeBtn.classList.remove("disabled");
409
+ filesInput.disabled = false;
410
+ clearBtn.disabled = false;
411
+ }
412
+ });
413
+ </script>
414
  </body>
415
  </html>