stat2025 commited on
Commit
be4e2eb
·
verified ·
1 Parent(s): d985689

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +239 -67
index.html CHANGED
@@ -2,56 +2,151 @@
2
  <html lang="ar" dir="rtl">
3
  <head>
4
  <meta charset="UTF-8" />
5
- <title>دمج الملفات إلى PDF - stat2025</title>
 
6
  <link rel="stylesheet" href="style.css" />
7
- <!-- مكتبة PDF-LIB للمعالجة داخل المتصفح -->
8
  <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js"></script>
9
  </head>
10
  <body>
11
- <div class="container">
12
- <h1>أداة دمج وتحويل الملفات إلى PDF</h1>
13
- <p class="subtitle">
14
- يتم تنفيذ جميع العمليات على جهازك مباشرة، ولا يتم تخزين أي ملف في الخادم.
15
- </p>
16
-
17
- <!-- اختيار نوع الملفات قبل الرفع -->
18
- <div class="card">
19
- <h2>① اختر نوع الملفات</h2>
20
- <label class="option">
21
- <input type="radio" name="fileType" value="images" checked />
22
- تحويل مجموعة صور (JPG/PNG) إلى ملف PDF واحد
23
- </label>
24
- <label class="option">
25
- <input type="radio" name="fileType" value="pdfs" />
26
- دمج ملفات PDF متعددة في ملف PDF واحد
27
- </label>
28
- </div>
29
-
30
- <!-- رفع الملفات -->
31
- <div class="card">
32
- <h2>② اختر الملفات</h2>
33
- <input id="files" type="file" multiple />
34
- <p class="hint">
35
- للصور: اختر ملفات JPG أو PNG.
36
- لملفات PDF: اختر ملفات PDF المراد دمجها.
37
- </p>
38
- </div>
39
-
40
- <!-- زر التنفيذ -->
41
- <button id="mergeBtn" class="btn-main">دمج وإنشاء ملف PDF</button>
42
-
43
- <!-- منطقة الرسائل -->
44
- <div id="status" class="status"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  </div>
46
 
47
  <script>
48
  const filesInput = document.getElementById("files");
49
  const mergeBtn = document.getElementById("mergeBtn");
50
  const statusDiv = document.getElementById("status");
 
 
 
51
 
52
- function setStatus(msg, isError = false) {
53
  statusDiv.textContent = msg;
54
- statusDiv.className = "status " + (isError ? "error" : "ok");
 
55
  }
56
 
57
  function getSelectedType() {
@@ -59,6 +154,29 @@
59
  return checked ? checked.value : null;
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  function downloadPdf(bytes, filename) {
63
  const blob = new Blob([bytes], { type: "application/pdf" });
64
  const url = URL.createObjectURL(blob);
@@ -71,32 +189,79 @@
71
  URL.revokeObjectURL(url);
72
  }
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  mergeBtn.addEventListener("click", async () => {
75
  const selectedType = getSelectedType();
76
- const files = Array.from(filesInput.files || []);
77
 
78
  if (!selectedType) {
79
- setStatus("الرجاء اختيار نوع الملفات ��ولاً.", true);
80
  return;
81
  }
82
 
83
  if (!files.length) {
84
- setStatus("الرجاء اختيار الملفات المراد معالجتها.", true);
85
  return;
86
  }
87
 
88
- // ترتيب حسب الاسم للحفاظ على التسلسل
89
- files.sort((a, b) =>
90
  a.name.localeCompare(b.name, undefined, { numeric: true })
91
  );
92
 
 
 
93
  try {
94
- setStatus("جاري المعالجة، يرجى الانتظار...");
 
 
95
 
96
  if (selectedType === "images") {
97
- // التحقق
98
  if (!files.every(f => f.type.startsWith("image/"))) {
99
- setStatus("جميع الملفات يجب أن تكون صور (JPG/PNG) عند اختيار هذا الخيار.", true);
 
 
100
  return;
101
  }
102
 
@@ -105,12 +270,13 @@
105
  for (const file of files) {
106
  const bytes = await file.arrayBuffer();
107
  let image;
 
108
 
109
  if (
110
  file.type === "image/jpeg" ||
111
  file.type === "image/jpg" ||
112
- file.name.toLowerCase().endsWith(".jpg") ||
113
- file.name.toLowerCase().endsWith(".jpeg")
114
  ) {
115
  image = await pdfDoc.embedJpg(bytes);
116
  } else {
@@ -120,35 +286,36 @@
120
  const imgWidth = image.width;
121
  const imgHeight = image.height;
122
 
123
- // حجم صفحة A4 تقريبي
124
- const pageWidth = 595.28;
125
- const pageHeight = 841.89;
126
 
127
  const page = pdfDoc.addPage([pageWidth, pageHeight]);
128
-
129
  const scale = Math.min(pageWidth / imgWidth, pageHeight / imgHeight);
130
  const drawWidth = imgWidth * scale;
131
  const drawHeight = imgHeight * scale;
132
  const x = (pageWidth - drawWidth) / 2;
133
  const y = (pageHeight - drawHeight) / 2;
134
 
135
- page.drawImage(image, {
136
- x,
137
- y,
138
- width: drawWidth,
139
- height: drawHeight
140
- });
141
  }
142
 
143
  const pdfBytes = await pdfDoc.save();
144
- downloadPdf(pdfBytes, "merged-images.pdf");
145
- setStatus("تم إنشاء وتحميل ملف PDF بنجاح. ✅");
 
146
  }
147
 
148
  if (selectedType === "pdfs") {
149
- // التحقق
150
- if (!files.every(f => f.type === "application/pdf" || f.name.toLowerCase().endsWith(".pdf"))) {
151
- setStatus("جميع الملفات يجب أن تكون بصيغة PDF عند اختيار هذا الخيار.", true);
 
 
 
 
 
 
 
152
  return;
153
  }
154
 
@@ -165,15 +332,20 @@
165
  }
166
 
167
  const pdfBytes = await pdfDoc.save();
168
- downloadPdf(pdfBytes, "merged-pdfs.pdf");
169
- setStatus("تم دمج ملفات PDF وتحميل الملف النهائي. ✅");
 
170
  }
171
 
172
- // تنظيف: لا نحتفظ بأي ملفات - كل شيء في الذاكرة فقط
173
  filesInput.value = "";
 
174
  } catch (err) {
175
  console.error(err);
176
- setStatus("حدث خطأ أثناء المعالجة. تأكد من الملفات وحاول مرة أخرى.", true);
 
 
 
177
  }
178
  });
179
  </script>
 
2
  <html lang="ar" dir="rtl">
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <title>stat2025 | دمج وتحويل الملفات إلى 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>
9
  </head>
10
  <body>
11
+ <div class="page">
12
+
13
+ <!-- شريط علوي -->
14
+ <header class="topbar">
15
+ <div class="brand">
16
+ <div class="logo-circle">P</div>
17
+ <div class="brand-text">
18
+ <div class="brand-title">stat2025 PDF Studio</div>
19
+ <div class="brand-sub">تحويل ودمج آمن بالكامل على جهازك</div>
20
+ </div>
21
+ </div>
22
+ <nav class="nav">
23
+ <a href="#" class="nav-link active">الرئيسية</a>
24
+ <a href="#" class="nav-link muted">الخصوصية</a>
25
+ </nav>
26
+ </header>
27
+
28
+ <!-- المحتوى الرئيسي -->
29
+ <main class="main">
30
+
31
+ <section class="hero">
32
+ <h1>أرفع ملفاتك، دمج احترافي، حفظ على جهازك فقط.</h1>
33
+ <p>
34
+ أداة بسيطة وأنيقة لدمج الصور أو ملفات PDF في ملف واحد.
35
+ جميع العمليات تتم في المتصفح، دون رفع أو تخزين في أي خادم.
36
+ </p>
37
+ </section>
38
+
39
+ <!-- خطوات إرشادية -->
40
+ <section class="steps">
41
+ <div class="step">
42
+ <div class="step-number">1</div>
43
+ <div class="step-text">اختر نوع الملفات (صور أو PDF).</div>
44
+ </div>
45
+ <div class="step">
46
+ <div class="step-number">2</div>
47
+ <div class="step-text">قم بسحب وإفلات الملفات أو اختيارها من جهازك.</div>
48
+ </div>
49
+ <div class="step">
50
+ <div class="step-number">3</div>
51
+ <div class="step-text">اضغط دمج لتحميل ملف PDF النهائي فورًا.</div>
52
+ </div>
53
+ </section>
54
+
55
+ <!-- كرت الإعداد والرفع -->
56
+ <section class="card main-card">
57
+ <!-- نوع الملفات -->
58
+ <div class="card-row">
59
+ <h2 class="card-title">نوع الملفات</h2>
60
+ <div class="options">
61
+ <label class="option-pill">
62
+ <input type="radio" name="fileType" value="images" checked />
63
+ <span>صور ➜ PDF واحد</span>
64
+ </label>
65
+ <label class="option-pill">
66
+ <input type="radio" name="fileType" value="pdfs" />
67
+ <span>دمج ملفات PDF متعددة</span>
68
+ </label>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- منطقة السحب والإفلات -->
73
+ <div class="card-row">
74
+ <h2 class="card-title">اختيار الملفات</h2>
75
+
76
+ <div id="dropzone" class="dropzone">
77
+ <div class="dropzone-icon">
78
+ <!-- أيقونة بسيطة -->
79
+ <svg viewBox="0 0 24 24" aria-hidden="true">
80
+ <path d="M12 2L7 7h3v6h4V7h3L12 2zM5 15h14v5H5z"/>
81
+ </svg>
82
+ </div>
83
+ <div class="dropzone-text">
84
+ اسحب الملفات إلى هنا
85
+ <span>أو اضغط للاختيار من جهازك</span>
86
+ </div>
87
+ <input id="files" type="file" multiple />
88
+ </div>
89
+
90
+ <p class="hint">
91
+ للصور: يدعم JPG / JPEG / PNG.
92
+ لملفات PDF: يدعم دمج جميع الصفحا�� بالترتيب حسب اسم الملف.
93
+ </p>
94
+ </div>
95
+
96
+ <!-- قائمة الملفات المختارة -->
97
+ <div id="fileList" class="file-list hidden"></div>
98
+
99
+ <!-- اسم الملف الناتج (اختياري) -->
100
+ <div class="card-row inline">
101
+ <label for="outputName" class="card-title small">اسم ملف الإخراج (اختياري)</label>
102
+ <input id="outputName" type="text" class="output-input"
103
+ placeholder="مثال: report-merged.pdf" />
104
+ </div>
105
+
106
+ <!-- زر التنفيذ -->
107
+ <div class="actions">
108
+ <button id="mergeBtn" class="btn-main">
109
+ دمج وإنشاء ملف PDF
110
+ </button>
111
+ </div>
112
+
113
+ <!-- حالة النظام -->
114
+ <div id="status" class="status"></div>
115
+ </section>
116
+
117
+ <!-- جزء توضيح الخصوصية -->
118
+ <section class="privacy">
119
+ <h3>الخصوصية أولاً</h3>
120
+ <p>
121
+ ❖ لا يتم إرسال ملفاتك إلى أي خادم.
122
+ ❖ تتم جميع العمليات باستخدام JavaScript داخل متصفحك.
123
+ ❖ بعد التحميل، يمكنك إغلاق الصفحة بأمان دون أي بقايا.
124
+ </p>
125
+ </section>
126
+
127
+ </main>
128
+
129
+ <!-- تذييل -->
130
+ <footer class="footer">
131
+ <span>stat2025 • PDF Tools</span>
132
+ <span class="dot">•</span>
133
+ <span>إصدار متقدم بواجهة احترافية</span>
134
+ </footer>
135
+
136
  </div>
137
 
138
  <script>
139
  const filesInput = document.getElementById("files");
140
  const mergeBtn = document.getElementById("mergeBtn");
141
  const statusDiv = document.getElementById("status");
142
+ const fileListDiv = document.getElementById("fileList");
143
+ const dropzone = document.getElementById("dropzone");
144
+ const outputNameInput = document.getElementById("outputName");
145
 
146
+ function setStatus(msg, type = "ok") {
147
  statusDiv.textContent = msg;
148
+ statusDiv.className = "status " + type;
149
+ if (!msg) statusDiv.className = "status";
150
  }
151
 
152
  function getSelectedType() {
 
154
  return checked ? checked.value : null;
155
  }
156
 
157
+ function renderFileList(files) {
158
+ if (!files.length) {
159
+ fileListDiv.classList.add("hidden");
160
+ fileListDiv.innerHTML = "";
161
+ return;
162
+ }
163
+ fileListDiv.classList.remove("hidden");
164
+ fileListDiv.innerHTML = `
165
+ <div class="file-list-header">
166
+ <span>الملفات المختارة (${files.length})</span>
167
+ <span class="file-note">يتم الدمج حسب الترتيب الأبجدي لاسم الملف.</span>
168
+ </div>
169
+ <ul class="file-list-ul">
170
+ ${files
171
+ .map(
172
+ (f, i) =>
173
+ `<li><span class="index">${i + 1}</span><span class="name">${f.name}</span><span class="size">${(f.size / 1024).toFixed(1)} كيلوبايت</span></li>`
174
+ )
175
+ .join("")}
176
+ </ul>
177
+ `;
178
+ }
179
+
180
  function downloadPdf(bytes, filename) {
181
  const blob = new Blob([bytes], { type: "application/pdf" });
182
  const url = URL.createObjectURL(blob);
 
189
  URL.revokeObjectURL(url);
190
  }
191
 
192
+ function getFilesArray() {
193
+ return Array.from(filesInput.files || []);
194
+ }
195
+
196
+ // تعامل مع السحب والإفلات
197
+ dropzone.addEventListener("click", () => filesInput.click());
198
+
199
+ ;["dragenter", "dragover"].forEach(evtName => {
200
+ dropzone.addEventListener(evtName, e => {
201
+ e.preventDefault();
202
+ e.stopPropagation();
203
+ dropzone.classList.add("hover");
204
+ });
205
+ });
206
+
207
+ ;["dragleave", "drop"].forEach(evtName => {
208
+ dropzone.addEventListener(evtName, e => {
209
+ e.preventDefault();
210
+ e.stopPropagation();
211
+ if (evtName !== "drop") dropzone.classList.remove("hover");
212
+ });
213
+ });
214
+
215
+ dropzone.addEventListener("drop", e => {
216
+ dropzone.classList.remove("hover");
217
+ const dtFiles = Array.from(e.dataTransfer.files || []);
218
+ if (dtFiles.length) {
219
+ // نحتاج لإنشاء FileList جديدة: نستخدم DataTransfer
220
+ const dataTransfer = new DataTransfer();
221
+ dtFiles.forEach(f => dataTransfer.items.add(f));
222
+ filesInput.files = dataTransfer.files;
223
+ renderFileList(getFilesArray());
224
+ setStatus("");
225
+ }
226
+ });
227
+
228
+ // تحديث القائمة عند اختيار ملفات
229
+ filesInput.addEventListener("change", () => {
230
+ renderFileList(getFilesArray());
231
+ setStatus("");
232
+ });
233
+
234
  mergeBtn.addEventListener("click", async () => {
235
  const selectedType = getSelectedType();
236
+ let files = getFilesArray();
237
 
238
  if (!selectedType) {
239
+ setStatus("الرجاء اختيار نوع الملفات أولاً.", "error");
240
  return;
241
  }
242
 
243
  if (!files.length) {
244
+ setStatus("الرجاء اختيار الملفات المراد معالجتها.", "error");
245
  return;
246
  }
247
 
248
+ // ترتيب حسب الاسم
249
+ files = files.sort((a, b) =>
250
  a.name.localeCompare(b.name, undefined, { numeric: true })
251
  );
252
 
253
+ renderFileList(files);
254
+
255
  try {
256
+ setStatus("جاري معالجة الملفات... لا تُغلق الصفحة.", "loading");
257
+ mergeBtn.disabled = true;
258
+ mergeBtn.classList.add("disabled");
259
 
260
  if (selectedType === "images") {
 
261
  if (!files.every(f => f.type.startsWith("image/"))) {
262
+ setStatus("عند اختيار (صور ➜ PDF) يجب أن تكون جميع الملفات صورًا.", "error");
263
+ mergeBtn.disabled = false;
264
+ mergeBtn.classList.remove("disabled");
265
  return;
266
  }
267
 
 
270
  for (const file of files) {
271
  const bytes = await file.arrayBuffer();
272
  let image;
273
+ const lower = file.name.toLowerCase();
274
 
275
  if (
276
  file.type === "image/jpeg" ||
277
  file.type === "image/jpg" ||
278
+ lower.endsWith(".jpg") ||
279
+ lower.endsWith(".jpeg")
280
  ) {
281
  image = await pdfDoc.embedJpg(bytes);
282
  } else {
 
286
  const imgWidth = image.width;
287
  const imgHeight = image.height;
288
 
289
+ const pageWidth = 595.28; // A4
290
+ const pageHeight = 841.89; // A4
 
291
 
292
  const page = pdfDoc.addPage([pageWidth, pageHeight]);
 
293
  const scale = Math.min(pageWidth / imgWidth, pageHeight / imgHeight);
294
  const drawWidth = imgWidth * scale;
295
  const drawHeight = imgHeight * scale;
296
  const x = (pageWidth - drawWidth) / 2;
297
  const y = (pageHeight - drawHeight) / 2;
298
 
299
+ page.drawImage(image, { x, y, width: drawWidth, height: drawHeight });
 
 
 
 
 
300
  }
301
 
302
  const pdfBytes = await pdfDoc.save();
303
+ const outName = (outputNameInput.value || "merged-images.pdf").trim() || "merged-images.pdf";
304
+ downloadPdf(pdfBytes, outName);
305
+ setStatus("تم إنشاء ملف PDF من الصور وتحميله بنجاح. ✅", "ok");
306
  }
307
 
308
  if (selectedType === "pdfs") {
309
+ if (
310
+ !files.every(
311
+ f =>
312
+ f.type === "application/pdf" ||
313
+ f.name.toLowerCase().endsWith(".pdf")
314
+ )
315
+ ) {
316
+ setStatus("عند اختيار (دمج PDF) يجب أن تكون جميع الملفات بصيغة PDF.", "error");
317
+ mergeBtn.disabled = false;
318
+ mergeBtn.classList.remove("disabled");
319
  return;
320
  }
321
 
 
332
  }
333
 
334
  const pdfBytes = await pdfDoc.save();
335
+ const outName = (outputNameInput.value || "merged-pdfs.pdf").trim() || "merged-pdfs.pdf";
336
+ downloadPdf(pdfBytes, outName);
337
+ setStatus("تم دمج ملفات PDF وتحميل الملف النهائي. ✅", "ok");
338
  }
339
 
340
+ // تنظيف
341
  filesInput.value = "";
342
+ renderFileList([]);
343
  } catch (err) {
344
  console.error(err);
345
+ setStatus("حدث خطأ أثناء المعالجة. تأكد من الملفات وحاول مرة أخرى.", "error");
346
+ } finally {
347
+ mergeBtn.disabled = false;
348
+ mergeBtn.classList.remove("disabled");
349
  }
350
  });
351
  </script>