Skip to content

Commit

Permalink
Merge pull request #1014 from nature-of-code/dev/fill-in-blank
Browse files Browse the repository at this point in the history
Feat: Add fill-in-blank and copy feature to the codesplit block
  • Loading branch information
shiffman authored Sep 24, 2024
2 parents 93c3c12 + d0e1bbd commit e57a90d
Show file tree
Hide file tree
Showing 8 changed files with 560 additions and 7 deletions.
73 changes: 73 additions & 0 deletions gatsby/lib/blank-span.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { toString } from 'hast-util-to-string';
import { visit } from 'unist-util-visit';

// Special characters for marking blanks and separators
const BLANK_MARKER = '\u2420';
const SEPARATOR = '\u2063';

export const preserveCustomSpans = () => (tree) => {
visit(tree, 'element', (node, index, parent) => {
if (isBlankSpan(node)) {
const reservedString = toString(node);
parent.properties['data-blanks'] = parent.properties['data-blanks']
? `${parent.properties['data-blanks']}${SEPARATOR}${reservedString}`
: reservedString;

parent.children.splice(index, 1, { type: 'text', value: BLANK_MARKER });
}
});
};

export const restoreCustomSpans = () => (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName === 'codesplit') {
let containsBlank = false;

visit(node, 'element', (nodeInCodesplit) => {
if (
nodeInCodesplit.tagName === 'code' &&
nodeInCodesplit.properties['data-blanks']
) {
const blanks =
nodeInCodesplit.properties['data-blanks'].split(SEPARATOR);
restoreBlankSpans(nodeInCodesplit, blanks);
delete nodeInCodesplit.properties['data-blanks'];
containsBlank = true;
}
});

if (containsBlank) {
node.properties['data-contains-blank'] = true;
}
}
});
};

const isBlankSpan = (node) =>
node.tagName === 'span' &&
Array.isArray(node.properties.className) &&
node.properties.className.includes('blank');

const restoreBlankSpans = (node, blanks) => {
let blankIndex = 0;

visit(node, 'text', (textNode, index, parent) => {
if (textNode.value.includes(BLANK_MARKER)) {
const parts = textNode.value.split(BLANK_MARKER);
const newNodes = parts.flatMap((part, i) => {
const nodes = part ? [{ type: 'text', value: part }] : [];
if (i < parts.length - 1 && blankIndex < blanks.length) {
nodes.push({
type: 'element',
tagName: 'span',
properties: { className: ['blank'] },
children: [{ type: 'text', value: blanks[blankIndex++] }],
});
}
return nodes;
});
parent.children.splice(index, 1, ...newNodes);
return index + newNodes.length;
}
});
};
16 changes: 12 additions & 4 deletions gatsby/lib/codesplit.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { h } from 'hastscript';
import { visit } from 'unist-util-visit';
import { toHtml } from 'hast-util-to-html';
import { toString } from 'hast-util-to-string';
import { fromHtml } from 'hast-util-from-html';

/**
* Modifed from https://github.com/magicbookproject/magicbook-codesplit/blob/master/src/codesplit.js
Expand All @@ -21,7 +23,8 @@ export const rehypeCodesplit = () => (tree) => {
node.properties.className &&
node.properties.className.includes('codesplit')
) {
const lines = toString(node).split('\n');
const raw = toString(node);
const lines = toHtml(node.children).split('\n');
const lang = node.properties.dataCodeLanguage;

// if the first line was a linebreak, let's get rid of it.
Expand Down Expand Up @@ -101,7 +104,7 @@ export const rehypeCodesplit = () => (tree) => {
pairs.push(pair);

const children = pairs.map((pair) => {
const code = pair.code.join('\n') + '\n';
const code = fromHtml(pair.code.join('\n') + '\n', { fragment: true });
const comments = pair.comment
.map((str) => str.replace('//', '').trim())
.filter((str) => !!str);
Expand All @@ -113,11 +116,16 @@ export const rehypeCodesplit = () => (tree) => {

return h('div', { className }, [
h('pre', [h('code', { class: ['code', `language-${lang}`] }, code)]),
h('div', { class: ['comment'] }, h('p', comments.join('\n'))),
h(
'div',
{ class: ['comment'] },
h('p', fromHtml(comments.join('\n'), { fragment: true })),
),
]);
});

node.tagName = 'div';
node.tagName = 'codesplit';
node.properties['data-raw'] = raw;
node.properties.className = ['codesplit', 'callout', 'not-prose'];
node.children = children;
}
Expand Down
3 changes: 3 additions & 0 deletions gatsby/lib/parse-content.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import rehypeHighlight from 'rehype-highlight';
import { toString } from 'hast-util-to-string';

import { rehypeCodesplit } from './codesplit.mjs';
import { preserveCustomSpans, restoreCustomSpans } from './blank-span.mjs';

function isHeading(node) {
return node.type === 'element' && /^h[1-6]$/i.test(node.tagName);
Expand Down Expand Up @@ -178,7 +179,9 @@ export function parseContent(html) {
.use(replaceMedia)
.use(externalLinkInNewTab)
.use(rehypeCodesplit)
.use(preserveCustomSpans)
.use(rehypeHighlight)
.use(restoreCustomSpans)
.use(rehypeSlug)
.use(rehypeKatex)
.runSync(ast);
Expand Down
Loading

0 comments on commit e57a90d

Please sign in to comment.