@cat-factory/app 0.47.9 → 0.47.11
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.
- package/app/components/brainstorm/BrainstormWindow.vue +96 -59
- package/app/components/clarity/ClarityReviewWindow.vue +84 -59
- package/app/components/consensus/ConsensusSessionWindow.vue +72 -34
- package/app/components/focus/BlockFocusView.vue +14 -10
- package/app/components/followUp/FollowUpWindow.vue +51 -26
- package/app/components/github/AddServiceFromRepoModal.vue +76 -23
- package/app/components/humanTest/HumanTestWindow.vue +70 -35
- package/app/components/requirements/RequirementsReviewWindow.vue +130 -82
- package/app/components/spec/ServiceSpecWindow.vue +54 -28
- package/app/components/testing/TestReportWindow.vue +82 -39
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +49 -32
- package/i18n/locales/en.json +587 -3
- package/i18n/locales/es.json +551 -3
- package/i18n/locales/fr.json +551 -3
- package/i18n/locales/pl.json +551 -3
- package/i18n/locales/uk.json +551 -3
- package/package.json +1 -1
|
@@ -11,6 +11,8 @@ const ui = useUiStore()
|
|
|
11
11
|
const models = useModelsStore()
|
|
12
12
|
const workspace = useWorkspaceStore()
|
|
13
13
|
|
|
14
|
+
const { t, n } = useI18n()
|
|
15
|
+
|
|
14
16
|
onMounted(() => models.ensureLoaded(workspace.workspaceId ?? undefined))
|
|
15
17
|
|
|
16
18
|
const block = computed<Block | undefined>(() =>
|
|
@@ -64,7 +66,7 @@ function openApprovalFor(approvalId: string) {
|
|
|
64
66
|
size="sm"
|
|
65
67
|
@click="close"
|
|
66
68
|
>
|
|
67
|
-
|
|
69
|
+
{{ t('focus.board') }}
|
|
68
70
|
</UButton>
|
|
69
71
|
<UIcon name="i-lucide-chevron-right" class="h-4 w-4 text-slate-600" />
|
|
70
72
|
<div
|
|
@@ -75,7 +77,9 @@ function openApprovalFor(approvalId: string) {
|
|
|
75
77
|
</div>
|
|
76
78
|
<div>
|
|
77
79
|
<h1 class="text-lg font-semibold text-white">{{ block.title }}</h1>
|
|
78
|
-
<div class="text-xs text-slate-500">
|
|
80
|
+
<div class="text-xs text-slate-500">
|
|
81
|
+
{{ t('focus.typeSubtitle', { type: typeMeta.label }) }}
|
|
82
|
+
</div>
|
|
79
83
|
</div>
|
|
80
84
|
<UBadge :color="statusMeta.chip as any" variant="subtle" class="ml-2">
|
|
81
85
|
{{ statusMeta.label }}
|
|
@@ -89,7 +93,7 @@ function openApprovalFor(approvalId: string) {
|
|
|
89
93
|
icon="i-lucide-play"
|
|
90
94
|
trailing-icon="i-lucide-chevron-down"
|
|
91
95
|
>
|
|
92
|
-
{{ instance ? '
|
|
96
|
+
{{ instance ? t('focus.rerunPipeline') : t('focus.runPipeline') }}
|
|
93
97
|
</UButton>
|
|
94
98
|
</UDropdownMenu>
|
|
95
99
|
<UButton icon="i-lucide-x" color="neutral" variant="ghost" @click="close" />
|
|
@@ -104,7 +108,7 @@ function openApprovalFor(approvalId: string) {
|
|
|
104
108
|
<div class="mb-4 flex items-center gap-2">
|
|
105
109
|
<UIcon name="i-lucide-workflow" class="h-4 w-4 text-slate-500" />
|
|
106
110
|
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-400">
|
|
107
|
-
{{ instance ? instance.pipelineName : '
|
|
111
|
+
{{ instance ? instance.pipelineName : t('focus.noPipelineRunning') }}
|
|
108
112
|
</h2>
|
|
109
113
|
</div>
|
|
110
114
|
|
|
@@ -119,7 +123,7 @@ function openApprovalFor(approvalId: string) {
|
|
|
119
123
|
v-else
|
|
120
124
|
class="flex flex-1 items-center justify-center rounded-xl border border-dashed border-slate-700 text-sm text-slate-500"
|
|
121
125
|
>
|
|
122
|
-
|
|
126
|
+
{{ t('focus.emptyPipelineHint') }}
|
|
123
127
|
</div>
|
|
124
128
|
</section>
|
|
125
129
|
|
|
@@ -129,29 +133,29 @@ function openApprovalFor(approvalId: string) {
|
|
|
129
133
|
>
|
|
130
134
|
<div>
|
|
131
135
|
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
132
|
-
|
|
136
|
+
{{ t('focus.description') }}
|
|
133
137
|
</div>
|
|
134
138
|
<p class="text-sm text-slate-300">{{ block.description }}</p>
|
|
135
139
|
</div>
|
|
136
140
|
<div v-if="instance">
|
|
137
141
|
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
138
|
-
|
|
142
|
+
{{ t('focus.overallProgress') }}
|
|
139
143
|
</div>
|
|
140
144
|
<UProgress :model-value="Math.round(block.progress * 100)" />
|
|
141
145
|
<div class="mt-1 text-[11px] text-slate-400">
|
|
142
|
-
{{
|
|
146
|
+
{{ n(block.progress, 'percent') }}
|
|
143
147
|
</div>
|
|
144
148
|
</div>
|
|
145
149
|
<div>
|
|
146
150
|
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
147
|
-
|
|
151
|
+
{{ t('focus.dependencies') }}
|
|
148
152
|
</div>
|
|
149
153
|
<div v-if="deps.length" class="flex flex-wrap gap-1">
|
|
150
154
|
<UBadge v-for="d in deps" :key="d.id" color="neutral" variant="subtle" size="sm">
|
|
151
155
|
{{ d.title }}
|
|
152
156
|
</UBadge>
|
|
153
157
|
</div>
|
|
154
|
-
<div v-else class="text-[11px] text-slate-500">
|
|
158
|
+
<div v-else class="text-[11px] text-slate-500">{{ t('focus.noDependencies') }}</div>
|
|
155
159
|
</div>
|
|
156
160
|
</aside>
|
|
157
161
|
</div>
|
|
@@ -17,6 +17,8 @@ const execution = useExecutionStore()
|
|
|
17
17
|
const board = useBoardStore()
|
|
18
18
|
const followUps = useFollowUpsStore()
|
|
19
19
|
|
|
20
|
+
const { t } = useI18n()
|
|
21
|
+
|
|
20
22
|
const { open, blockId, instanceId, stepIndex, close } = useResultView('follow-ups')
|
|
21
23
|
|
|
22
24
|
const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
|
|
@@ -57,15 +59,25 @@ async function onDismiss(item: FollowUpItem) {
|
|
|
57
59
|
if (id) await followUps.dismissItem(id, item.id).catch(() => {})
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
// Exhaustive map of the item status enum → label key (literal keys keep the typed-key
|
|
63
|
+
// drift guard live, vs a runtime-built `followUp.status.${status}`).
|
|
64
|
+
const STATUS_LABEL_KEYS: Record<FollowUpItem['status'], string> = {
|
|
65
|
+
pending: 'followUp.status.pending',
|
|
66
|
+
filed: 'followUp.status.filed',
|
|
67
|
+
queued: 'followUp.status.queued',
|
|
68
|
+
answered: 'followUp.status.answered',
|
|
69
|
+
dismissed: 'followUp.status.dismissed',
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
const STATUS_META: Record<
|
|
61
73
|
FollowUpItem['status'],
|
|
62
|
-
{
|
|
74
|
+
{ badge: 'neutral' | 'info' | 'success' | 'warning'; text: string }
|
|
63
75
|
> = {
|
|
64
|
-
pending: {
|
|
65
|
-
filed: {
|
|
66
|
-
queued: {
|
|
67
|
-
answered: {
|
|
68
|
-
dismissed: {
|
|
76
|
+
pending: { badge: 'warning', text: 'text-amber-300' },
|
|
77
|
+
filed: { badge: 'success', text: 'text-emerald-300' },
|
|
78
|
+
queued: { badge: 'info', text: 'text-sky-300' },
|
|
79
|
+
answered: { badge: 'info', text: 'text-sky-300' },
|
|
80
|
+
dismissed: { badge: 'neutral', text: 'text-slate-400' },
|
|
69
81
|
}
|
|
70
82
|
</script>
|
|
71
83
|
|
|
@@ -88,15 +100,20 @@ const STATUS_META: Record<
|
|
|
88
100
|
</span>
|
|
89
101
|
<div class="min-w-0 flex-1">
|
|
90
102
|
<h2 class="truncate text-sm font-semibold text-slate-100">
|
|
91
|
-
{{
|
|
103
|
+
{{
|
|
104
|
+
block ? t('followUp.titleWithBlock', { title: block.title }) : t('followUp.title')
|
|
105
|
+
}}
|
|
92
106
|
</h2>
|
|
93
107
|
<p class="truncate text-[11px] text-slate-400">
|
|
94
|
-
|
|
95
|
-
every item is decided.
|
|
108
|
+
{{ t('followUp.subtitle') }}
|
|
96
109
|
</p>
|
|
97
110
|
</div>
|
|
98
111
|
<UBadge :color="pendingCount > 0 ? 'warning' : 'success'" variant="subtle" size="sm">
|
|
99
|
-
{{
|
|
112
|
+
{{
|
|
113
|
+
pendingCount > 0
|
|
114
|
+
? t('followUp.badge.toDecide', { count: pendingCount }, pendingCount)
|
|
115
|
+
: t('followUp.badge.allDecided')
|
|
116
|
+
}}
|
|
100
117
|
</UBadge>
|
|
101
118
|
<button
|
|
102
119
|
class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
|
|
@@ -113,10 +130,9 @@ const STATUS_META: Record<
|
|
|
113
130
|
class="flex h-full flex-col items-center justify-center gap-2 py-10 text-center text-slate-400"
|
|
114
131
|
>
|
|
115
132
|
<UIcon :name="FOLLOW_UP_COMPANION_META.icon" class="h-8 w-8 opacity-40" />
|
|
116
|
-
<p class="text-sm">
|
|
133
|
+
<p class="text-sm">{{ t('followUp.empty.title') }}</p>
|
|
117
134
|
<p class="max-w-sm text-[11px] text-slate-500">
|
|
118
|
-
|
|
119
|
-
live — you can act on them before the Coder even finishes.
|
|
135
|
+
{{ t('followUp.empty.hint') }}
|
|
120
136
|
</p>
|
|
121
137
|
</div>
|
|
122
138
|
|
|
@@ -146,14 +162,15 @@ const STATUS_META: Record<
|
|
|
146
162
|
{{ item.title }}
|
|
147
163
|
</h3>
|
|
148
164
|
<UBadge :color="STATUS_META[item.status].badge" variant="subtle" size="sm">
|
|
149
|
-
{{
|
|
165
|
+
{{ t(STATUS_LABEL_KEYS[item.status]) }}
|
|
150
166
|
</UBadge>
|
|
151
167
|
</div>
|
|
152
168
|
<p v-if="item.detail" class="mt-1 whitespace-pre-wrap text-[12px] text-slate-300">
|
|
153
169
|
{{ item.detail }}
|
|
154
170
|
</p>
|
|
155
171
|
<p v-if="item.suggestedAction" class="mt-1 text-[11px] text-slate-400">
|
|
156
|
-
<span class="text-slate-500">
|
|
172
|
+
<span class="text-slate-500">{{ t('followUp.suggested') }}</span>
|
|
173
|
+
{{ item.suggestedAction }}
|
|
157
174
|
</p>
|
|
158
175
|
<p v-if="item.status === 'filed' && item.ticketUrl" class="mt-1 text-[11px]">
|
|
159
176
|
<a
|
|
@@ -162,14 +179,15 @@ const STATUS_META: Record<
|
|
|
162
179
|
rel="noopener"
|
|
163
180
|
class="text-emerald-300 hover:underline"
|
|
164
181
|
>
|
|
165
|
-
{{ item.ticketExternalId ?? '
|
|
182
|
+
{{ item.ticketExternalId ?? t('followUp.viewIssue') }}
|
|
166
183
|
</a>
|
|
167
184
|
</p>
|
|
168
185
|
<p
|
|
169
186
|
v-if="item.status === 'answered' && item.answer"
|
|
170
187
|
class="mt-1 text-[11px] text-slate-300"
|
|
171
188
|
>
|
|
172
|
-
<span class="text-slate-500">
|
|
189
|
+
<span class="text-slate-500">{{ t('followUp.yourAnswer') }}</span>
|
|
190
|
+
{{ item.answer }}
|
|
173
191
|
</p>
|
|
174
192
|
|
|
175
193
|
<!-- Actions (only while the item is still undecided) -->
|
|
@@ -179,7 +197,7 @@ const STATUS_META: Record<
|
|
|
179
197
|
<textarea
|
|
180
198
|
v-model="drafts[item.id]"
|
|
181
199
|
rows="2"
|
|
182
|
-
placeholder="
|
|
200
|
+
:placeholder="t('followUp.answerPlaceholder')"
|
|
183
201
|
class="w-full resize-y rounded-md border border-slate-700 bg-slate-950/60 px-2.5 py-1.5 text-[12px] text-slate-100 placeholder:text-slate-600 focus:border-sky-500 focus:outline-none"
|
|
184
202
|
/>
|
|
185
203
|
<div class="flex items-center gap-2">
|
|
@@ -190,7 +208,7 @@ const STATUS_META: Record<
|
|
|
190
208
|
:disabled="!(drafts[item.id] ?? '').trim()"
|
|
191
209
|
@click="onAnswer(item)"
|
|
192
210
|
>
|
|
193
|
-
|
|
211
|
+
{{ t('followUp.actions.answerAndSend') }}
|
|
194
212
|
</UButton>
|
|
195
213
|
<UButton
|
|
196
214
|
size="xs"
|
|
@@ -199,7 +217,7 @@ const STATUS_META: Record<
|
|
|
199
217
|
:loading="followUps.isActing(item.id)"
|
|
200
218
|
@click="onDismiss(item)"
|
|
201
219
|
>
|
|
202
|
-
|
|
220
|
+
{{ t('followUp.actions.dismiss') }}
|
|
203
221
|
</UButton>
|
|
204
222
|
</div>
|
|
205
223
|
</div>
|
|
@@ -213,7 +231,7 @@ const STATUS_META: Record<
|
|
|
213
231
|
:loading="followUps.isActing(item.id)"
|
|
214
232
|
@click="onFile(item)"
|
|
215
233
|
>
|
|
216
|
-
|
|
234
|
+
{{ t('followUp.actions.fileAsIssue') }}
|
|
217
235
|
</UButton>
|
|
218
236
|
<UButton
|
|
219
237
|
size="xs"
|
|
@@ -223,7 +241,7 @@ const STATUS_META: Record<
|
|
|
223
241
|
:loading="followUps.isActing(item.id)"
|
|
224
242
|
@click="onQueue(item)"
|
|
225
243
|
>
|
|
226
|
-
|
|
244
|
+
{{ t('followUp.actions.sendToCoder') }}
|
|
227
245
|
</UButton>
|
|
228
246
|
<UButton
|
|
229
247
|
size="xs"
|
|
@@ -232,7 +250,7 @@ const STATUS_META: Record<
|
|
|
232
250
|
:loading="followUps.isActing(item.id)"
|
|
233
251
|
@click="onDismiss(item)"
|
|
234
252
|
>
|
|
235
|
-
|
|
253
|
+
{{ t('followUp.actions.dismiss') }}
|
|
236
254
|
</UButton>
|
|
237
255
|
</div>
|
|
238
256
|
</div>
|
|
@@ -246,10 +264,17 @@ const STATUS_META: Record<
|
|
|
246
264
|
class="flex items-center justify-between border-t border-slate-800 px-5 py-2.5 text-[11px] text-slate-400"
|
|
247
265
|
>
|
|
248
266
|
<span>
|
|
249
|
-
{{
|
|
250
|
-
|
|
267
|
+
{{
|
|
268
|
+
t(
|
|
269
|
+
'followUp.footer.summary',
|
|
270
|
+
{ count: items.length, undecided: pendingCount },
|
|
271
|
+
items.length,
|
|
272
|
+
)
|
|
273
|
+
}}
|
|
251
274
|
</span>
|
|
252
|
-
<span v-if="maxLoops > 0">
|
|
275
|
+
<span v-if="maxLoops > 0">{{
|
|
276
|
+
t('followUp.footer.loops', { loops, max: maxLoops })
|
|
277
|
+
}}</span>
|
|
253
278
|
</footer>
|
|
254
279
|
</div>
|
|
255
280
|
</div>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// pinned to a subdirectory. When the selected repo is a monorepo, the user
|
|
11
11
|
// browses its tree and picks the service's directory before adding (and may add
|
|
12
12
|
// more than one, a subset of the repo's services).
|
|
13
|
+
import { refDebounced } from '@vueuse/core'
|
|
13
14
|
import GitHubConnect from '~/components/github/GitHubConnect.vue'
|
|
14
15
|
import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
|
|
15
16
|
import ServiceTestConfig from '~/components/panels/inspector/ServiceTestConfig.vue'
|
|
@@ -76,16 +77,53 @@ const repoItems = computed(() =>
|
|
|
76
77
|
}),
|
|
77
78
|
)
|
|
78
79
|
|
|
79
|
-
// The PAT (or a wide App install) can expose hundreds of repos, too many for a plain
|
|
80
|
-
// dropdown —
|
|
81
|
-
//
|
|
80
|
+
// The PAT (or a wide App install) can expose hundreds of repos, far too many for a plain
|
|
81
|
+
// dropdown — so the picker is a typeahead combobox. The user types and matching repos
|
|
82
|
+
// surface: matching is a debounced, case-insensitive substring over `owner/name` (so any
|
|
83
|
+
// part of either matches). For a LARGE list the search only kicks in once at least
|
|
84
|
+
// MIN_SEARCH_LEN characters are typed, to keep early keystrokes from listing hundreds of
|
|
85
|
+
// rows; but when the whole list is small enough to browse (<= BROWSE_ALL_MAX) the gate is
|
|
86
|
+
// dropped and every repo is offered up-front (typing then narrows), so a handful of repos
|
|
87
|
+
// stays pickable without having to type — matching the old always-open dropdown.
|
|
88
|
+
const MIN_SEARCH_LEN = 3
|
|
89
|
+
const BROWSE_ALL_MAX = 25
|
|
82
90
|
const repoSearch = ref('')
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
const repoSearchDebounced = refDebounced(repoSearch, 250)
|
|
92
|
+
// Trimmed (original case) for display; lowercased for matching.
|
|
93
|
+
const repoQueryRaw = computed(() => repoSearchDebounced.value.trim())
|
|
94
|
+
const repoQuery = computed(() => repoQueryRaw.value.toLowerCase())
|
|
95
|
+
|
|
96
|
+
// Small lists are browseable without typing; large lists require the min-length gate.
|
|
97
|
+
const browseAll = computed(() => repoItems.value.length <= BROWSE_ALL_MAX)
|
|
98
|
+
// True only on a large list whose query is still too short to search.
|
|
99
|
+
const belowMinChars = computed(() => !browseAll.value && repoQuery.value.length < MIN_SEARCH_LEN)
|
|
100
|
+
|
|
101
|
+
// Matches for the current query. On a small list an empty query lists everything; on a
|
|
102
|
+
// large list nothing surfaces until the query passes the min length.
|
|
103
|
+
const queryMatches = computed(() => {
|
|
104
|
+
if (belowMinChars.value) return []
|
|
105
|
+
if (!repoQuery.value) return repoItems.value
|
|
106
|
+
return repoItems.value.filter((r) => r.search.includes(repoQuery.value))
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Items fed to the combobox: the query matches plus the current selection kept present,
|
|
110
|
+
// so the menu can still render the selected repo's label once the (reset) search term no
|
|
111
|
+
// longer matches it.
|
|
112
|
+
const repoMenuItems = computed(() => {
|
|
113
|
+
const matches = queryMatches.value
|
|
114
|
+
if (selectedRepoId.value === undefined) return matches
|
|
115
|
+
if (matches.some((r) => r.value === selectedRepoId.value)) return matches
|
|
116
|
+
const selected = repoItems.value.find((r) => r.value === selectedRepoId.value)
|
|
117
|
+
return selected ? [selected, ...matches] : matches
|
|
87
118
|
})
|
|
88
119
|
|
|
120
|
+
// The count summary under the field is shown only when it's meaningful: there are matches
|
|
121
|
+
// to count AND the user is actually searching (or browsing a small list). After a
|
|
122
|
+
// selection resets the search term this is false, so the field doesn't claim "Showing 0"
|
|
123
|
+
// or nag "type 3 characters" right under the repo the user just picked. The zero-match and
|
|
124
|
+
// min-length messages are owned by the combobox's own empty state instead.
|
|
125
|
+
const showResultCount = computed(() => !belowMinChars.value && queryMatches.value.length > 0)
|
|
126
|
+
|
|
89
127
|
const hasRepos = computed(() => github.availableRepos.length > 0)
|
|
90
128
|
const selectedRepo = computed(() =>
|
|
91
129
|
github.availableRepos.find((r) => r.githubId === selectedRepoId.value),
|
|
@@ -121,6 +159,13 @@ function resetSelection() {
|
|
|
121
159
|
repoSearch.value = ''
|
|
122
160
|
}
|
|
123
161
|
|
|
162
|
+
// Clear the current repo selection (the combobox's trailing ✕) so the user can pick a
|
|
163
|
+
// different one — drops the selection-dependent state and resets the search term. The
|
|
164
|
+
// combobox has no built-in deselect, so the field would otherwise stay pinned to a repo.
|
|
165
|
+
function clearSelection() {
|
|
166
|
+
resetSelection()
|
|
167
|
+
}
|
|
168
|
+
|
|
124
169
|
// The App's installation settings page — where the user grants it access to a
|
|
125
170
|
// repo it can't see yet (mirrors the bootstrap modal's "grant access" link).
|
|
126
171
|
const manageInstallUrl = computed(() => {
|
|
@@ -236,34 +281,42 @@ function done() {
|
|
|
236
281
|
{{ t('github.addService.noReposAvailable') }}
|
|
237
282
|
</div>
|
|
238
283
|
<div v-else class="space-y-1.5">
|
|
239
|
-
<
|
|
240
|
-
v-model="
|
|
284
|
+
<UInputMenu
|
|
285
|
+
v-model="selectedRepoId"
|
|
286
|
+
v-model:search-term="repoSearch"
|
|
287
|
+
:items="repoMenuItems"
|
|
288
|
+
:ignore-filter="true"
|
|
289
|
+
value-key="value"
|
|
290
|
+
:loading="github.loadingAvailable"
|
|
241
291
|
icon="i-lucide-search"
|
|
242
|
-
:placeholder="t('github.addService.
|
|
292
|
+
:placeholder="t('github.addService.searchPlaceholder')"
|
|
243
293
|
class="w-full"
|
|
244
|
-
:ui="{ trailing: 'pe-1' }"
|
|
245
294
|
>
|
|
246
|
-
<template v-if="
|
|
295
|
+
<template v-if="selectedRepoId !== undefined" #trailing>
|
|
247
296
|
<UButton
|
|
248
297
|
color="neutral"
|
|
249
298
|
variant="link"
|
|
250
299
|
size="sm"
|
|
251
300
|
icon="i-lucide-x"
|
|
252
|
-
:aria-label="t('github.addService.
|
|
253
|
-
@click="
|
|
301
|
+
:aria-label="t('github.addService.clearSelection')"
|
|
302
|
+
@click.stop="clearSelection"
|
|
254
303
|
/>
|
|
255
304
|
</template>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
305
|
+
<template #empty>
|
|
306
|
+
<span v-if="belowMinChars">
|
|
307
|
+
{{
|
|
308
|
+
t('github.addService.searchMinChars', { min: MIN_SEARCH_LEN }, MIN_SEARCH_LEN)
|
|
309
|
+
}}
|
|
310
|
+
</span>
|
|
311
|
+
<span v-else>{{
|
|
312
|
+
t('github.addService.noMatches', { query: repoQueryRaw })
|
|
313
|
+
}}</span>
|
|
314
|
+
</template>
|
|
315
|
+
</UInputMenu>
|
|
316
|
+
<p v-if="showResultCount" class="text-xs text-slate-500">
|
|
264
317
|
{{
|
|
265
318
|
t('github.addService.showingCount', {
|
|
266
|
-
shown:
|
|
319
|
+
shown: queryMatches.length,
|
|
267
320
|
total: repoItems.length,
|
|
268
321
|
})
|
|
269
322
|
}}
|