mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-15 12:39:35 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f6f132a76 | ||
|
|
5517cc2846 | ||
|
|
8c24b5dc97 | ||
|
|
ec8bd5cdc4 | ||
|
|
6fab68baa8 | ||
|
|
fc951e2980 | ||
|
|
08ff6e6c09 | ||
|
|
9dcdaf474e | ||
|
|
e9f0224b91 | ||
|
|
929192fd46 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -2,6 +2,16 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.1.1](https://github.com/nuxtlabs/ui/compare/v0.1.0...v0.1.1) (2022-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **CommandPalette:** lint ([6fab68b](https://github.com/nuxtlabs/ui/commit/6fab68baa836c97680446e8cfdee7c5a64008539))
|
||||
* **Dropdown:** close on click item with `to` ([#103](https://github.com/nuxtlabs/ui/issues/103)) ([5517cc2](https://github.com/nuxtlabs/ui/commit/5517cc28467f957956a20126c1ce48e64677cb82))
|
||||
* **Popover:** avoid crash on mount if ref not loaded ([#105](https://github.com/nuxtlabs/ui/issues/105)) ([e9f0224](https://github.com/nuxtlabs/ui/commit/e9f0224b919445260d3b19511420a3fd448e4ea7))
|
||||
* **SelectCustom:** types and lint ([ec8bd5c](https://github.com/nuxtlabs/ui/commit/ec8bd5cdc49feb924dfdff352d9f3d788653c583))
|
||||
|
||||
## [0.1.0](https://github.com/nuxtlabs/ui/compare/v0.0.3...v0.1.0) (2022-10-25)
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<UButton variant="transparent" :icon="colorMode.value === 'dark' ? 'heroicons-outline:moon' : 'heroicons-outline:sun'" @click="toggleDark" />
|
||||
<ColorScheme placeholder="" tag="span">
|
||||
<UButton variant="transparent" :icon="colorMode.value === 'dark' ? 'heroicons-outline:moon' : 'heroicons-outline:sun'" @click="toggleDark" />
|
||||
</ColorScheme>
|
||||
<UButton to="https://github.com/nuxtlabs/ui" target="_blank" variant="transparent" icon="fa-brands:github" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,7 +21,7 @@
|
||||
|
||||
<UContainer class="mt-16">
|
||||
<div class="lg:grid lg:grid-cols-10 lg:gap-10 lg:relative">
|
||||
<aside class="lg:flex lg:flex-col lg:relative pb-8 lg:pb-0 lg:sticky lg:top-0 px-4 sm:px-6 lg:px-0 lg:pt-16 lg:-mt-16 lg:self-start lg:col-span-2 lg:overflow-hidden lg:sticky lg:h-screen">
|
||||
<aside class="lg:flex lg:flex-col pb-8 lg:pb-0 lg:sticky lg:top-0 px-4 sm:px-6 lg:px-0 lg:pt-16 lg:-mt-16 lg:self-start lg:col-span-2 lg:overflow-hidden lg:h-screen">
|
||||
<nav class="overflow-y-auto h-auto pt-8 lg:py-12">
|
||||
<ul class="space-y-6">
|
||||
<li v-for="section of sections" :key="section">
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { defineNuxtConfig } from 'nuxt'
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
import nuxtUI from '../src/module'
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-template-shadow -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||
<template>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="pb-10 border-b u-border-gray-200 mb-10">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxthq/ui",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"repository": "https://github.com/nuxtlabs/ui",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
@@ -34,8 +34,8 @@
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"@vueuse/core": "^9.3.1",
|
||||
"@vueuse/integrations": "^9.3.1",
|
||||
"@vueuse/core": "^9.4.0",
|
||||
"@vueuse/integrations": "^9.4.0",
|
||||
"defu": "^6.1.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
||||
@@ -114,6 +114,11 @@ export default defineNuxtModule<ModuleOptions>({
|
||||
filename: 'ui.mjs',
|
||||
getContents: () => `export default ${JSON.stringify(ui)}`
|
||||
})
|
||||
addTemplate({
|
||||
filename: 'ui.d.ts',
|
||||
write: true,
|
||||
getContents: () => 'declare const d: any; export default d;'
|
||||
})
|
||||
})
|
||||
|
||||
await installModule('@nuxtjs/color-mode', { classSuffix: '' })
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<transition appear v-bind="transitionClass">
|
||||
<MenuItems :class="baseClass" static>
|
||||
<div v-for="(subItems, index) of items" :key="index" :class="groupClass">
|
||||
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled }" :disabled="item.disabled" as="div">
|
||||
<Component v-bind="item" :is="(item.to && NuxtLink) || (item.click && 'button') || 'div'" :class="resolveItemClass({ active, disabled })" @click="e => onItemClick(e, item)">
|
||||
<MenuItem v-for="(item, subIndex) of subItems" :key="subIndex" v-slot="{ active, disabled }" :disabled="item.disabled" @click="e => item.click?.(e)">
|
||||
<Component v-bind="item" :is="(item.to && NuxtLink) || (item.click && 'button') || 'div'" :class="resolveItemClass({ active, disabled })">
|
||||
<slot :name="item.slot" :item="item">
|
||||
<Icon v-if="item.icon" :name="item.icon" :class="[itemIconClass, item.iconClass]" />
|
||||
<Avatar v-if="item.avatar" v-bind="{ size: 'xxs', ...item.avatar }" :class="itemAvatarClass" />
|
||||
@@ -129,6 +129,14 @@ const props = defineProps({
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => {}
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -144,17 +152,9 @@ function resolveItemClass ({ active, disabled }: { active: boolean, disabled: bo
|
||||
)
|
||||
}
|
||||
|
||||
function onItemClick (e, item: any) {
|
||||
if (item.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item.click) {
|
||||
item.click(e)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/menu/menu.ts#L131
|
||||
const menuApi: Ref<any> = ref(null)
|
||||
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
@@ -167,7 +167,7 @@ onMounted(() => {
|
||||
const menuProvidesSymbols = Object.getOwnPropertySymbols(menuProvides)
|
||||
menuApi.value = menuProvidesSymbols.length && menuProvides[menuProvidesSymbols[0]]
|
||||
// stop trigger click propagation on hover
|
||||
menuApi.value?.buttonRef.addEventListener('click', (e: Event) => {
|
||||
menuApi.value?.buttonRef?.addEventListener('click', (e: Event) => {
|
||||
if (props.mode === 'hover') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
@@ -192,7 +192,7 @@ function onMouseOver () {
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
menuApi.value.openMenu && menuApi.value.openMenu()
|
||||
openTimeout = null
|
||||
}, 50)
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
@@ -212,7 +212,7 @@ function onMouseLeave () {
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
menuApi.value.closeMenu && menuApi.value.closeMenu()
|
||||
closeTimeout = null
|
||||
}, 0)
|
||||
}, props.closeDelay)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
>
|
||||
<input :value="modelValue" :required="required" class="absolute inset-0 w-px opacity-0 cursor-default" tabindex="-1">
|
||||
|
||||
<ComboboxButton ref="trigger" v-slot="{ disabled }" as="div">
|
||||
<slot :open="open" :disabled="disabled">
|
||||
<ComboboxButton ref="trigger" v-slot="{ disabled: buttonDisabled }" as="div">
|
||||
<slot :open="open" :disabled="buttonDisabled">
|
||||
<button :class="selectCustomClass" :disabled="disabled" type="button">
|
||||
<slot name="label">
|
||||
<span v-if="modelValue" class="block truncate">{{ modelValue[textAttribute] }}</span>
|
||||
@@ -44,13 +44,13 @@
|
||||
/>
|
||||
<ComboboxOption
|
||||
v-for="(option, index) in filteredOptions"
|
||||
v-slot="{ active, selected, disabled }"
|
||||
v-slot="{ active, selected, disabled: optionDisabled }"
|
||||
:key="index"
|
||||
as="template"
|
||||
:value="option"
|
||||
:disabled="option.disabled"
|
||||
>
|
||||
<li :class="resolveOptionClass({ active, selected, disabled })">
|
||||
<li :class="resolveOptionClass({ active, selected, disabled: optionDisabled })">
|
||||
<div :class="listOptionContainerClass">
|
||||
<slot name="option" :option="option" :active="active" :selected="selected">
|
||||
<span class="block truncate">{{ option[textAttribute] }}</span>
|
||||
@@ -110,7 +110,7 @@ const props = defineProps({
|
||||
default: undefined
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<{ disabled?: boolean }[]>,
|
||||
type: Array as PropType<{ [key: string]: any, disabled?: boolean }[]>,
|
||||
default: () => []
|
||||
},
|
||||
required: {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<div class="flex items-center gap-1.5 min-w-0" :class="{ 'opacity-50': command.disabled }">
|
||||
<slot :name="`${group.key}-command`" :group="group" :command="command">
|
||||
<span v-if="command.prefix" class="u-text-gray-400">{{ command.prefix }}</span>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-if="command.matches?.length" class="truncate" :class="{ 'flex-none': command.suffix }" v-html="highlight(command.matches[0])" />
|
||||
<span v-else class="truncate" :class="{ 'flex-none': command.suffix }">{{ command[commandAttribute] }}</span>
|
||||
<span v-if="command.suffix" class="u-text-gray-400 truncate">{{ command.suffix }}</span>
|
||||
|
||||
@@ -56,6 +56,14 @@ const props = defineProps({
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => {}
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -63,6 +71,7 @@ const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions
|
||||
|
||||
const [trigger, container] = usePopper(popperOptions.value)
|
||||
|
||||
// https://github.com/tailwindlabs/headlessui/blob/f66f4926c489fc15289d528294c23a3dc2aee7b1/packages/%40headlessui-vue/src/components/popover/popover.ts#L151
|
||||
const popoverApi: Ref<any> = ref(null)
|
||||
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
@@ -77,7 +86,7 @@ onMounted(() => {
|
||||
const popoverProvidesSymbols = Object.getOwnPropertySymbols(popoverProvides)
|
||||
popoverApi.value = popoverProvidesSymbols.length && popoverProvides[popoverProvidesSymbols[0]]
|
||||
// stop trigger click propagation on hover
|
||||
popoverApi.value.button.addEventListener('click', (e: Event) => {
|
||||
popoverApi.value?.button?.addEventListener('click', (e: Event) => {
|
||||
if (props.mode === 'hover') {
|
||||
e.stopPropagation()
|
||||
}
|
||||
@@ -89,6 +98,7 @@ function onMouseOver () {
|
||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// cancel programmed closing
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout)
|
||||
@@ -101,13 +111,14 @@ function onMouseOver () {
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
popoverApi.value.togglePopover && popoverApi.value.togglePopover()
|
||||
openTimeout = null
|
||||
}, 50)
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
if (props.mode !== 'hover' || !popoverApi.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// cancel programmed opening
|
||||
if (openTimeout) {
|
||||
clearTimeout(openTimeout)
|
||||
@@ -120,7 +131,7 @@ function onMouseLeave () {
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
popoverApi.value.closePopover && popoverApi.value.closePopover()
|
||||
closeTimeout = null
|
||||
}, 0)
|
||||
}, props.closeDelay)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="trigger" :class="wrapperClass" @mouseover="open = true" @mouseleave="open = false">
|
||||
<div ref="trigger" :class="wrapperClass" @mouseover="onMouseOver" @mouseleave="onMouseLeave">
|
||||
<slot :open="open">
|
||||
Hover me
|
||||
</slot>
|
||||
@@ -52,6 +52,14 @@ const props = defineProps({
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => {}
|
||||
},
|
||||
openDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
closeDelay: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -60,6 +68,43 @@ const popperOptions = computed<PopperOptions>(() => defu({}, props.popperOptions
|
||||
const [trigger, container] = usePopper(popperOptions.value)
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
let openTimeout: NodeJS.Timeout | null = null
|
||||
let closeTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
// Methods
|
||||
|
||||
function onMouseOver () {
|
||||
// cancel programmed closing
|
||||
if (closeTimeout) {
|
||||
clearTimeout(closeTimeout)
|
||||
closeTimeout = null
|
||||
}
|
||||
// dropdown already open
|
||||
if (open.value) {
|
||||
return
|
||||
}
|
||||
openTimeout = openTimeout || setTimeout(() => {
|
||||
open.value = true
|
||||
openTimeout = null
|
||||
}, props.openDelay)
|
||||
}
|
||||
|
||||
function onMouseLeave () {
|
||||
// cancel programmed opening
|
||||
if (openTimeout) {
|
||||
clearTimeout(openTimeout)
|
||||
openTimeout = null
|
||||
}
|
||||
// dropdown already closed
|
||||
if (!open.value) {
|
||||
return
|
||||
}
|
||||
closeTimeout = closeTimeout || setTimeout(() => {
|
||||
open.value = false
|
||||
closeTimeout = null
|
||||
}, props.closeDelay)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
5
src/runtime/types/command-palette.d.ts
vendored
5
src/runtime/types/command-palette.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
||||
import type { FuseSortFunctionMatch, FuseSortFunctionMatchList } from 'fuse.js'
|
||||
import type { Avatar } from './avatar'
|
||||
|
||||
export interface Command {
|
||||
id: string | number
|
||||
prefix?: string
|
||||
@@ -11,6 +11,9 @@ export interface Command {
|
||||
chip?: string
|
||||
disabled?: boolean
|
||||
shortcuts?: string[]
|
||||
group?: string
|
||||
score?: number
|
||||
matches?: (FuseSortFunctionMatch | FuseSortFunctionMatchList)[]
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
|
||||
41
yarn.lock
41
yarn.lock
@@ -1121,14 +1121,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.41.tgz#fbc95422df654ea64e8428eced96ba6ad555d2bb"
|
||||
integrity sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==
|
||||
|
||||
"@vueuse/core@9.3.1", "@vueuse/core@^9.3.1":
|
||||
version "9.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.3.1.tgz#47bc65da9c705fef7b3beca4af764491f0479647"
|
||||
integrity sha512-xriyD+v3D2ObH/UtnkEl+1sbcLBVHNaZaLi/rqoNEe/B92hggDEFQIGXoQUjdRzYOjASHSezf9uCDtmd7LeWyA==
|
||||
"@vueuse/core@9.4.0", "@vueuse/core@^9.4.0":
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.4.0.tgz#afb30f9494b0954e51a489526566b14f1e2c5fb3"
|
||||
integrity sha512-JzgenGj1ZF2BHOen5rsFiAyyI9sXAv7aKhNLlm9b7SwYQeKTcxTWdhudonURCSP3Egl9NQaRBzes2lv/1JUt/Q==
|
||||
dependencies:
|
||||
"@types/web-bluetooth" "^0.0.16"
|
||||
"@vueuse/metadata" "9.3.1"
|
||||
"@vueuse/shared" "9.3.1"
|
||||
"@vueuse/metadata" "9.4.0"
|
||||
"@vueuse/shared" "9.4.0"
|
||||
vue-demi "*"
|
||||
|
||||
"@vueuse/head@~1.0.0-rc.9":
|
||||
@@ -1140,21 +1140,28 @@
|
||||
"@zhead/schema" "^0.9.8"
|
||||
"@zhead/schema-vue" "^0.9.8"
|
||||
|
||||
"@vueuse/integrations@^9.3.1":
|
||||
version "9.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.3.1.tgz#c339e1a862d328629eb3daefd7c38cccd3d18e91"
|
||||
integrity sha512-ydVHxJpLZrO9WbRXs1mNBbgEby8ERkiQumR2LebYVtY/1AFEn+mQ8MkL9pvcXwd9mrQSms+wzssu760n7DaDAw==
|
||||
"@vueuse/integrations@^9.4.0":
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-9.4.0.tgz#101413d5ab66a3a65ccb90764a6d45adc1ec17de"
|
||||
integrity sha512-r4M/HNU+v5qylh6x3bCS76IhEYHlWiQU7ezvVfC5uhwdTb0Ub0Xg4J0Vo/qUQVNYtDBn5NXMlxHhWhx1Wxi3KQ==
|
||||
dependencies:
|
||||
"@vueuse/core" "9.3.1"
|
||||
"@vueuse/shared" "9.3.1"
|
||||
"@vueuse/core" "9.4.0"
|
||||
"@vueuse/shared" "9.4.0"
|
||||
vue-demi "*"
|
||||
|
||||
"@vueuse/metadata@9.3.1":
|
||||
version "9.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.3.1.tgz#4e04d76df1e16f1aede28590c8b7a113ea3d3a7e"
|
||||
integrity sha512-G1BPhtx3OHaL/y4OZBofh6Xt02G1VA9PuOO8nac9sTKMkMqfyez5VfkF3D9GUjSRNO7cVWyH4rceeGXfr2wdMg==
|
||||
"@vueuse/metadata@9.4.0":
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.4.0.tgz#5c8eb105a8ad9eb7b47f78a226ff993560d0bd7f"
|
||||
integrity sha512-7GKMdGAsJyQJl35MYOz/RDpP0FxuiZBRDSN79QIPbdqYx4Sd0sVTnIC68KJ6Oln0t0SouvSUMvRHuno216Ud2Q==
|
||||
|
||||
"@vueuse/shared@9.3.1", "@vueuse/shared@^9.2.0", "@vueuse/shared@^9.3.1":
|
||||
"@vueuse/shared@9.4.0":
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.4.0.tgz#634022fe42b3d5ece1d81d749724966f5071c8c3"
|
||||
integrity sha512-fTuem51KwMCnqUKkI8B57qAIMcFovtGgsCtAeqxIzH3i6nE9VYge+gVfneNHAAy7lj8twbkNfqQSygOPJTm4tQ==
|
||||
dependencies:
|
||||
vue-demi "*"
|
||||
|
||||
"@vueuse/shared@^9.2.0", "@vueuse/shared@^9.3.1":
|
||||
version "9.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.3.1.tgz#78c6cf41d8b75f05460e18a80819bf8164b597a5"
|
||||
integrity sha512-YFu3qcnVeu0S2L4XdQJtBpDcjz6xwqHZtTv/XRhu66/yge1XVhxskUcc7VZbX52xF9A34V6KCfwncP9YDqYFiw==
|
||||
|
||||
Reference in New Issue
Block a user