GitHub

@@ -0,0 +1,119 @@

1+

<script lang="ts">

2+

import type { VNode } from 'vue'

3+

import type { AppConfig } from '@nuxt/schema'

4+

import theme from '#build/ui/prose/prompt'

5+

import type { IconProps } from '../../types'

6+

import type { ComponentConfig } from '../../types/tv'

7+8+

type ProsePrompt = ComponentConfig<typeof theme, AppConfig, 'prompt', 'ui.prose'>

9+10+

export interface ProsePromptProps {

11+

description?: string

12+

/**

13+

* @IconifyIcon

14+

*/

15+

icon?: IconProps['name']

16+

/**

17+

* @defaultValue ['copy']

18+

*/

19+

actions?: ('copy' | 'cursor' | 'windsurf')[]

20+

class?: any

21+

ui?: ProsePrompt['slots']

22+

}

23+24+

export interface ProsePromptSlots {

25+

default(props?: {}): VNode[]

26+

}

27+

</script>

28+29+

<script setup lang="ts">

30+

import { computed } from 'vue'

31+

import { useClipboard } from '@vueuse/core'

32+

import { useAppConfig } from '#imports'

33+

import { useComponentUI } from '../../composables/useComponentUI'

34+

import { useLocale } from '../../composables/useLocale'

35+

import { getSlotChildrenText } from '../../utils'

36+

import { tv } from '../../utils/tv'

37+

import UIcon from '../Icon.vue'

38+

import UButton from '../Button.vue'

39+40+

defineOptions({ inheritAttrs: false })

41+42+

const props = withDefaults(defineProps<ProsePromptProps>(), {

43+

actions: () => ['copy']

44+

})

45+

const slots = defineSlots<ProsePromptSlots>()

46+47+

const { t } = useLocale()

48+

const { copy, copied } = useClipboard()

49+

const appConfig = useAppConfig() as ProsePrompt['AppConfig']

50+

const uiProp = useComponentUI('prose.prompt', props)

51+52+

// eslint-disable-next-line vue/no-dupe-keys

53+

const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.prose?.prompt || {}) })())

54+55+

function getPromptText() {

56+

const children = slots.default?.()

57+

return children ? getSlotChildrenText(children).trim() : ''

58+

}

59+60+

function copyPrompt() {

61+

copy(getPromptText())

62+

}

63+64+

function openInCursor() {

65+

const url = new URL('cursor://anysphere.cursor-deeplink/prompt')

66+

url.searchParams.set('text', getPromptText())

67+68+

window.open(url.toString(), '_self')

69+

}

70+71+

function openInWindsurf() {

72+

const url = new URL('windsurf://cascade/newChat')

73+

url.searchParams.set('prompt', getPromptText())

74+75+

window.open(url.toString(), '_self')

76+

}

77+

</script>

78+79+

<template>

80+

<div :class="ui.root({ class: [uiProp?.root, props.class] })" v-bind="$attrs">

81+

<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: uiProp?.icon })" />

82+83+

<div :class="ui.content({ class: uiProp?.content })">

84+

<p v-if="description" :class="ui.description({ class: uiProp?.description })">

85+

{{ description }}

86+

</p>

87+

</div>

88+89+

<div :class="ui.actions({ class: uiProp?.actions })">

90+

<UButton

91+

v-if="actions.includes('copy')"

92+

:icon="copied ? appConfig.ui.icons.copyCheck : appConfig.ui.icons.copy"

93+

size="sm"

94+

:label="t('prose.prompt.copy')"

95+

@click="copyPrompt"

96+

/>

97+98+

<UButton

99+

v-if="actions.includes('cursor')"

100+

icon="i-simple-icons-cursor"

101+

color="neutral"

102+

variant="outline"

103+

size="sm"

104+

:label="t('prose.prompt.openIn', { name: 'Cursor' })"

105+

@click="openInCursor"

106+

/>

107+108+

<UButton

109+

v-if="actions.includes('windsurf')"

110+

icon="i-simple-icons-windsurf"

111+

color="neutral"

112+

variant="outline"

113+

size="sm"

114+

:label="t('prose.prompt.openIn', { name: 'Windsurf' })"

115+

@click="openInWindsurf"

116+

/>

117+

</div>

118+

</div>

119+

</template>

Read the original on github.com ↗