chunks
1 week ago
vendor
8 months ago
admin.build.js
1 week ago
admin.js
3 months ago
analytics-tracker.js
4 weeks ago
analytics.build.js
1 week ago
blocks.build.js
1 week ago
carousel.js
1 year ago
custom-player.build.js
1 week ago
documents-viewer-script.js
3 months ago
ep-pdf-lightbox.js
3 months ago
ep-view-count.js
1 week ago
ep-yt-queue.js
4 weeks ago
feature-notices.js
7 months ago
front.js
4 weeks ago
frontend.build.js
3 months ago
gallery-justify.js
1 week ago
gutneberg-script.js
1 month ago
index.html
7 years ago
initCarousel.js
2 years ago
initplyr.js
1 month ago
instafeed.js
4 weeks ago
instagram-shortcode-generator.js
1 month ago
lazy-load.js
6 months ago
license.js
3 months ago
meetup-timezone.js
3 months ago
onboarding.build.js
1 week ago
pdf-gallery-elementor-editor.js
3 months ago
pdf-gallery.js
3 months ago
preview.js
8 months ago
settings.js
3 months ago
sponsored.js
9 months ago
ep-view-count.js
473 lines
| 1 | /** |
| 2 | * EmbedPress — public-facing view + download count badge for PDF/Document. |
| 3 | * |
| 4 | * Discovers wrappers carrying [data-embed-type="PDF"|"Document"] across |
| 5 | * Gutenberg blocks, [embedpress] shortcode, and Elementor widgets. Renders |
| 6 | * a single inline badge that reads |
| 7 | * |
| 8 | * 👁 N views ⤓ M downloads |
| 9 | * |
| 10 | * Views are self-recorded on first paint (session-deduped server-side). |
| 11 | * Downloads are recorded when the bundled PDF.js viewer posts a |
| 12 | * {source:'embedpress-pdf-viewer', type:'download'} message from inside its |
| 13 | * iframe — i.e., when the visitor clicks Save/Download/Open from the PDF.js |
| 14 | * toolbar. There is intentionally NO external download button. |
| 15 | */ |
| 16 | (function () { |
| 17 | 'use strict'; |
| 18 | |
| 19 | var cfg = window.embedpressViewCount || {}; |
| 20 | if (!cfg.restUrl || !cfg.types || !cfg.types.length) { |
| 21 | return; |
| 22 | } |
| 23 | |
| 24 | var processed = new WeakSet(); |
| 25 | var SESSION_COOKIE = 'ep_vc_session'; |
| 26 | // contentId -> wrapper element, so postMessage receivers can find the |
| 27 | // badge they need to update. |
| 28 | var contentIdIndex = Object.create(null); |
| 29 | |
| 30 | function ensureSessionId() { |
| 31 | var m = document.cookie.match(/(?:^|;\s*)ep_vc_session=([^;]+)/); |
| 32 | if (m) return decodeURIComponent(m[1]); |
| 33 | var tm = document.cookie.match(/(?:^|;\s*)ep_session_id=([^;]+)/); |
| 34 | var sid = tm ? decodeURIComponent(tm[1]) : ('ep-vc-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10)); |
| 35 | document.cookie = SESSION_COOKIE + '=' + encodeURIComponent(sid) + '; path=/; max-age=86400; samesite=lax'; |
| 36 | return sid; |
| 37 | } |
| 38 | var sessionId = ensureSessionId(); |
| 39 | |
| 40 | function getEmbedUrl(el) { |
| 41 | var iframe = el.tagName === 'IFRAME' ? el : el.querySelector('iframe'); |
| 42 | var src = iframe && iframe.getAttribute('src'); |
| 43 | if (src) { |
| 44 | try { |
| 45 | var u = new URL(src, window.location.href); |
| 46 | var fileParam = u.searchParams.get('file'); |
| 47 | if (fileParam) return decodeURIComponent(fileParam); |
| 48 | } catch (e) { /* fall through */ } |
| 49 | return src; |
| 50 | } |
| 51 | return el.getAttribute('data-embed-url') |
| 52 | || el.getAttribute('data-url') |
| 53 | || el.getAttribute('data-src') |
| 54 | || el.getAttribute('href') |
| 55 | || ''; |
| 56 | } |
| 57 | |
| 58 | // A source-id is only usable if it actually identifies the embed. Blocks |
| 59 | // render data-source-id={`source-${clientId}`}; when clientId was undefined |
| 60 | // at save time that becomes the literal "source-undefined", which is shared |
| 61 | // by EVERY such embed — collapsing their counts into one ever-growing tally. |
| 62 | // Reject that (and any blank) so we fall back to a per-instance id instead. |
| 63 | function isUsableId(v) { |
| 64 | return !!v && v !== 'source-undefined' && v !== 'undefined' && v !== 'source-' && v !== 'source-null'; |
| 65 | } |
| 66 | |
| 67 | // Deterministic index of `el` among all embeds that resolve to the SAME |
| 68 | // url-hash, in DOCUMENT order. This must NOT depend on processing order: |
| 69 | // the flipbook and modern viewers load asynchronously, so a process-order |
| 70 | // counter would assign a different suffix to the same embed across reloads |
| 71 | // — splitting its count and making it look like counting "stopped". DOM |
| 72 | // order is fixed by the saved post content, so this is stable per instance. |
| 73 | function sameHashIndex(el, hash) { |
| 74 | var nodes = document.querySelectorAll('[data-embed-type]'); |
| 75 | var idx = 0; |
| 76 | for (var i = 0; i < nodes.length; i++) { |
| 77 | // Skip nested embeds (handled by their own outer wrapper). |
| 78 | if (nodes[i].parentElement && nodes[i].parentElement.closest('[data-embed-type]')) continue; |
| 79 | if (urlHash(getEmbedUrl(nodes[i])) !== hash) continue; |
| 80 | if (nodes[i] === el) return idx; |
| 81 | idx++; |
| 82 | } |
| 83 | return idx; |
| 84 | } |
| 85 | |
| 86 | // Distinctive hash over the FULL url. A previous version base64-encoded the |
| 87 | // url and kept the first 10 chars — but every "https://…" base64 starts with |
| 88 | // the same "aHR0cHM6Ly" prefix, so different files collapsed to one hash and |
| 89 | // then fought over a per-instance suffix. A djb2-style hash of the whole |
| 90 | // string keeps distinct files distinct. |
| 91 | function urlHash(url) { |
| 92 | url = String(url || ''); |
| 93 | var h = 5381; |
| 94 | for (var i = 0; i < url.length; i++) { |
| 95 | h = ((h << 5) + h + url.charCodeAt(i)) >>> 0; // h * 33 + c, unsigned |
| 96 | } |
| 97 | return h.toString(36); |
| 98 | } |
| 99 | |
| 100 | function deriveContentId(el, embedType) { |
| 101 | var stored = el.getAttribute('data-embedpress-content'); |
| 102 | if (isUsableId(stored)) return stored; |
| 103 | |
| 104 | var existing = el.getAttribute('data-source-id') || el.getAttribute('data-emid'); |
| 105 | if (isUsableId(existing)) { |
| 106 | el.setAttribute('data-embedpress-content', existing); |
| 107 | return existing; |
| 108 | } |
| 109 | |
| 110 | // No usable saved id (e.g. "source-undefined"): build a stable id from |
| 111 | // the file URL + this embed's DOCUMENT-ORDER index among same-file |
| 112 | // embeds, so multiple embeds of the same file are counted separately |
| 113 | // AND each keeps the same id across reloads. |
| 114 | var url = getEmbedUrl(el); |
| 115 | var hash = urlHash(url); |
| 116 | var contentId = 'ep-' + embedType + '-' + hash + '-' + sameHashIndex(el, hash); |
| 117 | el.setAttribute('data-embedpress-content', contentId); |
| 118 | return contentId; |
| 119 | } |
| 120 | |
| 121 | function fmt(n, key, fallback) { |
| 122 | var template = (cfg.labels && cfg.labels[key]) || fallback; |
| 123 | return template.replace('%s', n.toLocaleString()); |
| 124 | } |
| 125 | function formatViews(n) { return fmt(n, n === 1 ? 'singular' : 'plural', '%s views'); } |
| 126 | function formatDownloads(n) { return fmt(n, n === 1 ? 'downloadSingular' : 'downloadPlural', '%s downloads'); } |
| 127 | |
| 128 | function buildBadge() { |
| 129 | var wrapper = document.createElement('div'); |
| 130 | wrapper.className = 'ep-view-count'; |
| 131 | wrapper.innerHTML = |
| 132 | '<span class="ep-view-count__item ep-view-count__item--views" hidden>' + |
| 133 | '<svg class="ep-view-count__icon" width="14" height="14" viewBox="0 0 24 24" ' + |
| 134 | 'fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" ' + |
| 135 | 'stroke-linejoin="round" aria-hidden="true">' + |
| 136 | '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z"/>' + |
| 137 | '<circle cx="12" cy="12" r="3"/>' + |
| 138 | '</svg>' + |
| 139 | '<span class="ep-view-count__label" data-views></span>' + |
| 140 | '</span>' + |
| 141 | '<span class="ep-view-count__sep" hidden></span>' + |
| 142 | '<span class="ep-view-count__item ep-view-count__item--downloads" hidden>' + |
| 143 | '<svg class="ep-view-count__icon" width="14" height="14" viewBox="0 0 24 24" ' + |
| 144 | 'fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" ' + |
| 145 | 'stroke-linejoin="round" aria-hidden="true">' + |
| 146 | '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>' + |
| 147 | '<polyline points="7 10 12 15 17 10"/>' + |
| 148 | '<line x1="12" y1="15" x2="12" y2="3"/>' + |
| 149 | '</svg>' + |
| 150 | '<span class="ep-view-count__label" data-downloads></span>' + |
| 151 | '</span>'; |
| 152 | return wrapper; |
| 153 | } |
| 154 | |
| 155 | // Resolve the effective badge position. Positioning is a Pro feature — |
| 156 | // only the default 'below' is free. When Pro is inactive, any non-default |
| 157 | // position falls back to 'below' (and 'above' insert reverts to after). |
| 158 | function resolvePosition(el) { |
| 159 | var pos = el.getAttribute('data-ep-count-position'); |
| 160 | if (!pos || pos === 'below') return 'below'; |
| 161 | if (!cfg.isPro) return 'below'; |
| 162 | return pos; |
| 163 | } |
| 164 | |
| 165 | // The badge always sits OUTSIDE the embed (never overlaying it). Position |
| 166 | // only decides which side — 'above-*' inserts before the embed, otherwise |
| 167 | // after — and the CSS modifier class handles horizontal alignment. |
| 168 | function isAbove(el) { |
| 169 | return resolvePosition(el).indexOf('above') === 0; |
| 170 | } |
| 171 | |
| 172 | function placeBadge(el, node) { |
| 173 | var above = isAbove(el); |
| 174 | if (el.tagName === 'IFRAME') { |
| 175 | if (!el.parentNode) return; |
| 176 | // 'above' goes before the iframe; 'below' goes after it, but still |
| 177 | // before a trailing "Powered By EmbedPress" line so the count |
| 178 | // always sits above the branding. |
| 179 | var ref = above ? el : el.nextSibling; |
| 180 | if (!above) { |
| 181 | var sib = el.nextElementSibling; |
| 182 | while (sib) { |
| 183 | if (sib.classList && sib.classList.contains('embedpress-el-powered')) { ref = sib; break; } |
| 184 | sib = sib.nextElementSibling; |
| 185 | } |
| 186 | } |
| 187 | el.parentNode.insertBefore(node, ref); |
| 188 | } else if (above) { |
| 189 | el.insertBefore(node, el.firstChild); |
| 190 | } else { |
| 191 | // Keep the badge above any "Powered By EmbedPress" branding. |
| 192 | var powered = el.querySelector(':scope > .embedpress-el-powered') |
| 193 | || el.querySelector('.embedpress-el-powered'); |
| 194 | if (powered && powered.parentNode) { |
| 195 | powered.parentNode.insertBefore(node, powered); |
| 196 | } else { |
| 197 | el.appendChild(node); |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | // Apply the per-embed alignment/side class. 'below' keeps the original |
| 203 | // placement, so no class is added. |
| 204 | function applyPosition(el, badge) { |
| 205 | var pos = resolvePosition(el); |
| 206 | if (pos === 'below') return; |
| 207 | badge.classList.add('ep-view-count--pos-' + pos); |
| 208 | } |
| 209 | |
| 210 | function findExistingBadge(el) { |
| 211 | if (el.tagName === 'IFRAME') { |
| 212 | var sibs = [el.previousElementSibling, el.nextElementSibling]; |
| 213 | for (var i = 0; i < sibs.length; i++) { |
| 214 | var s = sibs[i]; |
| 215 | if (s && s.classList && s.classList.contains('ep-view-count')) return s; |
| 216 | } |
| 217 | return null; |
| 218 | } |
| 219 | // The badge may be nested (e.g. inserted before a deeper |
| 220 | // ".embedpress-el-powered" branding line), so search descendants — but |
| 221 | // ignore any badge that belongs to a NESTED embed wrapper so we don't |
| 222 | // mistake a child embed's badge for this one's. |
| 223 | var candidates = el.querySelectorAll('.ep-view-count'); |
| 224 | for (var j = 0; j < candidates.length; j++) { |
| 225 | var c = candidates[j]; |
| 226 | var owner = c.closest('[data-embed-type]'); |
| 227 | if (owner === el) return c; |
| 228 | } |
| 229 | return null; |
| 230 | } |
| 231 | |
| 232 | function ensureBadgeFor(el) { |
| 233 | var existing = findExistingBadge(el); |
| 234 | if (existing) return existing; |
| 235 | var badge = buildBadge(); |
| 236 | placeBadge(el, badge); |
| 237 | applyPosition(el, badge); |
| 238 | return badge; |
| 239 | } |
| 240 | |
| 241 | function setViewCount(el, count) { |
| 242 | var badge = ensureBadgeFor(el); |
| 243 | var item = badge.querySelector('.ep-view-count__item--views'); |
| 244 | item.hidden = false; |
| 245 | item.querySelector('[data-views]').textContent = formatViews(count); |
| 246 | el.setAttribute('data-ep-view-count', String(count)); |
| 247 | syncSeparator(badge); |
| 248 | } |
| 249 | |
| 250 | function setDownloadCount(el, count) { |
| 251 | var badge = ensureBadgeFor(el); |
| 252 | var item = badge.querySelector('.ep-view-count__item--downloads'); |
| 253 | item.hidden = false; |
| 254 | item.querySelector('[data-downloads]').textContent = formatDownloads(count); |
| 255 | el.setAttribute('data-ep-download-count', String(count)); |
| 256 | syncSeparator(badge); |
| 257 | } |
| 258 | |
| 259 | function syncSeparator(badge) { |
| 260 | var v = badge.querySelector('.ep-view-count__item--views'); |
| 261 | var d = badge.querySelector('.ep-view-count__item--downloads'); |
| 262 | var sep = badge.querySelector('.ep-view-count__sep'); |
| 263 | sep.hidden = v.hidden || d.hidden; |
| 264 | if (!sep.hidden) sep.textContent = '·'; |
| 265 | } |
| 266 | |
| 267 | function postForm(url, params) { |
| 268 | var body = new URLSearchParams(); |
| 269 | Object.keys(params).forEach(function (k) { |
| 270 | if (params[k] != null && params[k] !== '') body.set(k, params[k]); |
| 271 | }); |
| 272 | return fetch(url, { |
| 273 | method: 'POST', |
| 274 | credentials: 'same-origin', |
| 275 | headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, |
| 276 | body: body.toString() |
| 277 | }) |
| 278 | .then(function (r) { return r.ok ? r.json() : null; }) |
| 279 | .then(function (j) { return j && typeof j.count === 'number' ? j.count : null; }) |
| 280 | .catch(function () { return null; }); |
| 281 | } |
| 282 | function getJson(url, query) { |
| 283 | var sep = url.indexOf('?') === -1 ? '?' : '&'; |
| 284 | var full = url + sep + 'content_id=' + encodeURIComponent(query); |
| 285 | return fetch(full, { credentials: 'same-origin' }) |
| 286 | .then(function (r) { return r.ok ? r.json() : null; }) |
| 287 | .then(function (j) { return j && typeof j.count === 'number' ? j.count : null; }) |
| 288 | .catch(function () { return null; }); |
| 289 | } |
| 290 | |
| 291 | // The per-embed block/widget toggle is the ONLY thing that shows a counter. |
| 292 | // The global option never overrides it. Editors emit an explicit marker on |
| 293 | // the wrapper: |
| 294 | // data-ep-views="on" -> show this counter for this embed |
| 295 | // data-ep-views="off" -> hide this counter for this embed |
| 296 | // absent -> off (e.g. embeds saved before this feature) |
| 297 | function resolve(el, attr) { |
| 298 | return el.getAttribute(attr) === 'on'; |
| 299 | } |
| 300 | function viewAllowed(el) { |
| 301 | return resolve(el, 'data-ep-views'); |
| 302 | } |
| 303 | function downloadAllowed(el) { |
| 304 | return resolve(el, 'data-ep-downloads'); |
| 305 | } |
| 306 | |
| 307 | function processElement(el) { |
| 308 | if (processed.has(el)) return; |
| 309 | var embedType = el.getAttribute('data-embed-type'); |
| 310 | if (!embedType || cfg.types.indexOf(embedType) === -1) return; |
| 311 | processed.add(el); |
| 312 | |
| 313 | var contentId = deriveContentId(el, embedType); |
| 314 | var embedUrl = getEmbedUrl(el); |
| 315 | contentIdIndex[contentId] = el; |
| 316 | |
| 317 | if (viewAllowed(el) && cfg.trackUrl) { |
| 318 | postForm(cfg.trackUrl, { |
| 319 | content_id: contentId, |
| 320 | session_id: sessionId, |
| 321 | embed_type: embedType, |
| 322 | embed_url: embedUrl |
| 323 | }).then(function (count) { |
| 324 | if (count === null) return getJson(cfg.restUrl, contentId); |
| 325 | return count; |
| 326 | }).then(function (count) { |
| 327 | if (count === null) return; |
| 328 | setViewCount(el, count); |
| 329 | }); |
| 330 | } |
| 331 | |
| 332 | if (downloadAllowed(el) && cfg.downloadUrl) { |
| 333 | getJson(cfg.downloadUrl, contentId).then(function (count) { |
| 334 | setDownloadCount(el, count === null ? 0 : count); |
| 335 | }); |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | function scan(root) { |
| 340 | var nodes = (root || document).querySelectorAll('[data-embed-type]'); |
| 341 | for (var i = 0; i < nodes.length; i++) { |
| 342 | if (nodes[i].parentElement && nodes[i].parentElement.closest('[data-embed-type]')) continue; |
| 343 | processElement(nodes[i]); |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | if (document.readyState === 'loading') { |
| 348 | document.addEventListener('DOMContentLoaded', function () { scan(); }); |
| 349 | } else { |
| 350 | scan(); |
| 351 | } |
| 352 | |
| 353 | if ('MutationObserver' in window) { |
| 354 | new MutationObserver(function (mutations) { |
| 355 | mutations.forEach(function (m) { |
| 356 | m.addedNodes.forEach(function (n) { |
| 357 | if (n.nodeType !== 1) return; |
| 358 | if (n.matches && n.matches('[data-embed-type]')) processElement(n); |
| 359 | if (n.querySelectorAll) n.querySelectorAll('[data-embed-type]').forEach(processElement); |
| 360 | }); |
| 361 | }); |
| 362 | }).observe(document.body, { childList: true, subtree: true }); |
| 363 | } |
| 364 | |
| 365 | // Refresh view badge after analytics tracker fires its own view event. |
| 366 | document.addEventListener('embedpress:view', function (ev) { |
| 367 | var detail = ev && ev.detail; |
| 368 | if (!detail || !detail.contentId) return; |
| 369 | var el = contentIdIndex[detail.contentId]; |
| 370 | if (!el || !cfg.restUrl) return; |
| 371 | getJson(cfg.restUrl, detail.contentId).then(function (c) { |
| 372 | if (c !== null) setViewCount(el, c); |
| 373 | }); |
| 374 | }); |
| 375 | |
| 376 | /** |
| 377 | * Receive download notifications from the bundled PDF.js viewer |
| 378 | * (static/pdf/web/ep-scripts.js posts these when the toolbar |
| 379 | * Download / Save buttons are clicked). Resolves the wrapper from |
| 380 | * event.source.frameElement so we credit the right embed when the |
| 381 | * page contains multiple PDFs. |
| 382 | */ |
| 383 | window.addEventListener('message', function (ev) { |
| 384 | var data = ev && ev.data; |
| 385 | if (!data || data.source !== 'embedpress-pdf-viewer' || data.type !== 'download') return; |
| 386 | // Need an endpoint to POST to; the actual per-embed gate is |
| 387 | // downloadAllowed(el) below (after we resolve which embed sent this), |
| 388 | // so a per-embed opt-in still tracks when the global default is off. |
| 389 | if (!cfg.downloadTrackUrl) return; |
| 390 | |
| 391 | // Resolve which embed sent the download. The PDF.js viewer runs at |
| 392 | // admin-ajax.php (same origin), so `iframe.contentWindow === ev.source` |
| 393 | // is the primary match. Firefox, however, does not always keep that |
| 394 | // identity stable across the viewer's internal reloads/bfcache, so we |
| 395 | // fall back to matching the viewer iframe by its own `file=` src — the |
| 396 | // most reliable cross-browser signal — before the host-URL fallback. |
| 397 | var sourceFrame = null; |
| 398 | try { |
| 399 | var frames = document.getElementsByTagName('iframe'); |
| 400 | for (var i = 0; i < frames.length; i++) { |
| 401 | if (frames[i].contentWindow === ev.source) { sourceFrame = frames[i]; break; } |
| 402 | } |
| 403 | } catch (e) { /* cross-origin — try fallbacks below */ } |
| 404 | |
| 405 | var el = sourceFrame ? sourceFrame.closest('[data-embed-type]') : null; |
| 406 | |
| 407 | // Firefox fallback: the message carries the viewer's own href, whose |
| 408 | // `file=` param identifies the PDF. Match the iframe whose src points |
| 409 | // at the same file and resolve ITS wrapper — this credits a correct |
| 410 | // instance even when contentWindow identity didn't hold. |
| 411 | if (!el) { |
| 412 | try { |
| 413 | // Prefer the explicit `file` the viewer sent (flipbook runs in a |
| 414 | // blank frame, so its href has no file= to parse). Fall back to |
| 415 | // parsing href's file= query for the classic PDF.js viewer. |
| 416 | var target = ''; |
| 417 | if (data.file) { |
| 418 | target = data.file; |
| 419 | } else { |
| 420 | var u = new URL(data.href || '', window.location.href); |
| 421 | var fileParam = u.searchParams.get('file'); |
| 422 | if (fileParam) target = decodeURIComponent(fileParam); |
| 423 | } |
| 424 | if (target) { |
| 425 | var iframes = document.getElementsByTagName('iframe'); |
| 426 | for (var k = 0; k < iframes.length && !el; k++) { |
| 427 | var isrc = iframes[k].getAttribute('src') || ''; |
| 428 | if (isrc.indexOf('file=') === -1) continue; |
| 429 | try { |
| 430 | var iu = new URL(isrc, window.location.href); |
| 431 | var ifile = iu.searchParams.get('file'); |
| 432 | if (ifile && decodeURIComponent(ifile) === target) { |
| 433 | el = iframes[k].closest('[data-embed-type]'); |
| 434 | } |
| 435 | } catch (e2) { /* skip this iframe */ } |
| 436 | } |
| 437 | // Last resort: match an embed wrapper by its resolved URL. |
| 438 | if (!el) { |
| 439 | var nodes = document.querySelectorAll('[data-embed-type]'); |
| 440 | for (var n = 0; n < nodes.length && !el; n++) { |
| 441 | if (getEmbedUrl(nodes[n]) === target) el = nodes[n]; |
| 442 | } |
| 443 | } |
| 444 | } |
| 445 | } catch (e) { /* ignore */ } |
| 446 | } |
| 447 | if (!el) return; |
| 448 | if (!downloadAllowed(el)) return; |
| 449 | |
| 450 | var embedType = el.getAttribute('data-embed-type'); |
| 451 | var contentId = el.getAttribute('data-embedpress-content') || deriveContentId(el, embedType); |
| 452 | var embedUrl = getEmbedUrl(el); |
| 453 | |
| 454 | // Optimistically bump the visible count NOW so it feels instant — the |
| 455 | // flipbook's watermark/blob download and the REST round-trip can take a |
| 456 | // moment, and waiting for the server response made the badge look like |
| 457 | // it wasn't reacting. Reconcile with the authoritative count below. |
| 458 | var current = parseInt(el.getAttribute('data-ep-download-count') || '0', 10); |
| 459 | if (isNaN(current)) current = 0; |
| 460 | setDownloadCount(el, current + 1); |
| 461 | |
| 462 | postForm(cfg.downloadTrackUrl, { |
| 463 | content_id: contentId, |
| 464 | session_id: sessionId, |
| 465 | embed_type: embedType, |
| 466 | embed_url: embedUrl |
| 467 | }).then(function (count) { |
| 468 | if (count === null) return; |
| 469 | setDownloadCount(el, count); |
| 470 | }); |
| 471 | }); |
| 472 | })(); |
| 473 |