Spaces:
Running
Running
Update app.js
Browse files
app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
/* app.js —
|
| 2 |
|
| 3 |
-
/* ======== أعمدة الجدول
|
| 4 |
const EXPORT_COLUMNS = [
|
| 5 |
"التصنيف",
|
| 6 |
"نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
|
|
@@ -47,7 +47,8 @@ const TICKET_SEP = /\n\s*(?:\n|—+|-{3,}|={3,}|🔴+)+\s*\n/;
|
|
| 47 |
const arabicDigitsMap = {"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"};
|
| 48 |
function normalizeText(s){
|
| 49 |
if(typeof s!=="string") return "";
|
| 50 |
-
return s.replace(
|
|
|
|
| 51 |
.replace(/[٠-٩]/g, d => arabicDigitsMap[d] )
|
| 52 |
.replace(/[ــ]+/g,"")
|
| 53 |
.trim();
|
|
@@ -60,10 +61,9 @@ function alnumAr(s){
|
|
| 60 |
}
|
| 61 |
function digitsOnly(s){ return (s||"").replace(/\D+/g,""); }
|
| 62 |
|
| 63 |
-
/* وقت
|
| 64 |
function normalizeTimeLoose(val){
|
| 65 |
const t = normalizeText(val);
|
| 66 |
-
// HH:MM (مع أو بدون am/pm / ص/م)
|
| 67 |
let m = t.match(/(\d{1,2})[:٫\.\-:](\d{2})\s*(ص|صباح|صباحا|am|م|مساء|pm)?/i);
|
| 68 |
if(m){
|
| 69 |
let h = parseInt(m[1],10), mn = m[2], ampm = (m[3]||"").toLowerCase();
|
|
@@ -73,7 +73,6 @@ function normalizeTimeLoose(val){
|
|
| 73 |
}
|
| 74 |
return `${String(h).padStart(2,"0")}:${mn}`;
|
| 75 |
}
|
| 76 |
-
// HH فقط مع صباح/مساء
|
| 77 |
m = t.match(/(\d{1,2})\s*(ص|صباح|صباحا|am|م|مساء|pm)/i);
|
| 78 |
if(m){
|
| 79 |
let h = parseInt(m[1],10);
|
|
@@ -101,14 +100,14 @@ function normalizeDate(v){
|
|
| 101 |
function parseDateTime(raw){
|
| 102 |
const t = normalizeText(raw);
|
| 103 |
const d = normalizeDate(t);
|
| 104 |
-
const
|
| 105 |
-
if(d && /^\d{4}-\d{2}-\d{2}$/.test(d) &&
|
| 106 |
if(d && /^\d{4}-\d{2}-\d{2}$/.test(d)) return d;
|
| 107 |
-
if(
|
| 108 |
return t;
|
| 109 |
}
|
| 110 |
|
| 111 |
-
/*
|
| 112 |
function findAfterLabel(text, labels){
|
| 113 |
const hay = "\n" + normalizeText(text) + "\n";
|
| 114 |
for(const rawLbl of labels){
|
|
@@ -121,43 +120,54 @@ function findAfterLabel(text, labels){
|
|
| 121 |
return "";
|
| 122 |
}
|
| 123 |
|
| 124 |
-
/* تقسيم تذاكر
|
| 125 |
-
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
function splitTickets(raw){
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
if(!normalized) return [];
|
| 131 |
|
| 132 |
// 1) فواصل معروفة
|
| 133 |
-
if(TICKET_SEP.test(
|
| 134 |
-
return
|
| 135 |
}
|
| 136 |
|
| 137 |
-
// 2)
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
starts.push(pos);
|
| 144 |
-
}
|
| 145 |
if(starts.length >= 2){
|
| 146 |
-
|
|
|
|
| 147 |
for(let i=0;i<starts.length;i++){
|
| 148 |
const s = starts[i];
|
| 149 |
-
const e = i+1<starts.length ? starts[i+1] :
|
| 150 |
-
const slice =
|
| 151 |
if(slice) parts.push(slice);
|
| 152 |
}
|
| 153 |
return parts;
|
| 154 |
}
|
| 155 |
|
| 156 |
-
// 3) fallback: أسطر
|
| 157 |
-
return
|
| 158 |
}
|
| 159 |
|
| 160 |
-
/* استخراج الحقول
|
| 161 |
function extractFields(ticketText){
|
| 162 |
const text = normalizeText(ticketText);
|
| 163 |
const out = {
|
|
@@ -204,7 +214,7 @@ function extractFields(ticketText){
|
|
| 204 |
return out;
|
| 205 |
}
|
| 206 |
|
| 207 |
-
/* التصنيف */
|
| 208 |
function classifyTicket(text, fields){
|
| 209 |
const hay = normalizeText(`${text}\n${fields?.["نوع المشكلة"]||""}`).toLowerCase();
|
| 210 |
for(const label of CLASS_PRIORITY){
|
|
@@ -217,14 +227,13 @@ function classifyTicket(text, fields){
|
|
| 217 |
return "استفسار";
|
| 218 |
}
|
| 219 |
|
| 220 |
-
/*
|
| 221 |
function parseTicketsWithExtras(raw, agentName, defaultRegion){
|
| 222 |
const regionChosen = (defaultRegion || "").toString();
|
| 223 |
return splitTickets(raw||"").map(t => {
|
| 224 |
const f = extractFields(t);
|
| 225 |
const cls = classifyTicket(t, f);
|
| 226 |
const region = regionChosen ? regionChosen : (f["المنطقة"] || "");
|
| 227 |
-
|
| 228 |
return {
|
| 229 |
"التصنيف": cls,
|
| 230 |
"نوع المشكلة": f["نوع المشكلة"] || "",
|
|
@@ -241,7 +250,7 @@ function parseTicketsWithExtras(raw, agentName, defaultRegion){
|
|
| 241 |
});
|
| 242 |
}
|
| 243 |
|
| 244 |
-
/*
|
| 245 |
function buildTable(rows){
|
| 246 |
const theadRow = document.getElementById("theadRow");
|
| 247 |
const tbody = document.getElementById("tbody");
|
|
@@ -274,7 +283,7 @@ function readTable(){
|
|
| 274 |
return rows;
|
| 275 |
}
|
| 276 |
|
| 277 |
-
/*
|
| 278 |
function updateBadge(n){
|
| 279 |
const b = document.getElementById("countBadge");
|
| 280 |
b.textContent = n; b.hidden = (n===0);
|
|
@@ -311,7 +320,7 @@ document.addEventListener("input",(e)=>{
|
|
| 311 |
}
|
| 312 |
});
|
| 313 |
|
| 314 |
-
/*
|
| 315 |
function toast(msg){
|
| 316 |
const t = document.getElementById("toast");
|
| 317 |
t.textContent = msg; t.hidden = false;
|
|
@@ -319,7 +328,7 @@ function toast(msg){
|
|
| 319 |
setTimeout(()=>{ t.hidden = true; }, 2000);
|
| 320 |
}
|
| 321 |
|
| 322 |
-
/*
|
| 323 |
async function exportExcel(){
|
| 324 |
const rows = readTable();
|
| 325 |
if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
|
|
@@ -408,7 +417,7 @@ async function exportExcel(){
|
|
| 408 |
toast("تم تنزيل الملف بتنسيق القالب.");
|
| 409 |
}
|
| 410 |
|
| 411 |
-
/*
|
| 412 |
async function copyToClipboardTSV(){
|
| 413 |
const rows = readTable();
|
| 414 |
if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; }
|
|
@@ -432,7 +441,7 @@ async function copyToClipboardTSV(){
|
|
| 432 |
}
|
| 433 |
}
|
| 434 |
|
| 435 |
-
/*
|
| 436 |
const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق
|
| 437 |
وقت حدوث المشكلة: 21/8/2025 7 صباحا
|
| 438 |
اسم صاحب المشكلة : نوف الناصر
|
|
@@ -441,7 +450,6 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
|
|
| 441 |
رقم الجوال: 0558174717
|
| 442 |
اسم المسح: الخبر 2025
|
| 443 |
اسم المنطقة: الشرقية
|
| 444 |
-
|
| 445 |
نوع المشكلة: مشكله بالدخول (حدث خطأ في التطبيق)
|
| 446 |
وقت حدوث المشكله:5:00 م
|
| 447 |
اسم صاحب المشكله: شيماء عبدالرحمن
|
|
@@ -450,7 +458,6 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
|
|
| 450 |
رقم الجوال:0562974417
|
| 451 |
المنطقه: الخبر
|
| 452 |
المسح: تحديث الاسعار
|
| 453 |
-
|
| 454 |
نوع المشكلة: معلق على صفحه البدايه
|
| 455 |
وقت حدوث المشكله: 10:00
|
| 456 |
اسم صاحب المشكله: امال صالح العبدالعزيز
|
|
@@ -460,8 +467,8 @@ const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاست
|
|
| 460 |
المنطقه: الخبر
|
| 461 |
المسح:اسعار الجمله`;
|
| 462 |
|
| 463 |
-
/*
|
| 464 |
-
const STATE_KEY = "
|
| 465 |
function ensureColumns(rows, agentName, defaultRegion){
|
| 466 |
if(!Array.isArray(rows)) return rows||[];
|
| 467 |
return rows.map(r=>{
|
|
@@ -503,7 +510,7 @@ function loadState(){
|
|
| 503 |
}catch{ return false; }
|
| 504 |
}
|
| 505 |
|
| 506 |
-
/* مسح الكل
|
| 507 |
function clearAll(){
|
| 508 |
const rawEl = document.getElementById("raw");
|
| 509 |
const tbody = document.getElementById("tbody");
|
|
@@ -520,19 +527,19 @@ function clearAll(){
|
|
| 520 |
toast("تم مسح كل البيانات.");
|
| 521 |
}
|
| 522 |
|
| 523 |
-
/* لصق
|
| 524 |
function normalizeForPaste(text){
|
| 525 |
-
const
|
| 526 |
-
const
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
if(
|
|
|
|
| 531 |
const parts=[];
|
| 532 |
-
for(let i=0;i<
|
| 533 |
-
const s
|
| 534 |
-
const slice = norm.slice(
|
| 535 |
-
if(slice) parts.push(slice);
|
| 536 |
}
|
| 537 |
return parts.join("\n\n🔴🔴🔴\n");
|
| 538 |
}
|
|
@@ -544,12 +551,10 @@ async function smartPasteInto(el){
|
|
| 544 |
const formatted = normalizeForPaste(txt || "");
|
| 545 |
if(formatted){ el.value = formatted; saveState(); toast("تم اللصق والتنظيم."); }
|
| 546 |
else { toast("الحافظة فارغة."); }
|
| 547 |
-
}catch{
|
| 548 |
-
toast("تعذّر قراءة الحافظة — الصق/ي يدويًا.");
|
| 549 |
-
}
|
| 550 |
}
|
| 551 |
|
| 552 |
-
/*
|
| 553 |
function init(){
|
| 554 |
const parseBtn = document.getElementById("btn-parse");
|
| 555 |
const exportBtn = document.getElementById("btn-export");
|
|
|
|
| 1 |
+
/* app.js — v10: Robust ticket splitting (no/one-line gaps), keep all previous features */
|
| 2 |
|
| 3 |
+
/* ======== أعمدة الجدول ======== */
|
| 4 |
const EXPORT_COLUMNS = [
|
| 5 |
"التصنيف",
|
| 6 |
"نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
|
|
|
|
| 47 |
const arabicDigitsMap = {"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"};
|
| 48 |
function normalizeText(s){
|
| 49 |
if(typeof s!=="string") return "";
|
| 50 |
+
return s.replace(/\r\n/g,"\n")
|
| 51 |
+
.replace(/[\u200f\u200e\u202a-\u202e\u2066-\u2069\u00a0]/g," ")
|
| 52 |
.replace(/[٠-٩]/g, d => arabicDigitsMap[d] )
|
| 53 |
.replace(/[ــ]+/g,"")
|
| 54 |
.trim();
|
|
|
|
| 61 |
}
|
| 62 |
function digitsOnly(s){ return (s||"").replace(/\D+/g,""); }
|
| 63 |
|
| 64 |
+
/* وقت مثل "7:10 ص" أو "7 صباحا" أو "7مساء" */
|
| 65 |
function normalizeTimeLoose(val){
|
| 66 |
const t = normalizeText(val);
|
|
|
|
| 67 |
let m = t.match(/(\d{1,2})[:٫\.\-:](\d{2})\s*(ص|صباح|صباحا|am|م|مساء|pm)?/i);
|
| 68 |
if(m){
|
| 69 |
let h = parseInt(m[1],10), mn = m[2], ampm = (m[3]||"").toLowerCase();
|
|
|
|
| 73 |
}
|
| 74 |
return `${String(h).padStart(2,"0")}:${mn}`;
|
| 75 |
}
|
|
|
|
| 76 |
m = t.match(/(\d{1,2})\s*(ص|صباح|صباحا|am|م|مساء|pm)/i);
|
| 77 |
if(m){
|
| 78 |
let h = parseInt(m[1],10);
|
|
|
|
| 100 |
function parseDateTime(raw){
|
| 101 |
const t = normalizeText(raw);
|
| 102 |
const d = normalizeDate(t);
|
| 103 |
+
const hhmm = normalizeTimeLoose(t);
|
| 104 |
+
if(d && /^\d{4}-\d{2}-\d{2}$/.test(d) && hhmm) return `${d} ${hhmm}`;
|
| 105 |
if(d && /^\d{4}-\d{2}-\d{2}$/.test(d)) return d;
|
| 106 |
+
if(hhmm) return hhmm;
|
| 107 |
return t;
|
| 108 |
}
|
| 109 |
|
| 110 |
+
/* قيمة بعد اسم حقل */
|
| 111 |
function findAfterLabel(text, labels){
|
| 112 |
const hay = "\n" + normalizeText(text) + "\n";
|
| 113 |
for(const rawLbl of labels){
|
|
|
|
| 120 |
return "";
|
| 121 |
}
|
| 122 |
|
| 123 |
+
/* ==== تقسيم تذاكر قوي ==== */
|
| 124 |
+
const START_LABEL_GROUPS = [
|
| 125 |
+
["نوع المشكلة","نوع المشكله"],
|
| 126 |
+
["وقت حدوث المشكلة","وقت حدوث المشكله","وقت المشكلة","وقت حدوث"],
|
| 127 |
+
["اسم صاحب المشكلة","اسم صاحب المشكله","اسم صاحب البلاغ","الاسم"],
|
| 128 |
+
];
|
| 129 |
+
function findStartsByLabels(text, labels){
|
| 130 |
+
const lblRe = labels.map(l=>l.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join("|");
|
| 131 |
+
const re = new RegExp(`(^|\\n)\\s*(?:-+|\\*+|•+)?\\s*(?:${lblRe})\\s*[::]`, "gi");
|
| 132 |
+
const starts = [];
|
| 133 |
+
let m;
|
| 134 |
+
while((m = re.exec(text))){
|
| 135 |
+
starts.push(m.index + (m[1] ? m[1].length : 0));
|
| 136 |
+
}
|
| 137 |
+
return starts;
|
| 138 |
+
}
|
| 139 |
function splitTickets(raw){
|
| 140 |
+
const text = normalizeText(raw);
|
| 141 |
+
if(!text) return [];
|
|
|
|
| 142 |
|
| 143 |
// 1) فواصل معروفة
|
| 144 |
+
if(TICKET_SEP.test(text)){
|
| 145 |
+
return text.split(TICKET_SEP).map(p=>p.trim()).filter(Boolean);
|
| 146 |
}
|
| 147 |
|
| 148 |
+
// 2) ابحث عن بدايات حسب مجموعات الحقول (نوع/وقت/اسم)
|
| 149 |
+
let starts = findStartsByLabels(text, START_LABEL_GROUPS[0]); // نوع المشكلة
|
| 150 |
+
if(starts.length < 2) starts = [...new Set([...starts, ...findStartsByLabels(text, START_LABEL_GROUPS[1])])]; // وقت حدوث
|
| 151 |
+
if(starts.length < 2) starts = [...new Set([...starts, ...findStartsByLabels(text, START_LABEL_GROUPS[2])])]; // اسم صاحب
|
| 152 |
+
|
| 153 |
+
// لو طلعنا بأكثر من بداية، قصّي بينها
|
|
|
|
|
|
|
| 154 |
if(starts.length >= 2){
|
| 155 |
+
starts.sort((a,b)=>a-b);
|
| 156 |
+
const parts=[];
|
| 157 |
for(let i=0;i<starts.length;i++){
|
| 158 |
const s = starts[i];
|
| 159 |
+
const e = i+1<starts.length ? starts[i+1] : text.length;
|
| 160 |
+
const slice = text.slice(s,e).trim();
|
| 161 |
if(slice) parts.push(slice);
|
| 162 |
}
|
| 163 |
return parts;
|
| 164 |
}
|
| 165 |
|
| 166 |
+
// 3) fallback: أسطر فارغة/تجميع تلقائي
|
| 167 |
+
return text.split(/\n\s*\n+/).map(p=>p.trim()).filter(Boolean);
|
| 168 |
}
|
| 169 |
|
| 170 |
+
/* ==== استخراج الحقول ==== */
|
| 171 |
function extractFields(ticketText){
|
| 172 |
const text = normalizeText(ticketText);
|
| 173 |
const out = {
|
|
|
|
| 214 |
return out;
|
| 215 |
}
|
| 216 |
|
| 217 |
+
/* ==== التصنيف ==== */
|
| 218 |
function classifyTicket(text, fields){
|
| 219 |
const hay = normalizeText(`${text}\n${fields?.["نوع المشكلة"]||""}`).toLowerCase();
|
| 220 |
for(const label of CLASS_PRIORITY){
|
|
|
|
| 227 |
return "استفسار";
|
| 228 |
}
|
| 229 |
|
| 230 |
+
/* ==== تطبيق المنطقة/الاسم ==== */
|
| 231 |
function parseTicketsWithExtras(raw, agentName, defaultRegion){
|
| 232 |
const regionChosen = (defaultRegion || "").toString();
|
| 233 |
return splitTickets(raw||"").map(t => {
|
| 234 |
const f = extractFields(t);
|
| 235 |
const cls = classifyTicket(t, f);
|
| 236 |
const region = regionChosen ? regionChosen : (f["المنطقة"] || "");
|
|
|
|
| 237 |
return {
|
| 238 |
"التصنيف": cls,
|
| 239 |
"نوع المشكلة": f["نوع المشكلة"] || "",
|
|
|
|
| 250 |
});
|
| 251 |
}
|
| 252 |
|
| 253 |
+
/* ==== جدول ==== */
|
| 254 |
function buildTable(rows){
|
| 255 |
const theadRow = document.getElementById("theadRow");
|
| 256 |
const tbody = document.getElementById("tbody");
|
|
|
|
| 283 |
return rows;
|
| 284 |
}
|
| 285 |
|
| 286 |
+
/* ==== شارة + أزرار + تحقق ==== */
|
| 287 |
function updateBadge(n){
|
| 288 |
const b = document.getElementById("countBadge");
|
| 289 |
b.textContent = n; b.hidden = (n===0);
|
|
|
|
| 320 |
}
|
| 321 |
});
|
| 322 |
|
| 323 |
+
/* ==== Toast ==== */
|
| 324 |
function toast(msg){
|
| 325 |
const t = document.getElementById("toast");
|
| 326 |
t.textContent = msg; t.hidden = false;
|
|
|
|
| 328 |
setTimeout(()=>{ t.hidden = true; }, 2000);
|
| 329 |
}
|
| 330 |
|
| 331 |
+
/* ==== تصدير ExcelJS كما هو سابقًا (قالب منسّق) ==== */
|
| 332 |
async function exportExcel(){
|
| 333 |
const rows = readTable();
|
| 334 |
if(!rows.length){ toast("لا يوجد بيانات لتصديرها."); return; }
|
|
|
|
| 417 |
toast("تم تنزيل الملف بتنسيق القالب.");
|
| 418 |
}
|
| 419 |
|
| 420 |
+
/* ==== نسخ إلى الحافظة (TSV) ==== */
|
| 421 |
async function copyToClipboardTSV(){
|
| 422 |
const rows = readTable();
|
| 423 |
if(!rows.length){ toast("لا يوجد بيانات لنسخها."); return; }
|
|
|
|
| 441 |
}
|
| 442 |
}
|
| 443 |
|
| 444 |
+
/* ==== مثال ==== */
|
| 445 |
const SAMPLE = `نوع المشكلة : لا استطيع اكمال الاستمارة بسبب تعليق
|
| 446 |
وقت حدوث المشكلة: 21/8/2025 7 صباحا
|
| 447 |
اسم صاحب المشكلة : نوف الناصر
|
|
|
|
| 450 |
رقم الجوال: 0558174717
|
| 451 |
اسم المسح: الخبر 2025
|
| 452 |
اسم المنطقة: الشرقية
|
|
|
|
| 453 |
نوع المشكلة: مشكله بالدخول (حدث خطأ في التطبيق)
|
| 454 |
وقت حدوث المشكله:5:00 م
|
| 455 |
اسم صاحب المشكله: شيماء عبدالرحمن
|
|
|
|
| 458 |
رقم الجوال:0562974417
|
| 459 |
المنطقه: الخبر
|
| 460 |
المسح: تحديث الاسعار
|
|
|
|
| 461 |
نوع المشكلة: معلق على صفحه البدايه
|
| 462 |
وقت حدوث المشكله: 10:00
|
| 463 |
اسم صاحب المشكله: امال صالح العبدالعزيز
|
|
|
|
| 467 |
المنطقه: الخبر
|
| 468 |
المسح:اسعار الجمله`;
|
| 469 |
|
| 470 |
+
/* ==== تخزين ==== */
|
| 471 |
+
const STATE_KEY = "ticketParserState_v10";
|
| 472 |
function ensureColumns(rows, agentName, defaultRegion){
|
| 473 |
if(!Array.isArray(rows)) return rows||[];
|
| 474 |
return rows.map(r=>{
|
|
|
|
| 510 |
}catch{ return false; }
|
| 511 |
}
|
| 512 |
|
| 513 |
+
/* ==== مسح الكل ==== */
|
| 514 |
function clearAll(){
|
| 515 |
const rawEl = document.getElementById("raw");
|
| 516 |
const tbody = document.getElementById("tbody");
|
|
|
|
| 527 |
toast("تم مسح كل البيانات.");
|
| 528 |
}
|
| 529 |
|
| 530 |
+
/* ==== لصق مُنظم (يضيف فواصل) ==== */
|
| 531 |
function normalizeForPaste(text){
|
| 532 |
+
const norm = normalizeText(text||"");
|
| 533 |
+
const starts = findStartsByLabels(norm, ["نوع المشكلة","نوع المشكله"]);
|
| 534 |
+
let s = starts;
|
| 535 |
+
if(s.length<2) s = [...new Set([...s, ...findStartsByLabels(norm, START_LABEL_GROUPS[1])])];
|
| 536 |
+
if(s.length<2) s = [...new Set([...s, ...findStartsByLabels(norm, START_LABEL_GROUPS[2])])];
|
| 537 |
+
if(s.length>=2){
|
| 538 |
+
s.sort((a,b)=>a-b);
|
| 539 |
const parts=[];
|
| 540 |
+
for(let i=0;i<s.length;i++){
|
| 541 |
+
const st=s[i], en=i+1<s.length?s[i+1]:norm.length;
|
| 542 |
+
const slice = norm.slice(st,en).trim(); if(slice) parts.push(slice);
|
|
|
|
| 543 |
}
|
| 544 |
return parts.join("\n\n🔴🔴🔴\n");
|
| 545 |
}
|
|
|
|
| 551 |
const formatted = normalizeForPaste(txt || "");
|
| 552 |
if(formatted){ el.value = formatted; saveState(); toast("تم اللصق والتنظيم."); }
|
| 553 |
else { toast("الحافظة فارغة."); }
|
| 554 |
+
}catch{ toast("تعذّر قراءة الحافظة — الصق/ي يدويًا."); }
|
|
|
|
|
|
|
| 555 |
}
|
| 556 |
|
| 557 |
+
/* ==== تهيئة ==== */
|
| 558 |
function init(){
|
| 559 |
const parseBtn = document.getElementById("btn-parse");
|
| 560 |
const exportBtn = document.getElementById("btn-export");
|