You've already forked AstralRinth
forked from didirus/AstralRinth
polish(frontend): technical review QA (#5097)
* feat: filtering + sorting alignment * polish: malicious summary modal changes * feat: better filter row using floating panel * fix: re-enable request * fix: lint * polish: jump back to files tab qol * feat: scroll to top of next card when done * fix: show lock icon on preview msg * feat: download no _blank * feat: show also marked in notif * feat: auto expand if only one class in the file * feat: proper page titles * fix: text-contrast typo * fix: lint * feat: QA changes * feat: individual report page + more qa * fix: back btn * fix: broken import * feat: quick reply msgs * fix: in other queue filter * fix: caching threads wrongly * fix: flag filter * feat: toggle enabled by default * fix: dont make btns opacity 50 --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import { ModrinthApiError } from '../../../core/errors'
|
||||
import type { Labrinth } from '../types'
|
||||
|
||||
export class LabrinthProjectsV3Module extends AbstractModule {
|
||||
@@ -67,4 +68,39 @@ export class LabrinthProjectsV3Module extends AbstractModule {
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the organization that owns a project
|
||||
*
|
||||
* @param id - Project ID or slug
|
||||
* @returns Promise resolving to the organization data, or null if the project is not owned by an organization
|
||||
*/
|
||||
public async getOrganization(id: string): Promise<Labrinth.Projects.v3.Organization | null> {
|
||||
try {
|
||||
return await this.client.request<Labrinth.Projects.v3.Organization>(
|
||||
`/project/${id}/organization`,
|
||||
{ api: 'labrinth', version: 3, method: 'GET' },
|
||||
)
|
||||
} catch (error) {
|
||||
// 404 means the project is not owned by an organization
|
||||
if (error instanceof ModrinthApiError && error.statusCode === 404) {
|
||||
return null
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the team members of a project
|
||||
*
|
||||
* @param id - Project ID or slug
|
||||
* @returns Promise resolving to an array of team members
|
||||
*/
|
||||
public async getMembers(id: string): Promise<Labrinth.Projects.v3.TeamMember[]> {
|
||||
return this.client.request<Labrinth.Projects.v3.TeamMember[]>(`/project/${id}/members`, {
|
||||
api: 'labrinth',
|
||||
version: 3,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,4 +121,23 @@ export class LabrinthTechReviewInternalModule extends AbstractModule {
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project report and thread for a specific project.
|
||||
*
|
||||
* @param projectId - The project ID
|
||||
* @returns The project report (may be null if no reports exist) and the moderation thread
|
||||
*/
|
||||
public async getProjectReport(
|
||||
projectId: string,
|
||||
): Promise<Labrinth.TechReview.Internal.ProjectReportResponse> {
|
||||
return this.client.request<Labrinth.TechReview.Internal.ProjectReportResponse>(
|
||||
`/moderation/tech-review/project/${projectId}`,
|
||||
{
|
||||
api: 'labrinth',
|
||||
version: 'internal',
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,6 +363,40 @@ export namespace Labrinth {
|
||||
environment?: Environment
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type Organization = {
|
||||
id: string
|
||||
slug: string
|
||||
name: string
|
||||
team_id: string
|
||||
description: string
|
||||
icon_url: string | null
|
||||
color: number
|
||||
members: OrganizationMember[]
|
||||
}
|
||||
|
||||
export type OrganizationMember = {
|
||||
team_id: string
|
||||
user: Users.v3.User
|
||||
role: string
|
||||
is_owner: boolean
|
||||
permissions: number
|
||||
organization_permissions: number
|
||||
accepted: boolean
|
||||
payouts_split: number
|
||||
ordering: number
|
||||
}
|
||||
|
||||
export type TeamMember = {
|
||||
team_id: string
|
||||
user: Users.v3.User
|
||||
role: string
|
||||
permissions: number
|
||||
accepted: boolean
|
||||
payouts_split: number
|
||||
ordering: number
|
||||
is_owner: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,6 +783,9 @@ export namespace Labrinth {
|
||||
|
||||
export type SearchProjectsFilter = {
|
||||
project_type?: string[]
|
||||
replied_to?: 'replied' | 'unreplied'
|
||||
project_status?: string[]
|
||||
issue_type?: string[]
|
||||
}
|
||||
|
||||
export type SearchProjectsSort =
|
||||
@@ -912,6 +949,11 @@ export namespace Labrinth {
|
||||
export type DelphiSeverity = 'low' | 'medium' | 'high' | 'severe'
|
||||
|
||||
export type DelphiReportIssueStatus = 'pending' | 'safe' | 'unsafe'
|
||||
|
||||
export type ProjectReportResponse = {
|
||||
project_report: ProjectReport | null
|
||||
thread: Thread
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import _ArrowBigRightDashIcon from './icons/arrow-big-right-dash.svg?component'
|
||||
import _ArrowBigUpDashIcon from './icons/arrow-big-up-dash.svg?component'
|
||||
import _ArrowDownIcon from './icons/arrow-down.svg?component'
|
||||
import _ArrowDownLeftIcon from './icons/arrow-down-left.svg?component'
|
||||
import _ArrowLeftIcon from './icons/arrow-left.svg?component'
|
||||
import _ArrowLeftRightIcon from './icons/arrow-left-right.svg?component'
|
||||
import _ArrowUpIcon from './icons/arrow-up.svg?component'
|
||||
import _ArrowUpRightIcon from './icons/arrow-up-right.svg?component'
|
||||
@@ -17,6 +18,7 @@ import _BadgeDollarSignIcon from './icons/badge-dollar-sign.svg?component'
|
||||
import _BanIcon from './icons/ban.svg?component'
|
||||
import _BellIcon from './icons/bell.svg?component'
|
||||
import _BellRingIcon from './icons/bell-ring.svg?component'
|
||||
import _BlendIcon from './icons/blend.svg?component'
|
||||
import _BlocksIcon from './icons/blocks.svg?component'
|
||||
import _BoldIcon from './icons/bold.svg?component'
|
||||
import _BookIcon from './icons/book.svg?component'
|
||||
@@ -241,6 +243,7 @@ export const ArrowBigRightDashIcon = _ArrowBigRightDashIcon
|
||||
export const ArrowBigUpDashIcon = _ArrowBigUpDashIcon
|
||||
export const ArrowDownIcon = _ArrowDownIcon
|
||||
export const ArrowDownLeftIcon = _ArrowDownLeftIcon
|
||||
export const ArrowLeftIcon = _ArrowLeftIcon
|
||||
export const ArrowLeftRightIcon = _ArrowLeftRightIcon
|
||||
export const ArrowUpIcon = _ArrowUpIcon
|
||||
export const ArrowUpRightIcon = _ArrowUpRightIcon
|
||||
@@ -250,6 +253,7 @@ export const BadgeDollarSignIcon = _BadgeDollarSignIcon
|
||||
export const BanIcon = _BanIcon
|
||||
export const BellIcon = _BellIcon
|
||||
export const BellRingIcon = _BellRingIcon
|
||||
export const BlendIcon = _BlendIcon
|
||||
export const BlocksIcon = _BlocksIcon
|
||||
export const BoldIcon = _BoldIcon
|
||||
export const BookIcon = _BookIcon
|
||||
|
||||
16
packages/assets/icons/arrow-left.svg
Normal file
16
packages/assets/icons/arrow-left.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.562.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-arrow-left"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m12 19-7-7 7-7" />
|
||||
<path d="M19 12H5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 344 B |
16
packages/assets/icons/blend.svg
Normal file
16
packages/assets/icons/blend.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.562.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-blend"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="9" cy="9" r="7" />
|
||||
<circle cx="15" cy="15" r="7" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 353 B |
@@ -0,0 +1,5 @@
|
||||
## Indefinitely Rejected
|
||||
|
||||
A project you uploaded has been found to contain or distribute malicious files, this is strictly prohibited and a violation of [Modrinth's Terms of Use](https://modrinth.com/legal/terms).
|
||||
Our Moderation team has determined this project, and all projects associated with your account should be rejected indefinitely.
|
||||
We believe this is the best course of action at this time and ask that you **do not resubmit this project**.
|
||||
@@ -0,0 +1,7 @@
|
||||
## Source Code Requested
|
||||
|
||||
To ensure the safety of all Modrinth users, we ask that you provide the source code for this project before resubmission so that it can be reviewed by our Moderation Team.
|
||||
|
||||
We also ask that you provide the source for any included binary files, as well as detailed build instructions allowing us to verify that the compiled code you are distributing matches the provided source.
|
||||
|
||||
We understand that you may not want to publish the source code for this project, so you are welcome to share it privately to the [Modrinth Content Moderation Team](https://github.com/ModrinthModeration) on GitHub.
|
||||
@@ -0,0 +1,5 @@
|
||||
## Source Code Requested
|
||||
|
||||
To ensure the safety of all Modrinth users, we ask that you provide the source code for this project, steps on how to build it, and the process you used to obfuscate it before resubmission so that it can be reviewed by our Moderation Team.
|
||||
|
||||
We understand that you may not want to publish the source code for this project, so you are welcome to share it privately to the [Modrinth Content Moderation Team](https://github.com/ModrinthModeration) on GitHub.
|
||||
@@ -0,0 +1,5 @@
|
||||
## Source Code Requested
|
||||
|
||||
To ensure the safety of all Modrinth users, we ask that you provide the source code for this project before resubmission so that it can be reviewed by our Moderation Team.
|
||||
|
||||
We understand that you may not want to publish the source code for this project, so you are welcome to share it privately to the [Modrinth Content Moderation Team](https://github.com/ModrinthModeration) on GitHub.
|
||||
@@ -0,0 +1,7 @@
|
||||
## Description Clarity
|
||||
|
||||
Per section 2 of [Modrinth's Content Rules](https://modrinth.com/legal/rules) It's important that your Description accurately and honestly represents the content of your project.
|
||||
Currently, some elements in your Description may be confusing or misleading.
|
||||
Please edit your description to ensure it accurately represents the current functionality of your project.
|
||||
Avoid making hyperbolic claims that could misrepresent the facts of your project.
|
||||
Ensure that your Description is accurate and not likely to confuse users.
|
||||
@@ -8,4 +8,30 @@ export interface TechReviewContext {
|
||||
reports: Labrinth.TechReview.Internal.FileReport[]
|
||||
}
|
||||
|
||||
export default [] as ReadonlyArray<QuickReply<TechReviewContext>>
|
||||
export default [
|
||||
{
|
||||
label: '⚠️ Unclear/Misleading',
|
||||
message: async () => (await import('./messages/tech-review/unclear-misleading.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '📝 Request Source',
|
||||
message: async () => (await import('./messages/tech-review/request-source.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🔒 Request Source (Obf)',
|
||||
message: async () => (await import('./messages/tech-review/request-source-obf.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '📦 Request Source (Bin)',
|
||||
message: async () => (await import('./messages/tech-review/request-source-bin.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
{
|
||||
label: '🚫 Malware',
|
||||
message: async () => (await import('./messages/tech-review/malware.md?raw')).default,
|
||||
private: false,
|
||||
},
|
||||
] as ReadonlyArray<QuickReply<TechReviewContext>>
|
||||
|
||||
310
packages/ui/src/components/base/FloatingPanel.vue
Normal file
310
packages/ui/src/components/base/FloatingPanel.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<script setup lang="ts">
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import ButtonStyled from './ButtonStyled.vue'
|
||||
|
||||
const PANEL_VIEWPORT_MARGIN = 8
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'
|
||||
distance?: number
|
||||
disabled?: boolean
|
||||
buttonClass?: string
|
||||
panelClass?: string
|
||||
}>(),
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
distance: 8,
|
||||
disabled: false,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
open: []
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const triggerRef = ref<HTMLElement>()
|
||||
const panelRef = ref<HTMLElement>()
|
||||
const rafId = ref<number | null>(null)
|
||||
|
||||
const openDirection = ref<'up' | 'down'>('down')
|
||||
const horizontalAlignment = ref<'start' | 'end'>('end')
|
||||
|
||||
const panelStyle = ref({
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
})
|
||||
|
||||
const transformOrigin = computed(() => {
|
||||
const vertical = openDirection.value === 'down' ? 'top' : 'bottom'
|
||||
const horizontal = horizontalAlignment.value === 'end' ? 'right' : 'left'
|
||||
return `${vertical} ${horizontal}`
|
||||
})
|
||||
|
||||
function determineOpenDirection(
|
||||
triggerRect: DOMRect,
|
||||
panelRect: DOMRect,
|
||||
viewportHeight: number,
|
||||
): 'up' | 'down' {
|
||||
const preferDown = props.placement.startsWith('bottom')
|
||||
|
||||
const hasSpaceBelow =
|
||||
triggerRect.bottom + props.distance + panelRect.height + PANEL_VIEWPORT_MARGIN <= viewportHeight
|
||||
const hasSpaceAbove =
|
||||
triggerRect.top - props.distance - panelRect.height - PANEL_VIEWPORT_MARGIN >= 0
|
||||
|
||||
if (preferDown) {
|
||||
return hasSpaceBelow ? 'down' : hasSpaceAbove ? 'up' : 'down'
|
||||
} else {
|
||||
return hasSpaceAbove ? 'up' : hasSpaceBelow ? 'down' : 'up'
|
||||
}
|
||||
}
|
||||
|
||||
function calculateVerticalPosition(
|
||||
triggerRect: DOMRect,
|
||||
panelRect: DOMRect,
|
||||
direction: 'up' | 'down',
|
||||
): number {
|
||||
return direction === 'up'
|
||||
? triggerRect.top - panelRect.height - props.distance
|
||||
: triggerRect.bottom + props.distance
|
||||
}
|
||||
|
||||
function calculateHorizontalPosition(
|
||||
triggerRect: DOMRect,
|
||||
panelRect: DOMRect,
|
||||
viewportWidth: number,
|
||||
): number {
|
||||
const alignEnd = props.placement.endsWith('end')
|
||||
let left: number
|
||||
|
||||
if (alignEnd) {
|
||||
left = triggerRect.right - panelRect.width
|
||||
} else {
|
||||
left = triggerRect.left
|
||||
}
|
||||
|
||||
if (left + panelRect.width > viewportWidth - PANEL_VIEWPORT_MARGIN) {
|
||||
left = Math.max(PANEL_VIEWPORT_MARGIN, viewportWidth - panelRect.width - PANEL_VIEWPORT_MARGIN)
|
||||
}
|
||||
if (left < PANEL_VIEWPORT_MARGIN) {
|
||||
left = PANEL_VIEWPORT_MARGIN
|
||||
}
|
||||
|
||||
return left
|
||||
}
|
||||
|
||||
async function updatePanelPosition() {
|
||||
if (!triggerRef.value || !panelRef.value) return
|
||||
|
||||
await nextTick()
|
||||
|
||||
const triggerRect = triggerRef.value.getBoundingClientRect()
|
||||
const panelRect = panelRef.value.getBoundingClientRect()
|
||||
const viewportHeight = window.innerHeight
|
||||
const viewportWidth = window.innerWidth
|
||||
|
||||
const direction = determineOpenDirection(triggerRect, panelRect, viewportHeight)
|
||||
const top = calculateVerticalPosition(triggerRect, panelRect, direction)
|
||||
const left = calculateHorizontalPosition(triggerRect, panelRect, viewportWidth)
|
||||
|
||||
panelStyle.value = {
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
}
|
||||
|
||||
openDirection.value = direction
|
||||
horizontalAlignment.value = props.placement.endsWith('end') ? 'end' : 'start'
|
||||
}
|
||||
|
||||
function startPositionTracking() {
|
||||
function track() {
|
||||
updatePanelPosition()
|
||||
rafId.value = requestAnimationFrame(track)
|
||||
}
|
||||
rafId.value = requestAnimationFrame(track)
|
||||
}
|
||||
|
||||
function stopPositionTracking() {
|
||||
if (rafId.value !== null) {
|
||||
cancelAnimationFrame(rafId.value)
|
||||
rafId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function focusPanelContent() {
|
||||
if (!panelRef.value) return
|
||||
|
||||
const focusable = panelRef.value.querySelector<HTMLElement>(
|
||||
'button:not([data-focus-trap]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
||||
)
|
||||
if (focusable) {
|
||||
focusable.focus()
|
||||
}
|
||||
}
|
||||
|
||||
async function open() {
|
||||
if (props.disabled || isOpen.value) return
|
||||
|
||||
isOpen.value = true
|
||||
emit('open')
|
||||
|
||||
await nextTick()
|
||||
await updatePanelPosition()
|
||||
startPositionTracking()
|
||||
|
||||
setTimeout(() => {
|
||||
focusPanelContent()
|
||||
}, 50)
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!isOpen.value) return
|
||||
|
||||
stopPositionTracking()
|
||||
isOpen.value = false
|
||||
emit('close')
|
||||
|
||||
nextTick(() => {
|
||||
triggerRef.value?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (isOpen.value) {
|
||||
close()
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
onClickOutside(
|
||||
panelRef,
|
||||
() => {
|
||||
close()
|
||||
},
|
||||
{ ignore: [triggerRef, '#teleports'] },
|
||||
)
|
||||
|
||||
function handleTriggerKeydown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
event.preventDefault()
|
||||
toggle()
|
||||
break
|
||||
case 'ArrowDown':
|
||||
event.preventDefault()
|
||||
open()
|
||||
break
|
||||
case 'ArrowUp':
|
||||
event.preventDefault()
|
||||
open()
|
||||
break
|
||||
case 'Escape':
|
||||
if (isOpen.value) {
|
||||
event.preventDefault()
|
||||
close()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function handlePanelKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
function handleWindowResize() {
|
||||
if (isOpen.value) {
|
||||
updatePanelPosition()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleWindowResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleWindowResize)
|
||||
stopPositionTracking()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close,
|
||||
toggle,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative inline-block">
|
||||
<ButtonStyled v-bind="$attrs">
|
||||
<button
|
||||
ref="triggerRef"
|
||||
:class="buttonClass"
|
||||
:disabled="disabled"
|
||||
:aria-expanded="isOpen"
|
||||
aria-haspopup="true"
|
||||
@click="toggle"
|
||||
@keydown="handleTriggerKeydown"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
<Teleport to="body">
|
||||
<Transition
|
||||
enter-active-class="floating-panel-enter-active"
|
||||
enter-from-class="floating-panel-enter-from"
|
||||
enter-to-class="floating-panel-enter-to"
|
||||
leave-active-class="floating-panel-leave-active"
|
||||
leave-from-class="floating-panel-leave-from"
|
||||
leave-to-class="floating-panel-leave-to"
|
||||
>
|
||||
<div
|
||||
v-if="isOpen"
|
||||
ref="panelRef"
|
||||
class="fixed z-[9995] w-fit rounded-[14px] border border-surface-5 bg-surface-3 border-solid border-px p-3 shadow-2xl"
|
||||
:class="panelClass"
|
||||
:style="[panelStyle, { transformOrigin }]"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
@keydown="handlePanelKeydown"
|
||||
@mousedown.stop
|
||||
>
|
||||
<button class="sr-only" data-focus-trap @focusin="close"></button>
|
||||
<slot name="panel"></slot>
|
||||
<button class="sr-only" data-focus-trap @focusin="close"></button>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* .floating-panel-enter-active,
|
||||
.floating-panel-leave-active {
|
||||
transition:
|
||||
transform 0.125s ease-in-out,
|
||||
opacity 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
.floating-panel-enter-from,
|
||||
.floating-panel-leave-to {
|
||||
transform: scale(0.85);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.floating-panel-enter-to,
|
||||
.floating-panel-leave-from {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
} */
|
||||
</style>
|
||||
@@ -27,6 +27,7 @@ export { default as FileInput } from './FileInput.vue'
|
||||
export type { FilterBarOption } from './FilterBar.vue'
|
||||
export { default as FilterBar } from './FilterBar.vue'
|
||||
export { default as FloatingActionBar } from './FloatingActionBar.vue'
|
||||
export { default as FloatingPanel } from './FloatingPanel.vue'
|
||||
export { default as HeadingLink } from './HeadingLink.vue'
|
||||
export { default as HorizontalRule } from './HorizontalRule.vue'
|
||||
export { default as IconSelect } from './IconSelect.vue'
|
||||
|
||||
Reference in New Issue
Block a user