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 };