stat2025 commited on
Commit
06124c0
·
verified ·
1 Parent(s): d5229a7

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +69 -71
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
  <!-- خط Tajawal -->
8
  <link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -21,14 +21,18 @@
21
  <!-- المحتوى الرئيسي -->
22
  <main class="main">
23
 
24
- <!-- هيرو تعريفي -->
25
  <section class="hero">
26
  <div class="logo-mark">PDF</div>
27
- <h1>أدمـج ملـفاتك بسهولـة</h1>
28
-
 
 
 
 
29
  </section>
30
 
31
- <!-- خطوات سريعة -->
32
  <section class="steps">
33
  <div class="step">
34
  <span class="step-number">1</span>
@@ -36,20 +40,25 @@
36
  </div>
37
  <div class="step">
38
  <span class="step-number">2</span>
39
- <span class="step-text">رتّب الملفات باستخدام الأسهم أو احذف ما لا تحتاجه.</span>
40
  </div>
41
  <div class="step">
42
  <span class="step-number">3</span>
43
- <span class="step-text">اضغط دمج، وانتظر حتى يكتمل الشريط لتحميل ملف PDF النهائي.</span>
44
  </div>
45
  </section>
46
 
47
- <!-- الكارد الرئيسي -->
48
  <section class="card main-card">
49
 
50
- <!-- القسم 1: اختيار الملفات -->
51
  <div class="card-section card-select">
52
- <h2 class="card-title"> اختيار الملفات المراد دمجها بي دي اف او صور او الاثنين معاً</h2>
 
 
 
 
 
53
 
54
  <label class="file-picker">
55
  <span class="file-picker-icon">📂</span>
@@ -58,19 +67,19 @@
58
  </label>
59
  </div>
60
 
61
- <!-- القسم 2: قائمة الملفات -->
62
  <div class="card-section">
63
  <div id="fileList" class="file-list hidden"></div>
64
  </div>
65
 
66
- <!-- القسم 3: الإخراج والأزرار -->
67
  <div class="card-section card-output">
68
  <div class="card-row inline">
69
  <label for="outputName" class="card-label">
70
  اسم ملف الإخراج (اختياري)
71
  <span class="label-note">
72
- في حال تركه فارغًا سيتم استخدام تاريخ اليوم تلقائيًا،
73
- <strong>مثال: 2025-11-12.pdf</strong>
74
  </span>
75
  </label>
76
  <input
@@ -183,50 +192,43 @@
183
 
184
  fileListDiv.classList.remove("hidden");
185
  const { hasImages, hasPDFs } = getFilesInfo(files);
 
186
  let modeText = "";
187
  if (hasImages && hasPDFs) {
188
- modeText = "الوضع الحالي: دمج صور + ملفات PDF في ملف واحد، مع الحفاظ على المقاس الأصلي لكل صفحة.";
189
  } else if (hasPDFs) {
190
- modeText = "الوضع الحالي: دمج ملفات PDF في ملف واحد (نسخ الصفحات كما هي).";
191
  } else if (hasImages) {
192
- modeText = "الوضع الحالي: تحويل صور إلى PDF مع إنشاء صفحة بكل صورة وبنفس أبعادها الأصلية.";
193
- } else {
194
- modeText = "لا توجد ملفا�� مدعومة في القائمة.";
195
  }
196
 
197
  fileListDiv.innerHTML = `
198
  <div class="file-list-header">
199
  <span>الملفات المختارة: ${files.length}</span>
200
- <span class="file-note">يمكنك إعادة الترتيب أو حذف أي ملف قبل الدمج.</span>
201
  </div>
202
  <div class="mode-label">${modeText}</div>
203
  <ul class="file-list-ul">
204
- ${files
205
- .map(
206
- (f, i) => `
207
- <li>
208
- <span class="index">${i + 1}</span>
209
- <span class="name" title="${f.name}">${f.name}</span>
210
- <span class="size">${(f.size / 1024).toFixed(1)} كيلوبايت</span>
211
- <div class="row-actions">
212
- <button class="move-btn" data-index="${i}" data-dir="up" title="نقل لأعلى"></button>
213
- <button class="move-btn" data-index="${i}" data-dir="down" title="نقل لأسفل">↓</button>
214
- <button class="delete-btn" data-index="${i}" title="حذف الملف">×</button>
215
- </div>
216
- </li>`
217
- )
218
- .join("")}
219
  </ul>
220
  `;
221
 
222
  if (files.length > MAX_RECOMMENDED_FILES) {
223
- setStatus(
224
- "تنبيه: عدد الملفات كبير، قد تستغرق عملية الدمج وقتًا أطول على بعض الأجهزة.",
225
- "warning"
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);
@@ -241,7 +243,7 @@
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);
@@ -249,11 +251,9 @@
249
  if (isNaN(index)) return;
250
 
251
  if (dir === "up" && index > 0) {
252
- [selectedFiles[index - 1], selectedFiles[index]] =
253
- [selectedFiles[index], selectedFiles[index - 1]];
254
  } else if (dir === "down" && index < selectedFiles.length - 1) {
255
- [selectedFiles[index + 1], selectedFiles[index]] =
256
- [selectedFiles[index], selectedFiles[index + 1]];
257
  }
258
  renderFileList(selectedFiles);
259
  });
@@ -272,14 +272,15 @@
272
  URL.revokeObjectURL(url);
273
  }
274
 
275
- // إضافة ملفات على دفعات
276
  filesInput.addEventListener("change", () => {
277
  const newFilesRaw = Array.from(filesInput.files || []);
278
  if (!newFilesRaw.length) return;
279
 
280
  const newFiles = newFilesRaw.filter((f) => isImage(f) || isPDF(f));
281
- const map = new Map();
282
 
 
 
283
  [...selectedFiles, ...newFiles].forEach((f) => {
284
  const key = `${f.name}|${f.size}|${f.lastModified}`;
285
  if (!map.has(key)) map.set(key, f);
@@ -287,8 +288,9 @@
287
 
288
  selectedFiles = Array.from(map.values());
289
  renderFileList(selectedFiles);
 
290
  setStatus("");
291
- filesInput.value = "";
292
  });
293
 
294
  // مسح الكل
@@ -297,10 +299,11 @@
297
  renderFileList([]);
298
  setStatus("تم مسح جميع الملفات المختارة.", "ok");
299
  filesInput.value = "";
 
300
  showProgress(false);
301
  });
302
 
303
- // عملية الدمج
304
  mergeBtn.addEventListener("click", async () => {
305
  const files = [...selectedFiles];
306
 
@@ -315,23 +318,19 @@
315
  return;
316
  }
317
 
318
- const { hasImages, hasPDFs } = getFilesInfo(files);
319
- if (!hasImages && !hasPDFs) {
320
- setStatus("لا توجد ملفات مدعومة للدمج.", "error");
321
- return;
322
- }
323
-
324
  renderFileList(files);
325
 
326
  try {
327
- setStatus("جاري معالجة الملفات...", "loading");
328
  showProgress(true);
 
329
  mergeBtn.disabled = true;
330
  mergeBtn.classList.add("disabled");
331
  filesInput.disabled = true;
332
  clearBtn.disabled = true;
333
 
334
  const pdfDoc = await PDFLib.PDFDocument.create();
 
335
  const totalSteps = files.length;
336
  let currentStep = 0;
337
 
@@ -339,32 +338,30 @@
339
  const bytes = await file.arrayBuffer();
340
 
341
  if (isPDF(file)) {
342
- // نسخ صفحات PDF كما هي
343
  const donorPdf = await PDFLib.PDFDocument.load(bytes);
344
- const pages = await pdfDoc.copyPages(
345
- donorPdf,
346
- donorPdf.getPageIndices()
347
- );
348
  pages.forEach((p) => pdfDoc.addPage(p));
349
  } else if (isImage(file)) {
350
- // إدراج الصورة في صفحة جديدة بنفس أبعاد الصورة (بدون تغيير المقاس)
351
  const lower = file.name.toLowerCase();
352
  let image;
 
353
  if (
354
  file.type === "image/jpeg" ||
355
  file.type === "image/jpg" ||
356
  lower.endsWith(".jpg") ||
357
  lower.endsWith(".jpeg")
358
  ) {
359
- image = await pdfDoc.embedJpg(bytes);
360
  } else {
361
- image = await pdfDoc.embedPng(bytes);
362
  }
363
 
364
  const imgW = image.width;
365
  const imgH = image.height;
366
 
367
- // صفحة بنفس أبعاد الصورة
368
  const page = pdfDoc.addPage([imgW, imgH]);
369
  page.drawImage(image, { x: 0, y: 0, width: imgW, height: imgH });
370
  }
@@ -373,15 +370,16 @@
373
  setProgress(currentStep, totalSteps);
374
  }
375
 
376
- const pdfBytes = await pdfDoc.save();
 
377
 
378
- // الاسم الافتراضي: تاريخ اليوم إذا لم يُكتب اسم
379
  const defaultName = getTodayDateString() + ".pdf";
380
- const outName =
381
- (outputNameInput.value || defaultName).trim() || defaultName;
382
 
383
  downloadPdf(pdfBytes, outName);
384
- setStatus("تم إنشاء ملف PDF النهائي بنجاح.", "ok");
 
385
  showProgress(false);
386
 
387
  selectedFiles = [];
@@ -389,7 +387,7 @@
389
  filesInput.value = "";
390
  } catch (err) {
391
  console.error(err);
392
- setStatus("حدث خطأ أثناء المعالجة. تأكد من الملفات وحاول مرة أخرى.", "error");
393
  showProgress(false);
394
  } finally {
395
  mergeBtn.disabled = false;
 
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">
 
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
+ يدعم دمج صور (JPG/PNG) وملفات 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
+ <!-- اختيار الملفات -->
55
  <div class="card-section card-select">
56
+ <h2 class="card-title">اختيار الملفات المراد دمجها</h2>
57
+ <p class="hint">
58
+ ✅ الدمج حسب الترتيب في القائمة. <br>
59
+ ✅ الصور: تُدرج كما هي (بدون ضغط/بدون تغيير دقة/بدون تغيير مقاس). <br>
60
+ ✅ PDF: يتم نسخ الصفحات كما هي.
61
+ </p>
62
 
63
  <label class="file-picker">
64
  <span class="file-picker-icon">📂</span>
 
67
  </label>
68
  </div>
69
 
70
+ <!-- قائمة الملفات -->
71
  <div class="card-section">
72
  <div id="fileList" class="file-list hidden"></div>
73
  </div>
74
 
75
+ <!-- الإخراج -->
76
  <div class="card-section card-output">
77
  <div class="card-row inline">
78
  <label for="outputName" class="card-label">
79
  اسم ملف الإخراج (اختياري)
80
  <span class="label-note">
81
+ إذا تركته فارغًا سيتم استخدام تاريخ اليوم تلقائيًا.
82
+ <strong>مثال: 2026-01-27.pdf</strong>
83
  </span>
84
  </label>
85
  <input
 
192
 
193
  fileListDiv.classList.remove("hidden");
194
  const { hasImages, hasPDFs } = getFilesInfo(files);
195
+
196
  let modeText = "";
197
  if (hasImages && hasPDFs) {
198
+ modeText = "الوضع الحالي: دمج صور + ملفات PDF في ملف واحد (بدون تغيير الدقة/المقاس).";
199
  } else if (hasPDFs) {
200
+ modeText = "الوضع الحالي: دمج ملفات PDF فقط (نسخ الصفحات كما هي).";
201
  } else if (hasImages) {
202
+ modeText = "الوضع الحالي: تحويل صور إلى PDF (كل صورة بصفحة بنفس أبعادها الأصلية).";
 
 
203
  }
204
 
205
  fileListDiv.innerHTML = `
206
  <div class="file-list-header">
207
  <span>الملفات المختارة: ${files.length}</span>
208
+ <span class="file-note">رتّب الملفات أو احذف أي ملف قبل الدمج.</span>
209
  </div>
210
  <div class="mode-label">${modeText}</div>
211
  <ul class="file-list-ul">
212
+ ${files.map((f, i) => `
213
+ <li>
214
+ <span class="index">${i + 1}</span>
215
+ <span class="name" title="${f.name}">${f.name}</span>
216
+ <span class="size">${(f.size / 1024).toFixed(1)} كيلوبايت</span>
217
+ <div class="row-actions">
218
+ <button class="move-btn" data-index="${i}" data-dir="up" title="نقل لأعلى">↑</button>
219
+ <button class="move-btn" data-index="${i}" data-dir="down" title="نقل لأسفل">↓</button>
220
+ <button class="delete-btn" data-index="${i}" title="حذف الملف">×</button>
221
+ </div>
222
+ </li>
223
+ `).join("")}
 
 
 
224
  </ul>
225
  `;
226
 
227
  if (files.length > MAX_RECOMMENDED_FILES) {
228
+ setStatus("تنبيه: عدد الملفات كبير، قد تستغرق عملية الدمج وقتًا أطول على بعض الأجهزة.", "warning");
 
 
 
229
  }
230
 
231
+ // حذف
232
  fileListDiv.querySelectorAll(".delete-btn").forEach((btn) => {
233
  btn.addEventListener("click", (e) => {
234
  const index = parseInt(e.currentTarget.dataset.index, 10);
 
243
  });
244
  });
245
 
246
+ // ترتيب
247
  fileListDiv.querySelectorAll(".move-btn").forEach((btn) => {
248
  btn.addEventListener("click", (e) => {
249
  const index = parseInt(e.currentTarget.dataset.index, 10);
 
251
  if (isNaN(index)) return;
252
 
253
  if (dir === "up" && index > 0) {
254
+ [selectedFiles[index - 1], selectedFiles[index]] = [selectedFiles[index], selectedFiles[index - 1]];
 
255
  } else if (dir === "down" && index < selectedFiles.length - 1) {
256
+ [selectedFiles[index + 1], selectedFiles[index]] = [selectedFiles[index], selectedFiles[index + 1]];
 
257
  }
258
  renderFileList(selectedFiles);
259
  });
 
272
  URL.revokeObjectURL(url);
273
  }
274
 
275
+ // إضافة ملفات على دفعات (بدون فقد التحديد)
276
  filesInput.addEventListener("change", () => {
277
  const newFilesRaw = Array.from(filesInput.files || []);
278
  if (!newFilesRaw.length) return;
279
 
280
  const newFiles = newFilesRaw.filter((f) => isImage(f) || isPDF(f));
 
281
 
282
+ // إزالة التكرار (بالاسم+الحجم+آخر تعديل)
283
+ const map = new Map();
284
  [...selectedFiles, ...newFiles].forEach((f) => {
285
  const key = `${f.name}|${f.size}|${f.lastModified}`;
286
  if (!map.has(key)) map.set(key, f);
 
288
 
289
  selectedFiles = Array.from(map.values());
290
  renderFileList(selectedFiles);
291
+
292
  setStatus("");
293
+ filesInput.value = ""; // يسمح بإضافة ملفات مرة أخرى حتى لو نفس الاسم
294
  });
295
 
296
  // مسح الكل
 
299
  renderFileList([]);
300
  setStatus("تم مسح جميع الملفات المختارة.", "ok");
301
  filesInput.value = "";
302
+ outputNameInput.value = "";
303
  showProgress(false);
304
  });
305
 
306
+ // الدمج
307
  mergeBtn.addEventListener("click", async () => {
308
  const files = [...selectedFiles];
309
 
 
318
  return;
319
  }
320
 
 
 
 
 
 
 
321
  renderFileList(files);
322
 
323
  try {
324
+ setStatus("جاري الدمج بدون تغيير الدقة...", "loading");
325
  showProgress(true);
326
+
327
  mergeBtn.disabled = true;
328
  mergeBtn.classList.add("disabled");
329
  filesInput.disabled = true;
330
  clearBtn.disabled = true;
331
 
332
  const pdfDoc = await PDFLib.PDFDocument.create();
333
+
334
  const totalSteps = files.length;
335
  let currentStep = 0;
336
 
 
338
  const bytes = await file.arrayBuffer();
339
 
340
  if (isPDF(file)) {
341
+ // نسخ صفحات PDF كما هي تمامًا
342
  const donorPdf = await PDFLib.PDFDocument.load(bytes);
343
+ const pages = await pdfDoc.copyPages(donorPdf, donorPdf.getPageIndices());
 
 
 
344
  pages.forEach((p) => pdfDoc.addPage(p));
345
  } else if (isImage(file)) {
346
+ // إدراج الصورة كما هي: لا ضغط، لا تغيير بكسلات، لا تغيير تحجيم
347
  const lower = file.name.toLowerCase();
348
  let image;
349
+
350
  if (
351
  file.type === "image/jpeg" ||
352
  file.type === "image/jpg" ||
353
  lower.endsWith(".jpg") ||
354
  lower.endsWith(".jpeg")
355
  ) {
356
+ image = await pdfDoc.embedJpg(bytes); // لا إعادة ترميز
357
  } else {
358
+ image = await pdfDoc.embedPng(bytes); // لا إعادة ترميز
359
  }
360
 
361
  const imgW = image.width;
362
  const imgH = image.height;
363
 
364
+ // صفحة بنفس أبعاد الصورة تمامًا
365
  const page = pdfDoc.addPage([imgW, imgH]);
366
  page.drawImage(image, { x: 0, y: 0, width: imgW, height: imgH });
367
  }
 
370
  setProgress(currentStep, totalSteps);
371
  }
372
 
373
+ // حفظ بدون ضغط خساري، وتحسين تنظيم الكائنات فقط
374
+ const pdfBytes = await pdfDoc.save({ useObjectStreams: true });
375
 
376
+ // الاسم الافتراضي = تاريخ اليوم
377
  const defaultName = getTodayDateString() + ".pdf";
378
+ const outName = (outputNameInput.value || defaultName).trim() || defaultName;
 
379
 
380
  downloadPdf(pdfBytes, outName);
381
+
382
+ setStatus("تم إنشاء ملف PDF النهائي بنجاح (بدون تغيير الدقة).", "ok");
383
  showProgress(false);
384
 
385
  selectedFiles = [];
 
387
  filesInput.value = "";
388
  } catch (err) {
389
  console.error(err);
390
+ setStatus("حدث خطأ أثناء الدمج. تأكد من الملفات وحاول مرة أخرى.", "error");
391
  showProgress(false);
392
  } finally {
393
  mergeBtn.disabled = false;