mirror of
https://github.com/directus/directus.git
synced 2026-01-28 13:38:10 -05:00
Move to folder (#700)
* Add move to folder on file detail * Add save logic for move to folder on detail * Change style to be inline * Only stop propagation on toggle * Add move to folder on browse
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="v-list-group">
|
<div class="v-list-group">
|
||||||
<v-list-item :active="active" class="activator" :to="to" @click.stop="onClick">
|
<v-list-item :active="active" class="activator" :to="to" @click="onClick">
|
||||||
<slot name="activator" :active="groupActive" />
|
<slot name="activator" :active="groupActive" />
|
||||||
|
|
||||||
<v-list-item-icon class="activator-icon" :class="{ active: groupActive }">
|
<v-list-item-icon class="activator-icon" :class="{ active: groupActive }">
|
||||||
@@ -51,6 +51,7 @@ export default defineComponent({
|
|||||||
if (props.to) return null;
|
if (props.to) return null;
|
||||||
if (listeners.click) return emit('click', event);
|
if (listeners.click) return emit('click', event);
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
toggle();
|
toggle();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -121,6 +121,10 @@
|
|||||||
|
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
|
|
||||||
|
"move_to_folder": "Move to Folder",
|
||||||
|
"select_folder": "Select Folder",
|
||||||
|
"move": "Move",
|
||||||
|
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"include_system_collections": "Include System Collections",
|
"include_system_collections": "Include System Collections",
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<v-list-item
|
||||||
|
v-if="folder.children.length === 0"
|
||||||
|
@click="clickHandler(folder.id)"
|
||||||
|
:active="currentFolder === folder.id"
|
||||||
|
>
|
||||||
|
<v-list-item-icon><v-icon :name="currentFolder === folder.id ? 'folder_open' : 'folder'" /></v-list-item-icon>
|
||||||
|
<v-list-item-content>{{ folder.name }}</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-group v-else @click="clickHandler(folder.id)" :active="currentFolder === folder.id">
|
||||||
|
<template #activator>
|
||||||
|
<v-list-item-icon>
|
||||||
|
<v-icon :name="currentFolder === folder.id ? 'folder_open' : 'folder'" />
|
||||||
|
</v-list-item-icon>
|
||||||
|
<v-list-item-content>{{ folder.name }}</v-list-item-content>
|
||||||
|
</template>
|
||||||
|
<folder-picker-list-item
|
||||||
|
v-for="childFolder in folder.children"
|
||||||
|
:key="childFolder.id"
|
||||||
|
:folder="childFolder"
|
||||||
|
:current-folder="currentFolder"
|
||||||
|
:click-handler="clickHandler"
|
||||||
|
/>
|
||||||
|
</v-list-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from '@vue/composition-api';
|
||||||
|
|
||||||
|
type Folder = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
children: Folder[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'folder-picker-list-item',
|
||||||
|
props: {
|
||||||
|
folder: {
|
||||||
|
type: Object as PropType<Folder>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
currentFolder: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
clickHandler: {
|
||||||
|
type: Function,
|
||||||
|
default: () => undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
119
src/modules/files/components/folder-picker/folder-picker.vue
Normal file
119
src/modules/files/components/folder-picker/folder-picker.vue
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<v-skeleton-loader v-if="loading" />
|
||||||
|
<div class="folder-picker" v-else>
|
||||||
|
<v-list>
|
||||||
|
<v-list-group @click="$emit('input', null)" :active="value === null">
|
||||||
|
<template #activator>
|
||||||
|
<v-list-item-icon>
|
||||||
|
<v-icon name="folder_special" />
|
||||||
|
</v-list-item-icon>
|
||||||
|
<v-list-item-content>{{ $t('file_library') }}</v-list-item-content>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<folder-picker-list-item
|
||||||
|
v-for="folder in tree"
|
||||||
|
:key="folder.id"
|
||||||
|
:folder="folder"
|
||||||
|
:current-folder="value"
|
||||||
|
:click-handler="(id) => $emit('input', id)"
|
||||||
|
/>
|
||||||
|
</v-list-group>
|
||||||
|
</v-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref, computed } from '@vue/composition-api';
|
||||||
|
import useProjectsStore from '@/stores/projects';
|
||||||
|
import api from '@/api';
|
||||||
|
import FolderPickerListItem from './folder-picker-list-item.vue';
|
||||||
|
|
||||||
|
type FolderRaw = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
parent_folder: null | number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Folder = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
children: Folder[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { FolderPickerListItem },
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const projectsStore = useProjectsStore();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const folders = ref<FolderRaw[]>([]);
|
||||||
|
const error = ref<any>(null);
|
||||||
|
const tree = computed<Folder[]>(() => {
|
||||||
|
return folders.value
|
||||||
|
.filter((folder) => folder.parent_folder === null)
|
||||||
|
.map((folder) => {
|
||||||
|
return {
|
||||||
|
...folder,
|
||||||
|
children: getChildFolders(folder),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function getChildFolders(folder: FolderRaw): Folder[] {
|
||||||
|
return folders.value
|
||||||
|
.filter((childFolder) => {
|
||||||
|
return childFolder.parent_folder === folder.id;
|
||||||
|
})
|
||||||
|
.map((childFolder) => {
|
||||||
|
return {
|
||||||
|
...childFolder,
|
||||||
|
children: getChildFolders(childFolder),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedFolder = computed(() => {
|
||||||
|
return folders.value.find((folder) => folder.id === props.value) || {};
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchFolders();
|
||||||
|
|
||||||
|
return { loading, folders, tree, selectedFolder };
|
||||||
|
|
||||||
|
async function fetchFolders() {
|
||||||
|
if (folders.value.length > 0) return;
|
||||||
|
const { currentProjectKey } = projectsStore.state;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/${currentProjectKey}/folders`, {
|
||||||
|
params: {
|
||||||
|
limit: -1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
folders.value = response.data.data;
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.folder-picker {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: var(--background-normal);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
4
src/modules/files/components/folder-picker/index.ts
Normal file
4
src/modules/files/components/folder-picker/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import FolderPicker from './folder-picker.vue';
|
||||||
|
|
||||||
|
export default FolderPicker;
|
||||||
|
export { FolderPicker };
|
||||||
@@ -13,6 +13,33 @@
|
|||||||
<template #actions>
|
<template #actions>
|
||||||
<search-input v-model="searchQuery" />
|
<search-input v-model="searchQuery" />
|
||||||
|
|
||||||
|
<add-folder :parent="currentFolder" />
|
||||||
|
|
||||||
|
<v-dialog v-model="moveToDialogActive" v-if="selection.length > 0">
|
||||||
|
<template #activator="{ on }">
|
||||||
|
<v-button rounded icon @click="on" class="folder">
|
||||||
|
<v-icon name="folder" />
|
||||||
|
</v-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>{{ $t('move_to_folder') }}</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<folder-picker v-model="selectedFolder" />
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-button @click="moveToDialogActive = false" secondary>
|
||||||
|
{{ $t('cancel') }}
|
||||||
|
</v-button>
|
||||||
|
<v-button @click="moveToFolder" :loading="moving">
|
||||||
|
{{ $t('move') }}
|
||||||
|
</v-button>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
<v-dialog v-model="confirmDelete" v-if="selection.length > 0">
|
<v-dialog v-model="confirmDelete" v-if="selection.length > 0">
|
||||||
<template #activator="{ on }">
|
<template #activator="{ on }">
|
||||||
<v-button rounded icon class="action-delete" @click="on">
|
<v-button rounded icon class="action-delete" @click="on">
|
||||||
@@ -38,8 +65,6 @@
|
|||||||
<v-icon name="edit" />
|
<v-icon name="edit" />
|
||||||
</v-button>
|
</v-button>
|
||||||
|
|
||||||
<add-folder :parent="currentFolder" />
|
|
||||||
|
|
||||||
<v-button rounded icon :to="addNewLink">
|
<v-button rounded icon :to="addNewLink">
|
||||||
<v-icon name="add" />
|
<v-icon name="add" />
|
||||||
</v-button>
|
</v-button>
|
||||||
@@ -89,6 +114,7 @@ import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail'
|
|||||||
import AddFolder from '../../components/add-folder';
|
import AddFolder from '../../components/add-folder';
|
||||||
import SearchInput from '@/views/private/components/search-input';
|
import SearchInput from '@/views/private/components/search-input';
|
||||||
import marked from 'marked';
|
import marked from 'marked';
|
||||||
|
import FolderPicker from '../../components/folder-picker';
|
||||||
|
|
||||||
type Item = {
|
type Item = {
|
||||||
[field: string]: any;
|
[field: string]: any;
|
||||||
@@ -96,7 +122,7 @@ type Item = {
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'files-browse',
|
name: 'files-browse',
|
||||||
components: { FilesNavigation, FilterDrawerDetail, LayoutDrawerDetail, AddFolder, SearchInput },
|
components: { FilesNavigation, FilterDrawerDetail, LayoutDrawerDetail, AddFolder, SearchInput, FolderPicker },
|
||||||
props: {},
|
props: {},
|
||||||
setup() {
|
setup() {
|
||||||
const layout = ref<LayoutComponent | null>(null);
|
const layout = ref<LayoutComponent | null>(null);
|
||||||
@@ -153,6 +179,8 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { moveToDialogActive, moveToFolder, moving, selectedFolder } = useMovetoFolder();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addNewLink,
|
addNewLink,
|
||||||
batchDelete,
|
batchDelete,
|
||||||
@@ -170,6 +198,10 @@ export default defineComponent({
|
|||||||
filtersWithFolderAndType,
|
filtersWithFolderAndType,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
marked,
|
marked,
|
||||||
|
moveToDialogActive,
|
||||||
|
moveToFolder,
|
||||||
|
moving,
|
||||||
|
selectedFolder,
|
||||||
};
|
};
|
||||||
|
|
||||||
function useBatchDelete() {
|
function useBatchDelete() {
|
||||||
@@ -226,6 +258,32 @@ export default defineComponent({
|
|||||||
|
|
||||||
return { breadcrumb };
|
return { breadcrumb };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useMovetoFolder() {
|
||||||
|
const moveToDialogActive = ref(false);
|
||||||
|
const moving = ref(false);
|
||||||
|
const selectedFolder = ref<number | null>();
|
||||||
|
|
||||||
|
return { moveToDialogActive, moving, moveToFolder, selectedFolder };
|
||||||
|
|
||||||
|
async function moveToFolder() {
|
||||||
|
moving.value = true;
|
||||||
|
const { currentProjectKey } = projectsStore.state;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.patch(`/${currentProjectKey}/files/${selection.value}`, {
|
||||||
|
folder: selectedFolder.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
await layout.value?.refresh();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
moveToDialogActive.value = false;
|
||||||
|
moving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -245,6 +303,13 @@ export default defineComponent({
|
|||||||
--v-button-color-hover: var(--warning);
|
--v-button-color-hover: var(--warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.folder {
|
||||||
|
--v-button-background-color: var(--primary-25);
|
||||||
|
--v-button-color: var(--primary);
|
||||||
|
--v-button-background-color-hover: var(--primary-50);
|
||||||
|
--v-button-color-hover: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
.header-icon {
|
.header-icon {
|
||||||
--v-button-color-disabled: var(--foreground-normal);
|
--v-button-color-disabled: var(--foreground-normal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ export default defineComponent({
|
|||||||
watch(
|
watch(
|
||||||
() => props.file,
|
() => props.file,
|
||||||
async () => {
|
async () => {
|
||||||
|
if (!props.file) return null;
|
||||||
|
|
||||||
creationDate.value = await localizedFormat(
|
creationDate.value = await localizedFormat(
|
||||||
new Date(props.file.uploaded_on),
|
new Date(props.file.uploaded_on),
|
||||||
String(i18n.t('date-fns_date_short'))
|
String(i18n.t('date-fns_date_short'))
|
||||||
@@ -116,6 +118,8 @@ export default defineComponent({
|
|||||||
return { user };
|
return { user };
|
||||||
|
|
||||||
async function fetchUser() {
|
async function fetchUser() {
|
||||||
|
if (!props.file) return null;
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { currentProjectKey } = projectsStore.state;
|
const { currentProjectKey } = projectsStore.state;
|
||||||
|
|
||||||
@@ -153,6 +157,7 @@ export default defineComponent({
|
|||||||
return { folder };
|
return { folder };
|
||||||
|
|
||||||
async function fetchFolder() {
|
async function fetchFolder() {
|
||||||
|
if (!props.file) return null;
|
||||||
if (!props.file.folder) return;
|
if (!props.file.folder) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { currentProjectKey } = projectsStore.state;
|
const { currentProjectKey } = projectsStore.state;
|
||||||
|
|||||||
@@ -32,6 +32,31 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<v-dialog v-model="moveToDialogActive" v-if="isNew === false">
|
||||||
|
<template #activator="{ on }">
|
||||||
|
<v-button rounded icon :disabled="item === null" @click="on" class="folder">
|
||||||
|
<v-icon name="folder" />
|
||||||
|
</v-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>{{ $t('move_to_folder') }}</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<folder-picker v-model="selectedFolder" />
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-button @click="moveToDialogActive = false" secondary>
|
||||||
|
{{ $t('cancel') }}
|
||||||
|
</v-button>
|
||||||
|
<v-button @click="moveToFolder" :loading="moving">
|
||||||
|
{{ $t('move') }}
|
||||||
|
</v-button>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
<v-button v-if="item && item.type.includes('image')" rounded icon @click="editActive = true" class="edit">
|
<v-button v-if="item && item.type.includes('image')" rounded icon @click="editActive = true" class="edit">
|
||||||
<v-icon name="tune" />
|
<v-icon name="tune" />
|
||||||
</v-button>
|
</v-button>
|
||||||
@@ -136,6 +161,8 @@ import { Field } from '@/stores/fields/types';
|
|||||||
import FileInfoDrawerDetail from './components/file-info-drawer-detail.vue';
|
import FileInfoDrawerDetail from './components/file-info-drawer-detail.vue';
|
||||||
import marked from 'marked';
|
import marked from 'marked';
|
||||||
import useFormFields from '@/composables/use-form-fields';
|
import useFormFields from '@/composables/use-form-fields';
|
||||||
|
import FolderPicker from '../../components/folder-picker';
|
||||||
|
import api from '@/api';
|
||||||
|
|
||||||
type Values = {
|
type Values = {
|
||||||
[field: string]: any;
|
[field: string]: any;
|
||||||
@@ -164,6 +191,7 @@ export default defineComponent({
|
|||||||
ImageEditor,
|
ImageEditor,
|
||||||
FileLightbox,
|
FileLightbox,
|
||||||
FileInfoDrawerDetail,
|
FileInfoDrawerDetail,
|
||||||
|
FolderPicker,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
primaryKey: {
|
primaryKey: {
|
||||||
@@ -180,10 +208,20 @@ export default defineComponent({
|
|||||||
|
|
||||||
const revisionsDrawerDetail = ref<Vue | null>(null);
|
const revisionsDrawerDetail = ref<Vue | null>(null);
|
||||||
|
|
||||||
const { isNew, edits, item, saving, loading, error, save, remove, deleting, saveAsCopy, isBatch } = useItem(
|
const {
|
||||||
ref('directus_files'),
|
isNew,
|
||||||
primaryKey
|
edits,
|
||||||
);
|
item,
|
||||||
|
saving,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
save,
|
||||||
|
remove,
|
||||||
|
deleting,
|
||||||
|
saveAsCopy,
|
||||||
|
isBatch,
|
||||||
|
refresh,
|
||||||
|
} = useItem(ref('directus_files'), primaryKey);
|
||||||
|
|
||||||
const hasEdits = computed<boolean>(() => Object.keys(edits.value).length > 0);
|
const hasEdits = computed<boolean>(() => Object.keys(edits.value).length > 0);
|
||||||
const confirmDelete = ref(false);
|
const confirmDelete = ref(false);
|
||||||
@@ -214,6 +252,8 @@ export default defineComponent({
|
|||||||
const confirmLeave = ref(false);
|
const confirmLeave = ref(false);
|
||||||
const leaveTo = ref<string | null>(null);
|
const leaveTo = ref<string | null>(null);
|
||||||
|
|
||||||
|
const { moveToDialogActive, moveToFolder, moving, selectedFolder } = useMovetoFolder();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
item,
|
item,
|
||||||
loading,
|
loading,
|
||||||
@@ -241,6 +281,10 @@ export default defineComponent({
|
|||||||
confirmLeave,
|
confirmLeave,
|
||||||
leaveTo,
|
leaveTo,
|
||||||
discardAndLeave,
|
discardAndLeave,
|
||||||
|
moveToDialogActive,
|
||||||
|
moveToFolder,
|
||||||
|
moving,
|
||||||
|
selectedFolder,
|
||||||
};
|
};
|
||||||
|
|
||||||
function changeCacheBuster() {
|
function changeCacheBuster() {
|
||||||
@@ -295,6 +339,31 @@ export default defineComponent({
|
|||||||
edits.value = {};
|
edits.value = {};
|
||||||
router.push(leaveTo.value);
|
router.push(leaveTo.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useMovetoFolder() {
|
||||||
|
const moveToDialogActive = ref(false);
|
||||||
|
const moving = ref(false);
|
||||||
|
const selectedFolder = ref<number | null>();
|
||||||
|
|
||||||
|
return { moveToDialogActive, moving, moveToFolder, selectedFolder };
|
||||||
|
|
||||||
|
async function moveToFolder() {
|
||||||
|
moving.value = true;
|
||||||
|
const { currentProjectKey } = projectsStore.state;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.patch(`/${currentProjectKey}/files/${props.primaryKey}`, {
|
||||||
|
folder: selectedFolder.value,
|
||||||
|
});
|
||||||
|
await refresh();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
moveToDialogActive.value = false;
|
||||||
|
moving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -311,7 +380,8 @@ export default defineComponent({
|
|||||||
--v-button-background-color: var(--background-normal);
|
--v-button-background-color: var(--background-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit {
|
.edit,
|
||||||
|
.folder {
|
||||||
--v-button-background-color: var(--primary-25);
|
--v-button-background-color: var(--primary-25);
|
||||||
--v-button-color: var(--primary);
|
--v-button-color: var(--primary);
|
||||||
--v-button-background-color-hover: var(--primary-50);
|
--v-button-background-color-hover: var(--primary-50);
|
||||||
|
|||||||
Reference in New Issue
Block a user