@cat-factory/app 0.47.10 → 0.48.0
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/github/AddServiceFromRepoModal.vue +76 -23
- package/app/components/layout/AccountDeploymentSettings.vue +119 -0
- package/app/components/settings/IssueTrackerPanel.vue +48 -1
- package/app/components/tasks/TaskSourceConnectModal.vue +36 -1
- package/app/composables/api/tasks.ts +11 -0
- package/app/stores/tasks.ts +12 -0
- package/app/stores/tracker.ts +12 -1
- package/app/types/tasks.ts +1 -0
- package/i18n/locales/en.json +24 -5
- package/i18n/locales/es.json +21 -5
- package/i18n/locales/fr.json +21 -5
- package/i18n/locales/pl.json +21 -5
- package/i18n/locales/uk.json +21 -5
- package/package.json +2 -2
|
@@ -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
|
}}
|
|
@@ -15,8 +15,10 @@ const toast = useToast()
|
|
|
15
15
|
const { t } = useI18n()
|
|
16
16
|
|
|
17
17
|
const slack = reactive({ clientId: '', clientSecret: '', redirectUrl: '' })
|
|
18
|
+
const linear = reactive({ clientId: '', clientSecret: '', redirectUrl: '' })
|
|
18
19
|
const web = reactive({ braveApiKey: '', searxngUrl: '', searxngApiKey: '' })
|
|
19
20
|
const savingSlack = ref(false)
|
|
21
|
+
const savingLinear = ref(false)
|
|
20
22
|
const savingWeb = ref(false)
|
|
21
23
|
|
|
22
24
|
const summary = computed(() => store.view?.summary ?? null)
|
|
@@ -200,6 +202,61 @@ async function clearSlack() {
|
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
|
|
205
|
+
async function saveLinear() {
|
|
206
|
+
if (!linear.clientId.trim() || !linear.clientSecret.trim() || !linear.redirectUrl.trim()) {
|
|
207
|
+
toast.add({ title: t('layout.accountDeployment.linear.validation'), color: 'error' })
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
savingLinear.value = true
|
|
211
|
+
try {
|
|
212
|
+
await store.save(props.accountId, {
|
|
213
|
+
secrets: {
|
|
214
|
+
linearOAuth: {
|
|
215
|
+
clientId: linear.clientId.trim(),
|
|
216
|
+
clientSecret: linear.clientSecret.trim(),
|
|
217
|
+
redirectUrl: linear.redirectUrl.trim(),
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
linear.clientId = ''
|
|
222
|
+
linear.clientSecret = ''
|
|
223
|
+
linear.redirectUrl = ''
|
|
224
|
+
toast.add({
|
|
225
|
+
title: t('layout.accountDeployment.linear.saved'),
|
|
226
|
+
icon: 'i-lucide-check',
|
|
227
|
+
color: 'success',
|
|
228
|
+
})
|
|
229
|
+
} catch (e) {
|
|
230
|
+
toast.add({
|
|
231
|
+
title: t('layout.accountDeployment.linear.saveFailed'),
|
|
232
|
+
description: e instanceof Error ? e.message : String(e),
|
|
233
|
+
color: 'error',
|
|
234
|
+
})
|
|
235
|
+
} finally {
|
|
236
|
+
savingLinear.value = false
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function clearLinear() {
|
|
241
|
+
savingLinear.value = true
|
|
242
|
+
try {
|
|
243
|
+
await store.save(props.accountId, { secrets: { linearOAuth: null } })
|
|
244
|
+
toast.add({
|
|
245
|
+
title: t('layout.accountDeployment.linear.cleared'),
|
|
246
|
+
icon: 'i-lucide-check',
|
|
247
|
+
color: 'success',
|
|
248
|
+
})
|
|
249
|
+
} catch (e) {
|
|
250
|
+
toast.add({
|
|
251
|
+
title: t('layout.accountDeployment.linear.clearFailed'),
|
|
252
|
+
description: e instanceof Error ? e.message : String(e),
|
|
253
|
+
color: 'error',
|
|
254
|
+
})
|
|
255
|
+
} finally {
|
|
256
|
+
savingLinear.value = false
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
203
260
|
async function saveWeb() {
|
|
204
261
|
const brave = web.braveApiKey.trim()
|
|
205
262
|
const searxng = web.searxngUrl.trim()
|
|
@@ -329,6 +386,68 @@ async function clearWeb() {
|
|
|
329
386
|
</div>
|
|
330
387
|
</section>
|
|
331
388
|
|
|
389
|
+
<!-- Linear app OAuth -->
|
|
390
|
+
<section class="space-y-2 border-t border-slate-800 pt-6">
|
|
391
|
+
<div class="flex items-center gap-2">
|
|
392
|
+
<h4 class="text-sm font-semibold text-slate-200">
|
|
393
|
+
{{ t('layout.accountDeployment.linear.title') }}
|
|
394
|
+
</h4>
|
|
395
|
+
<UBadge
|
|
396
|
+
:color="summary?.linearOAuthConfigured ? 'success' : 'neutral'"
|
|
397
|
+
variant="subtle"
|
|
398
|
+
size="xs"
|
|
399
|
+
>
|
|
400
|
+
{{
|
|
401
|
+
summary?.linearOAuthConfigured
|
|
402
|
+
? t('layout.accountDeployment.configured')
|
|
403
|
+
: t('layout.accountDeployment.notSet')
|
|
404
|
+
}}
|
|
405
|
+
</UBadge>
|
|
406
|
+
</div>
|
|
407
|
+
<p class="text-[11px] text-slate-400">
|
|
408
|
+
{{ t('layout.accountDeployment.linear.description') }}
|
|
409
|
+
</p>
|
|
410
|
+
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
|
411
|
+
<UInput
|
|
412
|
+
v-model="linear.clientId"
|
|
413
|
+
:placeholder="t('layout.accountDeployment.linear.clientId')"
|
|
414
|
+
size="sm"
|
|
415
|
+
/>
|
|
416
|
+
<UInput
|
|
417
|
+
v-model="linear.clientSecret"
|
|
418
|
+
type="password"
|
|
419
|
+
:placeholder="t('layout.accountDeployment.linear.clientSecret')"
|
|
420
|
+
size="sm"
|
|
421
|
+
/>
|
|
422
|
+
<UInput
|
|
423
|
+
v-model="linear.redirectUrl"
|
|
424
|
+
:placeholder="t('layout.accountDeployment.linear.redirectUrl')"
|
|
425
|
+
size="sm"
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="flex gap-2">
|
|
429
|
+
<UButton
|
|
430
|
+
color="primary"
|
|
431
|
+
size="xs"
|
|
432
|
+
icon="i-lucide-save"
|
|
433
|
+
:loading="savingLinear"
|
|
434
|
+
@click="saveLinear"
|
|
435
|
+
>
|
|
436
|
+
{{ t('common.save') }}
|
|
437
|
+
</UButton>
|
|
438
|
+
<UButton
|
|
439
|
+
v-if="summary?.linearOAuthConfigured"
|
|
440
|
+
color="neutral"
|
|
441
|
+
variant="ghost"
|
|
442
|
+
size="xs"
|
|
443
|
+
:loading="savingLinear"
|
|
444
|
+
@click="clearLinear"
|
|
445
|
+
>
|
|
446
|
+
{{ t('layout.accountDeployment.clear') }}
|
|
447
|
+
</UButton>
|
|
448
|
+
</div>
|
|
449
|
+
</section>
|
|
450
|
+
|
|
332
451
|
<!-- Web search keys -->
|
|
333
452
|
<section class="space-y-2 border-t border-slate-800 pt-6">
|
|
334
453
|
<div class="flex items-center gap-2">
|
|
@@ -68,6 +68,39 @@ const canSave = computed(() => {
|
|
|
68
68
|
return true
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
// Linear team picker: load the connected workspace's teams so filing offers a
|
|
72
|
+
// dropdown instead of a raw team-id paste. Falls back to the text input if the
|
|
73
|
+
// teams can't be loaded (a broken connection shouldn't block configuration).
|
|
74
|
+
const teamsLoading = ref(false)
|
|
75
|
+
const teamsError = ref(false)
|
|
76
|
+
const teamOptions = computed(() =>
|
|
77
|
+
tracker.linearTeams.map((tm) => ({
|
|
78
|
+
label: tm.key ? `${tm.name} (${tm.key})` : tm.name,
|
|
79
|
+
value: tm.id,
|
|
80
|
+
})),
|
|
81
|
+
)
|
|
82
|
+
async function loadLinearTeams() {
|
|
83
|
+
if (!linearConnected.value) return
|
|
84
|
+
teamsLoading.value = true
|
|
85
|
+
teamsError.value = false
|
|
86
|
+
try {
|
|
87
|
+
await tracker.loadLinearTeams()
|
|
88
|
+
} catch {
|
|
89
|
+
teamsError.value = true
|
|
90
|
+
} finally {
|
|
91
|
+
teamsLoading.value = false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
watch(
|
|
95
|
+
() => [trackerKind.value, linearConnected.value] as const,
|
|
96
|
+
([kind, connected]) => {
|
|
97
|
+
if (kind === 'linear' && connected && tracker.linearTeams.length === 0 && !teamsError.value) {
|
|
98
|
+
void loadLinearTeams()
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{ immediate: true },
|
|
102
|
+
)
|
|
103
|
+
|
|
71
104
|
async function save() {
|
|
72
105
|
if (!canSave.value) return
|
|
73
106
|
saving.value = true
|
|
@@ -284,7 +317,21 @@ const STATUS_UI: Record<
|
|
|
284
317
|
:label="t('settings.issueTracker.filing.linearTeamId')"
|
|
285
318
|
class="w-64"
|
|
286
319
|
>
|
|
287
|
-
|
|
320
|
+
<!-- Typeahead combobox when the connection's teams loaded (built-in client-side
|
|
321
|
+
filter over the option labels — a large org's team list is too long for a
|
|
322
|
+
plain dropdown); raw-id fallback otherwise. Mirrors the repo picker. -->
|
|
323
|
+
<UInputMenu
|
|
324
|
+
v-if="linearConnected && !teamsError && teamOptions.length > 0"
|
|
325
|
+
v-model="linearTeamId"
|
|
326
|
+
:items="teamOptions"
|
|
327
|
+
value-key="value"
|
|
328
|
+
:loading="teamsLoading"
|
|
329
|
+
icon="i-lucide-search"
|
|
330
|
+
:placeholder="t('settings.issueTracker.filing.linearTeamSearchPlaceholder')"
|
|
331
|
+
size="sm"
|
|
332
|
+
class="w-full"
|
|
333
|
+
/>
|
|
334
|
+
<UInput v-else v-model="linearTeamId" placeholder="team_…" size="sm" class="w-full" />
|
|
288
335
|
<template #help>
|
|
289
336
|
<span class="text-[11px] text-slate-500">
|
|
290
337
|
{{ t('settings.issueTracker.filing.linearTeamIdHelp') }}
|
|
@@ -24,6 +24,9 @@ const connection = computed(() => (source.value ? tasks.connectionFor(source.val
|
|
|
24
24
|
const connected = computed(() => connection.value !== undefined)
|
|
25
25
|
// A credentialless source (GitHub Issues) reuses the installed GitHub App: no form.
|
|
26
26
|
const credentialless = computed(() => (descriptor.value?.credentialFields.length ?? 0) === 0)
|
|
27
|
+
// An OAuth source (Linear) offers a "Connect with X" button alongside the manual fields.
|
|
28
|
+
const oauth = computed(() => descriptor.value?.oauth ?? false)
|
|
29
|
+
const oauthStarting = ref(false)
|
|
27
30
|
// Usable right now: a credentialed source is connected; GitHub Issues' App is installed.
|
|
28
31
|
const available = computed(() => descriptor.value?.available ?? false)
|
|
29
32
|
|
|
@@ -77,6 +80,23 @@ async function submit() {
|
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
|
|
83
|
+
async function startOAuth() {
|
|
84
|
+
if (!source.value) return
|
|
85
|
+
oauthStarting.value = true
|
|
86
|
+
try {
|
|
87
|
+
// Only Linear wires an OAuth flow today; the browser navigates away on success.
|
|
88
|
+
if (source.value === 'linear') await tasks.startLinearOAuth()
|
|
89
|
+
} catch (e) {
|
|
90
|
+
toast.add({
|
|
91
|
+
title: t('tasks.connect.connectFailed'),
|
|
92
|
+
description: e instanceof Error ? e.message : String(e),
|
|
93
|
+
icon: 'i-lucide-triangle-alert',
|
|
94
|
+
color: 'error',
|
|
95
|
+
})
|
|
96
|
+
oauthStarting.value = false
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
80
100
|
async function disconnect() {
|
|
81
101
|
if (!source.value) return
|
|
82
102
|
await tasks.disconnect(source.value)
|
|
@@ -129,8 +149,23 @@ async function toggleEnabled(enabled: boolean) {
|
|
|
129
149
|
</p>
|
|
130
150
|
</template>
|
|
131
151
|
|
|
132
|
-
<!-- Credentialed source (Jira): the connect form, shown until connected. -->
|
|
152
|
+
<!-- Credentialed source (Jira/Linear): the connect form, shown until connected. -->
|
|
133
153
|
<div v-else-if="!connected" class="space-y-3">
|
|
154
|
+
<!-- OAuth source (Linear): the redirect button, with the manual key form below. -->
|
|
155
|
+
<template v-if="oauth">
|
|
156
|
+
<UButton
|
|
157
|
+
block
|
|
158
|
+
color="primary"
|
|
159
|
+
icon="i-lucide-plug"
|
|
160
|
+
:loading="oauthStarting"
|
|
161
|
+
@click="startOAuth"
|
|
162
|
+
>
|
|
163
|
+
{{ t('tasks.connect.oauthButton', { label: descriptor.label }) }}
|
|
164
|
+
</UButton>
|
|
165
|
+
<p class="text-center text-[11px] text-slate-500">
|
|
166
|
+
{{ t('tasks.connect.oauthOr') }}
|
|
167
|
+
</p>
|
|
168
|
+
</template>
|
|
134
169
|
<UFormField
|
|
135
170
|
v-for="field in descriptor.credentialFields"
|
|
136
171
|
:key="field.key"
|
|
@@ -3,9 +3,11 @@ import {
|
|
|
3
3
|
createTaskFromIssueContract,
|
|
4
4
|
diagnoseTaskSourceContract,
|
|
5
5
|
disconnectTaskSourceContract,
|
|
6
|
+
getLinearInstallUrlContract,
|
|
6
7
|
getTrackerSettingsContract,
|
|
7
8
|
importTaskContract,
|
|
8
9
|
linkTaskContract,
|
|
10
|
+
listLinearTeamsContract,
|
|
9
11
|
listTaskConnectionsContract,
|
|
10
12
|
listTaskSourcesContract,
|
|
11
13
|
listTasksContract,
|
|
@@ -95,6 +97,15 @@ export function tasksApi({ send, ws }: ApiContext) {
|
|
|
95
97
|
body: { ref: string; containerId: string; position?: { x: number; y: number } },
|
|
96
98
|
) => send(spawnEpicContract, { pathPrefix: ws(workspaceId), pathParams: { source }, body }),
|
|
97
99
|
|
|
100
|
+
// ---- Linear-specific --------------------------------------------------
|
|
101
|
+
// The connection's Linear teams, for the ticket-filing team picker.
|
|
102
|
+
listLinearTeams: (workspaceId: string) =>
|
|
103
|
+
send(listLinearTeamsContract, { pathPrefix: ws(workspaceId) }),
|
|
104
|
+
|
|
105
|
+
// The "Connect with Linear" OAuth authorize URL (the browser is redirected to it).
|
|
106
|
+
getLinearInstallUrl: (workspaceId: string) =>
|
|
107
|
+
send(getLinearInstallUrlContract, { pathPrefix: ws(workspaceId) }),
|
|
108
|
+
|
|
98
109
|
// ---- issue-tracker selection (workspace-level) ------------------------
|
|
99
110
|
getTrackerSettings: (workspaceId: string) =>
|
|
100
111
|
send(getTrackerSettingsContract, { pathPrefix: ws(workspaceId) }),
|
package/app/stores/tasks.ts
CHANGED
|
@@ -90,6 +90,17 @@ export const useTasksStore = defineStore('tasks', () => {
|
|
|
90
90
|
available.value = true
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Start the "Connect with Linear" OAuth flow by navigating the browser to the
|
|
95
|
+
* authorize URL the backend mints (carrying a signed `state`). Linear redirects
|
|
96
|
+
* back to the public callback, which stores the token; the settings panel's
|
|
97
|
+
* `probe()` on return then reflects the new connection.
|
|
98
|
+
*/
|
|
99
|
+
async function startLinearOAuth() {
|
|
100
|
+
const { url } = await api.getLinearInstallUrl(workspace.requireId())
|
|
101
|
+
window.location.href = url
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
/** Disconnect the workspace from a source. */
|
|
94
105
|
async function disconnect(source: TaskSourceKind) {
|
|
95
106
|
await api.disconnectTaskSource(workspace.requireId(), source)
|
|
@@ -203,6 +214,7 @@ export const useTasksStore = defineStore('tasks', () => {
|
|
|
203
214
|
probe,
|
|
204
215
|
checkSetup,
|
|
205
216
|
connect,
|
|
217
|
+
startLinearOAuth,
|
|
206
218
|
disconnect,
|
|
207
219
|
setEnabled,
|
|
208
220
|
loadTasks,
|
package/app/stores/tracker.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
2
|
import { ref } from 'vue'
|
|
3
|
+
import type { LinearTeam } from '~/types/domain'
|
|
3
4
|
import type { PutTrackerSettingsInput, TrackerSettings } from '~/types/tracker'
|
|
4
5
|
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
6
|
|
|
@@ -20,6 +21,9 @@ export const useTrackerStore = defineStore('tracker', () => {
|
|
|
20
21
|
updatedAt: 0,
|
|
21
22
|
})
|
|
22
23
|
|
|
24
|
+
/** The connected Linear workspace's teams, for the filing team picker (lazily loaded). */
|
|
25
|
+
const linearTeams = ref<LinearTeam[]>([])
|
|
26
|
+
|
|
23
27
|
function hydrate(value: TrackerSettings | undefined) {
|
|
24
28
|
settings.value = value ?? {
|
|
25
29
|
tracker: null,
|
|
@@ -37,5 +41,12 @@ export const useTrackerStore = defineStore('tracker', () => {
|
|
|
37
41
|
return settings.value
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
/** Load the connected Linear workspace's teams for the filing team picker. */
|
|
45
|
+
async function loadLinearTeams() {
|
|
46
|
+
const ws = useWorkspaceStore()
|
|
47
|
+
const { teams } = await api.listLinearTeams(ws.requireId())
|
|
48
|
+
linearTeams.value = teams
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { settings, linearTeams, hydrate, save, loadLinearTeams }
|
|
41
52
|
})
|
package/app/types/tasks.ts
CHANGED
package/i18n/locales/en.json
CHANGED
|
@@ -828,6 +828,18 @@
|
|
|
828
828
|
"cleared": "Slack OAuth cleared",
|
|
829
829
|
"clearFailed": "Could not clear Slack OAuth"
|
|
830
830
|
},
|
|
831
|
+
"linear": {
|
|
832
|
+
"title": "Linear app (OAuth)",
|
|
833
|
+
"description": "Enables the \"Connect with Linear\" OAuth flow. Without it, workspaces can still connect Linear by pasting a personal API key.",
|
|
834
|
+
"clientId": "Client ID",
|
|
835
|
+
"clientSecret": "Client secret",
|
|
836
|
+
"redirectUrl": "Redirect URL",
|
|
837
|
+
"validation": "Enter the client id, secret and redirect URL",
|
|
838
|
+
"saved": "Linear OAuth saved",
|
|
839
|
+
"saveFailed": "Could not save Linear OAuth",
|
|
840
|
+
"cleared": "Linear OAuth cleared",
|
|
841
|
+
"clearFailed": "Could not clear Linear OAuth"
|
|
842
|
+
},
|
|
831
843
|
"web": {
|
|
832
844
|
"title": "Container web search",
|
|
833
845
|
"description": "The search upstream container agents reach through the backend proxy. Set a Brave key (recommended), or a self-hosted SearXNG URL (with an optional bearer key).",
|
|
@@ -1340,7 +1352,8 @@
|
|
|
1340
1352
|
"jiraProjectKey": "Jira project key",
|
|
1341
1353
|
"jiraProjectKeyHelp": "New tickets are filed under this project.",
|
|
1342
1354
|
"linearTeamId": "Linear team id",
|
|
1343
|
-
"linearTeamIdHelp": "New issues are created under this team (Linear requires a team to create an issue)."
|
|
1355
|
+
"linearTeamIdHelp": "New issues are created under this team (Linear requires a team to create an issue).",
|
|
1356
|
+
"linearTeamSearchPlaceholder": "Search teams…"
|
|
1344
1357
|
},
|
|
1345
1358
|
"vendor": {
|
|
1346
1359
|
"github": "GitHub Issues",
|
|
@@ -1936,9 +1949,13 @@
|
|
|
1936
1949
|
"repository": "Repository",
|
|
1937
1950
|
"repositoryHint": "Repositories the GitHub App can access. Don't see yours? Grant the App access below, then refresh.",
|
|
1938
1951
|
"noReposAvailable": "No repositories available yet. Grant the App access to one below, then refresh.",
|
|
1939
|
-
"
|
|
1940
|
-
"
|
|
1941
|
-
"
|
|
1952
|
+
"searchPlaceholder": "Search repositories by owner or name…",
|
|
1953
|
+
"searchMinChars": "Type at least {min} character to search. | Type at least {min} characters to search.",
|
|
1954
|
+
"@searchMinChars": {
|
|
1955
|
+
"description": "Shown when a large repo list needs a typed query before searching. Resolved via t(key, { min }, min) so {min} also drives the plural choice; min is always 2 or more. Provide ALL plural forms your language needs (English has 2; Polish/Ukrainian need 3 - one/few/many - and rely on the custom pluralRules wired in i18n.config.ts)."
|
|
1956
|
+
},
|
|
1957
|
+
"noMatches": "No repositories found for {query}.",
|
|
1958
|
+
"clearSelection": "Clear selection",
|
|
1942
1959
|
"showingCount": "Showing {shown} of {total} repositories.",
|
|
1943
1960
|
"repoLabel": {
|
|
1944
1961
|
"private": " (private)",
|
|
@@ -2164,7 +2181,9 @@
|
|
|
2164
2181
|
"offerHint": "When off, {label} is hidden from import and linking.",
|
|
2165
2182
|
"disconnect": "Disconnect",
|
|
2166
2183
|
"updateConnection": "Update connection",
|
|
2167
|
-
"connect": "Connect"
|
|
2184
|
+
"connect": "Connect",
|
|
2185
|
+
"oauthButton": "Connect with {label}",
|
|
2186
|
+
"oauthOr": "or connect with an API key"
|
|
2168
2187
|
}
|
|
2169
2188
|
},
|
|
2170
2189
|
"pipeline": {
|
package/i18n/locales/es.json
CHANGED
|
@@ -789,6 +789,18 @@
|
|
|
789
789
|
"cleared": "OAuth de Slack borrado",
|
|
790
790
|
"clearFailed": "No se pudo borrar el OAuth de Slack"
|
|
791
791
|
},
|
|
792
|
+
"linear": {
|
|
793
|
+
"title": "Aplicación de Linear (OAuth)",
|
|
794
|
+
"description": "Habilita el flujo de OAuth \"Conectar con Linear\". Sin ello, los espacios de trabajo aún pueden conectar Linear pegando una clave de API personal.",
|
|
795
|
+
"clientId": "ID de cliente",
|
|
796
|
+
"clientSecret": "Secreto de cliente",
|
|
797
|
+
"redirectUrl": "URL de redirección",
|
|
798
|
+
"validation": "Introduce el id de cliente, el secreto y la URL de redirección",
|
|
799
|
+
"saved": "OAuth de Linear guardado",
|
|
800
|
+
"saveFailed": "No se pudo guardar OAuth de Linear",
|
|
801
|
+
"cleared": "OAuth de Linear borrado",
|
|
802
|
+
"clearFailed": "No se pudo borrar OAuth de Linear"
|
|
803
|
+
},
|
|
792
804
|
"web": {
|
|
793
805
|
"title": "Búsqueda web del contenedor",
|
|
794
806
|
"description": "El proveedor de búsqueda al que los agentes del contenedor acceden a través del proxy del backend. Define una clave de Brave (recomendado) o una URL de SearXNG autoalojada (con una clave bearer opcional).",
|
|
@@ -1298,7 +1310,8 @@
|
|
|
1298
1310
|
"jiraProjectKey": "Clave de proyecto de Jira",
|
|
1299
1311
|
"jiraProjectKeyHelp": "Los nuevos tickets se registran en este proyecto.",
|
|
1300
1312
|
"linearTeamId": "Id de equipo de Linear",
|
|
1301
|
-
"linearTeamIdHelp": "Las nuevas incidencias se crean en este equipo (Linear requiere un equipo para crear una incidencia)."
|
|
1313
|
+
"linearTeamIdHelp": "Las nuevas incidencias se crean en este equipo (Linear requiere un equipo para crear una incidencia).",
|
|
1314
|
+
"linearTeamSearchPlaceholder": "Buscar equipos…"
|
|
1302
1315
|
},
|
|
1303
1316
|
"vendor": {
|
|
1304
1317
|
"github": "GitHub Issues",
|
|
@@ -1885,9 +1898,10 @@
|
|
|
1885
1898
|
"repository": "Repositorio",
|
|
1886
1899
|
"repositoryHint": "Repositorios a los que la GitHub App puede acceder. ¿No ves el tuyo? Concede acceso a la App abajo y luego actualiza.",
|
|
1887
1900
|
"noReposAvailable": "Aún no hay repositorios disponibles. Concede acceso a la App a uno abajo y luego actualiza.",
|
|
1888
|
-
"
|
|
1889
|
-
"
|
|
1890
|
-
"
|
|
1901
|
+
"searchPlaceholder": "Busca repositorios por propietario o nombre…",
|
|
1902
|
+
"searchMinChars": "Escribe al menos {min} carácter para buscar. | Escribe al menos {min} caracteres para buscar.",
|
|
1903
|
+
"noMatches": "No se encontraron repositorios para {query}.",
|
|
1904
|
+
"clearSelection": "Borrar selección",
|
|
1891
1905
|
"showingCount": "Mostrando {shown} de {total} repositorios.",
|
|
1892
1906
|
"repoLabel": {
|
|
1893
1907
|
"private": " (privado)",
|
|
@@ -2110,7 +2124,9 @@
|
|
|
2110
2124
|
"offerHint": "Cuando está desactivada, {label} se oculta de la importación y la vinculación.",
|
|
2111
2125
|
"disconnect": "Desconectar",
|
|
2112
2126
|
"updateConnection": "Actualizar conexión",
|
|
2113
|
-
"connect": "Conectar"
|
|
2127
|
+
"connect": "Conectar",
|
|
2128
|
+
"oauthButton": "Conectar con {label}",
|
|
2129
|
+
"oauthOr": "o conecta con una clave API"
|
|
2114
2130
|
}
|
|
2115
2131
|
},
|
|
2116
2132
|
"pipeline": {
|
package/i18n/locales/fr.json
CHANGED
|
@@ -789,6 +789,18 @@
|
|
|
789
789
|
"cleared": "OAuth Slack effacé",
|
|
790
790
|
"clearFailed": "Impossible d'effacer l'OAuth Slack"
|
|
791
791
|
},
|
|
792
|
+
"linear": {
|
|
793
|
+
"title": "Application Linear (OAuth)",
|
|
794
|
+
"description": "Active le flux OAuth « Se connecter avec Linear ». Sans cela, les espaces de travail peuvent toujours connecter Linear en collant une clé API personnelle.",
|
|
795
|
+
"clientId": "ID client",
|
|
796
|
+
"clientSecret": "Secret client",
|
|
797
|
+
"redirectUrl": "URL de redirection",
|
|
798
|
+
"validation": "Saisissez l'id client, le secret et l'URL de redirection",
|
|
799
|
+
"saved": "OAuth Linear enregistré",
|
|
800
|
+
"saveFailed": "Impossible d'enregistrer OAuth Linear",
|
|
801
|
+
"cleared": "OAuth Linear effacé",
|
|
802
|
+
"clearFailed": "Impossible d'effacer OAuth Linear"
|
|
803
|
+
},
|
|
792
804
|
"web": {
|
|
793
805
|
"title": "Recherche web du conteneur",
|
|
794
806
|
"description": "Le fournisseur de recherche que les agents en conteneur atteignent via le proxy du backend. Définissez une clé Brave (recommandé) ou une URL SearXNG auto-hébergée (avec une clé bearer facultative).",
|
|
@@ -1298,7 +1310,8 @@
|
|
|
1298
1310
|
"jiraProjectKey": "Clé de projet Jira",
|
|
1299
1311
|
"jiraProjectKeyHelp": "Les nouveaux tickets sont créés dans ce projet.",
|
|
1300
1312
|
"linearTeamId": "Id d'équipe Linear",
|
|
1301
|
-
"linearTeamIdHelp": "Les nouveaux tickets sont créés dans cette équipe (Linear exige une équipe pour créer un ticket)."
|
|
1313
|
+
"linearTeamIdHelp": "Les nouveaux tickets sont créés dans cette équipe (Linear exige une équipe pour créer un ticket).",
|
|
1314
|
+
"linearTeamSearchPlaceholder": "Rechercher des équipes…"
|
|
1302
1315
|
},
|
|
1303
1316
|
"vendor": {
|
|
1304
1317
|
"github": "GitHub Issues",
|
|
@@ -1885,9 +1898,10 @@
|
|
|
1885
1898
|
"repository": "Dépôt",
|
|
1886
1899
|
"repositoryHint": "Dépôts auxquels la GitHub App peut accéder. Vous ne voyez pas le vôtre ? Accordez l'accès à l'App ci-dessous, puis actualisez.",
|
|
1887
1900
|
"noReposAvailable": "Aucun dépôt disponible pour le moment. Accordez l'accès de l'App à l'un d'eux ci-dessous, puis actualisez.",
|
|
1888
|
-
"
|
|
1889
|
-
"
|
|
1890
|
-
"
|
|
1901
|
+
"searchPlaceholder": "Rechercher des dépôts par propriétaire ou nom…",
|
|
1902
|
+
"searchMinChars": "Saisissez au moins {min} caractère pour rechercher. | Saisissez au moins {min} caractères pour rechercher.",
|
|
1903
|
+
"noMatches": "Aucun dépôt trouvé pour {query}.",
|
|
1904
|
+
"clearSelection": "Effacer la sélection",
|
|
1891
1905
|
"showingCount": "Affichage de {shown} dépôts sur {total}.",
|
|
1892
1906
|
"repoLabel": {
|
|
1893
1907
|
"private": " (privé)",
|
|
@@ -2110,7 +2124,9 @@
|
|
|
2110
2124
|
"offerHint": "Lorsqu'elle est désactivée, {label} est masquée de l'importation et de la liaison.",
|
|
2111
2125
|
"disconnect": "Déconnecter",
|
|
2112
2126
|
"updateConnection": "Mettre à jour la connexion",
|
|
2113
|
-
"connect": "Connecter"
|
|
2127
|
+
"connect": "Connecter",
|
|
2128
|
+
"oauthButton": "Se connecter avec {label}",
|
|
2129
|
+
"oauthOr": "ou connectez-vous avec une clé API"
|
|
2114
2130
|
}
|
|
2115
2131
|
},
|
|
2116
2132
|
"pipeline": {
|
package/i18n/locales/pl.json
CHANGED
|
@@ -789,6 +789,18 @@
|
|
|
789
789
|
"cleared": "Wyczyszczono OAuth Slacka",
|
|
790
790
|
"clearFailed": "Nie udało się wyczyścić OAuth Slacka"
|
|
791
791
|
},
|
|
792
|
+
"linear": {
|
|
793
|
+
"title": "Aplikacja Linear (OAuth)",
|
|
794
|
+
"description": "Włącza przepływ OAuth „Połącz z Linear”. Bez niego przestrzenie robocze nadal mogą połączyć Linear, wklejając osobisty klucz API.",
|
|
795
|
+
"clientId": "Identyfikator klienta",
|
|
796
|
+
"clientSecret": "Sekret klienta",
|
|
797
|
+
"redirectUrl": "Adres URL przekierowania",
|
|
798
|
+
"validation": "Podaj identyfikator klienta, sekret i adres URL przekierowania",
|
|
799
|
+
"saved": "Zapisano OAuth Linear",
|
|
800
|
+
"saveFailed": "Nie udało się zapisać OAuth Linear",
|
|
801
|
+
"cleared": "Wyczyszczono OAuth Linear",
|
|
802
|
+
"clearFailed": "Nie udało się wyczyścić OAuth Linear"
|
|
803
|
+
},
|
|
792
804
|
"web": {
|
|
793
805
|
"title": "Wyszukiwanie w sieci w kontenerze",
|
|
794
806
|
"description": "Dostawca wyszukiwania, do którego agenci w kontenerze sięgają przez proxy backendu. Ustaw klucz Brave (zalecane) lub własny adres URL SearXNG (z opcjonalnym kluczem bearer).",
|
|
@@ -1298,7 +1310,8 @@
|
|
|
1298
1310
|
"jiraProjectKey": "Klucz projektu Jira",
|
|
1299
1311
|
"jiraProjectKeyHelp": "Nowe zgłoszenia są rejestrowane w tym projekcie.",
|
|
1300
1312
|
"linearTeamId": "Identyfikator zespołu Linear",
|
|
1301
|
-
"linearTeamIdHelp": "Nowe zgłoszenia są tworzone w tym zespole (Linear wymaga zespołu do utworzenia zgłoszenia)."
|
|
1313
|
+
"linearTeamIdHelp": "Nowe zgłoszenia są tworzone w tym zespole (Linear wymaga zespołu do utworzenia zgłoszenia).",
|
|
1314
|
+
"linearTeamSearchPlaceholder": "Szukaj zespołów…"
|
|
1302
1315
|
},
|
|
1303
1316
|
"vendor": {
|
|
1304
1317
|
"github": "GitHub Issues",
|
|
@@ -1885,9 +1898,10 @@
|
|
|
1885
1898
|
"repository": "Repozytorium",
|
|
1886
1899
|
"repositoryHint": "Repozytoria, do których aplikacja GitHub ma dostęp. Nie widzisz swojego? Przyznaj aplikacji dostęp poniżej, a następnie odśwież.",
|
|
1887
1900
|
"noReposAvailable": "Brak dostępnych repozytoriów. Przyznaj aplikacji dostęp do jednego poniżej, a następnie odśwież.",
|
|
1888
|
-
"
|
|
1889
|
-
"
|
|
1890
|
-
"
|
|
1901
|
+
"searchPlaceholder": "Szukaj repozytoriów według właściciela lub nazwy…",
|
|
1902
|
+
"searchMinChars": "Wpisz co najmniej {min} znak, aby wyszukać. | Wpisz co najmniej {min} znaki, aby wyszukać. | Wpisz co najmniej {min} znaków, aby wyszukać.",
|
|
1903
|
+
"noMatches": "Nie znaleziono repozytoriów dla {query}.",
|
|
1904
|
+
"clearSelection": "Wyczyść wybór",
|
|
1891
1905
|
"showingCount": "Wyświetlanie {shown} z {total} repozytoriów.",
|
|
1892
1906
|
"repoLabel": {
|
|
1893
1907
|
"private": " (prywatne)",
|
|
@@ -2110,7 +2124,9 @@
|
|
|
2110
2124
|
"offerHint": "Po wyłączeniu {label} jest ukryte przed importem i powiązywaniem.",
|
|
2111
2125
|
"disconnect": "Rozłącz",
|
|
2112
2126
|
"updateConnection": "Zaktualizuj połączenie",
|
|
2113
|
-
"connect": "Połącz"
|
|
2127
|
+
"connect": "Połącz",
|
|
2128
|
+
"oauthButton": "Połącz z {label}",
|
|
2129
|
+
"oauthOr": "lub połącz za pomocą klucza API"
|
|
2114
2130
|
}
|
|
2115
2131
|
},
|
|
2116
2132
|
"pipeline": {
|
package/i18n/locales/uk.json
CHANGED
|
@@ -789,6 +789,18 @@
|
|
|
789
789
|
"cleared": "OAuth Slack очищено",
|
|
790
790
|
"clearFailed": "Не вдалося очистити OAuth Slack"
|
|
791
791
|
},
|
|
792
|
+
"linear": {
|
|
793
|
+
"title": "Застосунок Linear (OAuth)",
|
|
794
|
+
"description": "Вмикає потік OAuth «Підключити через Linear». Без нього робочі простори все одно можуть підключити Linear, вставивши особистий ключ API.",
|
|
795
|
+
"clientId": "ID клієнта",
|
|
796
|
+
"clientSecret": "Секрет клієнта",
|
|
797
|
+
"redirectUrl": "URL перенаправлення",
|
|
798
|
+
"validation": "Введіть ID клієнта, секрет і URL перенаправлення",
|
|
799
|
+
"saved": "OAuth Linear збережено",
|
|
800
|
+
"saveFailed": "Не вдалося зберегти OAuth Linear",
|
|
801
|
+
"cleared": "OAuth Linear очищено",
|
|
802
|
+
"clearFailed": "Не вдалося очистити OAuth Linear"
|
|
803
|
+
},
|
|
792
804
|
"web": {
|
|
793
805
|
"title": "Вебпошук контейнера",
|
|
794
806
|
"description": "Постачальник пошуку, до якого агенти в контейнері звертаються через проксі бекенду. Задайте ключ Brave (рекомендовано) або власний URL SearXNG (з необов'язковим ключем bearer).",
|
|
@@ -1298,7 +1310,8 @@
|
|
|
1298
1310
|
"jiraProjectKey": "Ключ проєкту Jira",
|
|
1299
1311
|
"jiraProjectKeyHelp": "Нові тикети реєструються в цьому проєкті.",
|
|
1300
1312
|
"linearTeamId": "Ідентифікатор команди Linear",
|
|
1301
|
-
"linearTeamIdHelp": "Нові тикети створюються в цій команді (Linear вимагає команду для створення тикета)."
|
|
1313
|
+
"linearTeamIdHelp": "Нові тикети створюються в цій команді (Linear вимагає команду для створення тикета).",
|
|
1314
|
+
"linearTeamSearchPlaceholder": "Пошук команд…"
|
|
1302
1315
|
},
|
|
1303
1316
|
"vendor": {
|
|
1304
1317
|
"github": "GitHub Issues",
|
|
@@ -1885,9 +1898,10 @@
|
|
|
1885
1898
|
"repository": "Репозиторій",
|
|
1886
1899
|
"repositoryHint": "Репозиторії, до яких застосунок GitHub має доступ. Не бачите свого? Надайте застосунку доступ нижче, а потім оновіть.",
|
|
1887
1900
|
"noReposAvailable": "Поки що немає доступних репозиторіїв. Надайте застосунку доступ до одного нижче, а потім оновіть.",
|
|
1888
|
-
"
|
|
1889
|
-
"
|
|
1890
|
-
"
|
|
1901
|
+
"searchPlaceholder": "Шукайте репозиторії за власником або назвою…",
|
|
1902
|
+
"searchMinChars": "Введіть щонайменше {min} символ для пошуку. | Введіть щонайменше {min} символи для пошуку. | Введіть щонайменше {min} символів для пошуку.",
|
|
1903
|
+
"noMatches": "Не знайдено репозиторіїв для {query}.",
|
|
1904
|
+
"clearSelection": "Очистити вибір",
|
|
1891
1905
|
"showingCount": "Показано {shown} із {total} репозиторіїв.",
|
|
1892
1906
|
"repoLabel": {
|
|
1893
1907
|
"private": " (приватний)",
|
|
@@ -2110,7 +2124,9 @@
|
|
|
2110
2124
|
"offerHint": "Коли вимкнено, {label} приховано з імпорту та прив'язування.",
|
|
2111
2125
|
"disconnect": "Відключити",
|
|
2112
2126
|
"updateConnection": "Оновити підключення",
|
|
2113
|
-
"connect": "Підключити"
|
|
2127
|
+
"connect": "Підключити",
|
|
2128
|
+
"oauthButton": "Підключити через {label}",
|
|
2129
|
+
"oauthOr": "або підключіться за допомогою ключа API"
|
|
2114
2130
|
}
|
|
2115
2131
|
},
|
|
2116
2132
|
"pipeline": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"valibot": "^1.4.2",
|
|
35
35
|
"vue": "^3.5.39",
|
|
36
36
|
"wretch": "^3.0.9",
|
|
37
|
-
"@cat-factory/contracts": "0.
|
|
37
|
+
"@cat-factory/contracts": "0.46.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@toad-contracts/testing": "0.3.2",
|