Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,6 @@ Let's flourish this text, that's what I mean.
:::
```

### Bugs to fix

* Right now, I believe it's not sanitized against rx special characters; e.g. if you want to flourish the literal character "*" that will not work.

* In HTML, some special characters appear different, e.g. `<` appears as `&lt;`. That means that choosing `x <- 1:10` as a flourish target won't work, because the `<` won't get matched.

### Things you might want but we don't plan to do

* Functionality for pdf or docx. This extension is written in JavaScript for HTML; any other doc formats would require a total rebuild from the ground up.
Expand Down
56 changes: 33 additions & 23 deletions _extensions/flourish/flourish.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -45,14 +49,24 @@ document.addEventListener("DOMContentLoaded", () => {
}

// Apply flourishes with the appropriate class name
content = injectFlourishes(content, new RegExp(pattern.regex.source, 'g'), className, pattern.mask);
content = injectFlourishes(content, pattern.regex, className, pattern.mask);
}

el.innerHTML = content;
el.dataset.flourished = 'true';
}
}
});

// Hardcode swaps for sanitized HTML
function htmlEntityEscapeForSearch(s) {
return s
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}

// Helper function for parsing YAML
function parseDataFlourish(flourishAttr) {
try {
Expand All @@ -70,37 +84,32 @@ function parseDataFlourish(flourishAttr) {
// pull style out and collect patterns
let style = entry.style || 'default';
let mask = entry.mask || false;
let flags = key === 'target-rx' ? 'g' : undefined;
let flags = entry.flags || 'g';
const pats = [];

for (const it of items) {
if (typeof it === 'string') {
pats.push(it);
}
else if (it && it.style) {
pats.push(key === 'target' ? htmlEntityEscapeForSearch(it) : it);
} else if (it && it.style) {
style = it.style;
}
else if (it && it.source) {
pats.push(it.source);
} else if (it && it.source) {
pats.push(key === 'target' ? htmlEntityEscapeForSearch(it.source) : it.source);
if (it.flags) flags = it.flags;
}
else if (it && it.mask) {
} else if (it && it.mask) {
mask = it.mask;
}
}

if (pats.length) {
const re = new RegExp(
pats.map(p => `(${p})`).join('|'),
flags
);
result.push({ type: key, regex: re, style: style, mask: mask });
const pattern = key === 'target' ? pats.join('|')
: pats.map(p => `(${p})`).join('|');
const re = new RegExp(pattern, flags);
result.push({ type: key, regex: re, style, mask });
}
}
}
return result;
}
catch {
} catch {
return null;
}
}
Expand All @@ -110,12 +119,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);
}
207 changes: 128 additions & 79 deletions example.html

Large diffs are not rendered by default.

41 changes: 25 additions & 16 deletions example.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -100,41 +100,50 @@ 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:
#| - 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)
```
Expand Down