@opentiny/tiny-robot 0.2.0-alpha.0 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/action-group/ActionGroup.vue.d.ts +26 -0
  2. package/dist/action-group/ActionGroupItem.vue.d.ts +18 -0
  3. package/dist/action-group/index.d.ts +12 -0
  4. package/dist/action-group/index.type.d.ts +16 -0
  5. package/dist/feedback/components/SourceList.vue.d.ts +11 -0
  6. package/dist/feedback/components/index.d.ts +1 -0
  7. package/dist/feedback/index.d.ts +7 -0
  8. package/dist/feedback/index.type.d.ts +25 -0
  9. package/dist/feedback/index.vue.d.ts +13 -0
  10. package/dist/history/components/index.d.ts +2 -0
  11. package/dist/history/components/item-tag.vue.d.ts +5 -0
  12. package/dist/history/components/search-empty.vue.d.ts +7 -0
  13. package/dist/history/composables/index.d.ts +1 -0
  14. package/dist/history/composables/useEditItemTitle.d.ts +12 -0
  15. package/dist/history/index.d.ts +6 -0
  16. package/dist/history/index.type.d.ts +43 -0
  17. package/dist/history/index.vue.d.ts +2 -0
  18. package/dist/icon-button/index.d.ts +7 -0
  19. package/dist/icon-button/index.type.d.ts +7 -0
  20. package/dist/icon-button/index.vue.d.ts +6 -0
  21. package/dist/index.d.ts +10 -1
  22. package/dist/index.js +57 -27
  23. package/dist/node_modules/.pnpm/@opentiny_utils@3.22.0/node_modules/@opentiny/utils/dist/index.es.js +1335 -884
  24. package/dist/node_modules/.pnpm/@opentiny_vue-common@3.22.0/node_modules/@opentiny/vue-common/lib/index.js +660 -144
  25. package/dist/node_modules/.pnpm/@opentiny_vue-hooks@3.22.0/node_modules/@opentiny/vue-hooks/dist/src/vue-popper.js +85 -0
  26. package/dist/node_modules/.pnpm/@opentiny_vue-locale@3.22.0/node_modules/@opentiny/vue-locale/lib/index.js +1783 -0
  27. package/dist/node_modules/.pnpm/@opentiny_vue-renderless@3.22.0/node_modules/@opentiny/vue-renderless/tooltip/index.js +77 -0
  28. package/dist/node_modules/.pnpm/@opentiny_vue-renderless@3.22.0/node_modules/@opentiny/vue-renderless/tooltip/vue.js +90 -0
  29. package/dist/node_modules/.pnpm/@opentiny_vue-tooltip@3.22.0/node_modules/@opentiny/vue-tooltip/lib/index.js +176 -0
  30. package/dist/node_modules/.pnpm/@opentiny_vue-tooltip@3.22.0/node_modules/@opentiny/vue-tooltip/lib/pc.js +248 -0
  31. package/dist/node_modules/.pnpm/@vueuse_core@13.1.0_vue@3.5.13/node_modules/@vueuse/core/index.js +190 -0
  32. package/dist/node_modules/.pnpm/@vueuse_shared@13.1.0_vue@3.5.13/node_modules/@vueuse/shared/index.js +53 -0
  33. package/dist/packages/components/src/action-group/ActionGroup.vue.js +7 -0
  34. package/dist/packages/components/src/action-group/ActionGroup.vue2.js +97 -0
  35. package/dist/packages/components/src/action-group/ActionGroupItem.vue.js +14 -0
  36. package/dist/packages/components/src/action-group/ActionGroupItem.vue2.js +4 -0
  37. package/dist/packages/components/src/action-group/index.js +17 -0
  38. package/dist/packages/components/src/bubble/bubble.vue.js +2 -2
  39. package/dist/packages/components/src/bubble/bubble.vue2.js +46 -46
  40. package/dist/packages/components/src/container/index.vue.js +1 -1
  41. package/dist/packages/components/src/container/index.vue2.js +17 -17
  42. package/dist/packages/components/src/feedback/components/SourceList.vue.js +7 -0
  43. package/dist/packages/components/src/feedback/components/SourceList.vue2.js +52 -0
  44. package/dist/packages/components/src/feedback/index.js +9 -0
  45. package/dist/packages/components/src/feedback/index.vue.js +7 -0
  46. package/dist/packages/components/src/feedback/index.vue2.js +142 -0
  47. package/dist/packages/components/src/history/components/item-tag.vue.js +7 -0
  48. package/dist/packages/components/src/history/components/item-tag.vue2.js +21 -0
  49. package/dist/packages/components/src/history/components/search-empty.vue.js +7 -0
  50. package/dist/packages/components/src/history/components/search-empty.vue2.js +20 -0
  51. package/dist/packages/components/src/history/composables/useEditItemTitle.js +43 -0
  52. package/dist/packages/components/src/history/index.js +11 -0
  53. package/dist/packages/components/src/history/index.vue.js +7 -0
  54. package/dist/packages/components/src/history/index.vue2.js +130 -0
  55. package/dist/packages/components/src/icon-button/index.js +9 -0
  56. package/dist/packages/components/src/icon-button/index.vue.js +7 -0
  57. package/dist/packages/components/src/icon-button/index.vue2.js +40 -0
  58. package/dist/packages/components/src/prompts/prompt.vue.js +2 -2
  59. package/dist/packages/components/src/prompts/prompt.vue2.js +17 -15
  60. package/dist/packages/components/src/question/components/HotQuestions.vue.js +23 -23
  61. package/dist/packages/components/src/question/index.vue.js +18 -18
  62. package/dist/packages/components/src/sender/components/TemplateEditor.vue.js +7 -0
  63. package/dist/packages/components/src/sender/components/TemplateEditor.vue2.js +121 -0
  64. package/dist/packages/components/src/sender/index.vue.js +149 -128
  65. package/dist/packages/components/src/suggestion/components/CategoryNav.vue.js +38 -0
  66. package/dist/packages/components/src/suggestion/components/CategoryNav.vue2.js +4 -0
  67. package/dist/packages/components/src/suggestion/components/SuggestionCapsule.vue.js +107 -0
  68. package/dist/packages/components/src/suggestion/components/SuggestionCapsule.vue2.js +4 -0
  69. package/dist/packages/components/src/suggestion/components/SuggestionPanel.vue.js +123 -0
  70. package/dist/packages/components/src/suggestion/components/SuggestionPanel.vue2.js +4 -0
  71. package/dist/packages/components/src/suggestion/composables/useKeyboardNavigation.js +45 -0
  72. package/dist/packages/components/src/suggestion/composables/useTriggerDetection.js +17 -0
  73. package/dist/packages/components/src/suggestion/index.js +9 -0
  74. package/dist/packages/components/src/suggestion/index.vue.js +179 -0
  75. package/dist/packages/components/src/suggestion/index.vue2.js +4 -0
  76. package/dist/packages/components/src/suggestion/utils/dom.js +18 -0
  77. package/dist/packages/svgs/dist/tiny-robot-svgs.js +306 -90
  78. package/dist/question/components/HotQuestions.vue.d.ts +2 -2
  79. package/dist/sender/components/TemplateEditor.vue.d.ts +18 -0
  80. package/dist/sender/index.type.d.ts +47 -0
  81. package/dist/sender/index.vue.d.ts +68 -3
  82. package/dist/style.css +1 -1
  83. package/dist/suggestion/components/CategoryNav.vue.d.ts +45 -0
  84. package/dist/suggestion/components/SuggestionCapsule.vue.d.ts +32 -0
  85. package/dist/suggestion/components/SuggestionPanel.vue.d.ts +84 -0
  86. package/dist/suggestion/composables/useKeyboardNavigation.d.ts +18 -0
  87. package/dist/suggestion/composables/useSuggestionFilter.d.ts +10 -0
  88. package/dist/suggestion/composables/useTriggerDetection.d.ts +11 -0
  89. package/dist/suggestion/index.d.ts +7 -0
  90. package/dist/suggestion/index.type.d.ts +94 -0
  91. package/dist/suggestion/index.vue.d.ts +343 -0
  92. package/dist/suggestion/utils/dom.d.ts +20 -0
  93. package/package.json +4 -3
  94. package/src/action-group/ActionGroup.vue +232 -0
  95. package/src/action-group/ActionGroupItem.vue +9 -0
  96. package/src/action-group/index.ts +25 -0
  97. package/src/action-group/index.type.ts +20 -0
  98. package/src/bubble/bubble.vue +4 -14
  99. package/src/container/index.vue +1 -2
  100. package/src/feedback/components/SourceList.vue +112 -0
  101. package/src/feedback/components/index.ts +1 -0
  102. package/src/feedback/index.ts +12 -0
  103. package/src/feedback/index.type.ts +27 -0
  104. package/src/feedback/index.vue +166 -0
  105. package/src/history/components/index.ts +2 -0
  106. package/src/history/components/item-tag.vue +49 -0
  107. package/src/history/components/search-empty.vue +38 -0
  108. package/src/history/composables/index.ts +1 -0
  109. package/src/history/composables/useEditItemTitle.ts +75 -0
  110. package/src/history/index.ts +12 -0
  111. package/src/history/index.type.ts +50 -0
  112. package/src/history/index.vue +292 -0
  113. package/src/icon-button/index.ts +12 -0
  114. package/src/icon-button/index.type.ts +8 -0
  115. package/src/icon-button/index.vue +52 -0
  116. package/src/index.ts +33 -1
  117. package/src/prompts/prompt.vue +7 -21
  118. package/src/question/components/HotQuestions.vue +1 -1
  119. package/src/question/index.less +9 -10
  120. package/src/sender/components/TemplateEditor.vue +274 -0
  121. package/src/sender/index.less +17 -7
  122. package/src/sender/index.type.ts +51 -0
  123. package/src/sender/index.vue +56 -8
  124. package/src/sender/vars.less +3 -3
  125. package/src/suggestion/components/CategoryNav.vue +38 -0
  126. package/src/suggestion/components/SuggestionCapsule.vue +183 -0
  127. package/src/suggestion/components/SuggestionPanel.vue +147 -0
  128. package/src/suggestion/composables/useKeyboardNavigation.ts +101 -0
  129. package/src/suggestion/composables/useSuggestionFilter.ts +34 -0
  130. package/src/suggestion/composables/useTriggerDetection.ts +46 -0
  131. package/src/suggestion/index.less +497 -0
  132. package/src/suggestion/index.ts +12 -0
  133. package/src/suggestion/index.type.ts +101 -0
  134. package/src/suggestion/index.vue +338 -0
  135. package/src/suggestion/utils/dom.ts +66 -0
  136. package/src/suggestion/vars.less +141 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentiny/tiny-robot",
3
- "version": "0.2.0-alpha.0",
3
+ "version": "0.2.0-alpha.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -16,12 +16,13 @@
16
16
  "vue": "^3.3.11"
17
17
  },
18
18
  "dependencies": {
19
- "@opentiny/tiny-robot-svgs": "0.2.0-alpha.0",
19
+ "@opentiny/tiny-robot-svgs": "0.2.0-alpha.1",
20
20
  "@opentiny/vue": "^3.21.0",
21
21
  "@opentiny/vue-button": "^3.21.0",
22
22
  "@opentiny/vue-icon": "^3.22.0",
23
23
  "@opentiny/vue-input": "^3.21.0",
24
24
  "@opentiny/vue-tooltip": "^3.22.0",
25
+ "@vueuse/core": "^13.1.0",
25
26
  "markdown-it": "^14.1.0"
26
27
  },
27
28
  "devDependencies": {
@@ -39,5 +40,5 @@
39
40
  "vue": "^3.3.11",
40
41
  "vue-tsc": "^2.2.8"
41
42
  },
42
- "gitHead": "3517724651afbbb05b1be20487a9f3341c06133c"
43
+ "gitHead": "3000951723ed8b1caa1361ebac131229bec9c237"
43
44
  }
@@ -0,0 +1,232 @@
1
+ <script setup lang="ts">
2
+ import { IconMenu } from '@opentiny/tiny-robot-svgs'
3
+ import { onClickOutside, useWindowSize } from '@vueuse/core'
4
+ import { computed, nextTick, ref, useTemplateRef, VNode, watch } from 'vue'
5
+ import IconButton from '../icon-button'
6
+ import { ActionGroupEvents, ActionGroupProps, ActionGroupSlots } from './index.type'
7
+
8
+ const props = defineProps<ActionGroupProps>()
9
+
10
+ const slots = defineSlots<ActionGroupSlots>()
11
+
12
+ const emit = defineEmits<ActionGroupEvents>()
13
+
14
+ const children = computed(() => {
15
+ const defaultSlot = slots.default()
16
+
17
+ if (Array.isArray(defaultSlot)) {
18
+ if (defaultSlot.length === 1 && defaultSlot[0].type?.toString() === 'Symbol(v-fgt)') {
19
+ return defaultSlot[0].children as VNode[]
20
+ }
21
+
22
+ return defaultSlot
23
+ }
24
+
25
+ if (defaultSlot.type?.toString() === 'Symbol(v-fgt)') {
26
+ return defaultSlot.children as VNode[]
27
+ }
28
+
29
+ return [defaultSlot]
30
+ })
31
+
32
+ const maxNum = computed(() => {
33
+ const n = props.maxNum ?? Number.MAX_SAFE_INTEGER
34
+
35
+ return n > 0 ? n : Number.MAX_SAFE_INTEGER
36
+ })
37
+
38
+ const showMore = computed(() => {
39
+ return children.value.length > maxNum.value
40
+ })
41
+
42
+ const list = computed(() => {
43
+ if (showMore.value) {
44
+ return children.value.slice(0, maxNum.value)
45
+ }
46
+
47
+ return children.value
48
+ })
49
+
50
+ const moreList = computed(() => {
51
+ if (showMore.value) {
52
+ return children.value.slice(maxNum.value)
53
+ }
54
+
55
+ return []
56
+ })
57
+
58
+ const moreBtn = useTemplateRef('moreBtnRef')
59
+ const dropDown = useTemplateRef('dropDownRef')
60
+ const showDropdown = ref(false)
61
+
62
+ const handleMoreClick = () => {
63
+ showDropdown.value = !showDropdown.value
64
+ }
65
+
66
+ onClickOutside(dropDown, (ev) => {
67
+ if (moreBtn.value?.contains(ev.target as Node)) {
68
+ return
69
+ }
70
+
71
+ showDropdown.value = false
72
+ })
73
+
74
+ const handleItemClick = (name: string) => {
75
+ emit('item-click', name)
76
+ showDropdown.value = false
77
+ }
78
+
79
+ const dropDownPlacement = ref('placement-bottom')
80
+ const { height: windowHeight } = useWindowSize()
81
+
82
+ const updateDropDownPlacement = () => {
83
+ if (!dropDown.value || !moreBtn.value) {
84
+ return 'placement-bottom'
85
+ }
86
+
87
+ const dropDownRect = dropDown.value.getBoundingClientRect()
88
+ const moreBtnRect = moreBtn.value.getBoundingClientRect()
89
+
90
+ dropDownPlacement.value =
91
+ moreBtnRect.bottom + dropDownRect.height + 16 > windowHeight.value ? 'placement-top' : 'placement-bottom'
92
+ }
93
+
94
+ watch(showDropdown, (show) => {
95
+ if (show) {
96
+ nextTick(() => {
97
+ updateDropDownPlacement()
98
+ })
99
+ }
100
+ })
101
+
102
+ watch(windowHeight, () => {
103
+ if (showDropdown.value) {
104
+ updateDropDownPlacement()
105
+ }
106
+ })
107
+ </script>
108
+
109
+ <template>
110
+ <div class="tr-action-group">
111
+ <span
112
+ v-for="(item, index) in list"
113
+ :key="index"
114
+ class="tr-action-group__btn-wrapper"
115
+ @click="handleItemClick(item.props?.name)"
116
+ >
117
+ <component :is="item" />
118
+ </span>
119
+ <span v-if="showMore" ref="moreBtnRef" class="tr-action-group__btn-wrapper" @click="handleMoreClick">
120
+ <slot name="moreBtn">
121
+ <icon-button :icon="IconMenu" tooltip="更多" />
122
+ </slot>
123
+ <transition name="tr-action-group-dropdown">
124
+ <ul v-show="showDropdown" ref="dropDownRef" :class="['tr-action-group__dropdown', dropDownPlacement]">
125
+ <li
126
+ class="tr-action-group__dropdown-item"
127
+ v-for="(item, index) in moreList"
128
+ :key="index"
129
+ @click.stop="handleItemClick(item.props?.name)"
130
+ >
131
+ <component v-if="!props.dropDownShowLabelOnly" :is="item" />
132
+ <span class="tr-action-group__dropdown-item-text">{{ item.props?.label }}</span>
133
+ </li>
134
+ </ul>
135
+ </transition>
136
+ </span>
137
+ </div>
138
+ </template>
139
+
140
+ <style lang="less" scoped>
141
+ .tr-action-group {
142
+ display: inline-flex;
143
+ align-items: center;
144
+ gap: 4px;
145
+
146
+ .tr-action-group__btn-wrapper {
147
+ display: inline-flex;
148
+ line-height: 0;
149
+ position: relative;
150
+ }
151
+
152
+ .tr-action-group__dropdown {
153
+ width: max-content;
154
+ position: absolute;
155
+ z-index: 100;
156
+ right: 0;
157
+ background-color: white;
158
+ padding: 4px;
159
+ border-radius: 12px;
160
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.08);
161
+
162
+ &.placement-top {
163
+ bottom: calc(100% + 8px);
164
+ }
165
+
166
+ &.placement-bottom {
167
+ top: calc(100% + 8px);
168
+ }
169
+
170
+ .tr-action-group__dropdown-item {
171
+ border-radius: 8px;
172
+ cursor: pointer;
173
+ height: 32px;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: flex-start;
177
+ padding: 4px 16px 4px 8px;
178
+ gap: 4px;
179
+ transition: background-color 0.3s;
180
+
181
+ &:not(:first-child) {
182
+ margin-top: 4px;
183
+ }
184
+
185
+ &:hover {
186
+ background-color: rgba(0, 0, 0, 0.04);
187
+ }
188
+
189
+ &:active {
190
+ background-color: rgba(0, 0, 0, 0.15);
191
+ }
192
+
193
+ & > * {
194
+ flex-shrink: 0;
195
+ }
196
+
197
+ &:has(> *:only-child) {
198
+ padding: 4px 16px;
199
+ }
200
+
201
+ .tr-action-group__dropdown-item-text {
202
+ font-size: 12px;
203
+ line-height: 20px;
204
+ color: rgb(25, 25, 25);
205
+ }
206
+
207
+ :deep(button) {
208
+ --tr-icon-button-hover-bg: unset;
209
+ --tr-icon-button-active-bg: unset;
210
+ }
211
+ }
212
+ }
213
+
214
+ // 下拉动画
215
+ .tr-action-group-dropdown {
216
+ &-enter-active,
217
+ &-leave-active {
218
+ transition: opacity 0.3s ease;
219
+ }
220
+
221
+ &-enter-from,
222
+ &-leave-to {
223
+ opacity: 0;
224
+ }
225
+
226
+ &-enter-to,
227
+ &-leave-from {
228
+ opacity: 1;
229
+ }
230
+ }
231
+ }
232
+ </style>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ import { ActionGroupItemProps } from './index.type'
3
+
4
+ defineProps<ActionGroupItemProps>()
5
+ </script>
6
+
7
+ <template>
8
+ <slot></slot>
9
+ </template>
@@ -0,0 +1,25 @@
1
+ import { App } from 'vue'
2
+ import ActionGroup from './ActionGroup.vue'
3
+ import ActionGroupItemComp from './ActionGroupItem.vue'
4
+
5
+ ActionGroup.name = 'TrActionGroup'
6
+
7
+ const install = function <T>(app: App<T>) {
8
+ app.component(ActionGroup.name!, ActionGroup)
9
+ }
10
+
11
+ ActionGroup.install = install
12
+
13
+ export default ActionGroup as typeof ActionGroup & { install: typeof install }
14
+
15
+ ActionGroupItemComp.name = 'TrActionGroupItem'
16
+
17
+ const installActionGroupItem = function <T>(app: App<T>) {
18
+ app.component(ActionGroupItemComp.name!, ActionGroupItemComp)
19
+ }
20
+
21
+ ActionGroupItemComp.install = installActionGroupItem
22
+
23
+ export const ActionGroupItem = ActionGroupItemComp as typeof ActionGroupItemComp & {
24
+ install: typeof installActionGroupItem
25
+ }
@@ -0,0 +1,20 @@
1
+ import { VNode } from 'vue'
2
+
3
+ export interface ActionGroupProps {
4
+ maxNum?: number
5
+ dropDownShowLabelOnly?: boolean
6
+ }
7
+
8
+ export interface ActionGroupEvents {
9
+ (e: 'item-click', name: string): void
10
+ }
11
+
12
+ export interface ActionGroupSlots {
13
+ default: () => VNode | VNode[]
14
+ moreBtn: () => VNode
15
+ }
16
+
17
+ export interface ActionGroupItemProps {
18
+ name: string
19
+ label: string
20
+ }
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import markdownit from 'markdown-it'
3
- import { computed, defineComponent, useSlots } from 'vue'
4
- import { BubbleActionOptions, BubbleEvents, BubbleProps, BubbleSlots } from './index.type'
3
+ import { computed, useSlots } from 'vue'
5
4
  import { CopyAction, RefreshAction } from './components/actions'
5
+ import { BubbleActionOptions, BubbleEvents, BubbleProps, BubbleSlots } from './index.type'
6
6
 
7
7
  const props = withDefaults(defineProps<BubbleProps>(), {
8
8
  content: '',
@@ -26,16 +26,6 @@ const bubbleContent = computed(() => {
26
26
 
27
27
  const placementStart = computed(() => props.placement === 'start')
28
28
 
29
- const AvatarComp = computed(() => {
30
- if (!props.avatar) {
31
- return null
32
- }
33
-
34
- return defineComponent(() => {
35
- return () => props.avatar
36
- })
37
- })
38
-
39
29
  const defaultActionsMap = new Map<string, BubbleActionOptions>([
40
30
  [
41
31
  'copy',
@@ -99,8 +89,8 @@ const handleActionClick = (name: string, ...args: unknown[]) => {
99
89
  },
100
90
  ]"
101
91
  >
102
- <div v-if="AvatarComp" class="tr-bubble__avatar">
103
- <component :is="AvatarComp"></component>
92
+ <div v-if="props.avatar" class="tr-bubble__avatar">
93
+ <component :is="props.avatar"></component>
104
94
  </div>
105
95
  <div class="tr-bubble__content-wrapper">
106
96
  <slot v-if="props.loading" name="loading">
@@ -117,8 +117,7 @@ const IconFullScreenSwitcher = computed(() => (fullscreen.value ? IconCancelFull
117
117
  }
118
118
 
119
119
  svg {
120
- width: 20px;
121
- height: 20px;
120
+ font-size: 20px;
122
121
  }
123
122
  }
124
123
  }
@@ -0,0 +1,112 @@
1
+ <script setup lang="ts">
2
+ import { IconArrowUp } from '@opentiny/tiny-robot-svgs'
3
+ import { computed, nextTick, onMounted, ref, watch } from 'vue'
4
+
5
+ const props = withDefaults(
6
+ defineProps<{
7
+ sources: { label: string; link: string }[]
8
+ linesLimit?: number
9
+ }>(),
10
+ {
11
+ linesLimit: Number.MAX_SAFE_INTEGER,
12
+ },
13
+ )
14
+
15
+ const showAll = ref(false)
16
+ const pillRefs = ref<(HTMLElement | null)[]>([])
17
+ const morePillIndex = ref<number | null>(null)
18
+
19
+ const updateMoreIndex = () => {
20
+ nextTick(() => {
21
+ const tops = pillRefs.value.map((el) => el?.offsetTop || 0)
22
+ const uniqueTops = Array.from(new Set(tops))
23
+
24
+ if (uniqueTops.length > props.linesLimit) {
25
+ // 超过 linesLimit 行,找到第 linesLimit 行最后一个的index
26
+ const lastLineTop = uniqueTops[props.linesLimit - 1]
27
+ const lastIndex = tops.lastIndexOf(lastLineTop)
28
+ morePillIndex.value = lastIndex
29
+ } else {
30
+ morePillIndex.value = null
31
+ }
32
+ })
33
+ }
34
+
35
+ const leftPillCount = computed(() => {
36
+ return props.sources.length - (morePillIndex.value || 0)
37
+ })
38
+
39
+ onMounted(updateMoreIndex)
40
+
41
+ watch(() => props.sources, updateMoreIndex)
42
+
43
+ const setPillRef = (el: HTMLElement | null, idx: number) => {
44
+ if (el) {
45
+ pillRefs.value[idx] = el
46
+ }
47
+ }
48
+ </script>
49
+
50
+ <template>
51
+ <div class="tr-feedback__source-list">
52
+ <template v-for="(source, index) in props.sources" :key="index">
53
+ <a
54
+ v-if="!morePillIndex || showAll || index < morePillIndex"
55
+ class="pill"
56
+ :href="source.link"
57
+ target="_blank"
58
+ :ref="(el) => setPillRef(el as HTMLElement, index)"
59
+ >
60
+ {{ source.label }}
61
+ </a>
62
+ <span v-if="morePillIndex && !showAll && index === morePillIndex" class="pill" @click="showAll = true">
63
+ {{ leftPillCount }}+
64
+ </span>
65
+ </template>
66
+ <span v-if="showAll" class="pill collapse-pill" @click="showAll = false">
67
+ <IconArrowUp />
68
+ </span>
69
+ </div>
70
+ </template>
71
+
72
+ <style lang="less" scoped>
73
+ .tr-feedback__source-list {
74
+ display: flex;
75
+ flex-wrap: wrap;
76
+ gap: 8px;
77
+
78
+ .pill {
79
+ padding: 4px 12px;
80
+ font-size: 12px;
81
+ line-height: 20px;
82
+ border-radius: 999px;
83
+ border: none;
84
+ background-color: rgba(20, 118, 255, 0.06);
85
+ color: rgb(20, 118, 255);
86
+ cursor: pointer;
87
+
88
+ &:hover {
89
+ text-decoration: underline;
90
+ }
91
+
92
+ &.collapse {
93
+ font-size: 16px;
94
+ }
95
+ }
96
+
97
+ .collapse-pill {
98
+ padding: 0;
99
+ font-size: 16px;
100
+ width: 28px;
101
+ height: 28px;
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ transition: background-color 0.3s ease;
106
+
107
+ &:hover {
108
+ background-color: rgba(20, 118, 255, 0.12);
109
+ }
110
+ }
111
+ }
112
+ </style>
@@ -0,0 +1 @@
1
+ export { default as SourceList } from './SourceList.vue'
@@ -0,0 +1,12 @@
1
+ import { App } from 'vue'
2
+ import Feedback from './index.vue'
3
+
4
+ Feedback.name = 'TrFeedback'
5
+
6
+ const install = function <T>(app: App<T>) {
7
+ app.component(Feedback.name!, Feedback)
8
+ }
9
+
10
+ Feedback.install = install
11
+
12
+ export default Feedback as typeof Feedback & { install: typeof install }
@@ -0,0 +1,27 @@
1
+ import { Component, VNode } from 'vue'
2
+
3
+ export interface FeedbackProps {
4
+ operations?: {
5
+ name: string
6
+ label: string
7
+ onClick?: () => void
8
+ }[]
9
+ operationsLimit?: number
10
+ actions?: {
11
+ name: string
12
+ label: string
13
+ icon?: 'copy' | 'refresh' | 'like' | 'dislike' | VNode | Component
14
+ onClick?: () => void
15
+ }[]
16
+ actionsLimit?: number
17
+ sources?: {
18
+ label: string
19
+ link: string
20
+ }[]
21
+ sourcesLinesLimit?: number
22
+ }
23
+
24
+ export interface FeedbackEvents {
25
+ (e: 'operation', name: string): void
26
+ (e: 'action', name: string): void
27
+ }
@@ -0,0 +1,166 @@
1
+ <script lang="ts" setup>
2
+ import { IconArrowDown, IconArrowUp, IconCopy, IconDislike, IconLike, IconRefresh } from '@opentiny/tiny-robot-svgs'
3
+ import TinyButton from '@opentiny/vue-button'
4
+ import { ref } from 'vue'
5
+ import ActionGroup, { ActionGroupItem } from '../action-group'
6
+ import IconButton from '../icon-button'
7
+ import { SourceList } from './components'
8
+ import type { FeedbackEvents, FeedbackProps } from './index.type'
9
+
10
+ const props = withDefaults(defineProps<FeedbackProps>(), {
11
+ operationsLimit: Number.MAX_SAFE_INTEGER,
12
+ actionsLimit: Number.MAX_SAFE_INTEGER,
13
+ sourcesLinesLimit: Number.MAX_SAFE_INTEGER,
14
+ })
15
+
16
+ const iconMap = {
17
+ copy: IconCopy,
18
+ refresh: IconRefresh,
19
+ like: IconLike,
20
+ dislike: IconDislike,
21
+ }
22
+
23
+ const emit = defineEmits<FeedbackEvents>()
24
+
25
+ const handleOperation = (name: string) => {
26
+ props.operations?.find((operation) => operation.name === name)?.onClick?.()
27
+ emit('operation', name)
28
+ }
29
+
30
+ const handleAction = (name: string) => {
31
+ props.actions?.find((action) => action.name === name)?.onClick?.()
32
+ emit('action', name)
33
+ }
34
+
35
+ const showSourceList = ref(false)
36
+
37
+ const handleSourceList = () => {
38
+ showSourceList.value = !showSourceList.value
39
+ }
40
+ </script>
41
+
42
+ <template>
43
+ <div class="tr-feedback">
44
+ <div class="tr-feedback__operations">
45
+ <div v-if="props.operations?.length" class="tr-feedback__operations-left">
46
+ <action-group
47
+ :max-num="props.operationsLimit"
48
+ :drop-down-show-label-only="true"
49
+ @item-click="handleOperation"
50
+ class="tr-feedback__operations-left-action-group"
51
+ >
52
+ <action-group-item
53
+ v-for="operation in props.operations"
54
+ :key="operation.name"
55
+ :name="operation.name"
56
+ :label="operation.label"
57
+ >
58
+ <tiny-button round :reset-time="0" size="mini">
59
+ {{ operation.label }}
60
+ </tiny-button>
61
+ </action-group-item>
62
+ <template #moreBtn>
63
+ <tiny-button round size="mini" :reset-time="0" class="tr-feedback__operations-more-btn">
64
+ <span>更多</span>
65
+ <icon-arrow-down />
66
+ </tiny-button>
67
+ </template>
68
+ </action-group>
69
+ </div>
70
+ <div v-else-if="props.sources?.length">
71
+ <span class="tr-feedback__source" @click="handleSourceList">
72
+ <span>{{ props.sources?.length }}条来源</span>
73
+ <component :is="showSourceList ? IconArrowUp : IconArrowDown" />
74
+ </span>
75
+ </div>
76
+ <div class="tr-feedback__operations-right">
77
+ <action-group :max-num="props.actionsLimit" @item-click="handleAction">
78
+ <action-group-item
79
+ v-for="action in props.actions"
80
+ :key="action.name"
81
+ :name="action.name"
82
+ :label="action.label"
83
+ >
84
+ <icon-button
85
+ v-if="typeof action.icon === 'string'"
86
+ :icon="iconMap[action.icon]"
87
+ :tooltip="action.label"
88
+ ></icon-button>
89
+ <component v-else :is="action.icon"></component>
90
+ </action-group-item>
91
+ </action-group>
92
+ </div>
93
+ </div>
94
+ <div class="tr-feedback__footer">
95
+ <div v-if="props.operations?.length && props.sources?.length">
96
+ <span class="tr-feedback__source" @click="handleSourceList">
97
+ <span>{{ props.sources?.length }}条来源</span>
98
+ <component :is="showSourceList ? IconArrowUp : IconArrowDown" />
99
+ </span>
100
+ </div>
101
+ <source-list
102
+ v-if="showSourceList && props.sources"
103
+ :sources="props.sources"
104
+ :lines-limit="props.sourcesLinesLimit"
105
+ />
106
+ </div>
107
+ </div>
108
+ </template>
109
+
110
+ <style lang="less" scoped>
111
+ .tr-feedback {
112
+ .tr-feedback__operations {
113
+ display: flex;
114
+ justify-content: space-between;
115
+ align-items: center;
116
+ gap: 8px;
117
+
118
+ .tr-feedback__operations-left {
119
+ .tr-feedback__operations-left-action-group {
120
+ gap: 8px;
121
+ }
122
+
123
+ .tr-feedback__operations-more-btn {
124
+ display: inline-flex;
125
+ align-items: center;
126
+ gap: 4px;
127
+
128
+ svg {
129
+ font-size: 12px;
130
+ }
131
+ }
132
+ }
133
+
134
+ .tr-feedback__operations-right {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 4px;
138
+ }
139
+ }
140
+ .tr-feedback__footer {
141
+ margin-top: 8px;
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: 8px;
145
+ }
146
+
147
+ .tr-feedback__source {
148
+ height: 24px;
149
+ display: inline-flex;
150
+ align-items: center;
151
+ font-size: 12px;
152
+ line-height: 20px;
153
+ color: rgb(128, 128, 128);
154
+ cursor: pointer;
155
+ gap: 2px;
156
+
157
+ &:hover {
158
+ text-decoration: underline;
159
+ }
160
+
161
+ svg {
162
+ font-size: 16px;
163
+ }
164
+ }
165
+ }
166
+ </style>
@@ -0,0 +1,2 @@
1
+ export { default as ItemTag } from './item-tag.vue'
2
+ export { default as SearchEmpty } from './search-empty.vue'