feat: issues (#77)

* WIP

* feat: readonly issues in connectors

* fix created at on replies

* filter out by resourceStringId

* show label name if just one

* generate gql

* linting

* linting
This commit is contained in:
Oğuzhan Koral
2025-12-10 18:01:13 +03:00
committed by GitHub
parent 5174af78cc
commit fdfef1d496
21 changed files with 1698 additions and 205 deletions
+17
View File
@@ -0,0 +1,17 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.73336 1.45469C7.57004 1.29277 8.43001 1.29277 9.26669 1.45469M9.26669 14.5454C8.43001 14.7073 7.57004 14.7073 6.73336 14.5454M11.7394 2.48069C12.447 2.96017 13.0558 3.57127 13.5327 4.28069M1.45469 9.26669C1.29277 8.43001 1.29277 7.57004 1.45469 6.73336M13.5194 11.7394C13.0399 12.447 12.4288 13.0558 11.7194 13.5327M14.5454 6.73336C14.7073 7.57004 14.7073 8.43001 14.5454 9.26669M2.48069 4.26069C2.96017 3.55304 3.57127 2.94421 4.28069 2.46736M4.26069 13.5194C3.55304 13.0399 2.94421 12.4288 2.46736 11.7194"
stroke="#707070"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
+18
View File
@@ -0,0 +1,18 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8 1.33301C11.6819 1.33301 14.667 4.3181 14.667 8C14.6669 11.6819 11.6819 14.667 8 14.667C4.3182 14.6669 1.33305 11.6818 1.33301 8C1.33301 4.31816 4.31818 1.3331 8 1.33301ZM10.5303 6.13672C10.2374 5.84383 9.76262 5.84383 9.46973 6.13672L7.33301 8.27246L6.53027 7.46973C6.23742 7.17705 5.76258 7.17705 5.46973 7.46973C5.17713 7.76259 5.17708 8.23745 5.46973 8.53027L6.80273 9.86426C6.94329 10.0047 7.13433 10.0839 7.33301 10.084C7.53165 10.084 7.72268 10.0046 7.86328 9.86426L10.5303 7.19727C10.8231 6.90445 10.8229 6.42963 10.5303 6.13672Z"
fill="#15803D"
/>
<path
d="M8 1.33301C11.6819 1.33301 14.667 4.3181 14.667 8C14.6669 11.6819 11.6819 14.667 8 14.667C4.3182 14.6669 1.33305 11.6818 1.33301 8C1.33301 4.31816 4.31818 1.3331 8 1.33301ZM10.5303 6.13672C10.2374 5.84383 9.76262 5.84383 9.46973 6.13672L7.33301 8.27246L6.53027 7.46973C6.23742 7.17705 5.76258 7.17705 5.46973 7.46973C5.17713 7.76259 5.17708 8.23745 5.46973 8.53027L6.80273 9.86426C6.94329 10.0047 7.13433 10.0839 7.33301 10.084C7.53165 10.084 7.72268 10.0046 7.86328 9.86426L10.5303 7.19727C10.8231 6.90445 10.8229 6.42963 10.5303 6.13672Z"
fill="#16A34A"
/>
</svg>
</template>
+35
View File
@@ -0,0 +1,35 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.00024 2.08337C11.2678 2.08355 13.9163 4.73279 13.9163 8.00037C13.9161 11.2678 11.2677 13.9162 8.00024 13.9164C4.73267 13.9164 2.08343 11.2679 2.08325 8.00037C2.08325 4.73268 4.73256 2.08337 8.00024 2.08337Z"
stroke="#EAB308"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M8.75 4.83789C10.1832 5.17655 11.25 6.46328 11.25 8C11.25 9.53664 10.1831 10.8224 8.75 11.1611V4.83789Z"
fill="#EAB308"
/>
<path
d="M8.75 4.83789C10.1832 5.17655 11.25 6.46328 11.25 8C11.25 9.53664 10.1831 10.8224 8.75 11.1611V4.83789Z"
stroke="#7C7C7D"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M8.75 4.83789C10.1832 5.17655 11.25 6.46328 11.25 8C11.25 9.53664 10.1831 10.8224 8.75 11.1611V4.83789Z"
stroke="#EAB308"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
+51
View File
@@ -0,0 +1,51 @@
<!-- CommonTiptapViewer.vue -->
<template>
<!-- read-only output -->
<div
v-if="html"
class="p-1 pl-3 group w-full whitespace-pre-wrap break-words"
v-html="html"
/>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { JSONContent } from '@tiptap/core'
const props = defineProps<{
doc: JSONContent | null | undefined
}>()
const escapeHtml = (str: string): string =>
str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
function renderNode(node?: JSONContent): string {
if (!node) return ''
const children = (node.content ?? []).map(renderNode).join('')
switch (node.type) {
case 'doc':
return children
case 'paragraph':
// empty paragraph → visual empty line
return children ? `<p>${children}</p>` : '<p><br /></p>'
case 'text': {
const text = escapeHtml(node.text ?? '')
// if you need marks later (bold, italic, etc.), handle here
return text
}
case 'hardBreak':
return '<br />'
default:
// unknown node → just render its children
return children
}
}
const html = computed(() => (props.doc ? renderNode(props.doc) : ''))
</script>
+77
View File
@@ -0,0 +1,77 @@
<template>
<div class="p-0">
<slot name="activator" :toggle="toggleDialog"></slot>
<CommonDialog v-model:open="showIssuesDialog" :title="`Issues`" fullscreen="none">
<div class="flex flex-col space-y-2">
<div v-if="selectedIssue" class="flex flex-col space-y-1.5">
<div class="relative flex items-center h-8">
<div class="absolute left-0">
<FormButton
color="outline"
hide-text
:icon-left="ArrowLeft"
@click="selectedIssue = undefined"
/>
</div>
<div class="mx-auto text-foreground-2 font-medium font-mono text-body-xs">
{{ selectedIssue.identifier }}
</div>
<div class="absolute right-0">
<FormButton
v-tippy="'Open issue in browser'"
color="outline"
hide-text
:icon-left="ArrowTopRightOnSquareIcon"
@click="openIssueOnWeb(selectedIssue.id)"
/>
</div>
</div>
<hr />
<IssuesSelectedItem :issue="selectedIssue" />
</div>
<div v-if="!selectedIssue" class="flex flex-col space-y-2">
<IssuesItem
v-for="issue in issues"
:key="issue.id"
:issue="issue"
:model-card="modelCard"
@select="selectedIssue = issue"
@open-on-web="(issueId) => openIssueOnWeb(issueId)"
/>
</div>
</div>
</CommonDialog>
</div>
</template>
<script setup lang="ts">
import type { IssuesItemFragment } from '~/lib/common/generated/gql/graphql'
import type { IModelCard } from '~/lib/models/card'
import { ArrowLeft } from 'lucide-vue-next'
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid'
const props = defineProps<{
issues: IssuesItemFragment[]
modelCard: IModelCard
}>()
const app = useNuxtApp()
const showIssuesDialog = ref(false)
const selectedIssue = ref<IssuesItemFragment | undefined>(undefined)
const toggleDialog = () => {
showIssuesDialog.value = !showIssuesDialog.value
}
const openIssueOnWeb = (issueId: string) => {
app.$baseBinding.openUrl(
`${props.modelCard.serverUrl}/projects/${props.modelCard?.projectId}/models/${props.modelCard.modelId}#threadId=${issueId}`
)
}
watch(showIssuesDialog, (open) => {
if (!open) selectedIssue.value = undefined
})
</script>
+142
View File
@@ -0,0 +1,142 @@
<template>
<button
class="gap-1 border rounded-xl border-outline-3 p-1.5 pt-1 pl-3 group hover:shadow-md hover:cursor-pointer space-y-2"
@click="emit('select'), highlightModel()"
>
<!-- Item Header -->
<div class="flex justify-between items-center">
<div class="text-foreground-2 font-medium font-mono text-body-xs">
{{ issue.identifier }}
</div>
<div class="flex items-center">
<FormButton
v-if="store.hostAppName !== 'navisworks' && store.hostAppName !== 'etabs'"
v-tippy="'Highlight'"
color="subtle"
:icon-left="CursorArrowRaysIcon"
hide-text
size="sm"
@click.stop="highlightModel"
/>
<FormButton
v-tippy="'Open issue in browser'"
color="subtle"
:icon-left="ArrowTopRightOnSquareIcon"
hide-text
size="sm"
class="mr-1"
@click.stop="emit('open-on-web', issue.id)"
/>
<UserAvatar :user="issue.assignee?.user" size="xs" class="rounded-full" />
</div>
</div>
<!-- Item Title & status -->
<div class="flex items-center gap-1">
<IssuesStatusIcon :status="issue.status" />
<div class="line-clamp-2 font-medium text-body-2xs text-foreground">
{{ issue.title ? issue.title : 'No title' }}
</div>
</div>
<!-- Remaining secondary fields -->
<div class="flex items-center gap-4 ml-0.5">
<IssuesPriorityIcon :priority="issue.priority" />
<IssuesLabels :labels="issue.labels" />
<div v-if="formattedDate" class="flex items-center gap-1 h-6">
<Calendar class="text-foreground-2 shrink-0" :stroke-width="1.5" :size="12" />
<span class="text-body-3xs text-foreground-2 font-medium">
{{ formattedDate }}
</span>
</div>
<div v-else class="flex items-center gap-1 h-6">
<Calendar class="text-foreground-2 shrink-0" :stroke-width="1.5" :size="12" />
<span class="text-body-3xs text-foreground-2 font-medium">No due date</span>
</div>
</div>
</button>
</template>
<script lang="ts" setup>
import type { IssuesItemFragment } from '~/lib/common/generated/gql/graphql'
import { CursorArrowRaysIcon } from '@heroicons/vue/24/outline'
import { Calendar } from 'lucide-vue-next'
import dayjs from 'dayjs'
import { useHostAppStore } from '~~/store/hostApp'
import { ToastNotificationType } from '@speckle/ui-components'
import type { IModelCard } from '~/lib/models/card'
import type { SenderModelCard } from '~/lib/models/card/send'
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid'
const store = useHostAppStore()
const props = defineProps<{
modelCard: IModelCard
issue: IssuesItemFragment
}>()
const emit = defineEmits<{
(e: 'select'): void
(e: 'open-on-web', issueId: string): void
}>()
const app = useNuxtApp()
type IssueViewerState = {
ui: {
filters: {
selectedObjectApplicationIds?: Record<string, string>
}
}
}
const highlightModel = async () => {
if (!props.issue.viewerState) {
store.setNotification({
title: 'Objects not found to highlight',
type: ToastNotificationType.Info
})
return
}
if (props.modelCard.typeDiscriminator !== 'SenderModelCard') return
const sender = props.modelCard as SenderModelCard
type SelectedObjectMap = Record<string, string>
const selectedObjectApplicationIds = Object.values(
((props.issue.viewerState as IssueViewerState).ui.filters
.selectedObjectApplicationIds ?? {}) as SelectedObjectMap
)
const appIdsToHighlight = (sender.sendFilter?.selectedObjectIds ?? []).filter((id) =>
selectedObjectApplicationIds.includes(id)
)
if (appIdsToHighlight.length > 0) {
await app.$baseBinding.highlightObjects(appIdsToHighlight)
} else {
store.setNotification({
title: 'Objects not found to highlight on this model.',
type: ToastNotificationType.Info
})
}
}
const formattedDate = computed((): string | null => {
try {
const date = props.issue.dueDate ? dayjs(props.issue.dueDate).toDate() : null
if (!(date instanceof Date)) return null
const time = date.getTime()
if (isNaN(time)) return null
return new Intl.DateTimeFormat('en-GB', {
month: 'short',
day: 'numeric'
}).format(date)
} catch {
return null
}
})
</script>
+39
View File
@@ -0,0 +1,39 @@
<template>
<div class="flex items-center gap-1.5">
<div class="flex items-center -space-x-1">
<template
v-for="labelItem in maxVisible ? labels.slice(0, maxVisible) : labels"
:key="labelItem.id"
>
<div
v-if="labelItem.hexColor"
class="w-2 h-2 rounded-full shrink-0"
:style="{ backgroundColor: labelItem.hexColor }"
/>
</template>
</div>
<!-- Single label -->
<span
v-if="labels.length === 1"
class="text-body-3xs font-medium flex items-center gap-1"
:style="{ color: labels[0].hexColor || undefined }"
>
{{ labels[0].name }}
</span>
<!-- Multiple labels -->
<span v-else class="text-body-3xs text-foreground-2 font-medium">
{{ labels.length }} label{{ labels.length !== 1 ? 's' : '' }}
</span>
</div>
</template>
<script setup lang="ts">
import type { Label } from '~/lib/issues/types'
defineProps<{
labels: Label[]
maxVisible?: number
}>()
</script>
+34
View File
@@ -0,0 +1,34 @@
<template>
<Tippy interactive placement="bottom" :offset="[0, 6]">
<!-- Trigger -->
<template #default>
<IssuesLabelGroup :labels="labels" />
</template>
<!-- Tooltip content -->
<template v-if="labels.length > 0" #content>
<div class="rounded-md shadow-lg p-0.5 text-xs space-y-1">
<div
v-for="label in labels"
:key="label.id"
class="flex items-center space-x-2"
>
<span
class="w-2 h-2 rounded-full"
:style="{ backgroundColor: label.hexColor }"
/>
<span>{{ label.name }}</span>
</div>
</div>
</template>
</Tippy>
</template>
<script setup lang="ts">
import { Tippy } from 'vue-tippy'
import type { Label } from '~/lib/issues/types'
defineProps<{
labels: Label[]
}>()
</script>
+60
View File
@@ -0,0 +1,60 @@
<template>
<div class="flex items-center space-x-2">
<div
v-if="priority !== null && priority !== 'none'"
v-tippy="showLabel ? undefined : priorityText"
class="flex flex-col gap-0.5 items-start justify-center w-3 h-3"
>
<!-- Top line -->
<div
class="h-0.5 rounded-full bg-foreground-2 w-3"
:class="priority !== 'high' && 'opacity-25'"
/>
<!-- Middle line -->
<div
class="h-0.5 rounded-full bg-foreground-2 w-2"
:class="priority === 'low' && 'opacity-25'"
/>
<!-- Bottom line -->
<div class="h-0.5 rounded-full bg-foreground-2 w-1" />
</div>
<!-- No priority: Two dashes -->
<div v-else class="flex gap-0.5 items-center justify-center h-3 w-3">
<div class="h-px rounded-full bg-foreground-3 w-1" />
<div class="h-px rounded-full bg-foreground-3 w-1" />
</div>
<span v-if="showLabel" class="text-body-3xs text-foreground-2 font-medium">
{{ priorityText }}
</span>
</div>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
priority: 'none' | 'low' | 'medium' | 'high' | null
showLabel?: boolean
}>(),
{
showLabel: false
}
)
const priorityText = computed(() => {
switch (props.priority) {
case 'high':
return 'High'
case 'medium':
return 'Medium'
case 'low':
return 'Low'
case 'none':
return 'No priority'
case null:
return 'No priority'
default:
return ''
}
})
</script>
+112
View File
@@ -0,0 +1,112 @@
<template>
<div class="flex flex-col space-y-1.5">
<div class="flex flex-col items-start space-y-2 p-2">
<div class="line-clamp-2 font-medium text-body text-foreground">
{{ issue.title ? issue.title : 'No title' }}
</div>
<IssuesBasicTiptap
v-if="issue.description?.doc"
class="border rounded-xl border-outline-3"
:doc="issue.description?.doc"
></IssuesBasicTiptap>
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
<IssuesStatusIcon :status="issue.status" show-label />
<IssuesPriorityIcon :priority="issue.priority" show-label />
<div class="flex items-center justify-between space-x-1">
<UserAvatar :user="issue.assignee?.user" size="xs" />
<span class="text-body-3xs text-foreground-2 font-medium">
{{ issue.assignee ? issue.assignee?.user.name : 'No assignee' }}
</span>
</div>
<IssuesLabels :labels="issue.labels" />
<div v-if="formattedDate" class="flex items-center gap-1 h-6">
<Calendar class="text-foreground-2 shrink-0" :stroke-width="1.5" :size="12" />
<span class="text-body-3xs text-foreground-2 font-medium">
{{ formattedDate }}
</span>
</div>
<div v-else class="flex items-center gap-1 h-6">
<Calendar class="text-foreground-2 shrink-0" :stroke-width="1.5" :size="12" />
<span class="text-body-3xs text-foreground-2 font-medium">No due date</span>
</div>
</div>
<div
v-if="issue.activities && issue.activities.totalCount > 0"
class="flex items-center gap-2 p-1 min-w-0"
>
<UserAvatar
:user="issue.activities?.items?.[0]?.actor?.user"
size="xs"
class="shrink-0"
/>
<div class="text-body-2xs text-foreground-2 leading-tight min-w-0">
<span class="font-medium">
{{ issue.activities?.items?.[0]?.actor?.user.name }}
</span>
<span>
&nbsp;created this issue &middot;
{{ dayjs(issue.activities?.items?.[0].createdAt).from(dayjs()) }}
</span>
</div>
</div>
<div
v-if="issue.replies && issue.replies.totalCount > 0"
class="flex flex-col justify-between space-y-2 w-full"
>
<div
v-for="reply in issue.replies.items"
:key="reply.id"
class="flex flex-col items-start border rounded-xl border-outline-3 p-1 w-full"
>
<div class="flex items-center gap-2 w-full">
<UserAvatar :user="reply.author?.user" size="xs" class="shrink-0" />
<div class="text-body-2xs text-foreground-2 leading-tight min-w-0">
<span class="font-medium">
{{ reply.author?.user.name }}
</span>
<span>
&nbsp;replied &middot;
{{ dayjs(reply.createdAt).from(dayjs()) }}
</span>
</div>
</div>
<IssuesBasicTiptap
v-if="reply.description?.doc"
class="ml-4"
:doc="reply.description?.doc"
/>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type { IssuesItemFragment } from '~/lib/common/generated/gql/graphql'
import dayjs from 'dayjs'
import { Calendar } from 'lucide-vue-next'
const props = defineProps<{
issue: IssuesItemFragment
}>()
const formattedDate = computed((): string | null => {
try {
const date = props.issue.dueDate ? dayjs(props.issue.dueDate).toDate() : null
if (!(date instanceof Date)) return null
const time = date.getTime()
if (isNaN(time)) return null
return new Intl.DateTimeFormat('en-GB', {
month: 'short',
day: 'numeric'
}).format(date)
} catch {
return null
}
})
</script>
+49
View File
@@ -0,0 +1,49 @@
<template>
<div
v-tippy="showLabel ? undefined : statusText"
class="flex items-center gap-1 rounded-md hover:bg-foreground-1"
>
<GlobalIconStatusOpen v-if="status === 'open'" class="w-3 h-3 shrink-0" />
<GlobalIconStatusReview
v-else-if="status === 'readyForReview'"
class="w-3 h-3 shrink-0"
/>
<GlobalIconStatusResolved
v-else-if="status === 'resolved'"
class="w-3 h-3 shrink-0"
/>
<span v-if="showLabel" class="text-body-3xs text-foreground-2 font-medium">
{{ statusText }}
</span>
</div>
</template>
<script setup lang="ts">
import GlobalIconStatusOpen from '~/components/global/icon/StatusOpen.vue'
import GlobalIconStatusReview from '~/components/global/icon/StatusReview.vue'
import GlobalIconStatusResolved from '~/components/global/icon/StatusResolved.vue'
const props = withDefaults(
defineProps<{
status: 'open' | 'readyForReview' | 'resolved'
showLabel?: boolean
}>(),
{
showLabel: false
}
)
const statusText = computed(() => {
switch (props.status) {
case 'open':
return 'Open'
case 'readyForReview':
return 'Ready for review'
case 'resolved':
return 'Resolved'
default:
return ''
}
})
</script>
+32
View File
@@ -32,6 +32,18 @@
</button>
</template>
</ReportBase>
<IssuesDialog
v-if="issues && issues.length > 0"
:model-card="modelCard"
:issues="issues"
>
<template #activator="{ toggle }">
<button class="action action-normal" @click="toggle()">
<div class="truncate max-[275px]:text-xs">Issues</div>
<div><Cog6ToothIcon class="w-5 h-5" /></div>
</button>
</template>
</IssuesDialog>
<button
v-for="item in items"
:key="item.name"
@@ -57,6 +69,10 @@ import {
} from '@heroicons/vue/24/outline'
import type { IModelCard } from '~/lib/models/card'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { issuesListQuery } from '~/lib/issues/graphql/queries'
import { useAccountStore } from '~/store/accounts'
import { storeToRefs } from 'pinia'
import { useQuery } from '@vue/apollo-composable'
const { trackEvent } = useMixpanel()
@@ -113,6 +129,22 @@ const items = [
}
}
]
const accountStore = useAccountStore()
const { activeAccount } = storeToRefs(accountStore)
const accountId = computed(() => activeAccount.value.accountInfo.id)
const { result: issuesResult } = useQuery(
issuesListQuery,
() => ({ projectId: props.modelCard.projectId }),
() => ({
clientId: accountId.value,
debounce: 500,
fetchPolicy: 'network-only'
})
)
const issues = computed(() => issuesResult?.value?.project.issues.items)
</script>
<style scoped lang="postcss">
.action {
+41 -3
View File
@@ -67,6 +67,22 @@
size="sm"
@click="deleteSettings"
/> -->
<IssuesDialog
v-if="issues && issues.length > 0"
:model-card="modelCard"
:issues="issues"
>
<template #activator="{ toggle }">
<FormButton
v-tippy="'Issues'"
color="subtle"
:icon-left="MessageCircleMore"
hide-text
size="sm"
@click="toggle()"
/>
</template>
</IssuesDialog>
<FormButton
v-if="store.hostAppName !== 'navisworks' && store.hostAppName !== 'etabs'"
v-tippy="'Highlight'"
@@ -179,7 +195,7 @@
>
<div
v-tippy="
`${latestCommentNotification.comment?.author.name} just left a
`${latestCommentNotification.comment?.author?.name} just left a
comment.`
"
class="flex items-center space-x-1"
@@ -189,8 +205,8 @@
:users="[latestCommentNotification.comment?.author as AvatarUserWithId]"
/>
<span class="line-clamp-1">
{{ latestCommentNotification.comment?.author.name }} just left a
comment.
{{ latestCommentNotification.comment?.author?.name }} just left a
comment on the issue.
</span>
</div>
<div>
@@ -236,6 +252,8 @@ import type { ProjectCommentsUpdatedMessage } from '~/lib/common/generated/gql/g
import { useFunctionRunsStatusSummary } from '~/lib/automate/runStatus'
import { CursorArrowRaysIcon, XCircleIcon, TrashIcon } from '@heroicons/vue/24/outline'
import type { AvatarUserWithId } from '@speckle/ui-components'
import { issuesListQuery } from '~/lib/issues/graphql/queries'
import { MessageCircleMore } from 'lucide-vue-next'
const app = useNuxtApp()
const store = useHostAppStore()
@@ -327,6 +345,25 @@ const summary = computed(() => {
})
})
const { result: issuesResult, refetch: refetchIssues } = useQuery(
issuesListQuery,
() => ({ projectId: props.modelCard.projectId }),
() => ({
clientId,
debounce: 500,
fetchPolicy: 'network-only'
})
)
const issues = computed(() =>
issuesResult?.value?.project.issues.items.filter(
(issue) =>
issue.status !== 'resolved' &&
issue.resourceIdString &&
(issue.resourceIdString as string).includes(props.modelCard.modelId)
)
)
provide<IModelCard>('cardBase', props.modelCard)
const highlightModel = () => {
@@ -486,6 +523,7 @@ onCommentResult((res) => {
latestCommentNotification.value = res.data
?.projectCommentsUpdated as ProjectCommentsUpdatedMessage
startCommentClearTimeout()
refetchIssues()
})
const viewComment = () => {
-163
View File
@@ -1,163 +0,0 @@
<template>
<div
:class="[
'text-foreground-on-primary flex shrink-0 items-center justify-center overflow-hidden rounded-full font-semibold uppercase transition',
sizeClasses,
bgClasses,
borderClasses,
hoverClasses,
activeClasses
]"
>
<slot>
<div
v-if="user?.avatar"
class="h-full w-full bg-cover bg-center bg-no-repeat"
:style="{ backgroundImage: `url('${user.avatar}')` }"
/>
<div
v-else-if="initials"
class="flex h-full w-full select-none items-center justify-center"
>
{{ initials }}
</div>
<div v-else><UserCircleIcon :class="iconClasses" /></div>
</slot>
</div>
</template>
<script setup lang="ts">
import { UserCircleIcon } from '@heroicons/vue/20/solid'
type UserAvatar = {
name: string
avatar?: string | null | undefined
}
type UserAvatarSize = 'xs' | 'sm' | 'base' | 'lg' | 'xl' | 'editable'
const props = withDefaults(
defineProps<{
user?: UserAvatar
size?: UserAvatarSize
hoverEffect?: boolean
active?: boolean
noBorder?: boolean
noBackground?: boolean
}>(),
{
user: undefined,
size: 'base',
hoverEffect: false
}
)
const initials = computed(() => {
if (!props.user?.name.length) return
const parts = props.user.name.split(' ')
const firstLetter = parts[0]?.[0] || ''
const secondLetter = parts[1]?.[0] || ''
if (props.size === 'sm' || props.size === 'xs') return firstLetter
return firstLetter + secondLetter
})
const borderClasses = computed(() => {
if (props.noBorder) return ''
return 'border-2 border-foundation'
})
const bgClasses = computed(() => {
if (props.noBackground) return ''
return 'bg-primary'
})
const hoverClasses = computed(() => {
if (props.hoverEffect)
return 'hover:border-primary focus:border-primary active:scale-95'
return ''
})
const activeClasses = computed(() => {
if (props.active) return 'border-primary'
return ''
})
const heightClasses = computed(() => {
const size = props.size
switch (size) {
case 'xs':
return 'h-5'
case 'sm':
return 'h-6'
case 'lg':
return 'h-10'
case 'xl':
return 'h-14'
case 'editable':
return 'h-60'
case 'base':
default:
return 'h-8'
}
})
const widthClasses = computed(() => {
const size = props.size
switch (size) {
case 'xs':
return 'w-5'
case 'sm':
return 'w-6'
case 'lg':
return 'w-10'
case 'xl':
return 'w-14'
case 'editable':
return 'w-60'
case 'base':
default:
return 'w-8'
}
})
const textClasses = computed(() => {
const size = props.size
switch (size) {
case 'xs':
return 'text-tiny'
case 'sm':
return 'text-xs'
case 'lg':
return 'text-md'
case 'xl':
return 'text-2xl'
case 'editable':
return 'h1'
case 'base':
default:
return 'text-sm'
}
})
const iconClasses = computed(() => {
const size = props.size
switch (size) {
case 'xs':
return 'w-3 h-3'
case 'sm':
return 'w-3 h-3'
case 'lg':
return 'w-5 h-5'
case 'xl':
return 'w-8 h-8'
case 'editable':
return 'w-20 h-20'
case 'base':
default:
return 'w-4 h-4'
}
})
const sizeClasses = computed(
() => `${widthClasses.value} ${heightClasses.value} ${textClasses.value}`
)
</script>
+12
View File
@@ -54,6 +54,8 @@ type Documents = {
"\n subscription ProjectUpdated($projectId: String!) {\n projectUpdated(id: $projectId) {\n id\n project {\n visibility\n }\n }\n }\n": typeof types.ProjectUpdatedDocument,
"\n subscription Subscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n": typeof types.SubscriptionDocument,
"\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n": typeof types.ProjectCommentsUpdatedDocument,
"\n fragment IssuesItem on Issue {\n id\n status\n title\n priority\n viewerState\n identifier\n resourceIdString\n activities(input: { limit: 1, sortDirection: asc }) {\n totalCount\n items {\n actor {\n id\n user {\n name\n id\n avatar\n }\n }\n eventType\n createdAt\n }\n }\n replies {\n totalCount\n items {\n id\n author {\n id\n user {\n name\n id\n avatar\n }\n }\n createdAt\n description {\n doc\n }\n }\n }\n description {\n doc\n }\n labels {\n hexColor\n id\n name\n }\n author {\n id\n user {\n id\n name\n avatar\n }\n }\n dueDate\n assignee {\n id\n user {\n id\n avatar\n name\n }\n }\n }\n": typeof types.IssuesItemFragmentDoc,
"\n query IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\n }\n }\n }\n }\n": typeof types.IssuesListDocument,
};
const documents: Documents = {
"\n mutation SetActiveWorkspaceMutation($slug: String) {\n activeUserMutations {\n setActiveWorkspace(slug: $slug) {\n id\n }\n }\n }\n": types.SetActiveWorkspaceMutationDocument,
@@ -96,6 +98,8 @@ const documents: Documents = {
"\n subscription ProjectUpdated($projectId: String!) {\n projectUpdated(id: $projectId) {\n id\n project {\n visibility\n }\n }\n }\n": types.ProjectUpdatedDocument,
"\n subscription Subscription($target: ViewerUpdateTrackingTarget!) {\n viewerUserActivityBroadcasted(target: $target) {\n userName\n userId\n sessionId\n user {\n name\n id\n avatar\n }\n status\n }\n }\n": types.SubscriptionDocument,
"\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n": types.ProjectCommentsUpdatedDocument,
"\n fragment IssuesItem on Issue {\n id\n status\n title\n priority\n viewerState\n identifier\n resourceIdString\n activities(input: { limit: 1, sortDirection: asc }) {\n totalCount\n items {\n actor {\n id\n user {\n name\n id\n avatar\n }\n }\n eventType\n createdAt\n }\n }\n replies {\n totalCount\n items {\n id\n author {\n id\n user {\n name\n id\n avatar\n }\n }\n createdAt\n description {\n doc\n }\n }\n }\n description {\n doc\n }\n labels {\n hexColor\n id\n name\n }\n author {\n id\n user {\n id\n name\n avatar\n }\n }\n dueDate\n assignee {\n id\n user {\n id\n avatar\n name\n }\n }\n }\n": types.IssuesItemFragmentDoc,
"\n query IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\n }\n }\n }\n }\n": types.IssuesListDocument,
};
/**
@@ -272,6 +276,14 @@ export function graphql(source: "\n subscription Subscription($target: ViewerUp
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n"): (typeof documents)["\n subscription ProjectCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n comment {\n author {\n avatar\n id\n name\n }\n id\n hasParent\n parent {\n id\n }\n }\n type\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment IssuesItem on Issue {\n id\n status\n title\n priority\n viewerState\n identifier\n resourceIdString\n activities(input: { limit: 1, sortDirection: asc }) {\n totalCount\n items {\n actor {\n id\n user {\n name\n id\n avatar\n }\n }\n eventType\n createdAt\n }\n }\n replies {\n totalCount\n items {\n id\n author {\n id\n user {\n name\n id\n avatar\n }\n }\n createdAt\n description {\n doc\n }\n }\n }\n description {\n doc\n }\n labels {\n hexColor\n id\n name\n }\n author {\n id\n user {\n id\n name\n avatar\n }\n }\n dueDate\n assignee {\n id\n user {\n id\n avatar\n name\n }\n }\n }\n"): (typeof documents)["\n fragment IssuesItem on Issue {\n id\n status\n title\n priority\n viewerState\n identifier\n resourceIdString\n activities(input: { limit: 1, sortDirection: asc }) {\n totalCount\n items {\n actor {\n id\n user {\n name\n id\n avatar\n }\n }\n eventType\n createdAt\n }\n }\n replies {\n totalCount\n items {\n id\n author {\n id\n user {\n name\n id\n avatar\n }\n }\n createdAt\n description {\n doc\n }\n }\n }\n description {\n doc\n }\n labels {\n hexColor\n id\n name\n }\n author {\n id\n user {\n id\n name\n avatar\n }\n }\n dueDate\n assignee {\n id\n user {\n id\n avatar\n name\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\n }\n }\n }\n }\n"): (typeof documents)["\n query IssuesList($projectId: String!) {\n project(id: $projectId) {\n id\n issues {\n totalCount\n items {\n ...IssuesItem\n }\n }\n }\n }\n"];
export function graphql(source: string) {
return (documents as any)[source] ?? {};
File diff suppressed because one or more lines are too long
+71
View File
@@ -0,0 +1,71 @@
import { graphql } from '~~/lib/common/generated/gql'
export const issueFragment = graphql(`
fragment IssuesItem on Issue {
id
status
title
priority
viewerState
identifier
resourceIdString
activities(input: { limit: 1, sortDirection: asc }) {
totalCount
items {
actor {
id
user {
name
id
avatar
}
}
eventType
createdAt
}
}
replies {
totalCount
items {
id
author {
id
user {
name
id
avatar
}
}
createdAt
description {
doc
}
}
}
description {
doc
}
labels {
hexColor
id
name
}
author {
id
user {
id
name
avatar
}
}
dueDate
assignee {
id
user {
id
avatar
name
}
}
}
`)
+15
View File
@@ -0,0 +1,15 @@
import { graphql } from '~~/lib/common/generated/gql'
export const issuesListQuery = graphql(`
query IssuesList($projectId: String!) {
project(id: $projectId) {
id
issues {
totalCount
items {
...IssuesItem
}
}
}
}
`)
+7
View File
@@ -0,0 +1,7 @@
export type Label = {
id: string
name: string
hexColor?: string
}
export type LabelsValue = Label[]
+17
View File
@@ -38,6 +38,22 @@
"@speckle/tailwind-theme": "2.25.0",
"@speckle/ui-components": "^2.25.0",
"@speckle/ui-components-nuxt": "^2.25.0",
"@tiptap/core": "2.10.3",
"@tiptap/extension-bold": "2.10.3",
"@tiptap/extension-document": "2.10.3",
"@tiptap/extension-hard-break": "2.10.3",
"@tiptap/extension-history": "2.10.3",
"@tiptap/extension-italic": "2.10.3",
"@tiptap/extension-link": "2.10.3",
"@tiptap/extension-mention": "2.10.3",
"@tiptap/extension-paragraph": "2.10.3",
"@tiptap/extension-placeholder": "2.10.3",
"@tiptap/extension-strike": "2.10.3",
"@tiptap/extension-text": "2.10.3",
"@tiptap/extension-underline": "2.10.3",
"@tiptap/pm": "2.10.3",
"@tiptap/suggestion": "2.10.3",
"@tiptap/vue-3": "2.10.3",
"@vue/apollo-composable": "^4.0.0-beta.5",
"@vueuse/core": "^9.13.0",
"apollo-upload-client": "^17.0.0",
@@ -47,6 +63,7 @@
"graphql": "^16.6.0",
"graphql-tag": "^2.12.6",
"lodash-es": "^4.17.21",
"lucide-vue-next": "^0.537.0",
"nanoevents": "^8.0.0",
"pinia": "^2.1.4",
"portal-vue": "^3.0.0",
+530
View File
@@ -3552,6 +3552,13 @@ __metadata:
languageName: node
linkType: hard
"@remirror/core-constants@npm:3.0.0":
version: 3.0.0
resolution: "@remirror/core-constants@npm:3.0.0"
checksum: 10c0/15909dd00a2d90cf1f65583bb03ff97c27bb3ec3e22467cdaec3e9cfdae50c687d044df342b985a951d28306cc94cf9188bf7742c7a811ebbb62fd9c5a16ed44
languageName: node
linkType: hard
"@repeaterjs/repeater@npm:^3.0.4, @repeaterjs/repeater@npm:^3.0.6":
version: 3.0.6
resolution: "@repeaterjs/repeater@npm:3.0.6"
@@ -4088,6 +4095,204 @@ __metadata:
languageName: node
linkType: hard
"@tiptap/core@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/core@npm:2.10.3"
peerDependencies:
"@tiptap/pm": ^2.7.0
checksum: 10c0/b72a6956720f766bc909954a919870907379875ba41b122db28eadc4e8f67b5b2ebe01cfc9df9dae715ef5d585853b97714c00607514d040ae4fd4d1c2ce70e2
languageName: node
linkType: hard
"@tiptap/extension-bold@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-bold@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
checksum: 10c0/5c5c168da9a994f77b2e6a0a1924c1d648d3af34c3c36b1fd33ca6a0824fff9192bdc92b5e2433350d641e7efc4b6c40a1f1d5c8b47cb99b05c2d4c6070ed88c
languageName: node
linkType: hard
"@tiptap/extension-bubble-menu@npm:^2.10.3":
version: 2.27.1
resolution: "@tiptap/extension-bubble-menu@npm:2.27.1"
dependencies:
tippy.js: "npm:^6.3.7"
peerDependencies:
"@tiptap/core": ^2.7.0
"@tiptap/pm": ^2.7.0
checksum: 10c0/b7cd0c75be3b93b44f5ed35e752c5db3e98281feaa456013b4016b75fc5a362511e3cd3758b79a32a5bda94cfa0058246ecaf066be617b535d164122121b13dd
languageName: node
linkType: hard
"@tiptap/extension-document@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-document@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
checksum: 10c0/7c30c8418f2b78298eb84ea92da3a99889f357937bc9207db3f698f1d83c1cb7f9b54d8c9a87205a998180e4b8229b5170eff3fe72a5f989fabf0a01effb3679
languageName: node
linkType: hard
"@tiptap/extension-floating-menu@npm:^2.10.3":
version: 2.27.1
resolution: "@tiptap/extension-floating-menu@npm:2.27.1"
dependencies:
tippy.js: "npm:^6.3.7"
peerDependencies:
"@tiptap/core": ^2.7.0
"@tiptap/pm": ^2.7.0
checksum: 10c0/85c1b99fafa2ec16842806c1d05801a747a75b0bb67a216b309ee83e026319c1d9d56bace3d81d5e699757913d4257fa87ffe3c34987f31e040ece22d1dfc36a
languageName: node
linkType: hard
"@tiptap/extension-hard-break@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-hard-break@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
checksum: 10c0/0cc805819690ff9f5efa15e5db1d4bb7e604ac859ce46ef93306454a4668e1fa784c0f6fc7e19b5a5ea5d54cffc2a6161a1c5a58836b90db79da2c3f1a9dd729
languageName: node
linkType: hard
"@tiptap/extension-history@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-history@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
"@tiptap/pm": ^2.7.0
checksum: 10c0/cb6fd2f8e931d2da14c551679cf1d78522b2fda63d92c93d80a114531be36f34b4b096bfed695b71cb9a348f2e5928a93b2d1c2cd550f086b0afaffb49bc057f
languageName: node
linkType: hard
"@tiptap/extension-italic@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-italic@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
checksum: 10c0/8cc067d934dc43813524509482e93f949bdb0428695a0629d3e0f966d0f0382de5f35db0c4e02ec5dec978af403683e15cb6f5ee89eccb417f95a8de09412f26
languageName: node
linkType: hard
"@tiptap/extension-link@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-link@npm:2.10.3"
dependencies:
linkifyjs: "npm:^4.1.0"
peerDependencies:
"@tiptap/core": ^2.7.0
"@tiptap/pm": ^2.7.0
checksum: 10c0/c127fdac8408b9d5a120003c36817b443cae8f0aa1568280007b5d44198dac60af55df44b202f4204be8aefc762a3933c71a864b0af06679b31d326ee589cf15
languageName: node
linkType: hard
"@tiptap/extension-mention@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-mention@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
"@tiptap/pm": ^2.7.0
"@tiptap/suggestion": ^2.7.0
checksum: 10c0/833e495f611def2619ec4ebd7e47e8714ef756b1ce642aa778336c4be63e56a1a2d1b06c09688e7d6fc3655ddd24d7fb0179bee10e6ceb05ebc5cc3498cc9e87
languageName: node
linkType: hard
"@tiptap/extension-paragraph@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-paragraph@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
checksum: 10c0/4c55fef6600d5ae37b149c3f3bcb1a49ea62e37728e7f6032e2093ad50da34fda1bcc3007f8693749da9dd5ca2929066a29565e61627d987d74a6472a0ca699d
languageName: node
linkType: hard
"@tiptap/extension-placeholder@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-placeholder@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
"@tiptap/pm": ^2.7.0
checksum: 10c0/9a081ba6b4e3f295633ed2f6cf950891850ea282b24e131f7f2ad942a9f881e337d9757c1df0b7187e39b56c93259b7ade8fc8b46364d8b84067e0491d3b6110
languageName: node
linkType: hard
"@tiptap/extension-strike@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-strike@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
checksum: 10c0/c22eda35bfb9122ed8fa081de03d2e90b7b57174b107401ca3fde67f97968d4d6aa45c0b8218e3c16e3d5df9b0b1c8802bba46a12ca28c45649f98ed480fe7fa
languageName: node
linkType: hard
"@tiptap/extension-text@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-text@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
checksum: 10c0/4f4514bbf119285f49fef18a67100f845162780aefd0df8fee385c56c3dd0f1be6d4da2a0d9207f21902d8bcb795b87cfff18376738e8de0df540b4b19ecd971
languageName: node
linkType: hard
"@tiptap/extension-underline@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/extension-underline@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
checksum: 10c0/ceb517232ade674da57d360d0ce589911e349ba87c79c9d7cb48cfbe67a28e2c410c0103b379564c3fbc64ed1ec9d3d5b7ef3c364f4bc22d1f890775beb58090
languageName: node
linkType: hard
"@tiptap/pm@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/pm@npm:2.10.3"
dependencies:
prosemirror-changeset: "npm:^2.2.1"
prosemirror-collab: "npm:^1.3.1"
prosemirror-commands: "npm:^1.6.2"
prosemirror-dropcursor: "npm:^1.8.1"
prosemirror-gapcursor: "npm:^1.3.2"
prosemirror-history: "npm:^1.4.1"
prosemirror-inputrules: "npm:^1.4.0"
prosemirror-keymap: "npm:^1.2.2"
prosemirror-markdown: "npm:^1.13.1"
prosemirror-menu: "npm:^1.2.4"
prosemirror-model: "npm:^1.23.0"
prosemirror-schema-basic: "npm:^1.2.3"
prosemirror-schema-list: "npm:^1.4.1"
prosemirror-state: "npm:^1.4.3"
prosemirror-tables: "npm:^1.6.1"
prosemirror-trailing-node: "npm:^3.0.0"
prosemirror-transform: "npm:^1.10.2"
prosemirror-view: "npm:^1.37.0"
checksum: 10c0/8da579aa1e052f056cee7765dafb3a00bed2a9e110e0db01225d0e6d3a3e701ff4bc58e5bd13cc48eb946370edabaa6f19d6fcf33aa790fad3b4845f99fdfd56
languageName: node
linkType: hard
"@tiptap/suggestion@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/suggestion@npm:2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
"@tiptap/pm": ^2.7.0
checksum: 10c0/552e0842dce3feb088e28bbcfe936ce4c4b041dcd593eba09a51a83bc1824a2605e27e6b5251b8d0edc3db798975637ea09776427510d76a836dd68744d22a38
languageName: node
linkType: hard
"@tiptap/vue-3@npm:2.10.3":
version: 2.10.3
resolution: "@tiptap/vue-3@npm:2.10.3"
dependencies:
"@tiptap/extension-bubble-menu": "npm:^2.10.3"
"@tiptap/extension-floating-menu": "npm:^2.10.3"
peerDependencies:
"@tiptap/core": ^2.7.0
"@tiptap/pm": ^2.7.0
vue: ^3.0.0
checksum: 10c0/10eff4df3450bf672ef8ee9ce9f5679df6a920f21418ad55c4fdbc2b83827ab3a0aca6538f35ae7bfb56351f51740a645d9f87a5d61db0f6d61c347b24c787fc
languageName: node
linkType: hard
"@trysound/sax@npm:0.2.0":
version: 0.2.0
resolution: "@trysound/sax@npm:0.2.0"
@@ -4178,6 +4383,13 @@ __metadata:
languageName: node
linkType: hard
"@types/linkify-it@npm:^5":
version: 5.0.0
resolution: "@types/linkify-it@npm:5.0.0"
checksum: 10c0/7bbbf45b9dde17bf3f184fee585aef0e7342f6954f0377a24e4ff42ab5a85d5b806aaa5c8d16e2faf2a6b87b2d94467a196b7d2b85c9c7de2f0eaac5487aaab8
languageName: node
linkType: hard
"@types/lodash-es@npm:^4.17.6":
version: 4.17.12
resolution: "@types/lodash-es@npm:4.17.12"
@@ -4194,6 +4406,23 @@ __metadata:
languageName: node
linkType: hard
"@types/markdown-it@npm:^14.0.0":
version: 14.1.2
resolution: "@types/markdown-it@npm:14.1.2"
dependencies:
"@types/linkify-it": "npm:^5"
"@types/mdurl": "npm:^2"
checksum: 10c0/34f709f0476bd4e7b2ba7c3341072a6d532f1f4cb6f70aef371e403af8a08a7c372ba6907ac426bc618d356dab660c5b872791ff6c1ead80c483e0d639c6f127
languageName: node
linkType: hard
"@types/mdurl@npm:^2":
version: 2.0.0
resolution: "@types/mdurl@npm:2.0.0"
checksum: 10c0/cde7bb571630ed1ceb3b92a28f7b59890bb38b8f34cd35326e2df43eebfc74985e6aa6fd4184e307393bad8a9e0783a519a3f9d13c8e03788c0f98e5ec869c5e
languageName: node
linkType: hard
"@types/node@npm:*, @types/node@npm:>=10.0.0":
version: 22.15.17
resolution: "@types/node@npm:22.15.17"
@@ -6863,6 +7092,13 @@ __metadata:
languageName: node
linkType: hard
"crelt@npm:^1.0.0":
version: 1.0.6
resolution: "crelt@npm:1.0.6"
checksum: 10c0/e0fb76dff50c5eb47f2ea9b786c17f9425c66276025adee80876bdbf4a84ab72e899e56d3928431ab0cb057a105ef704df80fe5726ef0f7b1658f815521bdf09
languageName: node
linkType: hard
"cron-parser@npm:^4.9.0":
version: 4.9.0
resolution: "cron-parser@npm:4.9.0"
@@ -10827,6 +11063,22 @@ __metadata:
languageName: node
linkType: hard
"linkify-it@npm:^5.0.0":
version: 5.0.0
resolution: "linkify-it@npm:5.0.0"
dependencies:
uc.micro: "npm:^2.0.0"
checksum: 10c0/ff4abbcdfa2003472fc3eb4b8e60905ec97718e11e33cca52059919a4c80cc0e0c2a14d23e23d8c00e5402bc5a885cdba8ca053a11483ab3cc8b3c7a52f88e2d
languageName: node
linkType: hard
"linkifyjs@npm:^4.1.0":
version: 4.3.2
resolution: "linkifyjs@npm:4.3.2"
checksum: 10c0/1a85e6b368304a4417567fe5e38651681e3e82465590836942d1b4f3c834cc35532898eb1e2479f6337d9144b297d418eb708b6be8ed0b3dc3954a3588e07971
languageName: node
linkType: hard
"listhen@npm:^1.5.6, listhen@npm:^1.9.0":
version: 1.9.0
resolution: "listhen@npm:1.9.0"
@@ -11113,6 +11365,15 @@ __metadata:
languageName: node
linkType: hard
"lucide-vue-next@npm:^0.537.0":
version: 0.537.0
resolution: "lucide-vue-next@npm:0.537.0"
peerDependencies:
vue: ">=3.0.1"
checksum: 10c0/5004c551894c19394ec15340ebb588e64ab7e237a12de713780b56b58f82cd473cfd2007e33a5a6d678f513aa559a4f84a0375321466f4e465e9755d585df877
languageName: node
linkType: hard
"luxon@npm:^3.2.1":
version: 3.6.1
resolution: "luxon@npm:3.6.1"
@@ -11193,6 +11454,22 @@ __metadata:
languageName: node
linkType: hard
"markdown-it@npm:^14.0.0":
version: 14.1.0
resolution: "markdown-it@npm:14.1.0"
dependencies:
argparse: "npm:^2.0.1"
entities: "npm:^4.4.0"
linkify-it: "npm:^5.0.0"
mdurl: "npm:^2.0.0"
punycode.js: "npm:^2.3.1"
uc.micro: "npm:^2.1.0"
bin:
markdown-it: bin/markdown-it.mjs
checksum: 10c0/9a6bb444181d2db7016a4173ae56a95a62c84d4cbfb6916a399b11d3e6581bf1cc2e4e1d07a2f022ae72c25f56db90fbe1e529fca16fbf9541659dc53480d4b4
languageName: node
linkType: hard
"matcher@npm:^3.0.0":
version: 3.0.0
resolution: "matcher@npm:3.0.0"
@@ -11237,6 +11514,13 @@ __metadata:
languageName: node
linkType: hard
"mdurl@npm:^2.0.0":
version: 2.0.0
resolution: "mdurl@npm:2.0.0"
checksum: 10c0/633db522272f75ce4788440669137c77540d74a83e9015666a9557a152c02e245b192edc20bc90ae953bbab727503994a53b236b4d9c99bdaee594d0e7dd2ce0
languageName: node
linkType: hard
"media-typer@npm:0.3.0":
version: 0.3.0
resolution: "media-typer@npm:0.3.0"
@@ -12404,6 +12688,13 @@ __metadata:
languageName: node
linkType: hard
"orderedmap@npm:^2.0.0":
version: 2.1.1
resolution: "orderedmap@npm:2.1.1"
checksum: 10c0/8d7d266659d1828937046e8b2a7b5f75914e0391db985da0ca75cd2246cccbf6d6f3a0886aa2034da15ee4923e8c45f95f8b588f575f535f0adecdefccc54634
languageName: node
linkType: hard
"os-tmpdir@npm:~1.0.2":
version: 1.0.2
resolution: "os-tmpdir@npm:1.0.2"
@@ -13592,6 +13883,200 @@ __metadata:
languageName: node
linkType: hard
"prosemirror-changeset@npm:^2.2.1":
version: 2.3.1
resolution: "prosemirror-changeset@npm:2.3.1"
dependencies:
prosemirror-transform: "npm:^1.0.0"
checksum: 10c0/efd6578ee4535d72d11c032b49921f14b3f7ccae680eb14c8d9f6cc1fbec00299c598475af0ab432864976bdbb7f94f011193278b2d19eadda83b754fe6d8a35
languageName: node
linkType: hard
"prosemirror-collab@npm:^1.3.1":
version: 1.3.1
resolution: "prosemirror-collab@npm:1.3.1"
dependencies:
prosemirror-state: "npm:^1.0.0"
checksum: 10c0/5d7553c136929cfd847b8781be599561d0f21e78fae80d930eb5f1d4d644307bc779cdfaeae86dd31a8be8f562c28dee19f1a06a2900e9b591b02957151fe90c
languageName: node
linkType: hard
"prosemirror-commands@npm:^1.0.0, prosemirror-commands@npm:^1.6.2":
version: 1.7.1
resolution: "prosemirror-commands@npm:1.7.1"
dependencies:
prosemirror-model: "npm:^1.0.0"
prosemirror-state: "npm:^1.0.0"
prosemirror-transform: "npm:^1.10.2"
checksum: 10c0/4884ea7a66b79b51e72bb2ef358284d70e9a071deb4cbfab3dd8ee3449e9a0e34cb391d92f487c013d3716b823fc5568ad5e409a9444b3630ae0b87617c2fca1
languageName: node
linkType: hard
"prosemirror-dropcursor@npm:^1.8.1":
version: 1.8.2
resolution: "prosemirror-dropcursor@npm:1.8.2"
dependencies:
prosemirror-state: "npm:^1.0.0"
prosemirror-transform: "npm:^1.1.0"
prosemirror-view: "npm:^1.1.0"
checksum: 10c0/c3d9e456a64fecc77a6e6a0350116598550dee8cb55f74e8b66fdb26150c48340ddd1f43184134b24d0f2e710b6879ff6ec72c215dc618a6a673320a91c90478
languageName: node
linkType: hard
"prosemirror-gapcursor@npm:^1.3.2":
version: 1.4.0
resolution: "prosemirror-gapcursor@npm:1.4.0"
dependencies:
prosemirror-keymap: "npm:^1.0.0"
prosemirror-model: "npm:^1.0.0"
prosemirror-state: "npm:^1.0.0"
prosemirror-view: "npm:^1.0.0"
checksum: 10c0/c9f8274198642ca37209338d4222099d7d50c4dd743c8aab25703e2845c6ced691ca9368ad6ac2aaa1899b51709e7411c5fbada306c58301df82f168fbc4517e
languageName: node
linkType: hard
"prosemirror-history@npm:^1.0.0, prosemirror-history@npm:^1.4.1":
version: 1.5.0
resolution: "prosemirror-history@npm:1.5.0"
dependencies:
prosemirror-state: "npm:^1.2.2"
prosemirror-transform: "npm:^1.0.0"
prosemirror-view: "npm:^1.31.0"
rope-sequence: "npm:^1.3.0"
checksum: 10c0/9f24c99316c30a52ff40ddd59fc83b5180f1326ee6f466bfa82b847a503b0cdff234c35d5e3decb39ae1382a189ceec7462333ed7d6c34e2c95921892bb98b78
languageName: node
linkType: hard
"prosemirror-inputrules@npm:^1.4.0":
version: 1.5.1
resolution: "prosemirror-inputrules@npm:1.5.1"
dependencies:
prosemirror-state: "npm:^1.0.0"
prosemirror-transform: "npm:^1.0.0"
checksum: 10c0/cff1ff9f7e726bf324e6cddd2c48984e6b2970cc98cd5dc59e6dbe2e9df0e01e4a2d100c77c721067804170b9607769c0fee7c98f2cfb4f3cff9af919fce31b9
languageName: node
linkType: hard
"prosemirror-keymap@npm:^1.0.0, prosemirror-keymap@npm:^1.2.2, prosemirror-keymap@npm:^1.2.3":
version: 1.2.3
resolution: "prosemirror-keymap@npm:1.2.3"
dependencies:
prosemirror-state: "npm:^1.0.0"
w3c-keyname: "npm:^2.2.0"
checksum: 10c0/0ec2f8bd9b608d0e6a0cdab1d66f9a6b41edcff0239b32ccca1018a0733e52448e4758218a2d472fb8c33c1609426dc6bad4944b28c1c3d509a83201a23035e9
languageName: node
linkType: hard
"prosemirror-markdown@npm:^1.13.1":
version: 1.13.2
resolution: "prosemirror-markdown@npm:1.13.2"
dependencies:
"@types/markdown-it": "npm:^14.0.0"
markdown-it: "npm:^14.0.0"
prosemirror-model: "npm:^1.25.0"
checksum: 10c0/53c48ef0d0d18ca0a7c39bdb18485508bfe10582f9b1d04d7114e6f6e9678a4481b318f310b19d4e95f65d947fbe6348affddcb909ad9b8c9f865cc07ceff22b
languageName: node
linkType: hard
"prosemirror-menu@npm:^1.2.4":
version: 1.2.5
resolution: "prosemirror-menu@npm:1.2.5"
dependencies:
crelt: "npm:^1.0.0"
prosemirror-commands: "npm:^1.0.0"
prosemirror-history: "npm:^1.0.0"
prosemirror-state: "npm:^1.0.0"
checksum: 10c0/a4da649aa3c7bfb74128da203984009b44fd48638ff76ec7b209635fafd23b05d7d5bed9520282cdcf886f73eafcfbda4e77f55d81a92db333f8807d84ded2f9
languageName: node
linkType: hard
"prosemirror-model@npm:^1.0.0, prosemirror-model@npm:^1.20.0, prosemirror-model@npm:^1.21.0, prosemirror-model@npm:^1.23.0, prosemirror-model@npm:^1.25.0, prosemirror-model@npm:^1.25.4":
version: 1.25.4
resolution: "prosemirror-model@npm:1.25.4"
dependencies:
orderedmap: "npm:^2.0.0"
checksum: 10c0/5ba99a235497df3452c0e2dfb71ee05d898fb9cb539c2a92583524fc4f337d8abab5c6b49b3af242c86327ea27cce41c7169a1e0c7bb4f7ef1502b311bd00cfc
languageName: node
linkType: hard
"prosemirror-schema-basic@npm:^1.2.3":
version: 1.2.4
resolution: "prosemirror-schema-basic@npm:1.2.4"
dependencies:
prosemirror-model: "npm:^1.25.0"
checksum: 10c0/cd86f88a5eb51ab5459aa91e6824e73ec15b0f1546fee89be7826663663ef11eefaacacda5a14c43b4c8d8477fd653642418b9c7d485bb92e323f9b8e7607a78
languageName: node
linkType: hard
"prosemirror-schema-list@npm:^1.4.1":
version: 1.5.1
resolution: "prosemirror-schema-list@npm:1.5.1"
dependencies:
prosemirror-model: "npm:^1.0.0"
prosemirror-state: "npm:^1.0.0"
prosemirror-transform: "npm:^1.7.3"
checksum: 10c0/e6fd27446bc90556a9797f6ca0cb54e7db53cc7c20fbf633b7d0f4709c45accfa2f3a0f6575fe47aa83cb75781a9b773198d236a44db9d8eef2802a1501e4301
languageName: node
linkType: hard
"prosemirror-state@npm:^1.0.0, prosemirror-state@npm:^1.2.2, prosemirror-state@npm:^1.4.3, prosemirror-state@npm:^1.4.4":
version: 1.4.4
resolution: "prosemirror-state@npm:1.4.4"
dependencies:
prosemirror-model: "npm:^1.0.0"
prosemirror-transform: "npm:^1.0.0"
prosemirror-view: "npm:^1.27.0"
checksum: 10c0/1428636a37c127afe0d11a4f4eb44d75a7f16717940405082bd16fa4c28cfeef9375d49f48e411e5f0fa26f3e5798af7f270edc9bc9b0e647df8bb79983bcd59
languageName: node
linkType: hard
"prosemirror-tables@npm:^1.6.1":
version: 1.8.3
resolution: "prosemirror-tables@npm:1.8.3"
dependencies:
prosemirror-keymap: "npm:^1.2.3"
prosemirror-model: "npm:^1.25.4"
prosemirror-state: "npm:^1.4.4"
prosemirror-transform: "npm:^1.10.5"
prosemirror-view: "npm:^1.41.4"
checksum: 10c0/3c1a7ca0684b9182159af679173dde6cbf3c5af4dd3f2278712b0f33d5d065d014a913d0897881e44c82204d57b89c52cb8876d66f2737a36519f092c9613fed
languageName: node
linkType: hard
"prosemirror-trailing-node@npm:^3.0.0":
version: 3.0.0
resolution: "prosemirror-trailing-node@npm:3.0.0"
dependencies:
"@remirror/core-constants": "npm:3.0.0"
escape-string-regexp: "npm:^4.0.0"
peerDependencies:
prosemirror-model: ^1.22.1
prosemirror-state: ^1.4.2
prosemirror-view: ^1.33.8
checksum: 10c0/d512054543a872c667bcd661f207c54a38287a8e62a2ff4aa87d65aefbad0bf3a6315cc7531d9c63cc7a7ef93504966b6c9496af90287a710914688feba72454
languageName: node
linkType: hard
"prosemirror-transform@npm:^1.0.0, prosemirror-transform@npm:^1.1.0, prosemirror-transform@npm:^1.10.2, prosemirror-transform@npm:^1.10.5, prosemirror-transform@npm:^1.7.3":
version: 1.10.5
resolution: "prosemirror-transform@npm:1.10.5"
dependencies:
prosemirror-model: "npm:^1.21.0"
checksum: 10c0/64e5aeaa30f15a2873214913b3fda6fe9973802e6291d5913a549c71bf6de3e167ab0d967ee880041e3fecdcb3d950388346d805f21b3a1b6ec77285c17f40bd
languageName: node
linkType: hard
"prosemirror-view@npm:^1.0.0, prosemirror-view@npm:^1.1.0, prosemirror-view@npm:^1.27.0, prosemirror-view@npm:^1.31.0, prosemirror-view@npm:^1.37.0, prosemirror-view@npm:^1.41.4":
version: 1.41.4
resolution: "prosemirror-view@npm:1.41.4"
dependencies:
prosemirror-model: "npm:^1.20.0"
prosemirror-state: "npm:^1.0.0"
prosemirror-transform: "npm:^1.1.0"
checksum: 10c0/613e36cb27757c115ab301d3f7674e979450c928064b95fe26666765a2fc3efa80402ca8f6e4b8484e5d2f6945a14d842e1ff2900c86e4deea73613be0f42d5a
languageName: node
linkType: hard
"proto-list@npm:~1.2.1":
version: 1.2.4
resolution: "proto-list@npm:1.2.4"
@@ -13626,6 +14111,13 @@ __metadata:
languageName: node
linkType: hard
"punycode.js@npm:^2.3.1":
version: 2.3.1
resolution: "punycode.js@npm:2.3.1"
checksum: 10c0/1d12c1c0e06127fa5db56bd7fdf698daf9a78104456a6b67326877afc21feaa821257b171539caedd2f0524027fa38e67b13dd094159c8d70b6d26d2bea4dfdb
languageName: node
linkType: hard
"punycode@npm:^2.1.0":
version: 2.3.1
resolution: "punycode@npm:2.3.1"
@@ -14296,6 +14788,13 @@ __metadata:
languageName: node
linkType: hard
"rope-sequence@npm:^1.3.0":
version: 1.3.4
resolution: "rope-sequence@npm:1.3.4"
checksum: 10c0/caa90be3d7a7cad155fb354a4679a1280dc9819c81bd319542a0d893a64e152284abb9cc1631d4351b328016a8d6c35a48c912234edfaf5173daef44b2e3609b
languageName: node
linkType: hard
"router@npm:^2.2.0":
version: 2.2.0
resolution: "router@npm:2.2.0"
@@ -14966,6 +15465,22 @@ __metadata:
"@speckle/tailwind-theme": "npm:2.25.0"
"@speckle/ui-components": "npm:^2.25.0"
"@speckle/ui-components-nuxt": "npm:^2.25.0"
"@tiptap/core": "npm:2.10.3"
"@tiptap/extension-bold": "npm:2.10.3"
"@tiptap/extension-document": "npm:2.10.3"
"@tiptap/extension-hard-break": "npm:2.10.3"
"@tiptap/extension-history": "npm:2.10.3"
"@tiptap/extension-italic": "npm:2.10.3"
"@tiptap/extension-link": "npm:2.10.3"
"@tiptap/extension-mention": "npm:2.10.3"
"@tiptap/extension-paragraph": "npm:2.10.3"
"@tiptap/extension-placeholder": "npm:2.10.3"
"@tiptap/extension-strike": "npm:2.10.3"
"@tiptap/extension-text": "npm:2.10.3"
"@tiptap/extension-underline": "npm:2.10.3"
"@tiptap/pm": "npm:2.10.3"
"@tiptap/suggestion": "npm:2.10.3"
"@tiptap/vue-3": "npm:2.10.3"
"@types/apollo-upload-client": "npm:^17.0.1"
"@types/eslint": "npm:^9.6.1"
"@types/lodash-es": "npm:^4.17.6"
@@ -14988,6 +15503,7 @@ __metadata:
graphql: "npm:^16.6.0"
graphql-tag: "npm:^2.12.6"
lodash-es: "npm:^4.17.21"
lucide-vue-next: "npm:^0.537.0"
nanoevents: "npm:^8.0.0"
nuxt: "npm:^3.17.3"
pinia: "npm:^2.1.4"
@@ -16088,6 +16604,13 @@ __metadata:
languageName: node
linkType: hard
"uc.micro@npm:^2.0.0, uc.micro@npm:^2.1.0":
version: 2.1.0
resolution: "uc.micro@npm:2.1.0"
checksum: 10c0/8862eddb412dda76f15db8ad1c640ccc2f47cdf8252a4a30be908d535602c8d33f9855dfcccb8b8837855c1ce1eaa563f7fa7ebe3c98fd0794351aab9b9c55fa
languageName: node
linkType: hard
"ufo@npm:^1.1.2, ufo@npm:^1.3.2, ufo@npm:^1.5.4, ufo@npm:^1.6.1":
version: 1.6.1
resolution: "ufo@npm:1.6.1"
@@ -17051,6 +17574,13 @@ __metadata:
languageName: node
linkType: hard
"w3c-keyname@npm:^2.2.0":
version: 2.2.8
resolution: "w3c-keyname@npm:2.2.8"
checksum: 10c0/37cf335c90efff31672ebb345577d681e2177f7ff9006a9ad47c68c5a9d265ba4a7b39d6c2599ceea639ca9315584ce4bd9c9fbf7a7217bfb7a599e71943c4c4
languageName: node
linkType: hard
"wcwidth@npm:^1.0.1":
version: 1.0.1
resolution: "wcwidth@npm:1.0.1"