Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@flourish/layout

Package Overview
Dependencies
Maintainers
21
Versions
87
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@flourish/layout - npm Package Compare versions

Comparing version
8.7.3
to
8.8.0
+135
src/lib/iframe-credit.js
/**
* Iframe Credit Component
*
* Displays a localized Flourish credit inside iframe embeds for Free/Presenter users.
* - Shows template-specific and localized credit text (e.g., "Made with Flourish • Create a parliament chart")
* - Can be hidden via postMessage in platform code (embedded.js) when script embed handles credit externally
* - Credit text and URL are provided by server in Flourish.metadata
*/
let credit_element = null;
/**
* Determines whether we should show the internal iframe credit.
* Returns true when Flourish.metadata.show_internal_credit is set (Free/Presenter users in embed environment).
*/
function shouldShowCredit() {
if (typeof Flourish === "undefined") return false;
const metadata = Flourish.metadata || {};
const should_show = metadata.show_internal_credit === true;
return should_show;
}
const DEFAULT_CREDIT_URL = "https://flourish.studio";
const DEFAULT_CREDIT_TEXT = "Made with Flourish • Create your own";
/**
* Gets localized credit text and URL from server-provided metadata.
* Server calculates template-specific and localized credits based on template categories and language.
* Falls back to default English text/URL if metadata is not available.
*/
function getCreditTextAndUrl() {
const metadata = (typeof Flourish !== "undefined" && Flourish.metadata) || {};
return {
credit_url: metadata.credit_url || DEFAULT_CREDIT_URL,
credit_text: metadata.credit_text || DEFAULT_CREDIT_TEXT,
};
}
/**
* Creates the credit element with styling matching the external embed credit.
* Matches the style from common/embed/credit.js createFlourishCredit()
*/
function createCreditElement() {
const credit_info = getCreditTextAndUrl();
const credit_url = credit_info.credit_url;
const query_string = "?utm_source=embed&utm_campaign=visualisation";
const credit_text = credit_info.credit_text;
const credit = document.createElement("div");
credit.setAttribute("class", "flourish-credit");
credit.setAttribute(
"style",
"margin:4px 8px;text-align:right;font-family:system-ui,sans-serif;color:inherit;opacity:0.8;font-size:12px;font-weight:normal;font-style:normal;-webkit-font-smoothing:antialiased;box-shadow:none;order:50;",
);
// Split credit text into prefix and CTA (linked part)
const parts = credit_text.split("•");
const UNICODE_NO_BREAK_SPACE = "\u00A0";
const prefix_text =
parts.length > 1
? parts[0].trim() + UNICODE_NO_BREAK_SPACE + "•" + UNICODE_NO_BREAK_SPACE
: "";
const cta_text = parts.length > 1 ? parts[1].trim() : credit_text;
if (prefix_text) {
const prefix = document.createElement("span");
prefix.setAttribute(
"style",
"font:inherit;color:inherit;vertical-align:middle;display:inline-block;box-shadow:none;",
);
prefix.appendChild(document.createTextNode(prefix_text));
credit.appendChild(prefix);
}
const a = document.createElement("a");
a.setAttribute("href", credit_url + query_string);
a.setAttribute("target", "_blank");
a.setAttribute("aria-label", cta_text + " (opens in new tab)");
a.setAttribute("rel", "noopener noreferrer");
a.setAttribute(
"style",
"display:inline-block;text-decoration:none;font:inherit;color:inherit;border:none;box-shadow:none;",
);
credit.appendChild(a);
const span = document.createElement("span");
span.setAttribute(
"style",
"font:inherit;color:inherit;vertical-align:middle;display:inline-block;box-shadow:none;",
);
span.appendChild(document.createTextNode(cta_text));
a.appendChild(span);
span.addEventListener("mouseover", function () {
span.style.textDecoration = "underline";
});
span.addEventListener("mouseout", function () {
span.style.textDecoration = "none";
});
return credit;
}
export function init() {
if (credit_element) return; // Already initialized
if (!shouldShowCredit()) return;
credit_element = createCreditElement();
// Append after footer container (outside it to avoid being hidden when footer is empty)
const footer_container = document.getElementById(
"fl-layout-footer-container",
);
const wrapper = document.getElementById("fl-layout-wrapper");
if (footer_container && footer_container.parentNode) {
footer_container.parentNode.insertBefore(
credit_element,
footer_container.nextSibling,
);
} else if (wrapper) {
wrapper.appendChild(credit_element);
} else {
document.body.appendChild(credit_element);
}
}
// used for testing purposes
export function destroy() {
if (credit_element && credit_element.parentNode) {
credit_element.parentNode.removeChild(credit_element);
}
credit_element = null;
}
/* global global */
import { expect } from "chai";
import { init, destroy } from "./iframe-credit.js";
describe("iframe-credit", function () {
beforeEach(function () {
// Clean up any existing credit elements
destroy();
const existing_credit = document.querySelector(".flourish-credit");
if (existing_credit) existing_credit.remove();
// Create footer container for DOM insertion tests
const footer = document.createElement("div");
footer.id = "fl-layout-footer-container";
document.body.appendChild(footer);
});
afterEach(function () {
// Clean up global
delete global.Flourish;
// Clean up DOM
destroy();
const footer = document.getElementById("fl-layout-footer-container");
if (footer) footer.remove();
});
describe("shouldShowCredit", function () {
it("should show credit when show_internal_credit is true", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
},
};
init();
const credit = document.querySelector(".flourish-credit");
expect(credit).to.not.be.null;
});
it("should not show credit when conditions are not met", function () {
const scenarios = [
{ Flourish: { metadata: { show_internal_credit: false } } },
{ Flourish: { metadata: {} } },
{ Flourish: undefined },
];
scenarios.forEach((scenario) => {
global.Flourish = scenario.Flourish;
init();
const credit = document.querySelector(".flourish-credit");
expect(credit).to.be.null;
destroy();
});
});
it("should append credit after footer container", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
},
};
init();
const footer = document.getElementById("fl-layout-footer-container");
const credit = document.querySelector(".flourish-credit");
expect(footer.nextSibling).to.equal(credit);
});
});
describe("getCreditTextAndUrl", function () {
it("should return server-provided credit text and URL when available", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
credit_text: "Made with Flourish • Create a parliament chart",
credit_url: "https://flourish.studio/parliament",
},
};
init();
const credit = document.querySelector(".flourish-credit");
const link = credit.querySelector("a");
expect(link.href).to.include("https://flourish.studio/parliament");
expect(credit.textContent).to.include("Create a parliament chart");
});
it("should fall back to default text and URL when metadata missing", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
},
};
init();
const credit = document.querySelector(".flourish-credit");
const link = credit.querySelector("a");
expect(link.href).to.include("https://flourish.studio");
expect(credit.textContent).to.include("Made with Flourish");
expect(credit.textContent).to.include("Create your own");
});
});
describe("createCreditElement", function () {
afterEach(function () {
destroy();
});
it("should create correct DOM structure and attributes", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
},
};
init();
const credit = document.querySelector(".flourish-credit");
const link = credit.querySelector("a");
expect(credit).to.not.be.null;
expect(credit.tagName).to.equal("DIV");
expect(credit.getAttribute("class")).to.equal("flourish-credit");
expect(link.getAttribute("target")).to.equal("_blank");
expect(link.getAttribute("rel")).to.equal("noopener noreferrer");
});
it("should split text with bullet into prefix and CTA", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
credit_text: "Made with Flourish • Create your own",
},
};
init();
const credit = document.querySelector(".flourish-credit");
const spans = credit.querySelectorAll("span");
expect(spans.length).to.equal(2);
expect(spans[0].textContent).to.include("Made with Flourish");
expect(spans[0].textContent).to.include("•");
expect(spans[1].textContent).to.equal("Create your own");
});
it("should handle text without bullet as CTA only", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
credit_text: "Create your own visualization",
},
};
init();
const credit = document.querySelector(".flourish-credit");
const spans = credit.querySelectorAll("span");
expect(spans.length).to.equal(1);
expect(spans[0].textContent).to.equal("Create your own visualization");
});
it("should set link href with UTM parameters and aria-label", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
credit_url: "https://flourish.studio/",
credit_text: "Create your own",
},
};
init();
const link = document.querySelector(".flourish-credit a");
expect(link.href).to.include("https://flourish.studio/");
expect(link.href).to.include("utm_source=embed");
expect(link.href).to.include("utm_campaign=visualisation");
expect(link.getAttribute("aria-label")).to.equal(
"Create your own (opens in new tab)",
);
});
it("should not create duplicate on second call", function () {
global.Flourish = {
metadata: {
show_internal_credit: true,
},
};
init();
init();
const credits = document.querySelectorAll(".flourish-credit");
expect(credits.length).to.equal(1);
});
});
});
+6
-6
{
"name": "@flourish/layout",
"type": "module",
"version": "8.7.3",
"version": "8.8.0",
"private": false,

@@ -40,3 +40,3 @@ "description": "Flourish module to control layouts",

"uglify-js": "^3.19.3",
"@flourish/cypress": "^9.2.0",
"@flourish/cypress": "^9.3.0",
"@flourish/mocha-env": "^1.0.0"

@@ -49,7 +49,7 @@ },

"file-saver-es": "^2.0.5",
"@flourish/interpreter": "^9.2.0",
"@flourish/formatters": "^2.3.0",
"@flourish/pocket-knife": "^2.3.0",
"@flourish/formatters": "^2.4.0",
"@flourish/number-formatter": "^2.0.0",
"@flourish/utils-color": "^2.0.0"
"@flourish/utils-color": "^2.0.0",
"@flourish/interpreter": "^9.3.0",
"@flourish/pocket-knife": "^2.3.0"
},

@@ -56,0 +56,0 @@ "exports": "./src/index.js",

@@ -0,2 +1,18 @@

# 8.8.0
* Change the default formatting for floating point numbers to remove non-significant trailing zeros while keeping two-decimal rounding
# 8.7.6
* Added before:browser:launch event to cypress to enable cpu webGL rendering in CI
# 8.7.5
* Add cacheable_for_standalone_downloads to URL fields for standalone HTML downloads
# 8.7.4
* Renders a localized Flourish credit bar after the footer
# 8.7.3
* Renders a localized Flourish credit bar after the footer when Flourish.metadata.show_internal_credit is true
* New tests cover show/hide logic, metadata fallbacks, DOM placement, and duplicate guarding
# 8.7.3
* Font loading should now work with relative as well as absolute URLs

@@ -3,0 +19,0 @@

@@ -37,2 +37,3 @@ - Layout

width: half
cacheable_for_standalone_downloads: true
show_if: background_image_enabled

@@ -646,2 +647,3 @@ - property: background_image_size

type: url
cacheable_for_standalone_downloads: true
show_if: header_logo_enabled

@@ -865,2 +867,3 @@ - property: header_logo_alt

width: half
cacheable_for_standalone_downloads: true
show_if: footer_logo_enabled

@@ -867,0 +870,0 @@ - property: footer_logo_src_light

@@ -13,2 +13,3 @@ import { createScreenshotSVG } from "@flourish/pocket-knife";

import { remToPx } from "../lib/utils.js";
import * as iframeCredit from "../lib/iframe-credit.js";

@@ -353,2 +354,3 @@ // Globals ------------------------------------------------------

createOverlay();
iframeCredit.init();

@@ -355,0 +357,0 @@ return {

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display