@tolinku/web-sdk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/index.d.mts +70 -1
- package/dist/index.d.ts +70 -1
- package/dist/index.js +307 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +307 -57
- package/dist/index.mjs.map +1 -1
- package/dist/tolinku.min.js +71 -0
- package/dist/tolinku.min.js.map +1 -0
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -167,7 +167,11 @@ var HttpClient = class {
|
|
|
167
167
|
}
|
|
168
168
|
headers() {
|
|
169
169
|
return {
|
|
170
|
-
"X-API-Key": this.apiKey
|
|
170
|
+
"X-API-Key": this.apiKey,
|
|
171
|
+
// Browsers default to "Accept: */*", which a server content-negotiating an
|
|
172
|
+
// error page reads as "HTML is fine". Saying so explicitly keeps an error
|
|
173
|
+
// parseable instead of arriving as a rendered page.
|
|
174
|
+
"Accept": "application/json"
|
|
171
175
|
};
|
|
172
176
|
}
|
|
173
177
|
};
|
|
@@ -525,6 +529,7 @@ var Referrals = class {
|
|
|
525
529
|
};
|
|
526
530
|
|
|
527
531
|
// src/deferred.ts
|
|
532
|
+
var CLAIMED_KEY = "tolinku_deferred_claimed";
|
|
528
533
|
var Deferred = class {
|
|
529
534
|
constructor(client) {
|
|
530
535
|
this.client = client;
|
|
@@ -538,19 +543,85 @@ var Deferred = class {
|
|
|
538
543
|
return null;
|
|
539
544
|
}
|
|
540
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* Recover the link that led here, once.
|
|
548
|
+
*
|
|
549
|
+
* There is no Play Install Referrer on the web, so this is signal matching
|
|
550
|
+
* with the bookkeeping that makes calling it safe. That bookkeeping is the
|
|
551
|
+
* point: a claim is consumed the first time it succeeds, so an app calling
|
|
552
|
+
* claimBySignals on every page load asks again after the answer is already
|
|
553
|
+
* spent, and every one of those asks is recorded as a miss. The match rate on
|
|
554
|
+
* the dashboard then falls towards zero while the integration is working
|
|
555
|
+
* correctly, which is a hard thing to diagnose from the outside.
|
|
556
|
+
*
|
|
557
|
+
* Call it once on first run. Calling it again is free after the first.
|
|
558
|
+
*
|
|
559
|
+
* Named to match the Android, React Native and Flutter SDKs, where the same
|
|
560
|
+
* call also tries the install referrer before falling back to here.
|
|
561
|
+
*/
|
|
562
|
+
async claimDeferredLink(options) {
|
|
563
|
+
if (!options.appspaceId || !options.appspaceId.trim()) {
|
|
564
|
+
throw new Error("Tolinku: appspaceId is required and must not be blank for claimDeferredLink.");
|
|
565
|
+
}
|
|
566
|
+
if (!options.force && this.alreadyAttempted()) return null;
|
|
567
|
+
const { link, settled } = await this.attemptSignals({ appspaceId: options.appspaceId });
|
|
568
|
+
if (settled) this.rememberAttempt();
|
|
569
|
+
return link;
|
|
570
|
+
}
|
|
571
|
+
alreadyAttempted() {
|
|
572
|
+
try {
|
|
573
|
+
return window.localStorage.getItem(CLAIMED_KEY) !== null;
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
rememberAttempt() {
|
|
579
|
+
try {
|
|
580
|
+
window.localStorage.setItem(CLAIMED_KEY, (/* @__PURE__ */ new Date()).toISOString());
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
}
|
|
541
584
|
/** Claim a deferred deep link by device signal matching */
|
|
542
585
|
async claimBySignals(options) {
|
|
586
|
+
return (await this.attemptSignals(options)).link;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* The signal claim, with whether the server actually answered.
|
|
590
|
+
*
|
|
591
|
+
* `settled` separates "nothing is waiting for this device", which no amount
|
|
592
|
+
* of asking will change, from "the request never got there". Both surface as
|
|
593
|
+
* null to callers of claimBySignals, but claimDeferredLink has to tell them
|
|
594
|
+
* apart: recording an attempt that never reached the server would spend an
|
|
595
|
+
* install's one chance at attribution on a dropped connection.
|
|
596
|
+
*/
|
|
597
|
+
async attemptSignals(options) {
|
|
543
598
|
try {
|
|
544
|
-
|
|
599
|
+
const link = await this.client.postPublic("/v1/api/deferred/claim-by-signals", {
|
|
545
600
|
appspace_id: options.appspaceId,
|
|
546
601
|
timezone: options.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
547
602
|
language: options.language || navigator.language,
|
|
548
603
|
screen_width: options.screenWidth || window.screen.width,
|
|
549
|
-
screen_height: options.screenHeight || window.screen.height
|
|
604
|
+
screen_height: options.screenHeight || window.screen.height,
|
|
605
|
+
// Separates devices reporting identical logical dimensions.
|
|
606
|
+
device_pixel_ratio: options.devicePixelRatio || window.devicePixelRatio || 1
|
|
550
607
|
});
|
|
608
|
+
return { link, settled: true };
|
|
551
609
|
} catch (err) {
|
|
610
|
+
const status = err?.statusCode ?? err?.status;
|
|
611
|
+
if (status === 404) {
|
|
612
|
+
return { link: null, settled: true };
|
|
613
|
+
}
|
|
614
|
+
if (status === 403) {
|
|
615
|
+
console.warn(
|
|
616
|
+
"[Tolinku] Failed to claim deferred link by signals: HTTP 403.",
|
|
617
|
+
"Check that appspaceId is your Appspace ID (copy it from the dashboard",
|
|
618
|
+
"under Settings), not your subdomain or slug.",
|
|
619
|
+
err
|
|
620
|
+
);
|
|
621
|
+
return { link: null, settled: false };
|
|
622
|
+
}
|
|
552
623
|
console.warn("[Tolinku] Failed to claim deferred link by signals:", err);
|
|
553
|
-
return null;
|
|
624
|
+
return { link: null, settled: false };
|
|
554
625
|
}
|
|
555
626
|
}
|
|
556
627
|
};
|
|
@@ -638,11 +709,56 @@ function sanitizeCssColor(value) {
|
|
|
638
709
|
}
|
|
639
710
|
|
|
640
711
|
// src/banners.ts
|
|
712
|
+
var themes = {
|
|
713
|
+
light: {
|
|
714
|
+
bg: "#ffffff",
|
|
715
|
+
border: "",
|
|
716
|
+
shadow: "sm",
|
|
717
|
+
title: { color: "#000000", size: 14, weight: 600 },
|
|
718
|
+
body: { color: "#000000", size: 12, weight: 400 },
|
|
719
|
+
cta: { size: 13, weight: 600, radius: 100 },
|
|
720
|
+
icon: { size: 40, radius: 10 }
|
|
721
|
+
},
|
|
722
|
+
dark: {
|
|
723
|
+
bg: "#1B1B1B",
|
|
724
|
+
border: "",
|
|
725
|
+
shadow: "sm",
|
|
726
|
+
title: { color: "#ffffff", size: 14, weight: 600 },
|
|
727
|
+
body: { color: "#ffffff", size: 12, weight: 400 },
|
|
728
|
+
cta: { size: 13, weight: 600, radius: 100 },
|
|
729
|
+
icon: { size: 40, radius: 10 }
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
var SHADOW_VALUES = {
|
|
733
|
+
none: "none",
|
|
734
|
+
sm: "0 2px 8px rgba(0,0,0,0.15)",
|
|
735
|
+
md: "0 6px 20px rgba(0,0,0,0.18)",
|
|
736
|
+
lg: "0 12px 36px rgba(0,0,0,0.22)"
|
|
737
|
+
};
|
|
738
|
+
function clampInt(val, min, max) {
|
|
739
|
+
if (val === void 0 || val === null || isNaN(val)) return null;
|
|
740
|
+
return Math.max(min, Math.min(max, Math.floor(val)));
|
|
741
|
+
}
|
|
742
|
+
function sanitizeClass(val) {
|
|
743
|
+
if (!val || typeof val !== "string") return "";
|
|
744
|
+
return val.replace(/[^a-zA-Z0-9_\-\s]/g, "").slice(0, 100).trim();
|
|
745
|
+
}
|
|
746
|
+
function isSafeUrl(url) {
|
|
747
|
+
try {
|
|
748
|
+
const parsed = new URL(url, window.location.href);
|
|
749
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
750
|
+
} catch {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
641
754
|
var Banners = class {
|
|
642
755
|
constructor(client) {
|
|
643
756
|
this.client = client;
|
|
644
757
|
this.container = null;
|
|
645
758
|
this.styleEl = null;
|
|
759
|
+
this.currentAnimation = "slide";
|
|
760
|
+
this.currentStyle = "pinned";
|
|
761
|
+
this.currentPosition = "top";
|
|
646
762
|
}
|
|
647
763
|
/** Fetch banner config and show the highest-priority banner */
|
|
648
764
|
async show(options = {}, userId) {
|
|
@@ -660,94 +776,200 @@ var Banners = class {
|
|
|
660
776
|
}
|
|
661
777
|
}
|
|
662
778
|
if (!banner) return;
|
|
663
|
-
|
|
779
|
+
const delay = clampInt(options.delay, 0, 6e4);
|
|
780
|
+
if (delay && delay > 0) {
|
|
781
|
+
setTimeout(() => this.render(config, banner, options), delay);
|
|
782
|
+
} else {
|
|
783
|
+
this.render(config, banner, options);
|
|
784
|
+
}
|
|
664
785
|
}
|
|
665
|
-
/** Remove the banner from the DOM */
|
|
786
|
+
/** Remove the banner from the DOM, respecting the active animation */
|
|
666
787
|
dismiss() {
|
|
667
|
-
if (this.container)
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
788
|
+
if (!this.container) return;
|
|
789
|
+
const container = this.container;
|
|
790
|
+
const styleEl = this.styleEl;
|
|
791
|
+
container.classList.remove("tolk-visible");
|
|
792
|
+
if (this.currentStyle === "pinned") {
|
|
793
|
+
document.body.style.removeProperty(this.currentPosition === "top" ? "padding-top" : "padding-bottom");
|
|
794
|
+
}
|
|
795
|
+
const cleanup = () => {
|
|
796
|
+
container.remove();
|
|
797
|
+
styleEl?.remove();
|
|
798
|
+
if (this.container === container) {
|
|
674
799
|
this.container = null;
|
|
675
800
|
this.styleEl = null;
|
|
676
|
-
}
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
if (this.currentAnimation === "none") {
|
|
804
|
+
cleanup();
|
|
805
|
+
} else {
|
|
806
|
+
setTimeout(cleanup, 400);
|
|
677
807
|
}
|
|
678
808
|
}
|
|
679
809
|
render(config, banner, options) {
|
|
680
810
|
if (this.container) {
|
|
681
811
|
this.container.remove();
|
|
682
812
|
this.styleEl?.remove();
|
|
813
|
+
if (this.currentStyle === "pinned") {
|
|
814
|
+
document.body.style.removeProperty(this.currentPosition === "top" ? "padding-top" : "padding-bottom");
|
|
815
|
+
}
|
|
683
816
|
}
|
|
684
|
-
const
|
|
685
|
-
const
|
|
686
|
-
|
|
817
|
+
const themeName = options.theme && themes[options.theme] ? options.theme : "light";
|
|
818
|
+
const theme = themes[themeName];
|
|
819
|
+
let position = "top";
|
|
820
|
+
if (options.position === "top" || options.position === "bottom") position = options.position;
|
|
821
|
+
else if (banner.position === "top" || banner.position === "bottom") position = banner.position;
|
|
822
|
+
let resolvedStyle = "pinned";
|
|
823
|
+
if (options.style === "pinned" || options.style === "floating" || options.style === "stacked") {
|
|
824
|
+
resolvedStyle = options.style;
|
|
825
|
+
} else if (banner.style === "pinned" || banner.style === "floating" || banner.style === "stacked") {
|
|
826
|
+
resolvedStyle = banner.style;
|
|
827
|
+
}
|
|
828
|
+
const floating = resolvedStyle === "floating";
|
|
829
|
+
const stacked = resolvedStyle === "stacked";
|
|
830
|
+
let animation = options.animation || "slide";
|
|
831
|
+
if (animation === "pop" && !floating && !stacked) animation = "slide";
|
|
832
|
+
this.currentPosition = position;
|
|
833
|
+
this.currentStyle = resolvedStyle;
|
|
834
|
+
this.currentAnimation = animation;
|
|
835
|
+
const serverBg = sanitizeCssColor(banner.background_color);
|
|
836
|
+
const serverText = sanitizeCssColor(banner.text_color);
|
|
837
|
+
const optBg = sanitizeCssColor(options.bg);
|
|
838
|
+
const optBorder = sanitizeCssColor(options.border);
|
|
839
|
+
const optTitleColor = sanitizeCssColor(options.titleColor);
|
|
840
|
+
const optBodyColor = sanitizeCssColor(options.bodyColor);
|
|
841
|
+
const optCtaBg = sanitizeCssColor(options.ctaBg);
|
|
842
|
+
const optCtaColor = sanitizeCssColor(options.ctaColor);
|
|
843
|
+
const bg = optBg || serverBg || theme.bg;
|
|
844
|
+
const border = optBorder || theme.border;
|
|
845
|
+
const titleColor = optTitleColor || serverText || theme.title.color;
|
|
846
|
+
const bodyColor = optBodyColor || serverText || theme.body.color;
|
|
847
|
+
const ctaBg = optCtaBg || theme.cta.bg || titleColor;
|
|
848
|
+
const ctaColor = optCtaColor || theme.cta.color || bg;
|
|
849
|
+
const titleSize = clampInt(options.titleSize, 10, 24) ?? theme.title.size;
|
|
850
|
+
const titleWeight = options.titleWeight ?? theme.title.weight;
|
|
851
|
+
const bodySize = clampInt(options.bodySize, 10, 20) ?? theme.body.size;
|
|
852
|
+
const bodyWeight = options.bodyWeight ?? theme.body.weight;
|
|
853
|
+
const ctaSize = clampInt(options.ctaSize, 10, 18) ?? theme.cta.size;
|
|
854
|
+
const ctaWeight = options.ctaWeight ?? theme.cta.weight;
|
|
855
|
+
const ctaRadius = clampInt(options.ctaRadius, 0, 100) ?? theme.cta.radius;
|
|
856
|
+
const iconSize = clampInt(options.iconSize, 24, 64) ?? theme.icon.size;
|
|
857
|
+
const iconRadius = clampInt(options.iconRadius, 0, 32) ?? theme.icon.radius;
|
|
858
|
+
const optRadius = clampInt(options.radius, 0, 24);
|
|
859
|
+
const serverRadius = typeof banner.radius === "number" && banner.radius >= 0 && banner.radius <= 24 ? banner.radius : null;
|
|
860
|
+
const optMargin = clampInt(options.margin, 0, 24);
|
|
861
|
+
const serverMargin = typeof banner.margin === "number" && banner.margin >= 0 && banner.margin <= 24 ? banner.margin : null;
|
|
862
|
+
const bannerRadius = floating ? optRadius ?? serverRadius ?? 12 : 0;
|
|
863
|
+
const bannerMargin = floating ? optMargin ?? serverMargin ?? 12 : 0;
|
|
864
|
+
const shadowKey = options.shadow && SHADOW_VALUES[options.shadow] ? options.shadow : banner.shadow && SHADOW_VALUES[banner.shadow] ? banner.shadow : theme.shadow;
|
|
865
|
+
const shadow = SHADOW_VALUES[shadowKey];
|
|
866
|
+
const hideIcon = !!options.hideIcon;
|
|
867
|
+
const hideClose = !!options.hideClose;
|
|
868
|
+
const hideBody = !!options.hideBody;
|
|
869
|
+
const customClass = sanitizeClass(options.customClass);
|
|
687
870
|
const ctaText = banner.cta_text || "Open";
|
|
688
|
-
const
|
|
689
|
-
const installUrl = banner.action_url || baseUrl + (config.install_url || "/install");
|
|
871
|
+
const installUrl = banner.action_url || this.client.baseUrl + (config.install_url || "/install");
|
|
690
872
|
const safeTop = position === "top" ? "padding-top: env(safe-area-inset-top, 0px);" : "";
|
|
691
873
|
const safeBottom = position === "bottom" ? "padding-bottom: env(safe-area-inset-bottom, 0px);" : "";
|
|
874
|
+
let containerPosition;
|
|
875
|
+
let containerInsets;
|
|
876
|
+
if (stacked) {
|
|
877
|
+
containerPosition = "position: sticky;";
|
|
878
|
+
containerInsets = `${position}: 0;`;
|
|
879
|
+
} else if (floating) {
|
|
880
|
+
containerPosition = "position: fixed;";
|
|
881
|
+
containerInsets = `left: ${bannerMargin}px; right: ${bannerMargin}px; ${position}: ${bannerMargin}px;`;
|
|
882
|
+
} else {
|
|
883
|
+
containerPosition = "position: fixed;";
|
|
884
|
+
containerInsets = `left: 0; right: 0; ${position}: 0;`;
|
|
885
|
+
}
|
|
886
|
+
const hideOffset = bannerMargin + 60;
|
|
887
|
+
const slideHide = position === "top" ? `translateY(calc(-100% - ${hideOffset}px))` : `translateY(calc(100% + ${hideOffset}px))`;
|
|
888
|
+
let hiddenCss = "";
|
|
889
|
+
let visibleCss = "";
|
|
890
|
+
let transitionCss = "";
|
|
891
|
+
if (animation === "slide") {
|
|
892
|
+
hiddenCss = `transform: ${slideHide};`;
|
|
893
|
+
visibleCss = "transform: translateY(0);";
|
|
894
|
+
transitionCss = "transition: transform 0.35s ease;";
|
|
895
|
+
} else if (animation === "fade") {
|
|
896
|
+
hiddenCss = "opacity: 0;";
|
|
897
|
+
visibleCss = "opacity: 1;";
|
|
898
|
+
transitionCss = "transition: opacity 0.3s ease;";
|
|
899
|
+
} else if (animation === "pop") {
|
|
900
|
+
hiddenCss = "opacity: 0; transform: scale(0.92);";
|
|
901
|
+
visibleCss = "opacity: 1; transform: scale(1);";
|
|
902
|
+
transitionCss = "transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.25s ease;";
|
|
903
|
+
}
|
|
692
904
|
const container = document.createElement("div");
|
|
693
905
|
container.id = "tolinku-banner";
|
|
694
906
|
container.setAttribute("role", "banner");
|
|
695
907
|
container.setAttribute("aria-live", "polite");
|
|
696
908
|
container.dataset.position = position;
|
|
909
|
+
if (customClass) container.className = customClass;
|
|
910
|
+
const borderCss = border ? `border: 1px solid ${border};` : "";
|
|
911
|
+
const radiusCss = bannerRadius > 0 ? `border-radius: ${bannerRadius}px;` : "";
|
|
697
912
|
const style = document.createElement("style");
|
|
698
913
|
style.textContent = `
|
|
699
914
|
#tolinku-banner {
|
|
700
|
-
|
|
701
|
-
${
|
|
702
|
-
left: 0; right: 0;
|
|
915
|
+
${containerPosition}
|
|
916
|
+
${containerInsets}
|
|
703
917
|
z-index: 999999;
|
|
704
918
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
705
|
-
|
|
706
|
-
|
|
919
|
+
${hiddenCss}
|
|
920
|
+
${transitionCss}
|
|
707
921
|
${safeTop}${safeBottom}
|
|
708
922
|
}
|
|
709
|
-
#tolinku-banner.tolk-visible {
|
|
923
|
+
#tolinku-banner.tolk-visible { ${visibleCss} }
|
|
710
924
|
#tolinku-banner .tolk-inner {
|
|
711
925
|
display: flex; align-items: center; gap: 10px;
|
|
712
926
|
padding: 10px 14px;
|
|
713
|
-
background: ${
|
|
714
|
-
|
|
927
|
+
background: ${bg};
|
|
928
|
+
${borderCss}
|
|
929
|
+
${radiusCss}
|
|
930
|
+
box-shadow: ${shadow};
|
|
715
931
|
}
|
|
716
932
|
#tolinku-banner .tolk-close {
|
|
717
933
|
background: none; border: none; font-size: 20px; line-height: 1;
|
|
718
|
-
cursor: pointer; color: ${
|
|
934
|
+
cursor: pointer; color: ${titleColor}; opacity: 0.6; padding: 0 4px; flex-shrink: 0;
|
|
719
935
|
}
|
|
720
936
|
#tolinku-banner .tolk-close:hover { opacity: 1; }
|
|
721
937
|
#tolinku-banner .tolk-icon {
|
|
722
|
-
width:
|
|
938
|
+
width: ${iconSize}px; height: ${iconSize}px; border-radius: ${iconRadius}px;
|
|
939
|
+
flex-shrink: 0; object-fit: cover;
|
|
723
940
|
}
|
|
724
941
|
#tolinku-banner .tolk-text { flex: 1; min-width: 0; }
|
|
725
942
|
#tolinku-banner .tolk-title {
|
|
726
|
-
font-size:
|
|
943
|
+
font-size: ${titleSize}px; font-weight: ${titleWeight}; line-height: 1.3;
|
|
944
|
+
color: ${titleColor}; margin: 0;
|
|
727
945
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
728
946
|
}
|
|
729
947
|
#tolinku-banner .tolk-body {
|
|
730
|
-
font-size:
|
|
948
|
+
font-size: ${bodySize}px; font-weight: ${bodyWeight}; line-height: 1.3;
|
|
949
|
+
color: ${bodyColor}; margin: 0; opacity: 0.75;
|
|
731
950
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
732
951
|
}
|
|
733
952
|
#tolinku-banner .tolk-cta {
|
|
734
|
-
display: inline-block; padding:
|
|
735
|
-
font-size:
|
|
736
|
-
background: ${
|
|
953
|
+
display: inline-block; padding: 0.5em 1.2em; border-radius: ${ctaRadius}px;
|
|
954
|
+
font-size: ${ctaSize}px; font-weight: ${ctaWeight}; line-height: 1.2;
|
|
955
|
+
text-decoration: none; background: ${ctaBg}; color: ${ctaColor};
|
|
956
|
+
flex-shrink: 0; text-align: center;
|
|
737
957
|
}
|
|
738
958
|
`;
|
|
739
959
|
const inner = document.createElement("div");
|
|
740
960
|
inner.className = "tolk-inner";
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
961
|
+
if (!hideClose) {
|
|
962
|
+
const closeBtn = document.createElement("button");
|
|
963
|
+
closeBtn.className = "tolk-close";
|
|
964
|
+
closeBtn.setAttribute("aria-label", "Dismiss banner");
|
|
965
|
+
closeBtn.textContent = "\xD7";
|
|
966
|
+
closeBtn.addEventListener("click", () => {
|
|
967
|
+
saveBannerDismissal(banner.id);
|
|
968
|
+
this.dismiss();
|
|
969
|
+
});
|
|
970
|
+
inner.appendChild(closeBtn);
|
|
971
|
+
}
|
|
972
|
+
if (!hideIcon && config.app_icon && isSafeUrl(config.app_icon)) {
|
|
751
973
|
const icon = document.createElement("img");
|
|
752
974
|
icon.className = "tolk-icon";
|
|
753
975
|
icon.src = config.app_icon;
|
|
@@ -760,7 +982,7 @@ var Banners = class {
|
|
|
760
982
|
titleEl.className = "tolk-title";
|
|
761
983
|
titleEl.textContent = banner.title || config.app_name || "Get the App";
|
|
762
984
|
textWrap.appendChild(titleEl);
|
|
763
|
-
if (banner.body) {
|
|
985
|
+
if (!hideBody && banner.body) {
|
|
764
986
|
const bodyEl = document.createElement("p");
|
|
765
987
|
bodyEl.className = "tolk-body";
|
|
766
988
|
bodyEl.textContent = banner.body;
|
|
@@ -774,29 +996,57 @@ var Banners = class {
|
|
|
774
996
|
inner.appendChild(cta);
|
|
775
997
|
container.appendChild(inner);
|
|
776
998
|
document.head.appendChild(style);
|
|
777
|
-
|
|
999
|
+
if (stacked) {
|
|
1000
|
+
const anchor = findStackedAnchor(position, options.anchor);
|
|
1001
|
+
if (anchor && anchor.parentNode) {
|
|
1002
|
+
if (position === "top") {
|
|
1003
|
+
anchor.parentNode.insertBefore(container, anchor);
|
|
1004
|
+
} else if (anchor.nextSibling) {
|
|
1005
|
+
anchor.parentNode.insertBefore(container, anchor.nextSibling);
|
|
1006
|
+
} else {
|
|
1007
|
+
anchor.parentNode.appendChild(container);
|
|
1008
|
+
}
|
|
1009
|
+
} else if (document.body) {
|
|
1010
|
+
if (position === "top") {
|
|
1011
|
+
document.body.insertBefore(container, document.body.firstChild);
|
|
1012
|
+
} else {
|
|
1013
|
+
document.body.appendChild(container);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
} else {
|
|
1017
|
+
document.body.appendChild(container);
|
|
1018
|
+
}
|
|
778
1019
|
this.container = container;
|
|
779
1020
|
this.styleEl = style;
|
|
780
1021
|
requestAnimationFrame(() => {
|
|
781
1022
|
requestAnimationFrame(() => {
|
|
782
1023
|
container.classList.add("tolk-visible");
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1024
|
+
if (!floating && !stacked) {
|
|
1025
|
+
const bannerHeight = container.offsetHeight + "px";
|
|
1026
|
+
if (position === "top") {
|
|
1027
|
+
document.body.style.paddingTop = bannerHeight;
|
|
1028
|
+
} else {
|
|
1029
|
+
document.body.style.paddingBottom = bannerHeight;
|
|
1030
|
+
}
|
|
788
1031
|
}
|
|
789
1032
|
});
|
|
790
1033
|
});
|
|
791
1034
|
}
|
|
792
1035
|
};
|
|
793
|
-
function
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1036
|
+
function findStackedAnchor(position, explicit) {
|
|
1037
|
+
if (explicit) {
|
|
1038
|
+
try {
|
|
1039
|
+
const el = document.querySelector(explicit);
|
|
1040
|
+
if (el) return el;
|
|
1041
|
+
} catch {
|
|
1042
|
+
}
|
|
799
1043
|
}
|
|
1044
|
+
const selectors = position === "top" ? ["header", '[role="banner"]', "nav", ".header", "#header", ".navbar"] : ["footer", '[role="contentinfo"]', ".footer", "#footer"];
|
|
1045
|
+
for (const s of selectors) {
|
|
1046
|
+
const el = document.body?.querySelector(s);
|
|
1047
|
+
if (el) return el;
|
|
1048
|
+
}
|
|
1049
|
+
return null;
|
|
800
1050
|
}
|
|
801
1051
|
|
|
802
1052
|
// src/messages.ts
|