166 lines
4.8 KiB
JavaScript
Raw Normal View History

2026-05-31 13:21:13 +02:00
import { insert, memo, effect, style, className, template, use, createComponent } from 'solid-js/web';
import { createSignal, createEffect, createMemo } from 'solid-js';
import { createMagicMoveMachine, codeToKeyedTokens, syncTokenKeys, toKeyedTokens } from './core.mjs';
import { MagicMoveRenderer } from './renderer.mjs';
import 'diff-match-patch-es';
import 'ohash';
function normalizeCSSProperties(css) {
if (typeof css === 'string') {
const style = {};
css?.split(';').forEach(pair => {
const [key, value] = pair.split(':');
if (key && value) style[key.trim()] = value.trim();
});
return style;
}
return css;
}
var _tmpl$ = /*#__PURE__*/template(`<pre><div>`),
_tmpl$2 = /*#__PURE__*/template(`<br>`),
_tmpl$3 = /*#__PURE__*/template(`<span>`);
/**
* A wrapper component to `MagicMoveRenderer`
*/
function ShikiMagicMoveRenderer(props) {
let container;
let renderer;
const [isMounted, setIsMounted] = createSignal(false);
createEffect(() => {
if (!container) return;
// Remove previous content
container.innerHTML = '';
setIsMounted(true);
renderer = new MagicMoveRenderer(container);
});
createEffect(() => {
async function render() {
if (!renderer) return;
Object.assign(renderer.options, props.options);
if (props.animate === undefined || props.animate === true) {
if (props.previous) renderer.replace(props.previous);
props.onStart?.();
await renderer.render(props.tokens);
props.onEnd?.();
} else {
renderer.replace(props.tokens);
}
}
render();
});
return (() => {
var _el$ = _tmpl$(),
_el$2 = _el$.firstChild;
var _ref$ = container;
typeof _ref$ === "function" ? use(_ref$, _el$) : container = _el$;
insert(_el$2, (() => {
var _c$ = memo(() => !!isMounted());
return () => _c$() ? undefined : props.tokens?.tokens.map(token => {
if (token.content === '\n') return _tmpl$2();
return (() => {
var _el$4 = _tmpl$3();
insert(_el$4, () => token.content);
effect(_p$ => {
var _v$3 = {
...normalizeCSSProperties(token.htmlStyle),
color: token.color
},
_v$4 = ['shiki-magic-move-item', token.htmlClass].filter(Boolean).join(' ');
_p$.e = style(_el$4, _v$3, _p$.e);
_v$4 !== _p$.t && className(_el$4, _p$.t = _v$4);
return _p$;
}, {
e: undefined,
t: undefined
});
return _el$4;
})();
});
})());
effect(_p$ => {
var _v$ = `shiki-magic-move-container ${props.class || ''}`.trim(),
_v$2 = props.style;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_p$.t = style(_el$, _v$2, _p$.t);
return _p$;
}, {
e: undefined,
t: undefined
});
return _el$;
})();
}
function ShikiMagicMove(props) {
const codeToTokens = (code, lineNumbers) => codeToKeyedTokens(props.highlighter, code, {
lang: props.lang,
theme: props.theme
}, lineNumbers);
const machine = createMagicMoveMachine((code, lineNumbers) => codeToTokens(code, lineNumbers));
const result = createMemo(() => {
const lineNumbers = props.options?.lineNumbers ?? false;
if (props.code === machine.current.code && props.theme === machine.current.themeName && props.lang === machine.current.lang && lineNumbers === machine.current.lineNumbers) {
return machine;
}
return machine.commit(props.code, props.options);
});
return createComponent(ShikiMagicMoveRenderer, {
get tokens() {
return result().current;
},
get options() {
return props.options;
},
get previous() {
return result().previous;
},
get onStart() {
return props.onStart;
},
get onEnd() {
return props.onEnd;
},
get ["class"]() {
return props.class;
}
});
}
const EMPTY = /* @__PURE__ */toKeyedTokens('', []);
/**
* Component to render a compiled magic move step,
* Where the tokens can be generated on build time.
*/
function ShikiMagicMovePrecompiled(props) {
const [previous, setPrevious] = createSignal(EMPTY);
const result = createMemo(() => {
const res = syncTokenKeys(previous(), props.steps[Math.min(props.step ?? 0, props.steps.length - 1)], props.options);
setPrevious(res.to);
return res;
});
return createComponent(ShikiMagicMoveRenderer, {
get tokens() {
return result().to;
},
get previous() {
return result().from;
},
get options() {
return props.options;
},
get animate() {
return props.animate ?? true;
},
get onStart() {
return props.onStart;
},
get onEnd() {
return props.onEnd;
}
});
}
export { ShikiMagicMove, ShikiMagicMovePrecompiled, ShikiMagicMoveRenderer };