Extract OCR searchable PDF from Enterprise Command Center documentation pages
Size
10.7 KB
Version
1.1.1
Created
Dec 17, 2025
Updated
about 2 months ago
1// ==UserScript==
2// @name Enterprise Command Center PDF Extractor
3// @description Extract OCR searchable PDF from Enterprise Command Center documentation pages
4// @version 1.1.1
5// @match https://*.eservice.addigital.gov.ae/*
6// @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Enterprise Command Center PDF Extractor loaded');
12
13 // Add styles for the PDF button
14 TM_addStyle(`
15 #ecc-pdf-button {
16 position: fixed;
17 top: 20px;
18 right: 20px;
19 z-index: 10000;
20 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
21 color: white;
22 border: none;
23 padding: 12px 24px;
24 border-radius: 8px;
25 font-size: 14px;
26 font-weight: 600;
27 cursor: pointer;
28 box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
29 transition: all 0.3s ease;
30 display: flex;
31 align-items: center;
32 gap: 8px;
33 }
34
35 #ecc-pdf-button:hover {
36 transform: translateY(-2px);
37 box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
38 }
39
40 #ecc-pdf-button:active {
41 transform: translateY(0);
42 }
43
44 #ecc-pdf-button.processing {
45 background: #95a5a6;
46 cursor: wait;
47 }
48
49 #ecc-pdf-button .spinner {
50 display: none;
51 width: 16px;
52 height: 16px;
53 border: 2px solid #ffffff;
54 border-top-color: transparent;
55 border-radius: 50%;
56 animation: spin 0.8s linear infinite;
57 }
58
59 #ecc-pdf-button.processing .spinner {
60 display: block;
61 }
62
63 @keyframes spin {
64 to { transform: rotate(360deg); }
65 }
66 `);
67
68 function extractPageContent() {
69 console.log('Extracting page content...');
70
71 // Get the main content area
72 const mainContent = document.body;
73
74 if (!mainContent) {
75 console.error('Could not find main content');
76 return null;
77 }
78
79 // Extract title
80 const title = document.title || 'Enterprise Command Center Documentation';
81
82 // Extract all text content with structure
83 const content = [];
84
85 // Get all headings, paragraphs, and images
86 const elements = mainContent.querySelectorAll('h1, h2, h3, h4, h5, h6, p, li, td, th, div, img');
87
88 elements.forEach(el => {
89 // Handle images
90 if (el.tagName.toLowerCase() === 'img') {
91 const src = el.src;
92 const alt = el.alt || 'Image';
93 if (src && !src.startsWith('data:image/svg')) {
94 content.push({
95 type: 'img',
96 src: src,
97 alt: alt,
98 width: el.naturalWidth || el.width,
99 height: el.naturalHeight || el.height
100 });
101 }
102 return;
103 }
104
105 const text = el.textContent.trim();
106 if (text && text.length > 0) {
107 const tagName = el.tagName.toLowerCase();
108
109 // Skip if this element's text is already included in a parent
110 let isNested = false;
111 let parent = el.parentElement;
112 while (parent && parent !== mainContent) {
113 if (parent.matches('h1, h2, h3, h4, h5, h6, p, li, td, th')) {
114 isNested = true;
115 break;
116 }
117 parent = parent.parentElement;
118 }
119
120 if (!isNested) {
121 content.push({
122 type: tagName,
123 text: text
124 });
125 }
126 }
127 });
128
129 console.log(`Extracted ${content.length} content elements`);
130 return { title, content };
131 }
132
133 async function generatePDF() {
134 console.log('Starting PDF generation...');
135
136 const button = document.getElementById('ecc-pdf-button');
137 button.classList.add('processing');
138 button.innerHTML = '<span class="spinner"></span> Generating PDF...';
139
140 try {
141 const pageData = extractPageContent();
142
143 if (!pageData || !pageData.content || pageData.content.length === 0) {
144 alert('No content found to extract');
145 return;
146 }
147
148 // Initialize jsPDF
149 const { jsPDF } = window.jspdf;
150 const doc = new jsPDF({
151 orientation: 'portrait',
152 unit: 'mm',
153 format: 'a4'
154 });
155
156 // Page settings
157 const pageWidth = doc.internal.pageSize.getWidth();
158 const pageHeight = doc.internal.pageSize.getHeight();
159 const margin = 20;
160 const maxWidth = pageWidth - (margin * 2);
161 let yPosition = margin;
162
163 // Add title
164 doc.setFontSize(18);
165 doc.setFont(undefined, 'bold');
166 const titleLines = doc.splitTextToSize(pageData.title, maxWidth);
167 doc.text(titleLines, margin, yPosition);
168 yPosition += titleLines.length * 8 + 10;
169
170 // Add URL and date
171 doc.setFontSize(10);
172 doc.setFont(undefined, 'normal');
173 doc.setTextColor(100, 100, 100);
174 doc.text(`Source: ${window.location.href}`, margin, yPosition);
175 yPosition += 6;
176 doc.text(`Generated: ${new Date().toLocaleString()}`, margin, yPosition);
177 yPosition += 15;
178
179 doc.setTextColor(0, 0, 0);
180
181 // Add content
182 for (const item of pageData.content) {
183 // Handle images
184 if (item.type === 'img') {
185 try {
186 // Calculate image dimensions to fit page
187 const maxImgWidth = maxWidth;
188 const maxImgHeight = pageHeight - margin - yPosition - 20;
189
190 let imgWidth = item.width;
191 let imgHeight = item.height;
192
193 // Scale image to fit width
194 if (imgWidth > maxImgWidth) {
195 const ratio = maxImgWidth / imgWidth;
196 imgWidth = maxImgWidth;
197 imgHeight = imgHeight * ratio;
198 }
199
200 // Check if image fits on current page
201 if (yPosition + imgHeight > pageHeight - margin) {
202 doc.addPage();
203 yPosition = margin;
204 }
205
206 // Add image to PDF
207 doc.addImage(item.src, 'JPEG', margin, yPosition, imgWidth, imgHeight);
208 yPosition += imgHeight + 5;
209
210 // Add image caption if available
211 if (item.alt && item.alt !== 'Image') {
212 doc.setFontSize(9);
213 doc.setTextColor(100, 100, 100);
214 const captionLines = doc.splitTextToSize(item.alt, maxWidth);
215 doc.text(captionLines, margin, yPosition);
216 yPosition += captionLines.length * 4 + 5;
217 doc.setTextColor(0, 0, 0);
218 }
219
220 console.log('Added image to PDF:', item.alt);
221 } catch (imgError) {
222 console.error('Error adding image to PDF:', imgError);
223 // Continue with next item if image fails
224 }
225 continue;
226 }
227
228 // Check if we need a new page
229 if (yPosition > pageHeight - margin - 20) {
230 doc.addPage();
231 yPosition = margin;
232 }
233
234 // Set font based on element type
235 if (item.type.startsWith('h')) {
236 const level = parseInt(item.type.charAt(1));
237 doc.setFontSize(18 - (level * 2));
238 doc.setFont(undefined, 'bold');
239 yPosition += 5;
240 } else {
241 doc.setFontSize(11);
242 doc.setFont(undefined, 'normal');
243 }
244
245 // Split text to fit width
246 const lines = doc.splitTextToSize(item.text, maxWidth);
247
248 // Check if lines fit on current page
249 const lineHeight = item.type.startsWith('h') ? 7 : 6;
250 const blockHeight = lines.length * lineHeight;
251
252 if (yPosition + blockHeight > pageHeight - margin) {
253 doc.addPage();
254 yPosition = margin;
255 }
256
257 // Add text
258 doc.text(lines, margin, yPosition);
259 yPosition += blockHeight + (item.type.startsWith('h') ? 5 : 3);
260 }
261
262 // Save the PDF
263 const fileName = `${pageData.title.replace(/[^a-z0-9]/gi, '_')}_${Date.now()}.pdf`;
264 doc.save(fileName);
265
266 console.log('PDF generated successfully:', fileName);
267
268 } catch (error) {
269 console.error('Error generating PDF:', error);
270 alert('Error generating PDF: ' + error.message);
271 } finally {
272 button.classList.remove('processing');
273 button.innerHTML = '📄 Download as PDF';
274 }
275 }
276
277 function init() {
278 console.log('Initializing PDF extractor...');
279
280 // Wait for page to be fully loaded
281 if (document.readyState === 'loading') {
282 document.addEventListener('DOMContentLoaded', init);
283 return;
284 }
285
286 // Create download button
287 const button = document.createElement('button');
288 button.id = 'ecc-pdf-button';
289 button.innerHTML = '📄 Download as PDF';
290 button.addEventListener('click', generatePDF);
291
292 document.body.appendChild(button);
293 console.log('PDF download button added');
294 }
295
296 // Start the extension
297 init();
298})();