166 lines
5.9 KiB
TypeScript
166 lines
5.9 KiB
TypeScript
import type { RoughAnnotationConfig } from '@slidev/rough-notation'
|
|
import type { RawAtValue } from '@slidev/types'
|
|
import type { App } from 'vue'
|
|
import { annotate } from '@slidev/rough-notation'
|
|
import { computed, watchEffect } from 'vue'
|
|
import { useNav } from '../composables/useNav'
|
|
import { resolveClick } from './v-click'
|
|
|
|
export interface RoughDirectiveOptions extends Partial<RoughAnnotationConfig> {
|
|
at: RawAtValue
|
|
}
|
|
|
|
export type RoughDirectiveValue = RawAtValue | RoughDirectiveOptions
|
|
|
|
function addClass(options: RoughDirectiveOptions, cls: string) {
|
|
options.class = [options.class, cls].filter(Boolean).join(' ')
|
|
return options
|
|
}
|
|
|
|
// Definitions of supported modifiers
|
|
const vMarkModifiers: Record<string, (options: RoughDirectiveOptions, value?: any) => RoughDirectiveOptions> = {
|
|
// Types
|
|
'box': options => Object.assign(options, { type: 'box' }),
|
|
'circle': options => Object.assign(options, { type: 'circle' }),
|
|
'underline': options => Object.assign(options, { type: 'underline' }),
|
|
'highlight': options => Object.assign(options, { type: 'highlight' }),
|
|
'strike-through': options => Object.assign(options, { type: 'strike-through' }),
|
|
'crossed-off': options => Object.assign(options, { type: 'crossed-off' }),
|
|
'bracket': options => Object.assign(options, { type: 'bracket' }),
|
|
|
|
// Type Aliases
|
|
'strike': options => Object.assign(options, { type: 'strike-through' }),
|
|
'cross': options => Object.assign(options, { type: 'crossed-off' }),
|
|
'crossed': options => Object.assign(options, { type: 'crossed-off' }),
|
|
'linethrough': options => Object.assign(options, { type: 'strike-through' }),
|
|
'line-through': options => Object.assign(options, { type: 'strike-through' }),
|
|
|
|
// Colors
|
|
// @unocss-include
|
|
'black': options => addClass(options, 'text-black'),
|
|
'blue': options => addClass(options, 'text-blue'),
|
|
'cyan': options => addClass(options, 'text-cyan'),
|
|
'gray': options => addClass(options, 'text-gray'),
|
|
'green': options => addClass(options, 'text-green'),
|
|
'indigo': options => addClass(options, 'text-indigo'),
|
|
'lime': options => addClass(options, 'text-lime'),
|
|
'orange': options => addClass(options, 'text-orange'),
|
|
'pink': options => addClass(options, 'text-pink'),
|
|
'purple': options => addClass(options, 'text-purple'),
|
|
'red': options => addClass(options, 'text-red'),
|
|
'teal': options => addClass(options, 'text-teal'),
|
|
'white': options => addClass(options, 'text-white'),
|
|
'yellow': options => addClass(options, 'text-yellow'),
|
|
}
|
|
|
|
const vMarkModifiersDynamic: [RegExp, (match: RegExpMatchArray, options: RoughDirectiveOptions, value?: any) => RoughDirectiveOptions][] = [
|
|
// Support setting delay like `v-mark.delay300="1"`
|
|
[/^delay-?(\d+)?$/, (match, options, value) => {
|
|
const ms = (match[1] ? Number.parseInt(match[1]) : value) || 300
|
|
options.delay = ms
|
|
return options
|
|
}],
|
|
// Support setting opacity like `v-mark.op50="1"`
|
|
[/^(?:op|opacity)-?(\d+)?$/, (match, options, value) => {
|
|
const opacity = (match[1] ? Number.parseInt(match[1]) : value) || 100
|
|
options.opacity = opacity / 100
|
|
return options
|
|
}],
|
|
]
|
|
|
|
/**
|
|
* This supports v-mark directive to add notations to elements, powered by `rough-notation`.
|
|
*/
|
|
export function createVMarkDirective() {
|
|
return {
|
|
install(app: App) {
|
|
app.directive<HTMLElement, RoughDirectiveValue>('mark', {
|
|
// @ts-expect-error extra prop
|
|
name: 'v-mark',
|
|
|
|
mounted: (el, binding) => {
|
|
const { isPrintMode } = useNav()
|
|
|
|
const options = computed(() => {
|
|
const bindingOptions = (typeof binding.value === 'object' && !Array.isArray(binding.value))
|
|
? { ...binding.value }
|
|
: { at: binding.value }
|
|
|
|
let modifierOptions: RoughDirectiveOptions = { at: bindingOptions.at }
|
|
const unknownModifiers = Object.entries(binding.modifiers)
|
|
.filter(([k, v]) => {
|
|
if (vMarkModifiers[k]) {
|
|
modifierOptions = vMarkModifiers[k](modifierOptions, v)
|
|
return false
|
|
}
|
|
for (const [re, fn] of vMarkModifiersDynamic) {
|
|
const match = k.match(re)
|
|
if (match) {
|
|
modifierOptions = fn(match, modifierOptions, v)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
if (unknownModifiers.length)
|
|
console.warn('[Slidev] Invalid modifiers for v-mark:', unknownModifiers)
|
|
|
|
const options = {
|
|
...modifierOptions,
|
|
...bindingOptions,
|
|
}
|
|
options.type ||= 'underline'
|
|
|
|
if (isPrintMode.value)
|
|
options.animationDuration = 1 /* millisecond */
|
|
|
|
return options
|
|
})
|
|
|
|
const annotation = annotate(el, options.value as RoughAnnotationConfig)
|
|
|
|
const resolvedClick = resolveClick(el, binding, options.value.at)
|
|
if (!resolvedClick) {
|
|
// No click animation, just show the mark
|
|
annotation.show()
|
|
return
|
|
}
|
|
|
|
// @ts-expect-error extra prop
|
|
el.watchStopHandle = watchEffect(() => {
|
|
let shouldShow: boolean | undefined
|
|
|
|
if (options.value.class)
|
|
annotation.class = options.value.class
|
|
if (options.value.color)
|
|
annotation.color = options.value.color
|
|
|
|
const at = options.value.at
|
|
|
|
if (at === true)
|
|
shouldShow = true
|
|
else if (at === false)
|
|
shouldShow = false
|
|
else
|
|
shouldShow = resolvedClick.isActive.value
|
|
|
|
if (shouldShow == null)
|
|
return
|
|
|
|
if (shouldShow)
|
|
annotation.show()
|
|
else
|
|
annotation.hide()
|
|
})
|
|
},
|
|
|
|
unmounted: (el) => {
|
|
// @ts-expect-error extra prop
|
|
el.watchStopHandle?.()
|
|
},
|
|
})
|
|
},
|
|
}
|
|
}
|