posthog-js
Version:
Posthog-js allows you to automatically capture usage and send events to PostHog.
365 lines • 15.8 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getProductTourStylesheet = getProductTourStylesheet;
exports.hasElementTarget = hasElementTarget;
exports.findElementBySelector = findElementBySelector;
exports.findStepElement = findStepElement;
exports.isElementVisible = isElementVisible;
exports.getElementMetadata = getElementMetadata;
exports.calculateTooltipPosition = calculateTooltipPosition;
exports.getSpotlightStyle = getSpotlightStyle;
exports.addProductTourCSSVariablesToElement = addProductTourCSSVariablesToElement;
exports.renderTipTapContent = renderTipTapContent;
exports.normalizeUrl = normalizeUrl;
exports.resolveStepTranslation = resolveStepTranslation;
exports.getStepImageUrls = getStepImageUrls;
exports.getStepHtml = getStepHtml;
exports.hasTourWaitPeriodPassed = hasTourWaitPeriodPassed;
var dompurify_1 = __importDefault(require("dompurify"));
var posthog_product_tours_types_1 = require("../../posthog-product-tours-types");
var element_inference_1 = require("./element-inference");
var stylesheet_loader_1 = require("@posthog/browser-common/utils/stylesheet-loader");
var globals_1 = require("@posthog/browser-common/utils/globals");
var surveys_extension_utils_1 = require("../surveys/surveys-extension-utils");
var logger_1 = require("@posthog/browser-common/utils/logger");
var storage_1 = require("../../storage");
var constants_1 = require("./constants");
var product_tour_css_1 = __importDefault(require("./product-tour.css"));
var core_1 = require("@posthog/core");
var matcher_utils_1 = require("@posthog/browser-common/utils/matcher-utils");
var logger = (0, logger_1.createLogger)('[Product Tours]');
var document = globals_1.document;
var window = globals_1.window;
function getProductTourStylesheet(posthog) {
var stylesheet = (0, stylesheet_loader_1.prepareStylesheet)(document, typeof product_tour_css_1.default === 'string' ? product_tour_css_1.default : '', posthog);
stylesheet === null || stylesheet === void 0 ? void 0 : stylesheet.setAttribute('data-ph-product-tour-style', 'true');
return stylesheet;
}
function hasElementTarget(step) {
if (step.useManualSelector) {
return !!step.selector;
}
return !!step.inferenceData;
}
function findElementBySelector(selector) {
try {
var elements = document.querySelectorAll(selector);
if (elements.length === 0) {
return { element: null, error: 'not_found', matchCount: 0 };
}
var element = elements[0];
if (!isElementVisible(element)) {
return { element: null, error: 'not_visible', matchCount: elements.length };
}
if (elements.length > 1) {
return { element: element, error: 'multiple_matches', matchCount: elements.length };
}
return { element: element, error: null, matchCount: 1 };
}
catch (_a) {
return { element: null, error: 'not_found', matchCount: 0 };
}
}
/**
* Find element for a step based on its lookup mode.
* Default: use inference. If useManualSelector is true: use CSS selector.
*/
function findStepElement(step) {
var _a;
var useManualSelector = (_a = step.useManualSelector) !== null && _a !== void 0 ? _a : false;
if (useManualSelector) {
if (!step.selector) {
return { element: null, error: 'not_found', matchCount: 0 };
}
return findElementBySelector(step.selector);
}
if (!step.inferenceData) {
return { element: null, error: 'not_found', matchCount: 0 };
}
var element = (0, element_inference_1.findElement)(step.inferenceData);
return element ? { element: element, error: null, matchCount: 1 } : { element: null, error: 'not_found', matchCount: 0 };
}
function isElementVisible(element) {
var style = window.getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
return false;
}
var rect = element.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
return false;
}
return true;
}
function getElementMetadata(element) {
var _a;
return {
tag: element.tagName,
id: element.id || undefined,
classes: element.className || undefined,
text: ((_a = element.innerText) === null || _a === void 0 ? void 0 : _a.slice(0, 100)) || undefined,
};
}
var TOOLTIP_MARGIN = 12;
var VIEWPORT_PADDING = 8;
function clampToViewport(value, dimension, viewportDimension) {
var min = VIEWPORT_PADDING + dimension / 2;
var max = viewportDimension - VIEWPORT_PADDING - dimension / 2;
var clamped = Math.max(min, Math.min(max, value));
return { clamped: clamped, offset: value - clamped };
}
function calculateTooltipPosition(targetRect, tooltipDimensions) {
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var width = tooltipDimensions.width, height = tooltipDimensions.height;
var spaceAbove = targetRect.top;
var spaceBelow = viewportHeight - targetRect.bottom;
var spaceLeft = targetRect.left;
var spaceRight = viewportWidth - targetRect.right;
var targetCenterY = targetRect.top + targetRect.height / 2;
var targetCenterX = targetRect.left + targetRect.width / 2;
if (spaceRight >= width + TOOLTIP_MARGIN) {
// right of element
var left_1 = targetRect.right + TOOLTIP_MARGIN;
var _a = clampToViewport(targetCenterY, height, viewportHeight), top_1 = _a.clamped, arrowOffset_1 = _a.offset;
return { position: 'right', top: top_1, left: left_1, arrowOffset: arrowOffset_1 };
}
if (spaceLeft >= width + TOOLTIP_MARGIN) {
// left of element
var right = viewportWidth - targetRect.left + TOOLTIP_MARGIN;
var _b = clampToViewport(targetCenterY, height, viewportHeight), top_2 = _b.clamped, arrowOffset_2 = _b.offset;
return { position: 'left', top: top_2, right: right, arrowOffset: arrowOffset_2 };
}
if (spaceAbove >= height + TOOLTIP_MARGIN && spaceBelow < height + TOOLTIP_MARGIN) {
// above element
var bottom = viewportHeight - targetRect.top + TOOLTIP_MARGIN;
var _c = clampToViewport(targetCenterX, width, viewportWidth), left_2 = _c.clamped, arrowOffset_3 = _c.offset;
return { position: 'top', bottom: bottom, left: left_2, arrowOffset: arrowOffset_3 };
}
// default: below element
var top = targetRect.bottom + TOOLTIP_MARGIN;
var _d = clampToViewport(targetCenterX, width, viewportWidth), left = _d.clamped, arrowOffset = _d.offset;
return { position: 'bottom', top: top, left: left, arrowOffset: arrowOffset };
}
function getSpotlightStyle(targetRect, padding) {
if (padding === void 0) { padding = 8; }
return {
top: "".concat(targetRect.top - padding, "px"),
left: "".concat(targetRect.left - padding, "px"),
width: "".concat(targetRect.width + padding * 2, "px"),
height: "".concat(targetRect.height + padding * 2, "px"),
};
}
function addProductTourCSSVariablesToElement(element, appearance) {
var merged = __assign(__assign({}, posthog_product_tours_types_1.DEFAULT_PRODUCT_TOUR_APPEARANCE), appearance);
var style = element.style;
// User-customizable variables
style.setProperty('--ph-tour-background-color', merged.backgroundColor);
style.setProperty('--ph-tour-text-color', merged.textColor);
style.setProperty('--ph-tour-button-color', merged.buttonColor);
style.setProperty('--ph-tour-border-radius', "".concat(merged.borderRadius, "px"));
style.setProperty('--ph-tour-button-border-radius', "".concat(merged.buttonBorderRadius, "px"));
style.setProperty('--ph-tour-border-color', merged.borderColor);
style.setProperty('--ph-tour-font-family', (0, surveys_extension_utils_1.getFontFamily)(merged.fontFamily));
// Derived colors
style.setProperty('--ph-tour-text-secondary-color', (0, surveys_extension_utils_1.hexToRgba)(merged.textColor, 0.6));
style.setProperty('--ph-tour-branding-text-color', (0, surveys_extension_utils_1.getContrastingTextColor)(merged.backgroundColor));
style.setProperty('--ph-tour-button-text-color', (0, surveys_extension_utils_1.getContrastingTextColor)(merged.buttonColor));
style.setProperty('--ph-tour-box-shadow', merged.boxShadow);
style.setProperty('--ph-tour-overlay-color', merged.showOverlay ? 'rgba(0, 0, 0, 0.5)' : 'transparent');
style.setProperty('--ph-tour-z-index', String(merged.zIndex));
// Internal styling variables (not customizable)
style.setProperty('--ph-tour-button-secondary-color', 'transparent');
style.setProperty('--ph-tour-button-secondary-text-color', merged.textColor);
style.setProperty('--ph-tour-max-width', '320px');
style.setProperty('--ph-tour-padding', '16px');
}
function renderTipTapContent(content) {
var e_1, _a;
var _b, _c;
if (!content) {
return '';
}
if (typeof content === 'string') {
return escapeHtml(content);
}
if (content.type === 'text') {
var text = escapeHtml(content.text || '');
if (content.marks) {
try {
for (var _d = __values(content.marks), _e = _d.next(); !_e.done; _e = _d.next()) {
var mark = _e.value;
switch (mark.type) {
case 'bold':
text = "<strong>".concat(text, "</strong>");
break;
case 'italic':
text = "<em>".concat(text, "</em>");
break;
case 'underline':
text = "<u>".concat(text, "</u>");
break;
case 'strike':
text = "<s>".concat(text, "</s>");
break;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
}
finally { if (e_1) throw e_1.error; }
}
}
return text;
}
var children = ((_b = content.content) === null || _b === void 0 ? void 0 : _b.map(renderTipTapContent).join('')) || '';
switch (content.type) {
case 'doc':
return children;
case 'paragraph':
return "<p>".concat(children, "</p>");
case 'heading': {
var level = ((_c = content.attrs) === null || _c === void 0 ? void 0 : _c.level) || 1;
return "<h".concat(level, ">").concat(children, "</h").concat(level, ">");
}
case 'bulletList':
return "<ul>".concat(children, "</ul>");
case 'orderedList':
return "<ol>".concat(children, "</ol>");
case 'listItem':
return "<li>".concat(children, "</li>");
case 'hardBreak':
return '<br>';
default:
return children;
}
}
function normalizeUrl(url) {
return url.endsWith('/') ? url.slice(0, -1) : url;
}
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function resolveStepTranslation(step, lang) {
var _a;
if (!lang || !step.translations) {
return step;
}
var translations = step.translations;
// exact match (en-US === en-US), then base match (en-US falls back to en)
var t = (_a = translations[lang]) !== null && _a !== void 0 ? _a : translations[lang.split('-')[0]];
if (!t) {
logger.info("No matching translation for \"".concat(lang, "\" in step ").concat(step.id, ", using default"));
return step;
}
var resolved = __assign({}, step);
if (!(0, core_1.isUndefined)(t.content)) {
resolved.content = t.content;
}
if (!(0, core_1.isUndefined)(t.contentHtml)) {
resolved.contentHtml = t.contentHtml;
}
if (t.buttons && resolved.buttons) {
resolved.buttons = {
primary: resolved.buttons.primary && __assign(__assign({}, resolved.buttons.primary), t.buttons.primary),
secondary: resolved.buttons.secondary && __assign(__assign({}, resolved.buttons.secondary), t.buttons.secondary),
};
}
if (t.survey && resolved.survey) {
resolved.survey = __assign(__assign({}, resolved.survey), t.survey);
}
return resolved;
}
function getStepImageUrls(step) {
var urls = [];
function walk(node) {
var _a, _b;
if (node.type === 'image' && ((_a = node.attrs) === null || _a === void 0 ? void 0 : _a.src)) {
urls.push(node.attrs.src);
}
(_b = node.content) === null || _b === void 0 ? void 0 : _b.forEach(walk);
}
if (step.content) {
walk(step.content);
}
return urls;
}
function getStepHtml(step) {
// Fall back to rendering legacy TipTap content when pre-rendered HTML is unavailable.
// Both paths must be sanitized because TipTap node attributes can contain untrusted values.
var html = step.contentHtml || renderTipTapContent(step.content);
return dompurify_1.default.sanitize(html, {
ADD_TAGS: ['iframe'],
ADD_ATTR: ['allowfullscreen', 'frameborder', 'referrerpolicy'],
});
}
function hasTourWaitPeriodPassed(seenTourWaitPeriod) {
var e_2, _a;
if (!seenTourWaitPeriod) {
return true;
}
var days = seenTourWaitPeriod.days, types = seenTourWaitPeriod.types;
if (!days || !types || types.length === 0) {
return true;
}
var mostRecentDate = null;
try {
for (var types_1 = __values(types), types_1_1 = types_1.next(); !types_1_1.done; types_1_1 = types_1.next()) {
var type = types_1_1.value;
var raw = storage_1.localStore._get("".concat(constants_1.LAST_SEEN_TOUR_DATE_KEY_PREFIX).concat(type));
if (raw) {
try {
var stored = JSON.parse(raw);
var date = new Date(stored);
if (!isNaN(date.getTime()) && (!mostRecentDate || date > mostRecentDate)) {
mostRecentDate = date;
}
}
catch (_b) {
// ignore malformed entries
}
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (types_1_1 && !types_1_1.done && (_a = types_1.return)) _a.call(types_1);
}
finally { if (e_2) throw e_2.error; }
}
if (!mostRecentDate) {
return true;
}
return (0, matcher_utils_1.hasPeriodPassed)(days, mostRecentDate);
}
//# sourceMappingURL=product-tours-utils.js.map