set.seed(0)
x <- rnorm(10)
-mean(x) [1] 12.62954285 -3.26233361 13.29799263 12.72429321 4.14641434
+ [6] -15.39950042 -9.28567035 -2.94720447 -0.05767173 24.04653389
+mean(x)[1] 0.358924
sd(x)sd(x)[1] 1.205336
Comprehensive Exampl
Fun With CSS
Note that flourish admits (almost) any valid CSS, which allows us to do stuff like this:
-#| flourish:
-#| - target:
-#| - "wackyyy"
-#| - style:
-#| - "background-color: #bd8c51;"
-#| - "text-decoration: line-through;"
-#| - "-webkit-text-stroke: 1px black;"
-#| - "filter: blur(1px);"
-#| - "font-family: 'Brush Script MT', cursive;"
+#| flourish:
+#| - target:
+#| - "wackyyy"
+#| - style:
+#| - "background-color: #bd8c51;"
+#| - "text-decoration: line-through;"
+#| - "-webkit-text-stroke: 1px black;"
+#| - "filter: blur(1px);"
+#| - "font-family: 'Brush Script MT', cursive;"
-wackyyy <- 1:10
-mean(wackyyy)
+wackyyy <- 1:10
+mean(wackyyy)
[1] 5.5
diff --git a/example.qmd b/example.qmd
index 7e7d2a1..e5220f8 100644
--- a/example.qmd
+++ b/example.qmd
@@ -118,23 +118,28 @@ This example seeks to serve as a reference for all your `flourish` needs.
```
```{r}
#| flourish:
-#| - target: "mean"
+#| - target:
+#| - "*"
+#| - "<-"
+#| - "|>"
#| - target:
#| - "sd"
#| - mask: true
#| - style: "text-decoration: underline;"
#| - target-rx: "[0-9]*"
#| - target:
-#| - "x"
-#| - style: "font-weight: bold;"
+#| - "mean"
+#| - "x"
+#| - style: "font-weight: bold;"
#| - target:
-#| - "set.seed"
-#| - "rnorm"
-#| - style:
-#| - "font-style: italic;"
-#| - "background-color: #468e5d;"
+#| - "set.seed"
+#| - "rnorm"
+#| - style:
+#| - "font-style: italic;"
+#| - "background-color: #468e5d;"
set.seed(0)
x <- rnorm(10)
+x * 10 |> median()
mean(x)
sd(x)
```
From 0075370696f10a29b209aee0d9f275cb79450527 Mon Sep 17 00:00:00 2001
From: VisruthSK <67435125+VisruthSK@users.noreply.github.com>
Date: Tue, 16 Sep 2025 11:46:11 -0700
Subject: [PATCH 3/4] Updated general flourish logic and sanitization
---
_extensions/flourish/flourish.js | 26 +++++++++++++++++---------
example.html | 26 +++++++++++++++++---------
example.qmd | 20 ++++++++++++--------
3 files changed, 46 insertions(+), 26 deletions(-)
diff --git a/_extensions/flourish/flourish.js b/_extensions/flourish/flourish.js
index 217d894..a163648 100644
--- a/_extensions/flourish/flourish.js
+++ b/_extensions/flourish/flourish.js
@@ -18,7 +18,11 @@ document.addEventListener("DOMContentLoaded", () => {
// Find only the code chunks you care about
const sourceEls = Array.from(cell.querySelectorAll('code'))
- .filter(el => !el.closest('.cell-output') && !el.closest('.cell-output-stdout'));
+ .filter(el =>
+ !el.closest('.cell-output') &&
+ !el.closest('.cell-output-stdout') &&
+ el.dataset.flourished !== 'true' // idempotent per element
+ );
// For each code chunk, add flourishes
for (const el of sourceEls) {
@@ -49,16 +53,19 @@ document.addEventListener("DOMContentLoaded", () => {
}
el.innerHTML = content;
+ el.dataset.flourished = 'true';
}
}
});
// Hardcode swaps for sanitized HTML
function htmlEntityEscapeForSearch(s) {
- return s.replace(/&/g, '&')
+ return s
.replace(//g, '>');
+ .replace(/>/g, '>')
+ .replace(/&(?![a-zA-Z]+;|#\d+;|#x[0-9A-Fa-f]+;)/g, '&');
}
+
function escapeForRegexLiteral(s) {
return RegExp.escape ? RegExp.escape(s) : s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
@@ -80,7 +87,7 @@ function parseDataFlourish(flourishAttr) {
// pull style out and collect patterns
let style = entry.style || 'default';
let mask = entry.mask || false;
- let flags = 'g';
+ let flags = entry.flags || 'g';
const pats = [];
for (const it of items) {
@@ -125,12 +132,13 @@ function addStyle(document, className = "flr-default",
styleText = `background-color: yellow;
display: inline;
color: inherit;`) {
+ // Deduplicate styles by class selector
+ const selector = `.${className}`;
+ const exists = Array.from(document.querySelectorAll('style'))
+ .some(s => s.textContent && s.textContent.includes(selector));
+ if (exists) return;
const styleSheet = document.createElement("style");
- styleSheet.textContent = `
- .${className} {
- ${styleText}
- }
-`;
+ styleSheet.textContent = `${selector} { ${styleText} }`;
document.head.appendChild(styleSheet);
}
diff --git a/example.html b/example.html
index fef6c07..e2a6336 100644
--- a/example.html
+++ b/example.html
@@ -2315,7 +2315,11 @@
// Find only the code chunks you care about
const sourceEls = Array.from(cell.querySelectorAll('code'))
- .filter(el => !el.closest('.cell-output') && !el.closest('.cell-output-stdout'));
+ .filter(el =>
+ !el.closest('.cell-output') &&
+ !el.closest('.cell-output-stdout') &&
+ el.dataset.flourished !== 'true' // idempotent per element
+ );
// For each code chunk, add flourishes
for (const el of sourceEls) {
@@ -2346,16 +2350,19 @@
}
el.innerHTML = content;
+ el.dataset.flourished = 'true';
}
}
});
// Hardcode swaps for sanitized HTML
function htmlEntityEscapeForSearch(s) {
- return s.replace(/&/g, '&')
+ return s
.replace(//g, '>');
+ .replace(/>/g, '>')
+ .replace(/&(?![a-zA-Z]+;|#\d+;|#x[0-9A-Fa-f]+;)/g, '&');
}
+
function escapeForRegexLiteral(s) {
return RegExp.escape ? RegExp.escape(s) : s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
@@ -2377,7 +2384,7 @@
// pull style out and collect patterns
let style = entry.style || 'default';
let mask = entry.mask || false;
- let flags = 'g';
+ let flags = entry.flags || 'g';
const pats = [];
for (const it of items) {
@@ -2422,13 +2429,14 @@
styleText = `background-color: yellow;
display: inline;
color: inherit;`) {
+ // Deduplicate styles by class selector
+ const selector = `.${className}`;
+ const exists = Array.from(document.querySelectorAll('style'))
+ .some(s => s.textContent && s.textContent.includes(selector));
+ if (exists) return;
const styleSheet = document.createElement("style");
- styleSheet.textContent = `
- .${className} {
- ${styleText}
- }
-`;
+ styleSheet.textContent = `${selector} { ${styleText} }`;
document.head.appendChild(styleSheet);
}
diff --git a/example.qmd b/example.qmd
index e5220f8..c8516e4 100644
--- a/example.qmd
+++ b/example.qmd
@@ -100,21 +100,25 @@ This example seeks to serve as a reference for all your `flourish` needs.
```{.yaml}
#| flourish:
-#| - target: "mean"
+#| - target:
+#| - "*"
+#| - "<-"
+#| - "|>"
#| - target:
#| - "sd"
#| - mask: true
#| - style: "text-decoration: underline;"
#| - target-rx: "[0-9]*"
#| - target:
-#| - "x"
-#| - style: "font-weight: bold;"
+#| - "mean"
+#| - "x"
+#| - style: "font-weight: bold;"
#| - target:
-#| - "set.seed"
-#| - "rnorm"
-#| - style:
-#| - "font-style: italic;"
-#| - "background-color: #468e5d;"
+#| - "set.seed"
+#| - "rnorm"
+#| - style:
+#| - "font-style: italic;"
+#| - "background-color: #468e5d;"
```
```{r}
#| flourish:
From 443789f53042a9c7a37907275afe95eaf66d90c8 Mon Sep 17 00:00:00 2001
From: VisruthSK <67435125+VisruthSK@users.noreply.github.com>
Date: Mon, 20 Oct 2025 13:47:31 -0700
Subject: [PATCH 4/4] Fixed escaping
---
_extensions/flourish/flourish.js | 23 +----
example.html | 155 ++++++++++++++++++-------------
2 files changed, 93 insertions(+), 85 deletions(-)
diff --git a/_extensions/flourish/flourish.js b/_extensions/flourish/flourish.js
index a163648..2133b52 100644
--- a/_extensions/flourish/flourish.js
+++ b/_extensions/flourish/flourish.js
@@ -61,13 +61,10 @@ document.addEventListener("DOMContentLoaded", () => {
// Hardcode swaps for sanitized HTML
function htmlEntityEscapeForSearch(s) {
return s
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ .replace(/&/g, '&')
.replace(//g, '>')
- .replace(/&(?![a-zA-Z]+;|#\d+;|#x[0-9A-Fa-f]+;)/g, '&');
-}
-
-function escapeForRegexLiteral(s) {
- return RegExp.escape ? RegExp.escape(s) : s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ .replace(/>/g, '>');
}
// Helper function for parsing YAML
@@ -92,21 +89,11 @@ function parseDataFlourish(flourishAttr) {
for (const it of items) {
if (typeof it === 'string') {
- if (key === 'target') {
- const encoded = htmlEntityEscapeForSearch(it);
- pats.push(escapeForRegexLiteral(encoded));
- } else {
- pats.push(it);
- }
+ pats.push(key === 'target' ? htmlEntityEscapeForSearch(it) : it);
} else if (it && it.style) {
style = it.style;
} else if (it && it.source) {
- if (key === 'target') {
- const encoded = htmlEntityEscapeForSearch(it.source);
- pats.push(escapeForRegexLiteral(encoded));
- } else {
- pats.push(it.source);
- }
+ pats.push(key === 'target' ? htmlEntityEscapeForSearch(it.source) : it.source);
if (it.flags) flags = it.flags;
} else if (it && it.mask) {
mask = it.mask;
diff --git a/example.html b/example.html
index e2a6336..067e026 100644
--- a/example.html
+++ b/example.html
@@ -2,7 +2,7 @@
-
+
@@ -65,8 +65,9 @@
* Licensed MIT © Zeno Rocha
*/
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1
-
+
+
Fun With CSS
Note that flourish admits (almost) any valid CSS, which allows us to do stuff like this:
#| flourish:
-#| - target:
-#| - "wackyyy"
-#| - style:
-#| - "background-color: #bd8c51;"
-#| - "text-decoration: line-through;"
-#| - "-webkit-text-stroke: 1px black;"
-#| - "filter: blur(1px);"
-#| - "font-family: 'Brush Script MT', cursive;"#| flourish:
+#| - target:
+#| - "wackyyy"
+#| - style:
+#| - "background-color: #bd8c51;"
+#| - "text-decoration: line-through;"
+#| - "-webkit-text-stroke: 1px black;"
+#| - "filter: blur(1px);"
+#| - "font-family: 'Brush Script MT', cursive;"wackyyy <- 1:10
-mean(wackyyy)wackyyy <- 1:10
+mean(wackyyy)[1] 5.5