stat2025 commited on
Commit
b0f49c3
·
verified ·
1 Parent(s): 103197f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +68 -32
index.html CHANGED
@@ -2,7 +2,7 @@
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
  <link rel="stylesheet" href="style.css" />
8
  <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
@@ -16,7 +16,7 @@
16
  <div class="logo-mark">PDF</div>
17
  <div class="brand-text">
18
  <div class="brand-title">أداة دمج ملفات المجموعة</div>
19
- <div class="brand-sub">دمج احترافي لصور وملفات PDF في ملف واحد</div>
20
  </div>
21
  </div>
22
  <div class="top-right">
@@ -29,10 +29,10 @@
29
 
30
  <!-- تعريف بسيط -->
31
  <section class="hero">
32
- <h1>ادمج ملفاتك بسهولة في ملف PDF واحد منسّق.</h1>
33
  <p>
34
- اختر صورًا، ملفات PDF، أو مزيجًا من الاثنين، وستقوم الأداة بإنشاء ملف PDF واحد
35
- وفق الترتيب الظاهر في القائمة، مناسب لمشاركات وتقارير المجموعة.
36
  </p>
37
  </section>
38
 
@@ -44,11 +44,11 @@
44
  </div>
45
  <div class="step">
46
  <span class="step-number">2</span>
47
- <span class="step-text">رتّب الملفات، احذف ما لا تحتاجه.</span>
48
  </div>
49
  <div class="step">
50
  <span class="step-number">3</span>
51
- <span class="step-text">اضغط دمج لتحميل ملف PDF النهائي مباشرة.</span>
52
  </div>
53
  </section>
54
 
@@ -64,7 +64,7 @@
64
  <strong>صور JPG / PNG</strong>،
65
  أو
66
  <strong>دمج الاثنين معًا</strong> في ملف PDF واحد.
67
- يتم الدمج بالترتيب في القائمة أدناه.
68
  </p>
69
 
70
  <label class="file-picker">
@@ -96,6 +96,14 @@
96
 
97
  <!-- حالة العملية -->
98
  <div id="status" class="status"></div>
 
 
 
 
 
 
 
 
99
  </section>
100
  </main>
101
  </div>
@@ -107,11 +115,12 @@
107
  const statusDiv = document.getElementById("status");
108
  const fileListDiv = document.getElementById("fileList");
109
  const outputNameInput = document.getElementById("outputName");
 
 
 
110
 
111
- // نحتفظ بالملفات المختارة (من عدة دفعات)
112
  let selectedFiles = [];
113
-
114
- const MAX_RECOMMENDED_FILES = 200; // للتنبيه فقط
115
 
116
  function setStatus(msg, type = "") {
117
  statusDiv.textContent = msg;
@@ -119,6 +128,23 @@
119
  if (!msg) statusDiv.className = "status";
120
  }
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  function isImage(file) {
123
  const name = file.name.toLowerCase();
124
  return (
@@ -155,15 +181,14 @@
155
  }
156
 
157
  fileListDiv.classList.remove("hidden");
158
-
159
  const { hasImages, hasPDFs } = getFilesInfo(files);
160
  let modeText = "";
161
  if (hasImages && hasPDFs) {
162
- modeText = "الوضع الحالي: دمج صور + ملفات PDF في ملف واحد حسب الترتيب.";
163
  } else if (hasPDFs) {
164
- modeText = "الوضع الحالي: دمج ملفات PDF في ملف واحد.";
165
  } else if (hasImages) {
166
- modeText = "الوضع الحالي: تحويل صور إلى ملف PDF واحد.";
167
  } else {
168
  modeText = "لا توجد ملفات مدعومة في القائمة.";
169
  }
@@ -171,7 +196,7 @@
171
  fileListDiv.innerHTML = `
172
  <div class="file-list-header">
173
  <span>الملفات المختارة: ${files.length}</span>
174
- <span class="file-note">يمكنك إعادة ترتيب الملفات أو حذف أي ملف قبل الدمج.</span>
175
  </div>
176
  <div class="mode-label">${modeText}</div>
177
  <ul class="file-list-ul">
@@ -185,7 +210,7 @@
185
  <div class="row-actions">
186
  <button class="move-btn" data-index="${i}" data-dir="up" title="نقل لأعلى">▲</button>
187
  <button class="move-btn" data-index="${i}" data-dir="down" title="نقل لأسفل">▼</button>
188
- <button class="delete-btn" data-index="${i}" title="حذف هذا الملف">🗑️</button>
189
  </div>
190
  </li>`
191
  )
@@ -193,7 +218,7 @@
193
  </ul>
194
  `;
195
 
196
- // تحذير إذا العدد كبير
197
  if (files.length > MAX_RECOMMENDED_FILES) {
198
  setStatus(
199
  "تنبيه: عدد الملفات كبير، قد تستغرق عملية الدمج وقتًا أطول على بعض الأجهزة.",
@@ -201,19 +226,22 @@
201
  );
202
  }
203
 
204
- // ربط أزرار الحذف
205
  fileListDiv.querySelectorAll(".delete-btn").forEach((btn) => {
206
  btn.addEventListener("click", (e) => {
207
  const index = parseInt(e.currentTarget.dataset.index, 10);
208
  if (!isNaN(index)) {
209
  selectedFiles.splice(index, 1);
210
  renderFileList(selectedFiles);
211
- if (!selectedFiles.length) setStatus("");
 
 
 
212
  }
213
  });
214
  });
215
 
216
- // ربط أزرار إعادة الترتيب
217
  fileListDiv.querySelectorAll(".move-btn").forEach((btn) => {
218
  btn.addEventListener("click", (e) => {
219
  const index = parseInt(e.currentTarget.dataset.index, 10);
@@ -251,7 +279,7 @@
251
  const newFilesRaw = Array.from(filesInput.files || []);
252
  if (!newFilesRaw.length) return;
253
 
254
- // نسمح فقط بالصور و PDF، نتجاهل غيرها
255
  const newFiles = newFilesRaw.filter((f) => isImage(f) || isPDF(f));
256
 
257
  const map = new Map();
@@ -264,20 +292,20 @@
264
  renderFileList(selectedFiles);
265
  setStatus("");
266
 
267
- // إفراغ input للسماح بالاختيار مرة أخرى
268
  filesInput.value = "";
269
  });
270
 
271
- // زر مسح جميع الملفات
272
  clearBtn.addEventListener("click", () => {
273
  selectedFiles = [];
274
  renderFileList([]);
275
  setStatus("تم مسح جميع الملفات المختارة.", "ok");
276
  filesInput.value = "";
277
  outputNameInput.value = "";
 
278
  });
279
 
280
- // عملية الدمج
281
  mergeBtn.addEventListener("click", async () => {
282
  const files = [...selectedFiles];
283
 
@@ -286,7 +314,7 @@
286
  return;
287
  }
288
 
289
- // تأكيد أن كل الملفات مدعومة
290
  const unsupported = files.filter((f) => !isImage(f) && !isPDF(f));
291
  if (unsupported.length) {
292
  setStatus("يوجد ملفات غير مدعومة. يرجى حذفها من القائمة.", "error");
@@ -303,16 +331,22 @@
303
 
304
  try {
305
  setStatus("جاري معالجة الملفات...", "loading");
 
306
  mergeBtn.disabled = true;
307
  mergeBtn.classList.add("disabled");
308
 
309
  const pdfDoc = await PDFLib.PDFDocument.create();
 
 
 
 
 
 
310
 
311
  for (const file of files) {
312
  const bytes = await file.arrayBuffer();
313
 
314
  if (isPDF(file)) {
315
- // نسخ صفحات PDF كما هي
316
  const donorPdf = await PDFLib.PDFDocument.load(bytes);
317
  const pages = await pdfDoc.copyPages(
318
  donorPdf,
@@ -320,7 +354,6 @@
320
  );
321
  pages.forEach((p) => pdfDoc.addPage(p));
322
  } else if (isImage(file)) {
323
- // إضافة الصورة في صفحة A4
324
  const lower = file.name.toLowerCase();
325
  let image;
326
  if (
@@ -336,18 +369,20 @@
336
 
337
  const imgWidth = image.width;
338
  const imgHeight = image.height;
339
- const pageWidth = 595.28; // A4
340
- const pageHeight = 841.89; // A4
341
 
342
- const page = pdfDoc.addPage([pageWidth, pageHeight]);
343
  const scale = Math.min(pageWidth / imgWidth, pageHeight / imgHeight);
344
  const drawWidth = imgWidth * scale;
345
  const drawHeight = imgHeight * scale;
346
  const x = (pageWidth - drawWidth) / 2;
347
  const y = (pageHeight - drawHeight) / 2;
348
 
 
349
  page.drawImage(image, { x, y, width: drawWidth, height: drawHeight });
350
  }
 
 
 
351
  }
352
 
353
  const pdfBytes = await pdfDoc.save();
@@ -362,14 +397,15 @@
362
 
363
  downloadPdf(pdfBytes, outName);
364
  setStatus("تم إنشاء ملف PDF النهائي بنجاح.", "ok");
 
365
 
366
- // لا نفرغ الاسم، لكن نفرغ الملفات
367
  selectedFiles = [];
368
  renderFileList([]);
369
  filesInput.value = "";
370
  } catch (err) {
371
  console.error(err);
372
  setStatus("حدث خطأ أثناء المعالجة. تأكد من الملفات وحاول مرة أخرى.", "error");
 
373
  } finally {
374
  mergeBtn.disabled = false;
375
  mergeBtn.classList.remove("disabled");
 
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
  <link rel="stylesheet" href="style.css" />
8
  <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
 
16
  <div class="logo-mark">PDF</div>
17
  <div class="brand-text">
18
  <div class="brand-title">أداة دمج ملفات المجموعة</div>
19
+ <div class="brand-sub">دمج احترافي لصور وملفات PDF في ملف واحد بحجم A4</div>
20
  </div>
21
  </div>
22
  <div class="top-right">
 
29
 
30
  <!-- تعريف بسيط -->
31
  <section class="hero">
32
+ <h1>ادمج ملفاتك بسهولة في ملف PDF واحد بحجم A4.</h1>
33
  <p>
34
+ يمكنك اختيار صور، ملفات PDF، أو مزيج منهما، وسيتم دمجها في ملف PDF واحد،
35
+ مع الحفاظ على كامل محتوى الصفحات والصور، وتنسيقها في صفحات A4 دون قص.
36
  </p>
37
  </section>
38
 
 
44
  </div>
45
  <div class="step">
46
  <span class="step-number">2</span>
47
+ <span class="step-text">أعد ترتيب الملفات، احذف ما لا تحتاجه قبل الدمج.</span>
48
  </div>
49
  <div class="step">
50
  <span class="step-number">3</span>
51
+ <span class="step-text">اضغط دمج وشاهد نسبة التقدم حتى يكتمل ملف PDF.</span>
52
  </div>
53
  </section>
54
 
 
64
  <strong>صور JPG / PNG</strong>،
65
  أو
66
  <strong>دمج الاثنين معًا</strong> في ملف PDF واحد.
67
+ يتم الدمج وفق الترتيب الظاهر في القائمة أدناه، مع وضع الصور في صفحات A4 كاملة دون قص.
68
  </p>
69
 
70
  <label class="file-picker">
 
96
 
97
  <!-- حالة العملية -->
98
  <div id="status" class="status"></div>
99
+
100
+ <!-- شريط التقدم -->
101
+ <div id="progress" class="progress hidden">
102
+ <div id="progressText" class="progress-text">جاري المعالجة...</div>
103
+ <div class="progress-bar">
104
+ <div id="progressFill" class="progress-fill"></div>
105
+ </div>
106
+ </div>
107
  </section>
108
  </main>
109
  </div>
 
115
  const statusDiv = document.getElementById("status");
116
  const fileListDiv = document.getElementById("fileList");
117
  const outputNameInput = document.getElementById("outputName");
118
+ const progressDiv = document.getElementById("progress");
119
+ const progressText = document.getElementById("progressText");
120
+ const progressFill = document.getElementById("progressFill");
121
 
 
122
  let selectedFiles = [];
123
+ const MAX_RECOMMENDED_FILES = 200; // تنبيه فقط
 
124
 
125
  function setStatus(msg, type = "") {
126
  statusDiv.textContent = msg;
 
128
  if (!msg) statusDiv.className = "status";
129
  }
130
 
131
+ function showProgress(show) {
132
+ if (show) {
133
+ progressDiv.classList.remove("hidden");
134
+ progressFill.style.width = "0%";
135
+ progressText.textContent = "جاري المعالجة...";
136
+ } else {
137
+ progressDiv.classList.add("hidden");
138
+ }
139
+ }
140
+
141
+ function setProgress(current, total, label = "جاري المعالجة") {
142
+ if (!total || total < 1) total = 1;
143
+ const percent = Math.floor((current / total) * 100);
144
+ progressFill.style.width = percent + "%";
145
+ progressText.textContent = `${label} (${current} من ${total}) - ${percent}%`;
146
+ }
147
+
148
  function isImage(file) {
149
  const name = file.name.toLowerCase();
150
  return (
 
181
  }
182
 
183
  fileListDiv.classList.remove("hidden");
 
184
  const { hasImages, hasPDFs } = getFilesInfo(files);
185
  let modeText = "";
186
  if (hasImages && hasPDFs) {
187
+ modeText = "الوضع الحالي: دمج صور + ملفات PDF في ملف واحد بحجم A4 لكل صفحة.";
188
  } else if (hasPDFs) {
189
+ modeText = "الوضع الحالي: دمج ملفات PDF في ملف واحد (بدون تغيير محتوى الصفحات).";
190
  } else if (hasImages) {
191
+ modeText = "الوضع الحالي: تحويل صور إلى ملف PDF واحد بحجم A4 لكل صفحة.";
192
  } else {
193
  modeText = "لا توجد ملفات مدعومة في القائمة.";
194
  }
 
196
  fileListDiv.innerHTML = `
197
  <div class="file-list-header">
198
  <span>الملفات المختارة: ${files.length}</span>
199
+ <span class="file-note">يمكنك إعادة الترتيب أو حذف أي ملف قبل الدمج.</span>
200
  </div>
201
  <div class="mode-label">${modeText}</div>
202
  <ul class="file-list-ul">
 
210
  <div class="row-actions">
211
  <button class="move-btn" data-index="${i}" data-dir="up" title="نقل لأعلى">▲</button>
212
  <button class="move-btn" data-index="${i}" data-dir="down" title="نقل لأسفل">▼</button>
213
+ <button class="delete-btn" data-index="${i}" title="حذف الملف">🗑️</button>
214
  </div>
215
  </li>`
216
  )
 
218
  </ul>
219
  `;
220
 
221
+ // تحذير عدد كبير
222
  if (files.length > MAX_RECOMMENDED_FILES) {
223
  setStatus(
224
  "تنبيه: عدد الملفات كبير، قد تستغرق عملية الدمج وقتًا أطول على بعض الأجهزة.",
 
226
  );
227
  }
228
 
229
+ // أزرار الحذف
230
  fileListDiv.querySelectorAll(".delete-btn").forEach((btn) => {
231
  btn.addEventListener("click", (e) => {
232
  const index = parseInt(e.currentTarget.dataset.index, 10);
233
  if (!isNaN(index)) {
234
  selectedFiles.splice(index, 1);
235
  renderFileList(selectedFiles);
236
+ if (!selectedFiles.length) {
237
+ setStatus("");
238
+ showProgress(false);
239
+ }
240
  }
241
  });
242
  });
243
 
244
+ // أزرار إعادة الترتيب
245
  fileListDiv.querySelectorAll(".move-btn").forEach((btn) => {
246
  btn.addEventListener("click", (e) => {
247
  const index = parseInt(e.currentTarget.dataset.index, 10);
 
279
  const newFilesRaw = Array.from(filesInput.files || []);
280
  if (!newFilesRaw.length) return;
281
 
282
+ // نسمح فقط بالصور و PDF
283
  const newFiles = newFilesRaw.filter((f) => isImage(f) || isPDF(f));
284
 
285
  const map = new Map();
 
292
  renderFileList(selectedFiles);
293
  setStatus("");
294
 
 
295
  filesInput.value = "";
296
  });
297
 
298
+ // مسح الكل
299
  clearBtn.addEventListener("click", () => {
300
  selectedFiles = [];
301
  renderFileList([]);
302
  setStatus("تم مسح جميع الملفات المختارة.", "ok");
303
  filesInput.value = "";
304
  outputNameInput.value = "";
305
+ showProgress(false);
306
  });
307
 
308
+ // عملية الدمج مع شريط التقدم
309
  mergeBtn.addEventListener("click", async () => {
310
  const files = [...selectedFiles];
311
 
 
314
  return;
315
  }
316
 
317
+ // التحقق من الملفات المدعومة
318
  const unsupported = files.filter((f) => !isImage(f) && !isPDF(f));
319
  if (unsupported.length) {
320
  setStatus("يوجد ملفات غير مدعومة. يرجى حذفها من القائمة.", "error");
 
331
 
332
  try {
333
  setStatus("جاري معالجة الملفات...", "loading");
334
+ showProgress(true);
335
  mergeBtn.disabled = true;
336
  mergeBtn.classList.add("disabled");
337
 
338
  const pdfDoc = await PDFLib.PDFDocument.create();
339
+ const totalSteps = files.length;
340
+ let currentStep = 0;
341
+
342
+ // مقاس A4 ثابت
343
+ const pageWidth = 595.28;
344
+ const pageHeight = 841.89;
345
 
346
  for (const file of files) {
347
  const bytes = await file.arrayBuffer();
348
 
349
  if (isPDF(file)) {
 
350
  const donorPdf = await PDFLib.PDFDocument.load(bytes);
351
  const pages = await pdfDoc.copyPages(
352
  donorPdf,
 
354
  );
355
  pages.forEach((p) => pdfDoc.addPage(p));
356
  } else if (isImage(file)) {
 
357
  const lower = file.name.toLowerCase();
358
  let image;
359
  if (
 
369
 
370
  const imgWidth = image.width;
371
  const imgHeight = image.height;
 
 
372
 
373
+ // تصغير ملائم: إدراج الصورة كاملة داخل A4 دون قص
374
  const scale = Math.min(pageWidth / imgWidth, pageHeight / imgHeight);
375
  const drawWidth = imgWidth * scale;
376
  const drawHeight = imgHeight * scale;
377
  const x = (pageWidth - drawWidth) / 2;
378
  const y = (pageHeight - drawHeight) / 2;
379
 
380
+ const page = pdfDoc.addPage([pageWidth, pageHeight]);
381
  page.drawImage(image, { x, y, width: drawWidth, height: drawHeight });
382
  }
383
+
384
+ currentStep += 1;
385
+ setProgress(currentStep, totalSteps, "معالجة الملفات");
386
  }
387
 
388
  const pdfBytes = await pdfDoc.save();
 
397
 
398
  downloadPdf(pdfBytes, outName);
399
  setStatus("تم إنشاء ملف PDF النهائي بنجاح.", "ok");
400
+ showProgress(false);
401
 
 
402
  selectedFiles = [];
403
  renderFileList([]);
404
  filesInput.value = "";
405
  } catch (err) {
406
  console.error(err);
407
  setStatus("حدث خطأ أثناء المعالجة. تأكد من الملفات وحاول مرة أخرى.", "error");
408
+ showProgress(false);
409
  } finally {
410
  mergeBtn.disabled = false;
411
  mergeBtn.classList.remove("disabled");