diff --git a/README.md b/README.md
index 9fbdfe76056d..a5d926ac903e 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Collection of essential Vue Composition Utilities
-
+
diff --git a/package.json b/package.json
index 4953aef7e9ae..3a9b0c1c73c9 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
"@vitest/ui": "^0.2.8",
"@vue/compiler-sfc": "^3.2.30",
"@vue/composition-api": "^1.4.6",
- "@vue/test-utils": "^1.3.0",
+ "@vue/test-utils": "^2.0.0-rc.18",
"@vue/theme": "^1.0.0",
"axios": "^0.25.0",
"bumpp": "^7.1.1",
diff --git a/packages/components/index.ts b/packages/components/index.ts
index 3d380cfb03a1..6a04b8390afd 100644
--- a/packages/components/index.ts
+++ b/packages/components/index.ts
@@ -1,5 +1,7 @@
export * from '../core/onClickOutside/component'
export * from '../core/onClickOutside/directive'
+export * from '../core/onLongPress/component'
+export * from '../core/onLongPress/directive'
export * from '../core/useActiveElement/component'
export * from '../core/useBattery/component'
export * from '../core/useBrowserLocation/component'
diff --git a/packages/core/index.ts b/packages/core/index.ts
index 8b5a049febf2..2471ee261956 100644
--- a/packages/core/index.ts
+++ b/packages/core/index.ts
@@ -4,6 +4,7 @@ export * from './computedInject'
export * from './createUnrefFn'
export * from './onClickOutside'
export * from './onKeyStroke'
+export * from './onLongPress'
export * from './onStartTyping'
export * from './templateRef'
export * from './unrefElement'
diff --git a/packages/core/onLongpress/component.ts b/packages/core/onLongpress/component.ts
new file mode 100644
index 000000000000..01547ff27c8b
--- /dev/null
+++ b/packages/core/onLongpress/component.ts
@@ -0,0 +1,28 @@
+import { defineComponent, h, ref } from 'vue-demi'
+import type { RenderableComponent } from '../types'
+import type { OnLongPressOptions } from '.'
+import { onLongPress } from '.'
+
+export interface OnLongPressProps extends RenderableComponent {
+ options?: OnLongPressOptions
+}
+
+export const OnLongPress = defineComponent({
+ name: 'OnLongPress',
+ props: ['as', 'options'] as unknown as undefined,
+ emits: ['trigger'],
+ setup(props, { slots, emit }) {
+ const target = ref()
+ onLongPress(
+ target,
+ (e) => {
+ emit('trigger', e)
+ },
+ props.options,
+ )
+ return () => {
+ if (slots.default)
+ return h(props.as || 'div', { ref: target }, slots.default())
+ }
+ },
+})
diff --git a/packages/core/onLongpress/demo.vue b/packages/core/onLongpress/demo.vue
new file mode 100644
index 000000000000..d972c2f8ca19
--- /dev/null
+++ b/packages/core/onLongpress/demo.vue
@@ -0,0 +1,33 @@
+
+
+
+ Long Pressed: {{ longPressed }}
+
+
+
+
diff --git a/packages/core/onLongpress/directive.test.ts b/packages/core/onLongpress/directive.test.ts
new file mode 100644
index 000000000000..6e4aff9e19b5
--- /dev/null
+++ b/packages/core/onLongpress/directive.test.ts
@@ -0,0 +1,91 @@
+import { defineComponent } from 'vue-demi'
+import type { VueWrapper } from '@vue/test-utils'
+import { mount } from '@vue/test-utils'
+import { promiseTimeout } from '@vueuse/shared'
+
+import { VOnLongPress } from './directive'
+import type { OnLongPressOptions } from '.'
+
+const App = defineComponent({
+ props: {
+ onLongPress: {
+ type: Function,
+ required: true,
+ },
+ options: {
+ type: Object,
+ required: false,
+ },
+ },
+
+ template: `
+ Press me
+ Press me
+
+ `,
+})
+
+describe('vOnLongPress', () => {
+ let onLongPress = vi.fn()
+ let wrapper: VueWrapper
+
+ describe('given no options', () => {
+ beforeEach(() => {
+ onLongPress = vi.fn()
+ wrapper = mount(App, {
+ props: {
+ onLongPress,
+ },
+ global: {
+ directives: {
+ 'on-longpress': VOnLongPress,
+ },
+ },
+ })
+ })
+
+ it('should be defined', () => {
+ expect(wrapper).toBeDefined()
+ })
+
+ it('should trigger longpress after 500ms', async() => {
+ const element = wrapper.get('[data-test=element]')
+ await element.trigger('pointerdown')
+ await promiseTimeout(500)
+ expect(onLongPress).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('given options', () => {
+ beforeEach(() => {
+ onLongPress = vi.fn()
+ const options: OnLongPressOptions = {
+ delay: 1000,
+ }
+ wrapper = mount(App, {
+ props: {
+ onLongPress,
+ options,
+ },
+ global: {
+ directives: {
+ 'on-longpress': VOnLongPress,
+ },
+ },
+ })
+ })
+
+ it('should be defined', () => {
+ expect(wrapper).toBeDefined()
+ })
+
+ it('should trigger longpress after 500ms', async() => {
+ const element = wrapper.get('[data-test=element]')
+ await element.trigger('pointerdown')
+ await promiseTimeout(500)
+ expect(onLongPress).toHaveBeenCalledTimes(0)
+ await promiseTimeout(500)
+ expect(onLongPress).toHaveBeenCalledTimes(1)
+ })
+ })
+})
diff --git a/packages/core/onLongpress/directive.ts b/packages/core/onLongpress/directive.ts
new file mode 100644
index 000000000000..165104024c13
--- /dev/null
+++ b/packages/core/onLongpress/directive.ts
@@ -0,0 +1,20 @@
+import type { FunctionDirective } from 'vue-demi'
+import type { OnLongPressOptions } from '.'
+import { onLongPress } from '.'
+
+type BindingValueFunction = (evt: PointerEvent) => void
+
+interface BindingValueObject {
+ handler: BindingValueFunction
+ options: OnLongPressOptions
+}
+
+export const VOnLongPress: FunctionDirective<
+HTMLElement,
+BindingValueFunction | BindingValueObject
+> = (el, binding) => {
+ if (typeof binding.value === 'function')
+ onLongPress(el, binding.value)
+ else
+ onLongPress(el, binding.value.handler, binding.value.options)
+}
diff --git a/packages/core/onLongpress/index.md b/packages/core/onLongpress/index.md
new file mode 100644
index 000000000000..6888343a4ed0
--- /dev/null
+++ b/packages/core/onLongpress/index.md
@@ -0,0 +1,110 @@
+---
+category: Sensors
+---
+
+# onLongPress
+
+Listen for a long press on an element.
+
+## Usage
+
+### As a hook
+
+```html
+
+
+
+ Long Pressed: {{ longPressedHook }}
+
+
+
+
+
+```
+
+### As a component
+
+
+
+```html
+
+
+
+ Long Pressed: {{ longPressedComponent }}
+
+
+ Press long
+
+
+
+
+```
+
+### As a directive
+
+
+
+```html
+
+
+
+ Long Pressed: {{ longPressedDirective }}
+
+
+
+
+
+
+
+```
diff --git a/packages/core/onLongpress/index.test.ts b/packages/core/onLongpress/index.test.ts
new file mode 100644
index 000000000000..a1b5cd09a837
--- /dev/null
+++ b/packages/core/onLongpress/index.test.ts
@@ -0,0 +1,68 @@
+import { promiseTimeout } from '@vueuse/shared'
+import type { Ref } from 'vue-demi'
+import { ref } from 'vue-demi'
+import { onLongPress } from '.'
+
+const pointerdownEvent = new PointerEvent('pointerdown')
+
+describe('onLongPress', () => {
+ let element: Ref
+
+ beforeEach(() => {
+ element = ref(document.createElement('div'))
+ })
+
+ it('should be defined', () => {
+ expect(onLongPress).toBeDefined()
+ })
+
+ describe('given argument is ref', () => {
+ describe('given no options', () => {
+ it('should trigger longpress after 500ms', async() => {
+ const onLongPressCallback = vi.fn()
+ onLongPress(element, onLongPressCallback)
+ element.value.dispatchEvent(pointerdownEvent)
+ expect(onLongPressCallback).toHaveBeenCalledTimes(0)
+ await promiseTimeout(500)
+ expect(onLongPressCallback).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('given options', () => {
+ it('should trigger longpress after options.delay ms', async() => {
+ const onLongPressCallback = vi.fn()
+ onLongPress(element, onLongPressCallback, { delay: 1000 })
+ element.value.dispatchEvent(pointerdownEvent)
+ await promiseTimeout(500)
+ expect(onLongPressCallback).toHaveBeenCalledTimes(0)
+ await promiseTimeout(500)
+ expect(onLongPressCallback).toHaveBeenCalledTimes(1)
+ })
+ })
+ })
+
+ describe('given argument is no ref', () => {
+ describe('given no options', () => {
+ it('should trigger longpress after 500ms', async() => {
+ const onLongPressCallback = vi.fn()
+ onLongPress(element.value, onLongPressCallback)
+ element.value.dispatchEvent(pointerdownEvent)
+ expect(onLongPressCallback).toHaveBeenCalledTimes(0)
+ await promiseTimeout(500)
+ expect(onLongPressCallback).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('given options', () => {
+ it('should trigger longpress after options.delay ms', async() => {
+ const onLongPressCallback = vi.fn()
+ onLongPress(element.value, onLongPressCallback, { delay: 1000 })
+ element.value.dispatchEvent(pointerdownEvent)
+ await promiseTimeout(500)
+ expect(onLongPressCallback).toHaveBeenCalledTimes(0)
+ await promiseTimeout(500)
+ expect(onLongPressCallback).toHaveBeenCalledTimes(1)
+ })
+ })
+ })
+})
diff --git a/packages/core/onLongpress/index.ts b/packages/core/onLongpress/index.ts
new file mode 100644
index 000000000000..1bfcba2d659f
--- /dev/null
+++ b/packages/core/onLongpress/index.ts
@@ -0,0 +1,43 @@
+import type { MaybeElementRef } from '@vueuse/core'
+import { unrefElement, useEventListener } from '@vueuse/core'
+import { computed } from 'vue-demi'
+
+const DEFAULT_DELAY = 500
+
+export interface OnLongPressOptions {
+ /**
+ * Time in ms till `longpress` gets called
+ *
+ * @default 500
+ */
+ delay?: number
+}
+
+export function onLongPress(
+ target: MaybeElementRef,
+ handler: (evt: PointerEvent) => void,
+ options?: OnLongPressOptions,
+) {
+ const elementRef = computed(() => unrefElement(target))
+
+ let timeout: number | null = null
+
+ function clear() {
+ if (timeout != null) {
+ clearTimeout(timeout)
+ timeout = null
+ }
+ }
+
+ function onDown(ev: PointerEvent) {
+ clear()
+ timeout = setTimeout(
+ () => handler(ev),
+ options?.delay ?? DEFAULT_DELAY,
+ ) as unknown as number
+ }
+
+ useEventListener(elementRef, 'pointerdown', onDown)
+ useEventListener(elementRef, 'pointerup', clear)
+ useEventListener(elementRef, 'pointerleave', clear)
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1da6672a2df0..c3aca9eca2eb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,7 +23,7 @@ importers:
'@vitest/ui': ^0.2.8
'@vue/compiler-sfc': ^3.2.30
'@vue/composition-api': ^1.4.6
- '@vue/test-utils': ^1.3.0
+ '@vue/test-utils': ^2.0.0-rc.18
'@vue/theme': ^1.0.0
axios: ^0.25.0
bumpp: ^7.1.1
@@ -83,7 +83,7 @@ importers:
'@vitest/ui': 0.2.8
'@vue/compiler-sfc': 3.2.30
'@vue/composition-api': 1.4.6_vue@3.2.30
- '@vue/test-utils': 1.3.0_vue@3.2.30
+ '@vue/test-utils': 2.0.0-rc.18_vue@3.2.30
'@vue/theme': 1.0.0_b4ee0005b62e2a8821f93ec48f0b95ae
axios: 0.25.0
bumpp: 7.1.1
@@ -2644,15 +2644,11 @@ packages:
resolution: {integrity: sha512-B3HouBtUxcfu2w2d+VhdLcVBXKYYhXiFMAfQ+hoe8NUhKkPRkWDIqhpuehCZxVQ3S2dN1P1WfKGlxGC+pfmxGg==}
dev: true
- /@vue/test-utils/1.3.0_vue@3.2.30:
- resolution: {integrity: sha512-Xk2Xiyj2k5dFb8eYUKkcN9PzqZSppTlx7LaQWBbdA8tqh3jHr/KHX2/YLhNFc/xwDrgeLybqd+4ZCPJSGPIqeA==}
+ /@vue/test-utils/2.0.0-rc.18_vue@3.2.30:
+ resolution: {integrity: sha512-aifolXjVdsogjaLmDoZ0FU8vN+R67aWmg9OuVeED4w5Ij5GFQLrlhM19uhWe/r5xXUL4fXMk3pX5wW6FJP1NcQ==}
peerDependencies:
- vue: 2.x
- vue-template-compiler: ^2.x
+ vue: ^3.0.1
dependencies:
- dom-event-types: 1.0.0
- lodash: 4.17.21
- pretty: 2.0.0
vue: 3.2.30
dev: true
@@ -2740,10 +2736,6 @@ packages:
resolution: {integrity: sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==}
dev: true
- /abbrev/1.1.1:
- resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
- dev: true
-
/acorn-globals/6.0.0:
resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==}
dependencies:
@@ -3403,21 +3395,13 @@ packages:
typedarray: 0.0.6
dev: true
- /condense-newlines/0.2.1:
- resolution: {integrity: sha1-PemFVTE5R10yUCyDsC9gaE0kxV8=}
- engines: {node: '>=0.10.0'}
- dependencies:
- extend-shallow: 2.0.1
- is-whitespace: 0.3.0
- kind-of: 3.2.2
- dev: true
-
/config-chain/1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
dependencies:
ini: 1.3.8
proto-list: 1.2.4
dev: true
+ optional: true
/consola/2.15.3:
resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
@@ -3669,10 +3653,6 @@ packages:
esutils: 2.0.3
dev: true
- /dom-event-types/1.0.0:
- resolution: {integrity: sha512-2G2Vwi2zXTHBGqXHsJ4+ak/iP0N8Ar+G8a7LiD2oup5o4sQWytwqqrZu/O6hIMV0KMID2PL69OhpshLO0n7UJQ==}
- dev: true
-
/dom-serializer/1.3.2:
resolution: {integrity: sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==}
dependencies:
@@ -3735,16 +3715,6 @@ packages:
resolution: {integrity: sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=}
dev: true
- /editorconfig/0.15.3:
- resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==}
- hasBin: true
- dependencies:
- commander: 2.20.3
- lru-cache: 4.1.5
- semver: 5.7.1
- sigmund: 1.0.1
- dev: true
-
/ejs/3.1.6:
resolution: {integrity: sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==}
engines: {node: '>=0.10.0'}
@@ -5279,6 +5249,7 @@ packages:
/ini/1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
dev: true
+ optional: true
/inquirer/8.2.0:
resolution: {integrity: sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==}
@@ -5500,11 +5471,6 @@ packages:
resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==}
dev: true
- /is-whitespace/0.3.0:
- resolution: {integrity: sha1-Fjnssb4DauxppUy7QBz77XEUq38=}
- engines: {node: '>=0.10.0'}
- dev: true
-
/is-word-character/1.0.4:
resolution: {integrity: sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==}
dev: true
@@ -5545,17 +5511,6 @@ packages:
engines: {node: '>=10'}
dev: true
- /js-beautify/1.14.0:
- resolution: {integrity: sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ==}
- engines: {node: '>=10'}
- hasBin: true
- dependencies:
- config-chain: 1.1.13
- editorconfig: 0.15.3
- glob: 7.2.0
- nopt: 5.0.0
- dev: true
-
/js-levenshtein/1.1.6:
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
engines: {node: '>=0.10.0'}
@@ -5729,13 +5684,6 @@ packages:
json-buffer: 3.0.0
dev: true
- /kind-of/3.2.2:
- resolution: {integrity: sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=}
- engines: {node: '>=0.10.0'}
- dependencies:
- is-buffer: 1.1.6
- dev: true
-
/kind-of/6.0.3:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
@@ -5939,13 +5887,6 @@ packages:
engines: {node: '>=8'}
dev: true
- /lru-cache/4.1.5:
- resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
- dependencies:
- pseudomap: 1.0.2
- yallist: 2.1.2
- dev: true
-
/lru-cache/6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@@ -6159,14 +6100,6 @@ packages:
resolution: {integrity: sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==}
dev: true
- /nopt/5.0.0:
- resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
- engines: {node: '>=6'}
- hasBin: true
- dependencies:
- abbrev: 1.1.1
- dev: true
-
/normalize-package-data/2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
@@ -6575,15 +6508,6 @@ packages:
engines: {node: '>=6'}
dev: true
- /pretty/2.0.0:
- resolution: {integrity: sha1-rbx5YLe7/iiaVX3F9zdhmiINBqU=}
- engines: {node: '>=0.10.0'}
- dependencies:
- condense-newlines: 0.2.1
- extend-shallow: 2.0.1
- js-beautify: 1.14.0
- dev: true
-
/prism-theme-vars/0.2.2:
resolution: {integrity: sha512-EL9ifuU/F8tEldoCa2sspiiLWysCL54xDbf2gN/ubwdtbuJROqOGopG5kSwunapwaioT+jLUQ/Ky+7jnv62xJA==}
dev: true
@@ -6624,6 +6548,7 @@ packages:
/proto-list/1.2.4:
resolution: {integrity: sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=}
dev: true
+ optional: true
/protobufjs/6.11.2:
resolution: {integrity: sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==}
@@ -6645,10 +6570,6 @@ packages:
long: 4.0.0
dev: true
- /pseudomap/1.0.2:
- resolution: {integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM=}
- dev: true
-
/psl/1.8.0:
resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==}
dev: true
@@ -7116,10 +7037,6 @@ packages:
object-inspect: 1.12.0
dev: true
- /sigmund/1.0.1:
- resolution: {integrity: sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=}
- dev: true
-
/signal-exit/3.0.6:
resolution: {integrity: sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==}
dev: true
@@ -8491,10 +8408,6 @@ packages:
engines: {node: '>=10'}
dev: true
- /yallist/2.1.2:
- resolution: {integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=}
- dev: true
-
/yallist/3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
diff --git a/scripts/changelog.ts b/scripts/changelog.ts
index 716b300665f1..fb19db640d22 100644
--- a/scripts/changelog.ts
+++ b/scripts/changelog.ts
@@ -38,23 +38,31 @@ export async function getChangeLog(count = 200) {
}
export async function getContributorsAt(path: string) {
- const list = (await git.raw(['log', '--pretty=format:"%an|%ae"', '--', path]))
- .split('\n')
- .map(i => i.slice(1, -1).split('|') as [string, string])
- const map: Record = {}
+ try {
+ const list = (await git.raw(['log', '--pretty=format:"%an|%ae"', '--', path]))
+ .split('\n')
+ .map(i => i.slice(1, -1).split('|') as [string, string])
+ const map: Record = {}
- list.forEach((i) => {
- if (!map[i[1]]) {
- map[i[1]] = {
- name: i[0],
- count: 0,
- hash: md5(i[1]),
- }
- }
- map[i[1]].count++
- })
+ list
+ .filter(i => i[1])
+ .forEach((i) => {
+ if (!map[i[1]]) {
+ map[i[1]] = {
+ name: i[0],
+ count: 0,
+ hash: md5(i[1]),
+ }
+ }
+ map[i[1]].count++
+ })
- return Object.values(map).sort((a, b) => b.count - a.count)
+ return Object.values(map).sort((a, b) => b.count - a.count)
+ }
+ catch (e) {
+ console.error(e)
+ return []
+ }
}
export async function getFunctionContributors() {