1(function () { 2 const WIDGET_ID = "dsij-top-stocks"; 3 4 // branding 5 const scriptTag = document.currentScript; 6 const showBranding = scriptTag.getAttribute("data-branding") === "true"; 7 8 // Detect language 9 const htmlLang = (document.documentElement.lang || "en") 10 .toLowerCase() 11 .split("-")[0]; 12 13 const SUPPORTED_LOCALES = ["en", "hi", "mr", "gu", "ta"]; 14 15 const currentLang = SUPPORTED_LOCALES.includes(htmlLang) ? htmlLang : "en"; 16 17 const LANG_CONFIG = { 18 en: { 19 gainers: "Top Gainers", 20 losers: "Top Losers", 21 lastUpdated: "Updated", 22 poweredBy: "Powered by", 23 branding: "Dalal Street Investment Journal", 24 viewMore: "View More", 25 pathPrefix: "", 26 }, 27 28 hi: { 29 gainers: "à¤à¤à¥à¤à¤¤à¤® लाà¤à¤¾à¤°à¥à¤¥à¥", 30 losers: "à¤à¤à¥à¤à¤¤à¤® हारनॠवालà¥", 31 lastUpdated: "ठपडà¥à¤", 32 poweredBy: "दà¥à¤µà¤¾à¤°à¤¾ सà¤à¤à¤¾à¤²à¤¿à¤¤", 33 branding: "दलाल सà¥à¤à¥à¤°à¥à¤ à¤à¤¨à¥à¤µà¥à¤¸à¥à¤à¤®à¥à¤à¤ à¤à¤°à¥à¤¨à¤²", 34 viewMore: "à¤à¤° दà¥à¤à¥à¤", 35 pathPrefix: "/hi", 36 }, 37 38 mr: { 39 gainers: "पà¥à¤°à¤®à¥à¤ लाà¤à¤¾à¤°à¥à¤¥à¥", 40 losers: "पà¥à¤°à¤®à¥à¤ तà¥à¤à¤¾ à¤à¤¾à¤²à¥à¤²à¥", 41 lastUpdated: "ठपडà¥à¤", 42 poweredBy: "दà¥à¤µà¤¾à¤°à¥ समरà¥à¤¥à¤¿à¤¤", 43 branding: "दलाल सà¥à¤à¥à¤°à¥à¤ à¤à¤¨à¥à¤µà¥à¤¹à¥à¤¸à¥à¤à¤®à¥à¤à¤ à¤à¤°à¥à¤¨à¤²", 44 viewMore: "ठधिठपहा", 45 pathPrefix: "/mr", 46 }, 47 48 gu: { 49 gainers: "àªà«àªàª¨àª¾ નફૠàªàª°àª¨àª¾àª°àª¾", 50 losers: "àªà«àªàª¨àª¾ àªà«àª®àª¾àªµàª¨àª¾àª°àª¾", 51 lastUpdated: "ઠપડà«àª", 52 poweredBy: "દà«àªµàª¾àª°àª¾ સàªàªàª¾àª²àª¿àª¤", 53 branding: "દલાલ સà«àªà«àª°à«àª àªàª¨à«àªµà«àª¸à«àªàª®à«àª¨à«àª àªàª°à«àª¨àª²", 54 viewMore: "વધૠàªà«àª", 55 viewMore: "à®®à¯à®²à¯à®®à¯ à®à®¾à®£à¯à®", 56 pathPrefix: "/gu", 57 }, 58 59 ta: { 60 gainers: "à®®à¯à®¤à®²à®¿à®à®®à¯ பà¯à®±à¯à®±à®µà®°à¯à®à®³à¯", 61 losers: "஠தி஠தà¯à®²à¯à®µà®¿à®¯à®à¯à®¨à¯à®¤à®µà®°à¯à®à®³à¯", 62 lastUpdated: "à®à®à¯à®à®¿à®¯à®¾à® பà¯à®¤à¯à®ªà¯à®ªà®¿à®à¯à®à®ªà¯à®ªà®à¯à®à®¤à¯", 63 poweredBy: "à®à®²à¯ à®à®¯à®à¯à®à®ªà¯à®ªà®à¯à®à®¿à®±à®¤à¯", 64 branding: "தலால௠ஸà¯à®à¯à®°à¯à®à¯ à®®à¯à®¤à®²à¯à®à¯à®à¯ à®à®¤à®´à¯", 65 pathPrefix: "/ta", 66 }, 67 }; 68 69 const TEXT = LANG_CONFIG[currentLang]; 70 71 const BASE_URL = "https://insights.dsij.in"; // Replace with actual base URL of the DSIJ website 72 73 const LOCALE = TEXT.pathPrefix; 74 75 const cache = { gainers: null, losers: null }; 76 77 // Auto-create container if missing 78 let container = document.getElementById(WIDGET_ID); 79 80 if (!container) { 81 container = document.createElement("div"); 82 container.id = WIDGET_ID; 83 84 document.currentScript.parentNode.insertBefore( 85 container, 86 document.currentScript, 87 ); 88 } 89 90 // Inject CSS 91 if (!document.getElementById("dsij-top-stocks-css")) { 92 const link = document.createElement("link"); 93 94 link.id = "dsij-top-stocks-css"; 95 link.rel = "stylesheet"; 96 97 const scriptSrc = document.currentScript.src; 98 const baseUrl = scriptSrc.split("/top-stocks-widget.js")[0]; 99 100 link.href = baseUrl + "/top-stocks-widget.css"; 101 102 document.head.appendChild(link); 103 } 104 105 container.innerHTML = ` 106 <div class="tabs"> 107 <div class="tab gainer active" data-type="gainers"> 108 <span class="arrow-up">↑</span> 109 ${TEXT.gainers} 110 </div> 111 112 <div class="tab loser" data-type="losers"> 113 <span class="arrow-down">↓</span> 114 ${TEXT.losers} 115 </div> 116 </div> 117 118 <div class="content" style="min-height: 240px;"></div> 119 120 <div class="last-updated"></div> 121 122 <div class="view-more-container"></div> 123 124 ${ 125 showBranding 126 ? ` 127 <a 128 class="branding"
129 href="https://www.dsij.in${LOCALE}" 130 target="_blank" 131 rel="noopener noreferrer" 132 > 133 <img 134 class="branding-logo" 135 src="https://dsijpub.s3.ap-south-1.amazonaws.com/portals/0/images/DSIJ_favicon.jpg" 136 alt="DS Logo" 137 /> 138 139 ${TEXT.poweredBy} 140 <span>${TEXT.branding}</span> 141 </a> 142 ` 143 : "" 144 } 145 `; 146 147 const content = container.querySelector(".content"); 148 149 const lastUpdatedEl = container.querySelector(".last-updated"); 150 151 const viewMoreContainer = container.querySelector(".view-more-container"); 152 153 function updateLastUpdated(data) { 154 if (!Array.isArray(data) || !data.length) { 155 lastUpdatedEl.innerHTML = ""; 156 return; 157 } 158 159 const maxDate = data.reduce((latest, stock) => { 160 const current = new Date(stock.trade_date); 161 162 return current > latest ? current : latest; 163 }, new Date(0)); 164 165 lastUpdatedEl.innerHTML = ` 166 <span class="last-updated"> 167 ${TEXT.lastUpdated}: ${maxDate.toLocaleString("en-IN", { 168 day: "2-digit", 169 month: "short", 170 year: "numeric", 171 hour: "2-digit", 172 minute: "2-digit", 173 })} (IST) 174 </span> 175 `; 176 } 177 178 function formatNumber(num) { 179 return Number(num || 0).toLocaleString("en-IN"); 180 } 181 182 function createSlug(companyname, code) { 183 if (!companyname || !code) return "#"; 184 185 return ( 186 String(companyname) 187 .trim() 188 .replace(/[^a-zA-Z0-9]+/g, "-") 189 .replace(/-+/g, "-") 190 .replace(/(^-|-$)/g, "") 191 .toLowerCase() + 192 "-" + 193 code 194 ); 195 } 196 197 function render(data, type) { 198 if (!data || !data.length) { 199 content.innerHTML = `<div class="center">No data available</div>`; 200 return; 201 } 202 203 const isGainer = type === "gainers"; 204 205 let html = "<table>"; 206
207 data.slice(0, 5).forEach((stock) => { 208 const pct = Number(stock.change_pct || 0); 209 210 html += ` 211 <tr> 212 <td> 213 <div class="stock-info"> 214 215 ${ 216 isGainer 217 ? ` 218 <div class="chart-line" style="background:#d0ffcf;"> 219 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20" width="20" height="20"> 220 <path d="M16 6l2.29 2.29-4.88 4.88-4-4L2 16.59 3.41 18l6-6 4 4 6.3-6.29L22 12V6z" fill="green"></path> 221 </svg> 222 </div> 223 ` 224 : ` 225 <div class="chart-line" style="background:#ffcfcf;"> 226 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 20"> 227 <path d="M16 18l2.29-2.29-4.88-4.88-4 4L2 7.41 3.41 6l6 6 4-4 6.3 6.29L22 12v6z" fill="red"></path> 228 </svg> 229 </div> 230 ` 231 } 232 233 <div> 234 <a 235 target="_parent" 236 rel="noopener noreferrer" 237 class="ticker-link" 238 href="${BASE_URL}${LOCALE}/share-price/${createSlug( 239 stock["company name"], 240 stock.companycode, 241 )}" 242 > 243 ${stock.ticker || "-"} 244 </a> 245 246 <p class="mb-0 small"> 247 ${stock["company name"] || "-"} 248 </p> 249 </div> 250 251 </div> 252 </td> 253 254 <td> 255 ${formatNumber(stock.last_amt)} 256 257 <p class="mb-0 small ${pct > 0 ? "green" : "red"}"> 258 ${stock.change_amt || 0} (${pct}%) 259 </p> 260 </td> 261 </tr> 262 `; 263 }); 264 265 html += "</table>"; 266 267 content.innerHTML = html; 268 269 const viewMoreUrl = isGainer 270 ? "https://www.dsij.in/r/xvBz" 271 : "https://www.dsij.in/r/z7Rd"; 272 273 viewMoreContainer.innerHTML = ` 274 <a 275 class="view-more-btn" 276 href="${viewMoreUrl}" 277 target="_parent" 278 rel="noopener noreferrer" 279 > 280 ${TEXT.viewMore} 281 </a> 282`; 283 } 284 285 async function load(type) { 286 if (cache[type]) { 287 updateLastUpdated(cache[type]); 288 render(cache[type], type); 289 return; 290 } 291 292 content.innerHTML = `<div class="center">Loading...</div>`; 293 294 const statType = type === "gainers" ? "TopGainer" : "TopLooser"; 295 296 try { 297 const res = await fetch(`${BASE_URL}/api/market-stats`, { 298 method: "POST", 299 300 headers: { 301 "Content-Type": "application/json", 302 }, 303 304 body: JSON.stringify({ 305 apiType: "TopGainerLooser", 306 statType, 307 }), 308 }); 309 310 if (!res.ok) { 311 throw new Error("API error"); 312 } 313 314 const response = await res.json(); 315 316 const data = Array.isArray(response) ? response : response.results || []; 317 318 updateLastUpdated(data); 319 320 cache[type] = data; 321 322 render(data, type); 323 } catch (e) { 324 console.error(e); 325 326 content.innerHTML = ` 327 <div class="center"> 328 Failed to load 329 </div> 330 `; 331 } 332 } 333 334 // Tab switching 335 container.querySelectorAll(".tab").forEach((tab) => { 336 tab.addEventListener("click", function () { 337 container 338 .querySelectorAll(".tab") 339 .forEach((t) => t.classList.remove("active")); 340 341 this.classList.add("active"); 342 343 load(this.dataset.type); 344 }); 345 }); 346 347 // Initial load 348 load("gainers"); 349})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.