| |
| |
| |
| |
|
|
|
|
| (global => {
|
| 'use strict';
|
|
|
| |
| |
| |
| |
|
|
| function normalizePhone(phone) {
|
| if (global.FritreeContactsResolver && typeof global.FritreeContactsResolver.normalize === 'function') {
|
| return global.FritreeContactsResolver.normalize(phone);
|
| }
|
| return phone ? String(phone).replace(/[^0-9+]/g, '') : '';
|
| }
|
|
|
| |
| |
| |
| |
| |
|
|
| function downloadBlob(content, type, filename) {
|
| const blob = new Blob([content], { type });
|
| const url = URL.createObjectURL(blob);
|
| const a = document.createElement('a');
|
| a.href = url;
|
| a.download = filename;
|
| a.click();
|
| URL.revokeObjectURL(url);
|
| }
|
|
|
| |
| |
|
|
| const BulkActionsEngine = {
|
| |
| |
| |
| |
|
|
| parseVCard: function(vcardText) {
|
| const parsedContacts = [];
|
| if (!vcardText) return parsedContacts;
|
|
|
| const vcardBlocks = vcardText.split(/END:VCARD/i);
|
| vcardBlocks.forEach(block => {
|
| if (!block.includes('BEGIN:VCARD')) return;
|
| let fullName = '';
|
| let phones = [];
|
| let email = '';
|
| let company = '';
|
| let jobTitle = '';
|
| let note = '';
|
|
|
| const lines = block.split(/\r?\n/);
|
| lines.forEach(line => {
|
| const trimmed = line.trim();
|
| if (!trimmed) return;
|
|
|
| if (trimmed.toUpperCase().startsWith('FN:') || trimmed.toUpperCase().startsWith('FN;')) {
|
| fullName = trimmed.substring(trimmed.indexOf(':') + 1).trim();
|
| } else if (!fullName && (trimmed.toUpperCase().startsWith('N:') || trimmed.toUpperCase().startsWith('N;'))) {
|
| const rawN = trimmed.substring(trimmed.indexOf(':') + 1).trim();
|
| const nParts = rawN.split(';');
|
| fullName = ((nParts[1] ? nParts[1] + ' ' : '') + (nParts[0] || '')).trim();
|
| } else if (trimmed.toUpperCase().startsWith('TEL') || trimmed.toUpperCase().includes('TEL;')) {
|
| const phoneVal = trimmed.substring(trimmed.indexOf(':') + 1).trim();
|
| const norm = normalizePhone(phoneVal);
|
| if (norm && norm.length >= 7) {
|
| phones.push({ phone: norm, type: 'mobile' });
|
| }
|
| } else if (trimmed.toUpperCase().startsWith('EMAIL')) {
|
| email = trimmed.substring(trimmed.indexOf(':') + 1).trim();
|
| } else if (trimmed.toUpperCase().startsWith('ORG:')) {
|
| company = trimmed.substring(trimmed.indexOf(':') + 1).replace(/;/g, ' ').trim();
|
| } else if (trimmed.toUpperCase().startsWith('TITLE:')) {
|
| jobTitle = trimmed.substring(trimmed.indexOf(':') + 1).trim();
|
| } else if (trimmed.toUpperCase().startsWith('NOTE:')) {
|
| note = trimmed.substring(trimmed.indexOf(':') + 1).trim();
|
| }
|
| });
|
|
|
| if (phones.length > 0) {
|
| parsedContacts.push({
|
| phone: phones[0].phone,
|
| altPhones: phones.slice(1),
|
| name: fullName || 'جهة اتصال vCard',
|
| email: email,
|
| company: company,
|
| jobTitle: jobTitle,
|
| lifecycle: 'lead',
|
| priority: 'medium',
|
| source: 'import',
|
| notes: note,
|
| tags: ['vCard_Import']
|
| });
|
| }
|
| });
|
| return parsedContacts;
|
| },
|
|
|
| |
| |
| |
| |
|
|
| parseGoogleCSV: function(csvText) {
|
| const parsedContacts = [];
|
| if (!csvText) return parsedContacts;
|
|
|
| const lines = csvText.split(/\r?\n/);
|
| if (lines.length < 2) return parsedContacts;
|
|
|
| const parseCSVLine = (textLine) => {
|
| const arr = [];
|
| let quote = false;
|
| let col = '';
|
| for (let c = 0; c < textLine.length; c++) {
|
| const cc = textLine[c];
|
| if (cc === '"') {
|
| quote = !quote;
|
| } else if (cc === ',' && !quote) {
|
| arr.push(col.trim().replace(/^"|"$/g, ''));
|
| col = '';
|
| } else {
|
| col += cc;
|
| }
|
| }
|
| arr.push(col.trim().replace(/^"|"$/g, ''));
|
| return arr;
|
| };
|
|
|
| const headers = parseCSVLine(lines[0]).map(h => h.toLowerCase());
|
| const nameIdx = headers.findIndex(h => h.includes('name') && !h.includes('given') && !h.includes('family'));
|
| const givenNameIdx = headers.findIndex(h => h.includes('given name'));
|
| const familyNameIdx = headers.findIndex(h => h.includes('family name'));
|
|
|
| const phoneIndices = [];
|
| headers.forEach((h, i) => {
|
| if (h.includes('phone') || h.includes('mobile') || h.includes('cellular') || h === 'value') {
|
| phoneIndices.push(i);
|
| }
|
| });
|
|
|
| const emailIdx = headers.findIndex(h => h.includes('email') || h.includes('e-mail'));
|
| const orgIdx = headers.findIndex(h => h.includes('organization') || h.includes('company'));
|
| const titleIdx = headers.findIndex(h => h.includes('title'));
|
| const groupIdx = headers.findIndex(h => h.includes('group') || h.includes('membership'));
|
|
|
| for (let i = 1; i < lines.length; i++) {
|
| if (!lines[i].trim()) continue;
|
| const row = parseCSVLine(lines[i]);
|
| let contactName = '';
|
|
|
| if (nameIdx !== -1 && row[nameIdx]) {
|
| contactName = row[nameIdx];
|
| } else {
|
| const gName = givenNameIdx !== -1 ? row[givenNameIdx] || '' : '';
|
| const fName = familyNameIdx !== -1 ? row[familyNameIdx] || '' : '';
|
| contactName = (gName + ' ' + fName).trim();
|
| }
|
|
|
| const emailVal = emailIdx !== -1 ? row[emailIdx] || '' : '';
|
| const companyVal = orgIdx !== -1 ? row[orgIdx] || '' : '';
|
| const jobTitleVal = titleIdx !== -1 ? row[titleIdx] || '' : '';
|
| const rawGroups = groupIdx !== -1 ? row[groupIdx] || '' : '';
|
|
|
| const tagsList = ['Google_CSV'];
|
| if (rawGroups) {
|
| rawGroups.split(':::').forEach(g => {
|
| const cleanG = g.replace('* myContacts', '').replace('* My Contacts', '').trim();
|
| if (cleanG) tagsList.push(cleanG);
|
| });
|
| }
|
|
|
| const extractedPhones = [];
|
| phoneIndices.forEach(pIdx => {
|
| const rawPhone = row[pIdx];
|
| if (rawPhone) {
|
| const normPhone = normalizePhone(rawPhone);
|
| if (normPhone && normPhone.length >= 7 && !extractedPhones.includes(normPhone)) {
|
| extractedPhones.push(normPhone);
|
| }
|
| }
|
| });
|
|
|
| if (extractedPhones.length > 0) {
|
| parsedContacts.push({
|
| phone: extractedPhones[0],
|
| altPhones: extractedPhones.slice(1).map(p => ({ phone: p, type: 'work' })),
|
| name: contactName || 'عميل جوجل',
|
| email: emailVal,
|
| company: companyVal,
|
| jobTitle: jobTitleVal,
|
| lifecycle: 'lead',
|
| priority: 'medium',
|
| source: 'import',
|
| notes: 'تم الاستيراد من Google Contacts',
|
| tags: Array.from(new Set(tagsList))
|
| });
|
| }
|
| }
|
| return parsedContacts;
|
| },
|
|
|
| |
| |
| |
|
|
| processImportFile: async function(file) {
|
| if (!file) return;
|
| const reader = new FileReader();
|
|
|
| reader.onload = async (e) => {
|
| const content = e.target.result;
|
| let imported = [];
|
|
|
| if (file.name.endsWith('.vcf') || content.includes('BEGIN:VCARD')) {
|
| imported = this.parseVCard(content);
|
| } else if (file.name.endsWith('.csv') && (content.includes('Given Name') || content.includes('Group Membership') || content.includes('Phone 1'))) {
|
| imported = this.parseGoogleCSV(content);
|
| } else {
|
| const lines = content.split(/\r?\n/);
|
| lines.forEach(line => {
|
| if (!line.trim()) return;
|
| const parts = line.split(',');
|
| const rawPhone = parts[0] ? parts[0].trim().replace(/[^0-9+]/g, '') : '';
|
| const rawName = parts[1] ? parts[1].trim() : 'المستلم';
|
|
|
| if (rawPhone.length >= 7) {
|
| imported.push({
|
| phone: normalizePhone(rawPhone),
|
| name: rawName,
|
| tags: ['CSV_Import']
|
| });
|
| }
|
| });
|
| }
|
|
|
| if (imported.length > 0) {
|
| const core = global.FritreeContacts;
|
| if (!core || !core.state) return;
|
| const state = core.state;
|
| let addCount = 0;
|
|
|
| imported.forEach(imp => {
|
| const normP = normalizePhone(imp.phone);
|
| const exists = state.contacts.some(c => normalizePhone(c.phone) === normP);
|
|
|
| if (!exists && normP) {
|
| state.contacts.unshift({
|
| id: 'cnt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 4),
|
| name: imp.name || 'المستلم',
|
| phone: normP,
|
| altPhones: imp.altPhones || [],
|
| email: imp.email || '',
|
| company: imp.company || '',
|
| jobTitle: imp.jobTitle || '',
|
| lifecycle: imp.lifecycle || 'lead',
|
| priority: 'medium',
|
| source: 'import',
|
| tags: imp.tags || ['Imported'],
|
| notes: imp.notes || '',
|
| blacklisted: false,
|
| createdAt: new Date().toISOString()
|
| });
|
| addCount++;
|
| }
|
| });
|
|
|
| await core.save();
|
| core.refreshUI();
|
| const modalImport = document.getElementById('gpifk7jnfx');
|
| if (modalImport) modalImport.style.display = 'none';
|
|
|
| alert(`تم استيراد [${addCount.toLocaleString('en-US')}] جهة اتصال جديدة بنجاح!`);
|
| } else {
|
| alert('ملقتش أرقام موبايل صالحة جوه الملف ده.');
|
| }
|
| };
|
| reader.readAsText(file);
|
| },
|
|
|
| |
| |
| |
| |
|
|
| exportToFile: function(format = 'vcf', contactsList = []) {
|
| if (!Array.isArray(contactsList) || contactsList.length === 0) {
|
| alert('لا توجد جهات اتصال متاحة للتصدير.');
|
| return;
|
| }
|
|
|
| const filename = `جهات_اتصال_فريتري_${new Date().toISOString().slice(0, 10)}`;
|
|
|
| if (format === 'vcf') {
|
| let vcfText = '';
|
| contactsList.forEach(c => {
|
| vcfText += 'BEGIN:VCARD\r\nVERSION:3.0\r\n';
|
| vcfText += `FN:${c.name || 'عميل'}\r\n`;
|
| vcfText += `TEL;TYPE=CELL:${c.phone}\r\n`;
|
| if (c.altPhones && Array.isArray(c.altPhones)) {
|
| c.altPhones.forEach(ap => {
|
| const pVal = typeof ap === 'object' ? ap.phone : ap;
|
| vcfText += `TEL;TYPE=WORK:${pVal}\r\n`;
|
| });
|
| }
|
| if (c.email) vcfText += `EMAIL:${c.email}\r\n`;
|
| if (c.company) vcfText += `ORG:${c.company}\r\n`;
|
| if (c.jobTitle) vcfText += `TITLE:${c.jobTitle}\r\n`;
|
| if (c.notes) vcfText += `NOTE:${c.notes}\r\n`;
|
| vcfText += 'END:VCARD\r\n';
|
| });
|
| downloadBlob(vcfText, 'text/vcard;charset=utf-8;', `${filename}.vcf`);
|
| } else if (format === 'google_csv') {
|
| let csv = '\ufeffName,Given Name,Family Name,Group Membership,Phone 1 - Type,Phone 1 - Value,E-mail 1 - Value,Organization 1 - Name,Organization 1 - Title,Notes\n';
|
| contactsList.forEach(c => {
|
| const groupStr = (c.tags || []).join(' ::: ');
|
| csv += `"${c.name || ''}","${c.name || ''}","","${groupStr}","Mobile","${c.phone || ''}","${c.email || ''}","${c.company || ''}","${c.jobTitle || ''}","${(c.notes || '').replace(/"/g, '""')}"\n`;
|
| });
|
| downloadBlob(csv, 'text/csv;charset=utf-8;', `${filename}_Google.csv`);
|
| } else if (format === 'csv') {
|
| let csv = '\ufeffالاسم,رقم الهاتف,الشركة,المسمى الوظيفي,مرحلة المبيعات,البريد الإلكتروني,الوسوم,الملاحظات\n';
|
| contactsList.forEach(c => {
|
| csv += `"${c.name || ''}","${c.phone || ''}","${c.company || ''}","${c.jobTitle || ''}","${c.lifecycle || 'lead'}","${c.email || ''}","${(c.tags || []).join(', ')}","${(c.notes || '').replace(/"/g, '""')}"\n`;
|
| });
|
| downloadBlob(csv, 'text/csv;charset=utf-8;', `${filename}.csv`);
|
| } else if (format === 'json') {
|
| downloadBlob(JSON.stringify(contactsList, null, 2), 'application/json;charset=utf-8;', `${filename}.json`);
|
| }
|
| },
|
|
|
| |
| |
|
|
| deduplicate: async function() {
|
| const core = global.FritreeContacts;
|
| if (!core || !core.state) return;
|
| const state = core.state;
|
|
|
| const map = new Map();
|
| let dedupCount = 0;
|
|
|
| state.contacts.forEach(c => {
|
| const norm = normalizePhone(c.phone);
|
| if (map.has(norm)) {
|
| const existing = map.get(norm);
|
| if (c.name && c.name !== 'المستلم' && existing.name === 'المستلم') {
|
| existing.name = c.name;
|
| }
|
| existing.tags = Array.from(new Set([...(existing.tags || []), ...(c.tags || [])]));
|
| if (c.notes && !existing.notes.includes(c.notes)) {
|
| existing.notes = (existing.notes ? existing.notes + ' | ' : '') + c.notes;
|
| }
|
| dedupCount++;
|
| } else {
|
| map.set(norm, { ...c, phone: norm });
|
| }
|
| });
|
|
|
| state.contacts = Array.from(map.values());
|
| await core.save();
|
| core.refreshUI();
|
| alert(`تم دمج وإزالة [${dedupCount.toLocaleString('en-US')}] جهة اتصال مكررة بنجاح!`);
|
| },
|
|
|
| |
| |
|
|
| clearAll: async function() {
|
| if (confirm('تنبيه: هل أنت متأكد إنك عايز تمسح كل جهات الاتصال؟ الإجراء ده ما ينفعش تتراجع عنه!')) {
|
| const core = global.FritreeContacts;
|
| if (!core || !core.state) return;
|
| core.state.contacts = [];
|
| core.state.selectedContactIds.clear();
|
| await core.save();
|
| core.refreshUI();
|
| }
|
| },
|
|
|
| |
| |
|
|
| applyRangeSelection: function() {
|
| const core = global.FritreeContacts;
|
| if (!core || !core.state) return;
|
| const state = core.state;
|
|
|
| const fromInput = document.getElementById('rv2zopf0tb');
|
| const toInput = document.getElementById('wll7mokn3p');
|
| const infoText = document.getElementById('wsbcxjitau');
|
| const clearBtn = document.getElementById('b7ab0plbsx');
|
|
|
| const displayedList = state.currentlyDisplayedContacts || state.contacts || [];
|
| const totalDisplayed = displayedList.length;
|
|
|
| if (totalDisplayed === 0) {
|
| alert('مفيش جهات اتصال معروضة عشان تحدد نطاق منها.');
|
| return;
|
| }
|
|
|
| let fromNum = parseInt(fromInput?.value, 10);
|
| let toNum = parseInt(toInput?.value, 10);
|
|
|
| if (isNaN(fromNum) || isNaN(toNum)) {
|
| alert('اكتب رقم البداية ورقم النهاية عشان تحدد النطاق (مثال: من 51 إلى 78).');
|
| return;
|
| }
|
|
|
| if (fromNum < 1) fromNum = 1;
|
| if (toNum > totalDisplayed) toNum = totalDisplayed;
|
|
|
| if (fromNum > toNum) {
|
| alert(`رقم البداية (${fromNum}) لازم يكون أصغر من أو بيساوي رقم النهاية (${toNum}).`);
|
| return;
|
| }
|
|
|
| state.selectedContactIds.clear();
|
| let selectedCount = 0;
|
|
|
| for (let idx = fromNum - 1; idx < toNum; idx++) {
|
| if (displayedList[idx]) {
|
| state.selectedContactIds.add(displayedList[idx].id);
|
| selectedCount++;
|
| }
|
| }
|
|
|
| core.refreshUI();
|
| if (clearBtn) clearBtn.style.display = 'inline-flex';
|
| if (infoText) {
|
| infoText.textContent = `تم تحديد ${selectedCount.toLocaleString('en-US')} جهة اتصال (من رقم #${fromNum} إلى #${toNum})`;
|
| }
|
|
|
| if (typeof window.addLog === 'function') {
|
| window.addLog(`تحديد النطاق: تم تحديد [${selectedCount.toLocaleString('en-US')}] جهة اتصال من رقم #${fromNum} إلى #${toNum}.`, 'success');
|
| }
|
| },
|
|
|
| |
| |
|
|
| clearRangeSelection: function() {
|
| const core = global.FritreeContacts;
|
| if (!core || !core.state) return;
|
| const state = core.state;
|
|
|
| const fromInput = document.getElementById('rv2zopf0tb');
|
| const toInput = document.getElementById('wll7mokn3p');
|
| const infoText = document.getElementById('wsbcxjitau');
|
| const clearBtn = document.getElementById('b7ab0plbsx');
|
|
|
| if (fromInput) fromInput.value = '';
|
| if (toInput) toInput.value = '';
|
| if (clearBtn) clearBtn.style.display = 'none';
|
|
|
| state.selectedContactIds.clear();
|
| core.refreshUI();
|
|
|
| const totalDisplayed = (state.currentlyDisplayedContacts || state.contacts || []).length;
|
| if (infoText) {
|
| infoText.textContent = `إجمالي المعروض دلوقتي: ${totalDisplayed.toLocaleString('en-US')} جهة اتصال (من 1 إلى ${totalDisplayed.toLocaleString('en-US')})`;
|
| }
|
| },
|
|
|
| |
| |
|
|
| bindEvents: function() {
|
| const core = global.FritreeContacts;
|
| if (!core || !core.state) return;
|
| const state = core.state;
|
|
|
| const btnImportModal = document.getElementById('e2rtgyindd');
|
| const btnCloseImport = document.getElementById('nx0cpoqxzw');
|
| const btnExportModal = document.getElementById('nxybir7u8r');
|
| const btnCloseExport = document.getElementById('has3vfwumz');
|
| const btnExecExport = document.getElementById('nah2wji7pd');
|
| const btnDedup = document.getElementById('cdykxxbjv2');
|
| const btnClearAll = document.getElementById('k7qyxs7zru');
|
| const btnBulkDelete = document.getElementById('xt7egwiac3');
|
| const btnBulkTag = document.getElementById('bfywn3ymkh');
|
| const btnBulkUntag = document.getElementById('vfn8iv8lzf');
|
| const btnBulkBlacklist = document.getElementById('jik3jfr08m');
|
| const btnBulkUnblacklist = document.getElementById('amfm0v8cs5');
|
| const btnBulkExport = document.getElementById('ygdgkhes7r');
|
| const btnBulkSendWA = document.getElementById('ybvs0y4u4q');
|
| const btnApplyRange = document.getElementById('zc5zjdewes');
|
| const btnClearRange = document.getElementById('b7ab0plbsx');
|
| const rangeFromInput = document.getElementById('rv2zopf0tb');
|
| const rangeToInput = document.getElementById('wll7mokn3p');
|
|
|
| if (btnApplyRange) btnApplyRange.onclick = () => this.applyRangeSelection();
|
| if (btnClearRange) btnClearRange.onclick = () => this.clearRangeSelection();
|
|
|
| if (rangeFromInput && rangeToInput) {
|
| const handleEnter = (e) => { if (e.key === 'Enter') this.applyRangeSelection(); };
|
| rangeFromInput.onkeydown = handleEnter;
|
| rangeToInput.onkeydown = handleEnter;
|
| }
|
|
|
| if (btnImportModal) btnImportModal.onclick = () => document.getElementById('gpifk7jnfx').style.display = 'flex';
|
| if (btnCloseImport) btnCloseImport.onclick = () => document.getElementById('gpifk7jnfx').style.display = 'none';
|
| if (btnExportModal) btnExportModal.onclick = () => document.getElementById('rbqob9x718').style.display = 'flex';
|
| if (btnCloseExport) btnCloseExport.onclick = () => document.getElementById('rbqob9x718').style.display = 'none';
|
|
|
| if (btnExecExport) {
|
| btnExecExport.onclick = () => {
|
| const fmt = document.getElementById('nealj97cx2').value;
|
| this.exportToFile(fmt, state.contacts);
|
| document.getElementById('rbqob9x718').style.display = 'none';
|
| };
|
| }
|
|
|
| const dropzone = document.getElementById('h525b2cyd0');
|
| const fileinput = document.getElementById('fi0jafn7lc');
|
| if (dropzone && fileinput) {
|
| dropzone.onclick = () => fileinput.click();
|
| fileinput.onchange = (e) => {
|
| if (e.target.files.length > 0) this.processImportFile(e.target.files[0]);
|
| };
|
| dropzone.ondragover = (e) => { e.preventDefault(); dropzone.style.borderColor = '#1877f2'; };
|
| dropzone.ondragleave = () => { dropzone.style.borderColor = '#00a884'; };
|
| dropzone.ondrop = (e) => {
|
| e.preventDefault();
|
| dropzone.style.borderColor = '#00a884';
|
| if (e.dataTransfer && e.dataTransfer.files.length > 0) {
|
| this.processImportFile(e.dataTransfer.files[0]);
|
| }
|
| };
|
| }
|
|
|
| if (btnDedup) btnDedup.onclick = () => this.deduplicate();
|
| if (btnClearAll) btnClearAll.onclick = () => this.clearAll();
|
|
|
| if (btnBulkDelete) {
|
| btnBulkDelete.onclick = async () => {
|
| if (state.selectedContactIds.size === 0) {
|
| alert('حدد جهة اتصال واحدة على الأقل الأول!');
|
| return;
|
| }
|
| if (confirm(`هل أنت متأكد إنك عايز تحذف [${state.selectedContactIds.size}] جهة اتصال نهائياً؟`)) {
|
| state.contacts = state.contacts.filter(c => !state.selectedContactIds.has(c.id));
|
| state.selectedContactIds.clear();
|
| await core.save();
|
| core.refreshUI();
|
| }
|
| };
|
| }
|
|
|
| if (btnBulkTag) {
|
| btnBulkTag.onclick = async () => {
|
| if (state.selectedContactIds.size === 0) {
|
| alert('حدد جهة اتصال واحدة على الأقل الأول!');
|
| return;
|
| }
|
| const newTag = prompt("اكتب اسم الوسم الجديد اللي عايز تطبقه على المحدد كلو:");
|
| if (newTag && newTag.trim()) {
|
| const tagVal = newTag.trim();
|
| state.contacts.forEach(c => {
|
| if (state.selectedContactIds.has(c.id)) {
|
| c.tags = Array.from(new Set([...(c.tags || []), tagVal]));
|
| }
|
| });
|
| await core.save();
|
| core.refreshUI();
|
| alert(`تم إضافة الوسم "${tagVal}" لجهات الاتصال المحددة بنجاح.`);
|
| }
|
| };
|
| }
|
|
|
| if (btnBulkUntag) {
|
| btnBulkUntag.onclick = async () => {
|
| if (state.selectedContactIds.size === 0) {
|
| alert('حدد جهة اتصال واحدة على الأقل الأول!');
|
| return;
|
| }
|
| const selectedContacts = state.contacts.filter(c => state.selectedContactIds.has(c.id));
|
| const tagsOnSelected = new Set();
|
| selectedContacts.forEach(c => (c.tags || []).forEach(t => tagsOnSelected.add(t)));
|
|
|
| if (tagsOnSelected.size === 0) {
|
| alert('جهات الاتصال المحددة ما عليهاش أي وسوم دلوقتي عشان تتشال.');
|
| return;
|
| }
|
|
|
| const tagListStr = Array.from(tagsOnSelected).join(', ');
|
| const tagToRemove = prompt(`اكتب اسم الوسم اللي عايز تشيله من العناصر المحددة:\nالوسوم المتاحة: [ ${tagListStr} ]`);
|
|
|
| if (tagToRemove && tagToRemove.trim()) {
|
| const cleanTag = tagToRemove.trim();
|
| let countRemoved = 0;
|
| state.contacts.forEach(c => {
|
| if (state.selectedContactIds.has(c.id) && c.tags && c.tags.includes(cleanTag)) {
|
| c.tags = c.tags.filter(t => t !== cleanTag);
|
| countRemoved++;
|
| }
|
| });
|
| await core.save();
|
| core.refreshUI();
|
| alert(`تم إزالة الوسم "${cleanTag}" من [${countRemoved}] جهة اتصال بنجاح.`);
|
| }
|
| };
|
| }
|
|
|
| if (btnBulkBlacklist) {
|
| btnBulkBlacklist.onclick = async () => {
|
| if (state.selectedContactIds.size === 0) return;
|
| state.contacts.forEach(c => {
|
| if (state.selectedContactIds.has(c.id)) c.blacklisted = true;
|
| });
|
| await core.save();
|
| core.refreshUI();
|
| };
|
| }
|
|
|
| if (btnBulkUnblacklist) {
|
| btnBulkUnblacklist.onclick = async () => {
|
| if (state.selectedContactIds.size === 0) return;
|
| state.contacts.forEach(c => {
|
| if (state.selectedContactIds.has(c.id)) c.blacklisted = false;
|
| });
|
| await core.save();
|
| core.refreshUI();
|
| };
|
| }
|
|
|
| if (btnBulkExport) {
|
| btnBulkExport.onclick = () => {
|
| if (state.selectedContactIds.size === 0) {
|
| alert('اختار جهات اتصال الأول عشان تصدرها!');
|
| return;
|
| }
|
| const selectedList = state.contacts.filter(c => state.selectedContactIds.has(c.id));
|
| this.exportToFile('vcf', selectedList);
|
| };
|
| }
|
|
|
| if (btnBulkSendWA) {
|
| btnBulkSendWA.onclick = () => {
|
| if (state.selectedContactIds.size === 0) {
|
| alert('حدد جهات اتصال الأول عشان تبدأ الحملة!');
|
| return;
|
| }
|
| const selectedList = state.contacts.filter(c => state.selectedContactIds.has(c.id) && !c.blacklisted);
|
| if (global.FritreeWhatsApp && typeof global.FritreeWhatsApp.setRecipients === 'function') {
|
| global.FritreeWhatsApp.setRecipients(selectedList.map(c => ({ phone: c.phone, name: c.name })));
|
| window.location.hash = 'sf8pufcpyl';
|
| }
|
| };
|
| }
|
| }
|
| };
|
|
|
| |
| |
|
|
| global.FritreeContactsBulkActions = BulkActionsEngine;
|
| global.FritreeContactsParsers = BulkActionsEngine;
|
|
|
| })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this); |