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