@globalbrain/sefirot 3.39.2 → 3.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/SErrorBoundary.vue +4 -4
- package/lib/components/SGrid.vue +4 -2
- package/lib/components/STooltip.vue +1 -2
- package/lib/composables/Error.ts +211 -0
- package/lib/errors/AuthorizationError.ts +5 -0
- package/lib/errors/PageNotFoundError.ts +5 -0
- package/lib/errors/UnexpectedError.ts +5 -0
- package/lib/errors/index.ts +3 -0
- package/lib/stores/Error.ts +24 -0
- package/lib/stores/Snackbars.ts +1 -1
- package/lib/support/Utils.ts +5 -0
- package/package.json +27 -24
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { onErrorCaptured,
|
|
2
|
+
import { onErrorCaptured, shallowRef } from 'vue'
|
|
3
3
|
import { useRouter } from 'vue-router'
|
|
4
4
|
|
|
5
5
|
const emit = defineEmits<{
|
|
6
6
|
(e: 'error', value: any): void
|
|
7
7
|
}>()
|
|
8
8
|
|
|
9
|
-
const error =
|
|
9
|
+
const error = shallowRef<Error>()
|
|
10
10
|
|
|
11
11
|
onErrorCaptured((e) => {
|
|
12
12
|
if (import.meta.env.DEV) {
|
|
13
|
-
console.error(e)
|
|
13
|
+
console.error(e, { '[cause]': e?.cause })
|
|
14
14
|
}
|
|
15
15
|
if (!import.meta.env.SSR) {
|
|
16
16
|
error.value = e
|
|
@@ -23,7 +23,7 @@ async function clearError(options: { redirect?: string } = {}) {
|
|
|
23
23
|
if (options.redirect) {
|
|
24
24
|
await useRouter().replace(options.redirect)
|
|
25
25
|
}
|
|
26
|
-
error.value =
|
|
26
|
+
error.value = undefined
|
|
27
27
|
}
|
|
28
28
|
</script>
|
|
29
29
|
|
package/lib/components/SGrid.vue
CHANGED
|
@@ -4,12 +4,14 @@ import { computed } from 'vue'
|
|
|
4
4
|
const props = defineProps<{
|
|
5
5
|
cols?: string | number
|
|
6
6
|
gap?: string | number
|
|
7
|
+
gapRow?: string | number
|
|
8
|
+
gapCol?: string | number
|
|
7
9
|
}>()
|
|
8
10
|
|
|
9
11
|
const styles = computed(() => {
|
|
10
12
|
return {
|
|
11
|
-
'--grid-template-columns': `repeat(${props.cols
|
|
12
|
-
'--gap': `${props.gap
|
|
13
|
+
'--grid-template-columns': `repeat(${props.cols || 1}, minmax(0, 1fr))`,
|
|
14
|
+
'--gap': `${props.gapRow || props.gap || 0}px ${props.gapCol || props.gap || 0}px`
|
|
13
15
|
}
|
|
14
16
|
})
|
|
15
17
|
</script>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { onKeyStroke } from '@vueuse/core'
|
|
3
3
|
import { computed, ref, shallowRef } from 'vue'
|
|
4
4
|
import { type Position, useTooltip } from '../composables/Tooltip'
|
|
5
|
-
import SMarkdown from './SMarkdown.vue'
|
|
6
5
|
|
|
7
6
|
const props = withDefaults(defineProps<{
|
|
8
7
|
tag?: string
|
|
@@ -107,7 +106,7 @@ function onBlur() {
|
|
|
107
106
|
<transition name="fade">
|
|
108
107
|
<span v-show="on" class="container" :class="containerClasses" ref="tip">
|
|
109
108
|
<span v-if="$slots.text" class="tip"><slot name="text" /></span>
|
|
110
|
-
<
|
|
109
|
+
<span v-else-if="text" class="tip" v-html="text" />
|
|
111
110
|
</span>
|
|
112
111
|
</transition>
|
|
113
112
|
</component>
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from
|
|
3
|
+
* @see https://github.com/vuejs/core/blob/76c9c742e9b045d1342f5952866972498e59f00b/packages/runtime-core/src/component.ts
|
|
4
|
+
* @see https://github.com/vuejs/core/blob/bc37258caa2f6f67f4554ab8587aca3798d92124/packages/runtime-core/src/warning.ts
|
|
5
|
+
* @see https://github.com/getsentry/sentry-javascript/blob/2cfb0ef3fa5c40f90c317267a4d10b969994d021/packages/vue/src/errorhandler.ts
|
|
6
|
+
*
|
|
7
|
+
* Original licenses:
|
|
8
|
+
*
|
|
9
|
+
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
10
|
+
* @license MIT
|
|
11
|
+
*
|
|
12
|
+
* (c) 2019 Sentry (https://sentry.io) and individual contributors
|
|
13
|
+
* @license MIT
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as Sentry from '@sentry/browser'
|
|
17
|
+
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
|
18
|
+
import {
|
|
19
|
+
type ComponentInternalInstance,
|
|
20
|
+
type ComponentOptions,
|
|
21
|
+
type ComponentPublicInstance,
|
|
22
|
+
type ConcreteComponent,
|
|
23
|
+
type MaybeRefOrGetter,
|
|
24
|
+
type VNode,
|
|
25
|
+
toValue
|
|
26
|
+
} from 'vue'
|
|
27
|
+
import { useError } from '../stores/Error'
|
|
28
|
+
import { isFunction } from '../support/Utils'
|
|
29
|
+
|
|
30
|
+
export interface User {
|
|
31
|
+
id?: string | number
|
|
32
|
+
email?: string
|
|
33
|
+
username?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type TraceEntry = { vnode: VNode; recurseCount: number }
|
|
37
|
+
|
|
38
|
+
type ComponentTraceStack = TraceEntry[]
|
|
39
|
+
|
|
40
|
+
const classifyRE = /(?:^|[-_])(\w)/g
|
|
41
|
+
function classify(str: string): string {
|
|
42
|
+
return str.replace(classifyRE, (c) => c.toUpperCase()).replace(/[-_]/g, '')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getComponentName(Component: ConcreteComponent): string | undefined {
|
|
46
|
+
return isFunction(Component)
|
|
47
|
+
? Component.displayName || Component.name
|
|
48
|
+
: Component.name || Component.__name
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatComponentName(instance: ComponentInternalInstance | null): string {
|
|
52
|
+
if (!instance) {
|
|
53
|
+
return 'Anonymous'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const Component = instance.type
|
|
57
|
+
const isRoot = instance.parent == null
|
|
58
|
+
|
|
59
|
+
let name = getComponentName(Component)
|
|
60
|
+
if (!name && Component.__file) {
|
|
61
|
+
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
|
|
62
|
+
if (match) {
|
|
63
|
+
name = match[1]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!name && instance && instance.parent) {
|
|
68
|
+
const inferFromRegistry = (registry: Record<string, unknown> | undefined) => {
|
|
69
|
+
for (const key in registry) {
|
|
70
|
+
if (registry[key] === Component) {
|
|
71
|
+
return key
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
name
|
|
76
|
+
= inferFromRegistry(
|
|
77
|
+
// @ts-expect-error internal api
|
|
78
|
+
instance.components || (instance.parent.type as ComponentOptions).components
|
|
79
|
+
) || inferFromRegistry(instance.appContext.components)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return name ? classify(name) : isRoot ? 'App' : 'Anonymous'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getComponentTrace(currentVNode: VNode | null): ComponentTraceStack {
|
|
86
|
+
if (!currentVNode) {
|
|
87
|
+
return []
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const normalizedStack: ComponentTraceStack = []
|
|
91
|
+
|
|
92
|
+
while (currentVNode) {
|
|
93
|
+
const last = normalizedStack[0]
|
|
94
|
+
if (last && last.vnode === currentVNode) {
|
|
95
|
+
last.recurseCount++
|
|
96
|
+
} else {
|
|
97
|
+
normalizedStack.push({ vnode: currentVNode, recurseCount: 0 })
|
|
98
|
+
}
|
|
99
|
+
const parentInstance: ComponentInternalInstance | null
|
|
100
|
+
= currentVNode.component && currentVNode.component.parent
|
|
101
|
+
currentVNode = parentInstance && parentInstance.vnode
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return normalizedStack
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatTrace(instance: ComponentInternalInstance | null): string {
|
|
108
|
+
return getComponentTrace(instance && instance.vnode)
|
|
109
|
+
.map(formatTraceEntry)
|
|
110
|
+
.join('\n')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function formatTraceEntry({ vnode, recurseCount }: TraceEntry): string {
|
|
114
|
+
return `at <${formatComponentName(vnode.component)}${
|
|
115
|
+
vnode.props ? ` ${formatProps(vnode.props)}` : ''
|
|
116
|
+
}>${
|
|
117
|
+
recurseCount > 0 ? ` ... (${recurseCount} recursive call${recurseCount > 1 ? 's' : ''})` : ''
|
|
118
|
+
}`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function formatProps(props: Record<string, unknown>): string {
|
|
122
|
+
return Object.keys(props)
|
|
123
|
+
.map((key) => {
|
|
124
|
+
let value = props[key]
|
|
125
|
+
if (typeof value === 'string') {
|
|
126
|
+
value = JSON.stringify(value)
|
|
127
|
+
return `${key}=${value}`
|
|
128
|
+
} else if (typeof value === 'function') {
|
|
129
|
+
return `:${key}="fn${value.name ? `<${value.name}>` : ''}"`
|
|
130
|
+
} else if (typeof value !== 'object' || value === null) {
|
|
131
|
+
return `:${key}="${String(value)}"`
|
|
132
|
+
} else {
|
|
133
|
+
return `${key}="..."`
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
.join(' ')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function useErrorHandler({
|
|
140
|
+
dsn,
|
|
141
|
+
environment,
|
|
142
|
+
user
|
|
143
|
+
}: {
|
|
144
|
+
dsn?: string
|
|
145
|
+
environment?: string
|
|
146
|
+
user?: MaybeRefOrGetter<User | null>
|
|
147
|
+
}) {
|
|
148
|
+
const error = useError()
|
|
149
|
+
|
|
150
|
+
const enabled = !!dsn && import.meta.env.PROD
|
|
151
|
+
|
|
152
|
+
// No need to manually report these errors as they are automatically
|
|
153
|
+
// captured by Sentry. And skip registering these in SSR. We can't use
|
|
154
|
+
// onMounted because it's not available outside component lifecycle.
|
|
155
|
+
if (typeof document !== 'undefined') {
|
|
156
|
+
addEventListener('error', (event) => {
|
|
157
|
+
error.set(event.error)
|
|
158
|
+
})
|
|
159
|
+
addEventListener('unhandledrejection', (event) => {
|
|
160
|
+
error.set(event.reason)
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (enabled) {
|
|
165
|
+
Sentry.init({
|
|
166
|
+
dsn,
|
|
167
|
+
environment,
|
|
168
|
+
ignoreErrors: [
|
|
169
|
+
'ResizeObserver loop limit exceeded',
|
|
170
|
+
'ResizeObserver loop completed with undelivered notifications'
|
|
171
|
+
]
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return function errorHandler(
|
|
176
|
+
error: any,
|
|
177
|
+
instance: ComponentPublicInstance | null = null,
|
|
178
|
+
info: string = ''
|
|
179
|
+
) {
|
|
180
|
+
error.set(error)
|
|
181
|
+
|
|
182
|
+
if (enabled) {
|
|
183
|
+
pauseTracking()
|
|
184
|
+
|
|
185
|
+
let userValue = toValue(user) || null
|
|
186
|
+
if (!userValue?.id && !userValue?.email && !userValue?.username) {
|
|
187
|
+
userValue = null
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (![403, 404].includes(error?.cause?.statusCode)) {
|
|
191
|
+
const $ = instance && instance.$
|
|
192
|
+
const metadata = $ && {
|
|
193
|
+
componentName: formatComponentName($),
|
|
194
|
+
lifecycleHook: info,
|
|
195
|
+
trace: formatTrace($),
|
|
196
|
+
propsData: $ && $.props
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
Sentry.withScope((scope) => {
|
|
201
|
+
scope.setUser(userValue)
|
|
202
|
+
scope.setContext('vue', metadata)
|
|
203
|
+
Sentry.captureException(error)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
resetTracking()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { shallowRef } from 'vue'
|
|
3
|
+
import { useRouter } from 'vue-router'
|
|
4
|
+
|
|
5
|
+
export const useError = defineStore('sefirot-error', () => {
|
|
6
|
+
const data = shallowRef<unknown>()
|
|
7
|
+
|
|
8
|
+
function set(err: unknown): void {
|
|
9
|
+
data.value = err
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function clear(options: { redirect?: string } = {}): Promise<void> {
|
|
13
|
+
if (options.redirect) {
|
|
14
|
+
await useRouter().replace(options.redirect)
|
|
15
|
+
}
|
|
16
|
+
data.value = undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
data,
|
|
21
|
+
set,
|
|
22
|
+
clear
|
|
23
|
+
}
|
|
24
|
+
})
|
package/lib/stores/Snackbars.ts
CHANGED
package/lib/support/Utils.ts
CHANGED
|
@@ -21,3 +21,8 @@ export function isObject(value: unknown): value is Record<string, any> {
|
|
|
21
21
|
export function isFile(value: unknown): value is File {
|
|
22
22
|
return value instanceof File
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
26
|
+
export function isFunction(value: unknown): value is Function {
|
|
27
|
+
return typeof value === 'function'
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@globalbrain/sefirot",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"packageManager": "pnpm@8.15.
|
|
3
|
+
"version": "3.40.1",
|
|
4
|
+
"packageManager": "pnpm@8.15.5",
|
|
5
5
|
"description": "Vue Components for Global Brain Design System.",
|
|
6
6
|
"author": "Kia Ishii <ka.ishii@globalbrains.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -45,35 +45,37 @@
|
|
|
45
45
|
"@types/body-scroll-lock": "^3.1.2",
|
|
46
46
|
"@types/lodash-es": "^4.17.12",
|
|
47
47
|
"@types/markdown-it": "^13.0.7",
|
|
48
|
+
"@vue/reactivity": "^3.4.21",
|
|
48
49
|
"@vuelidate/core": "^2.0.3",
|
|
49
50
|
"@vuelidate/validators": "^2.0.4",
|
|
50
|
-
"@vueuse/core": "^10.
|
|
51
|
+
"@vueuse/core": "^10.9.0",
|
|
51
52
|
"body-scroll-lock": "4.0.0-beta.0",
|
|
52
53
|
"fuse.js": "^7.0.0",
|
|
53
54
|
"lodash-es": "^4.17.21",
|
|
54
|
-
"markdown-it": "^14.
|
|
55
|
+
"markdown-it": "^14.1.0",
|
|
55
56
|
"normalize.css": "^8.0.1",
|
|
56
57
|
"pinia": "^2.1.7",
|
|
57
|
-
"postcss": "^8.4.
|
|
58
|
+
"postcss": "^8.4.38",
|
|
58
59
|
"postcss-nested": "^6.0.1",
|
|
59
60
|
"v-calendar": "^3.1.2",
|
|
60
|
-
"vue": "^3.4.
|
|
61
|
+
"vue": "^3.4.21",
|
|
61
62
|
"vue-router": "^4.3.0"
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
65
|
+
"@sentry/browser": "^8.0.0-alpha.7",
|
|
64
66
|
"@tanstack/vue-virtual": "3.0.0-beta.62",
|
|
65
67
|
"@tinyhttp/content-disposition": "^2.2.0",
|
|
66
68
|
"@tinyhttp/cookie": "^2.1.0",
|
|
67
69
|
"@types/file-saver": "^2.0.7",
|
|
68
|
-
"@types/qs": "^6.9.
|
|
70
|
+
"@types/qs": "^6.9.14",
|
|
69
71
|
"dayjs": "^1.11.10",
|
|
70
72
|
"file-saver": "^2.0.5",
|
|
71
|
-
"ofetch": "^1.3.
|
|
72
|
-
"qs": "^6.
|
|
73
|
+
"ofetch": "^1.3.4",
|
|
74
|
+
"qs": "^6.12.0"
|
|
73
75
|
},
|
|
74
76
|
"devDependencies": {
|
|
75
|
-
"@globalbrain/eslint-config": "^1.
|
|
76
|
-
"@histoire/plugin-vue": "^0.
|
|
77
|
+
"@globalbrain/eslint-config": "^1.6.0",
|
|
78
|
+
"@histoire/plugin-vue": "^0.17.14",
|
|
77
79
|
"@iconify-icons/ph": "^1.2.5",
|
|
78
80
|
"@iconify-icons/ri": "^1.2.10",
|
|
79
81
|
"@iconify/vue": "^4.1.1",
|
|
@@ -81,32 +83,33 @@
|
|
|
81
83
|
"@types/body-scroll-lock": "^3.1.2",
|
|
82
84
|
"@types/lodash-es": "^4.17.12",
|
|
83
85
|
"@types/markdown-it": "^13.0.7",
|
|
84
|
-
"@types/node": "^20.11.
|
|
86
|
+
"@types/node": "^20.11.30",
|
|
85
87
|
"@vitejs/plugin-vue": "^5.0.4",
|
|
86
|
-
"@vitest/coverage-v8": "^1.
|
|
87
|
-
"@vue/
|
|
88
|
+
"@vitest/coverage-v8": "^1.4.0",
|
|
89
|
+
"@vue/reactivity": "^3.4.21",
|
|
90
|
+
"@vue/test-utils": "^2.4.5",
|
|
88
91
|
"@vuelidate/core": "^2.0.3",
|
|
89
92
|
"@vuelidate/validators": "^2.0.4",
|
|
90
|
-
"@vueuse/core": "^10.
|
|
93
|
+
"@vueuse/core": "^10.9.0",
|
|
91
94
|
"body-scroll-lock": "4.0.0-beta.0",
|
|
92
95
|
"eslint": "^8.57.0",
|
|
93
96
|
"fuse.js": "^7.0.0",
|
|
94
|
-
"happy-dom": "^
|
|
95
|
-
"histoire": "^0.
|
|
97
|
+
"happy-dom": "^14.3.9",
|
|
98
|
+
"histoire": "^0.17.14",
|
|
96
99
|
"lodash-es": "^4.17.21",
|
|
97
|
-
"markdown-it": "^14.
|
|
100
|
+
"markdown-it": "^14.1.0",
|
|
98
101
|
"normalize.css": "^8.0.1",
|
|
99
102
|
"pinia": "^2.1.7",
|
|
100
|
-
"postcss": "^8.4.
|
|
103
|
+
"postcss": "^8.4.38",
|
|
101
104
|
"postcss-nested": "^6.0.1",
|
|
102
105
|
"punycode": "^2.3.1",
|
|
103
106
|
"release-it": "^17.1.1",
|
|
104
|
-
"typescript": "~5.
|
|
107
|
+
"typescript": "~5.4.3",
|
|
105
108
|
"v-calendar": "^3.1.2",
|
|
106
|
-
"vite": "^5.
|
|
107
|
-
"vitepress": "1.0.
|
|
108
|
-
"vitest": "^1.
|
|
109
|
-
"vue": "^3.4.
|
|
109
|
+
"vite": "^5.2.6",
|
|
110
|
+
"vitepress": "1.0.1",
|
|
111
|
+
"vitest": "^1.4.0",
|
|
112
|
+
"vue": "^3.4.21",
|
|
110
113
|
"vue-router": "^4.3.0",
|
|
111
114
|
"vue-tsc": "^1.8.27"
|
|
112
115
|
}
|