2026-05-31 13:54:31 +02:00

173 lines
4.2 KiB
JavaScript

import { defineComponent, ref, onMounted, watch, nextTick, h, renderList, computed } from 'vue';
import { createMagicMoveMachine, codeToKeyedTokens, toKeyedTokens, syncTokenKeys } from './core.mjs';
import { MagicMoveRenderer } from './renderer.mjs';
import 'diff-match-patch-es';
import 'ohash';
/**
* A wrapper component to `MagicMoveRenderer`
*/
const ShikiMagicMoveRenderer = /* #__PURE__ */defineComponent({
name: 'ShikiMagicMoveRenderer',
props: {
animate: {
type: Boolean,
default: true
},
tokens: {
type: Object,
required: true
},
previous: {
type: Object,
required: false
},
options: {
type: Object
}
},
emits: ['end', 'start'],
setup(props, {
emit
}) {
const container = ref();
let isMounted = false;
onMounted(() => {
// Remove previous content
container.value.innerHTML = '';
isMounted = true;
const renderer = new MagicMoveRenderer(container.value);
watch(() => props.tokens, async tokens => {
Object.assign(renderer.options, props.options);
if (props.animate) {
if (props.previous) renderer.replace(props.previous);
await nextTick();
const process = renderer.render(tokens);
await nextTick();
emit('start');
await process;
emit('end');
} else {
renderer.replace(tokens);
}
}, {
immediate: true
});
});
return () => h('pre', {
ref: container,
class: 'shiki-magic-move-container'
},
// Render initial tokens for SSR
isMounted ? undefined : renderList(props.tokens.tokens, token => {
if (token.content === '\n') return h('br', {
key: token.key
});
return h('span', {
style: [{
color: token.color
}, token.htmlStyle],
class: ['shiki-magic-move-item', token.htmlClass],
key: token.key
}, token.content);
}));
}
});
const ShikiMagicMove = /* #__PURE__ */defineComponent({
name: 'ShikiMagicMove',
props: {
highlighter: {
type: Object,
required: true
},
lang: {
type: String,
required: true
},
theme: {
type: String,
required: true
},
code: {
type: String,
required: true
},
options: {
type: Object,
default: () => ({})
}
},
emits: ['start', 'end'],
setup(props, {
emit
}) {
const machine = createMagicMoveMachine(code => codeToKeyedTokens(props.highlighter, code, {
lang: props.lang,
theme: props.theme
}, props.options.lineNumbers), props.options);
const result = computed(() => machine.commit(props.code));
return () => h(ShikiMagicMoveRenderer, {
tokens: result.value.current,
options: props.options,
previous: result.value.previous,
onStart: () => emit('start'),
onEnd: () => emit('end')
});
}
});
/**
* Component to render a compiled magic move step,
* Where the tokens can be generated on build time.
*/
const ShikiMagicMovePrecompiled = /* #__PURE__ */defineComponent({
name: 'ShikiMagicMovePrecompiled',
props: {
steps: {
type: Array,
required: true
},
step: {
type: Number,
default: 0
},
animate: {
type: Boolean,
default: true
},
options: {
type: Object,
default: () => ({})
}
},
emits: ['start', 'end'],
setup(props, {
emit
}) {
const EMPTY = toKeyedTokens('', []);
let previous = EMPTY;
const result = computed(() => {
const res = syncTokenKeys(previous, props.steps[Math.min(props.step, props.steps.length - 1)], props.options);
previous = res.to;
return res;
});
return () => h(ShikiMagicMoveRenderer, {
tokens: result.value.to,
previous: result.value.from,
options: props.options,
animate: props.animate,
onStart: () => emit('start'),
onEnd: () => emit('end')
});
}
});
function install(app) {
app.component('ShikiMagicMove', ShikiMagicMove);
app.component('ShikiMagicMovePrecompiled', ShikiMagicMovePrecompiled);
app.component('ShikiMagicMoveRenderer', ShikiMagicMoveRenderer);
}
export { ShikiMagicMove, ShikiMagicMovePrecompiled, ShikiMagicMoveRenderer, install };