You've already forked SmartisanNote.Remake
新增 Modal组件输入框功能,支持文本输入和Promise返回值
This commit is contained in:
@@ -88,6 +88,9 @@
|
|||||||
--white-60: #ffffff99; /* 60% white */
|
--white-60: #ffffff99; /* 60% white */
|
||||||
--white-80: #ffffffcc; /* 80% white */
|
--white-80: #ffffffcc; /* 80% white */
|
||||||
--white-90: #ffffffe6; /* 90% white */
|
--white-90: #ffffffe6; /* 90% white */
|
||||||
|
|
||||||
|
--confirmFontSize: 0.8rem;
|
||||||
|
--confirmBg: rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"vue": "^3.5.22",
|
"vue": "^3.5.22",
|
||||||
"vue-draggable-plus": "^0.6.0",
|
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
13
src/App.vue
13
src/App.vue
@@ -13,19 +13,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 设置页面 -->
|
<!-- 设置页面 -->
|
||||||
<transition
|
<transition name="settings-slide" v-show="isSettingsRoute" appear>
|
||||||
name="settings-slide"
|
|
||||||
v-show="isSettingsRoute"
|
|
||||||
appear>
|
|
||||||
<SettingsPage class="setting-page" />
|
<SettingsPage class="setting-page" />
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
|
<Modal ref="modalRef" showInput />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, computed } from 'vue'
|
import { ref, watch, computed, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import '@/common/base.css'
|
import '@/common/base.css'
|
||||||
|
import Modal from '@/components/Modal.vue'
|
||||||
|
|
||||||
// 导入页面组件
|
// 导入页面组件
|
||||||
import NoteListPage from './pages/NoteListPage.vue'
|
import NoteListPage from './pages/NoteListPage.vue'
|
||||||
@@ -33,6 +33,7 @@ import SettingsPage from './pages/SettingsPage.vue'
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const transitionName = ref('slide-left')
|
const transitionName = ref('slide-left')
|
||||||
|
const modalRef = ref()
|
||||||
|
|
||||||
// 计算是否为设置页面路由
|
// 计算是否为设置页面路由
|
||||||
const isSettingsRoute = computed(() => {
|
const isSettingsRoute = computed(() => {
|
||||||
@@ -72,8 +73,6 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 无额外处理函数
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@@ -1,249 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="visible" class="pd-mask" @click="handleMaskClick">
|
|
||||||
<div class="pd-confirm" @click.stop>
|
|
||||||
<div class="pd-blur"></div>
|
|
||||||
<div class="pd-plan"></div>
|
|
||||||
<h2 class="pd-title" v-if="title">{{ title }}</h2>
|
|
||||||
<p class="pd-message">{{ message }}</p>
|
|
||||||
<div class="pd-buttons">
|
|
||||||
<button v-if="showConfirm" class="pd-button pd-confirm-btn" @click="handleConfirm">
|
|
||||||
{{ confirmText }}
|
|
||||||
</button>
|
|
||||||
<button v-if="showCancel" class="pd-button pd-cancel" @click="handleCancel">
|
|
||||||
{{ cancelText }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
message: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
confirmText: {
|
|
||||||
type: String,
|
|
||||||
default: '确认',
|
|
||||||
},
|
|
||||||
cancelText: {
|
|
||||||
type: String,
|
|
||||||
default: '取消',
|
|
||||||
},
|
|
||||||
showConfirm: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
showCancel: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
maskClosable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['confirm', 'cancel', 'update:visible'])
|
|
||||||
const visible = defineModel('visible', { type: Boolean, default: false })
|
|
||||||
|
|
||||||
// Promise 控制变量
|
|
||||||
let resolvePromise, rejectPromise
|
|
||||||
|
|
||||||
// 返回 Promise 的方法,模拟原生 confirm 行为
|
|
||||||
const show = (options = {}) => {
|
|
||||||
// 更新 props 值
|
|
||||||
Object.assign(props, options)
|
|
||||||
|
|
||||||
// 显示对话框
|
|
||||||
visible.value = true
|
|
||||||
emit('update:visible', true)
|
|
||||||
|
|
||||||
// 返回 Promise
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
resolvePromise = resolve
|
|
||||||
rejectPromise = reject
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
|
||||||
emit('confirm')
|
|
||||||
visible.value = false
|
|
||||||
emit('update:visible', false)
|
|
||||||
|
|
||||||
// 解决 Promise
|
|
||||||
if (resolvePromise) {
|
|
||||||
resolvePromise()
|
|
||||||
resolvePromise = null
|
|
||||||
rejectPromise = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
emit('cancel')
|
|
||||||
visible.value = false
|
|
||||||
emit('update:visible', false)
|
|
||||||
|
|
||||||
// 拒绝 Promise
|
|
||||||
if (rejectPromise) {
|
|
||||||
rejectPromise()
|
|
||||||
resolvePromise = null
|
|
||||||
rejectPromise = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMaskClick = () => {
|
|
||||||
if (props.maskClosable) {
|
|
||||||
handleCancel() // 点击遮罩相当于取消
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加/移除 body 滚动锁定
|
|
||||||
const lockBodyScroll = () => {
|
|
||||||
document.body.style.overflow = 'hidden'
|
|
||||||
}
|
|
||||||
|
|
||||||
const unlockBodyScroll = () => {
|
|
||||||
document.body.style.overflow = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (visible.value) {
|
|
||||||
lockBodyScroll()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
unlockBodyScroll()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听 visible 变化
|
|
||||||
watch(visible, newVal => {
|
|
||||||
if (newVal) {
|
|
||||||
lockBodyScroll()
|
|
||||||
} else {
|
|
||||||
unlockBodyScroll()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 暴露 show 方法给父组件使用
|
|
||||||
defineExpose({
|
|
||||||
show,
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.pd-mask {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 999999999;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: inherit;
|
|
||||||
background: var(--confirmBg, inherit);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd-confirm {
|
|
||||||
background: inherit;
|
|
||||||
background: var(--confirmTheme, #fff);
|
|
||||||
text-align: center;
|
|
||||||
color: var(--confirmColor, #636363);
|
|
||||||
font-size: var(--confirmFontSize, 1rem);
|
|
||||||
max-width: 75vw;
|
|
||||||
min-width: 20em;
|
|
||||||
box-shadow: 0px 35px 35px -10px rgba(0, 0, 0, 0.33);
|
|
||||||
border-radius: 10px;
|
|
||||||
position: relative;
|
|
||||||
white-space: break-spaces;
|
|
||||||
word-break: break-all;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd-blur,
|
|
||||||
.pd-plan {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd-plan {
|
|
||||||
background: inherit;
|
|
||||||
background: rgba(255, 255, 255, 0.66);
|
|
||||||
filter: blur(10px) saturate(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd-title {
|
|
||||||
position: relative;
|
|
||||||
font-size: 1.3em;
|
|
||||||
min-height: 1em;
|
|
||||||
background: var(--confirmBtnBg, #fafafa);
|
|
||||||
color: var(--confirmBtnColor, #636363);
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd-message {
|
|
||||||
position: relative;
|
|
||||||
border-top: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
|
||||||
width: 100%;
|
|
||||||
max-height: 70vh;
|
|
||||||
border-bottom: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
|
||||||
margin: 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 2em 10%;
|
|
||||||
line-height: 1.4;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd-buttons {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pd-button {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 1em;
|
|
||||||
appearance: none;
|
|
||||||
background: var(--confirmBtnBg, #fafafa);
|
|
||||||
color: var(--confirmBtnColor, #636363);
|
|
||||||
border: none;
|
|
||||||
border-right: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
|
||||||
padding: 1em 0;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 当同时显示确认和取消按钮时,每个按钮占50%宽度 */
|
|
||||||
.pd-button:first-child:nth-last-child(2),
|
|
||||||
.pd-button:first-child:nth-last-child(2) ~ .pd-button {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 当只显示一个按钮时,该按钮占100%宽度 */
|
|
||||||
.pd-button:first-child:nth-last-child(1) {
|
|
||||||
width: 100%;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 只有最后一个按钮没有右边框 */
|
|
||||||
.pd-button:last-child {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -103,119 +103,79 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, nextTick } from 'vue'
|
import { ref, computed, nextTick } from 'vue'
|
||||||
|
|
||||||
import { useAppStore } from '../stores/useAppStore'
|
import { useAppStore } from '../stores/useAppStore'
|
||||||
|
|
||||||
import FolderItem from './FolderItem.vue'
|
import FolderItem from './FolderItem.vue'
|
||||||
|
|
||||||
const store = useAppStore()
|
const store = useAppStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
allCount: {
|
allCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
starredCount: {
|
starredCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
trashCount: {
|
trashCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
archiveCount: {
|
archiveCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
selectedFolder: {
|
selectedFolder: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
lastSyncTime: {
|
lastSyncTime: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
||||||
default: '10/10上午9:28',
|
default: '10/10上午9:28',
|
||||||
},
|
},
|
||||||
|
|
||||||
onAllClick: {
|
onAllClick: {
|
||||||
type: Function,
|
type: Function,
|
||||||
|
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
onStarredClick: {
|
onStarredClick: {
|
||||||
type: Function,
|
type: Function,
|
||||||
|
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
onTrashClick: {
|
onTrashClick: {
|
||||||
type: Function,
|
type: Function,
|
||||||
|
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
onArchiveClick: {
|
onArchiveClick: {
|
||||||
type: Function,
|
type: Function,
|
||||||
|
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
onAddFolder: {
|
onAddFolder: {
|
||||||
type: Function,
|
type: Function,
|
||||||
|
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
onFolderClick: {
|
onFolderClick: {
|
||||||
type: Function,
|
type: Function,
|
||||||
|
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 添加文件夹相关状态
|
// 添加文件夹相关状态
|
||||||
|
|
||||||
const showAddFolderModal = ref(false)
|
const showAddFolderModal = ref(false)
|
||||||
|
|
||||||
const newFolderName = ref('')
|
const newFolderName = ref('')
|
||||||
|
|
||||||
const folderInput = ref(null)
|
const folderInput = ref(null)
|
||||||
|
|
||||||
// 重命名文件夹相关状态
|
// 重命名文件夹相关状态
|
||||||
|
|
||||||
const showRenameFolderModal = ref(false)
|
const showRenameFolderModal = ref(false)
|
||||||
|
|
||||||
const renameFolderId = ref(null)
|
const renameFolderId = ref(null)
|
||||||
|
|
||||||
const renameFolderName = ref('')
|
const renameFolderName = ref('')
|
||||||
|
|
||||||
// 选择模式相关状态
|
// 选择模式相关状态
|
||||||
|
|
||||||
const isSelectionMode = ref(false)
|
const isSelectionMode = ref(false)
|
||||||
|
|
||||||
const selectedFolders = ref([])
|
const selectedFolders = ref([])
|
||||||
|
|
||||||
const showDeleteConfirmModal = ref(false)
|
const showDeleteConfirmModal = ref(false)
|
||||||
|
|
||||||
// 计算自定义文件夹(排除系统文件夹)
|
// 计算自定义文件夹(排除系统文件夹)
|
||||||
|
|
||||||
const customFolders = computed(() => {
|
const customFolders = computed(() => {
|
||||||
return store.folders.filter(folder => !['all', 'starred', 'trash', 'archive'].includes(folder.id))
|
return store.folders.filter(folder => !['all', 'starred', 'trash', 'archive'].includes(folder.id))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取文件夹中的便签数量
|
// 获取文件夹中的便签数量
|
||||||
|
|
||||||
const getFolderNoteCount = folderId => {
|
const getFolderNoteCount = folderId => {
|
||||||
return store.notes.filter(note => note.folderId === folderId && !note.isDeleted).length
|
return store.notes.filter(note => note.folderId === folderId && !note.isDeleted).length
|
||||||
}
|
}
|
||||||
@@ -224,28 +184,21 @@ const getFolderNoteCount = folderId => {
|
|||||||
|
|
||||||
const toggleSelectionMode = () => {
|
const toggleSelectionMode = () => {
|
||||||
isSelectionMode.value = !isSelectionMode.value
|
isSelectionMode.value = !isSelectionMode.value
|
||||||
|
|
||||||
// 退出选择模式时清空选中项
|
// 退出选择模式时清空选中项
|
||||||
|
|
||||||
if (!isSelectionMode.value) {
|
if (!isSelectionMode.value) {
|
||||||
selectedFolders.value = []
|
selectedFolders.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换文件夹选中状态
|
// 切换文件夹选中状态
|
||||||
|
|
||||||
const toggleFolderSelection = folderId => {
|
const toggleFolderSelection = folderId => {
|
||||||
if (!isSelectionMode.value) return
|
if (!isSelectionMode.value) return
|
||||||
|
|
||||||
const index = selectedFolders.value.indexOf(folderId)
|
const index = selectedFolders.value.indexOf(folderId)
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
// 已选中,取消选中
|
// 已选中,取消选中
|
||||||
|
|
||||||
selectedFolders.value.splice(index, 1)
|
selectedFolders.value.splice(index, 1)
|
||||||
} else {
|
} else {
|
||||||
// 未选中,添加选中
|
// 未选中,添加选中
|
||||||
|
|
||||||
selectedFolders.value.push(folderId)
|
selectedFolders.value.push(folderId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,7 +219,6 @@ const handleFolderClick = folderId => {
|
|||||||
|
|
||||||
const handleDeleteSelectedFolders = () => {
|
const handleDeleteSelectedFolders = () => {
|
||||||
if (selectedFolders.value.length === 0) return
|
if (selectedFolders.value.length === 0) return
|
||||||
|
|
||||||
showDeleteConfirmModal.value = true
|
showDeleteConfirmModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,21 +227,15 @@ const handleDeleteSelectedFolders = () => {
|
|||||||
const confirmDeleteSelectedFolders = async () => {
|
const confirmDeleteSelectedFolders = async () => {
|
||||||
try {
|
try {
|
||||||
// 删除选中的文件夹
|
// 删除选中的文件夹
|
||||||
|
|
||||||
for (const folderId of selectedFolders.value) {
|
for (const folderId of selectedFolders.value) {
|
||||||
// 跳过系统文件夹
|
// 跳过系统文件夹
|
||||||
|
|
||||||
if (['all', 'starred', 'trash', 'archive'].includes(folderId)) continue
|
if (['all', 'starred', 'trash', 'archive'].includes(folderId)) continue
|
||||||
|
|
||||||
await store.deleteFolder(folderId)
|
await store.deleteFolder(folderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空选中项并退出选择模式
|
// 清空选中项并退出选择模式
|
||||||
|
|
||||||
selectedFolders.value = []
|
selectedFolders.value = []
|
||||||
|
|
||||||
isSelectionMode.value = false
|
isSelectionMode.value = false
|
||||||
|
|
||||||
showDeleteConfirmModal.value = false
|
showDeleteConfirmModal.value = false
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除文件夹失败:', error)
|
console.error('删除文件夹失败:', error)
|
||||||
@@ -300,11 +246,9 @@ const confirmDeleteSelectedFolders = async () => {
|
|||||||
|
|
||||||
const handleDeleteFolder = folderId => {
|
const handleDeleteFolder = folderId => {
|
||||||
// 阻止事件冒泡到父元素
|
// 阻止事件冒泡到父元素
|
||||||
|
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
// 确认删除
|
// 确认删除
|
||||||
|
|
||||||
if (confirm(`确定要删除文件夹 "${getFolderName(folderId)}" 吗?文件夹中的便签将移至"全部便签"。`)) {
|
if (confirm(`确定要删除文件夹 "${getFolderName(folderId)}" 吗?文件夹中的便签将移至"全部便签"。`)) {
|
||||||
try {
|
try {
|
||||||
store.deleteFolder(folderId)
|
store.deleteFolder(folderId)
|
||||||
@@ -320,18 +264,13 @@ const handleEditFolder = folderId => {
|
|||||||
// 阻止事件冒泡到父元素
|
// 阻止事件冒泡到父元素
|
||||||
|
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
const folder = store.folders.find(f => f.id === folderId)
|
const folder = store.folders.find(f => f.id === folderId)
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
renameFolderId.value = folderId
|
renameFolderId.value = folderId
|
||||||
|
|
||||||
renameFolderName.value = folder.name
|
renameFolderName.value = folder.name
|
||||||
|
|
||||||
showRenameFolderModal.value = true
|
showRenameFolderModal.value = true
|
||||||
|
|
||||||
// 在下次DOM更新后聚焦输入框
|
// 在下次DOM更新后聚焦输入框
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (folderInput.value) {
|
if (folderInput.value) {
|
||||||
folderInput.value.focus()
|
folderInput.value.focus()
|
||||||
@@ -346,11 +285,8 @@ const confirmRenameFolder = async () => {
|
|||||||
if (renameFolderName.value.trim() && renameFolderId.value) {
|
if (renameFolderName.value.trim() && renameFolderId.value) {
|
||||||
try {
|
try {
|
||||||
await store.updateFolder(renameFolderId.value, { name: renameFolderName.value.trim() })
|
await store.updateFolder(renameFolderId.value, { name: renameFolderName.value.trim() })
|
||||||
|
|
||||||
showRenameFolderModal.value = false
|
showRenameFolderModal.value = false
|
||||||
|
|
||||||
renameFolderId.value = null
|
renameFolderId.value = null
|
||||||
|
|
||||||
renameFolderName.value = ''
|
renameFolderName.value = ''
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('重命名文件夹失败:', error)
|
console.error('重命名文件夹失败:', error)
|
||||||
@@ -362,7 +298,6 @@ const confirmRenameFolder = async () => {
|
|||||||
|
|
||||||
const getFolderName = folderId => {
|
const getFolderName = folderId => {
|
||||||
const folder = store.folders.find(f => f.id === folderId)
|
const folder = store.folders.find(f => f.id === folderId)
|
||||||
|
|
||||||
return folder ? folder.name : ''
|
return folder ? folder.name : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,27 +331,13 @@ const handleTrashClick = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleArchiveClick = () => {
|
|
||||||
if (isSelectionMode.value) {
|
|
||||||
toggleFolderSelection('archive')
|
|
||||||
} else {
|
|
||||||
if (props.onArchiveClick) {
|
|
||||||
props.onArchiveClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAddFolder = event => {
|
const handleAddFolder = event => {
|
||||||
// 阻止事件冒泡到父元素
|
// 阻止事件冒泡到父元素
|
||||||
|
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
showAddFolderModal.value = true
|
showAddFolderModal.value = true
|
||||||
|
|
||||||
newFolderName.value = ''
|
newFolderName.value = ''
|
||||||
|
|
||||||
// 在下次DOM更新后聚焦输入框
|
// 在下次DOM更新后聚焦输入框
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (folderInput.value) {
|
if (folderInput.value) {
|
||||||
folderInput.value.focus()
|
folderInput.value.focus()
|
||||||
@@ -429,16 +350,12 @@ const confirmAddFolder = async () => {
|
|||||||
try {
|
try {
|
||||||
const newFolder = {
|
const newFolder = {
|
||||||
name: newFolderName.value.trim(),
|
name: newFolderName.value.trim(),
|
||||||
|
|
||||||
id: `folder_${Date.now()}`, // 生成唯一ID
|
id: `folder_${Date.now()}`, // 生成唯一ID
|
||||||
|
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
await store.addFolder(newFolder)
|
await store.addFolder(newFolder)
|
||||||
|
|
||||||
showAddFolderModal.value = false
|
showAddFolderModal.value = false
|
||||||
|
|
||||||
newFolderName.value = ''
|
newFolderName.value = ''
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('添加文件夹失败:', error)
|
console.error('添加文件夹失败:', error)
|
||||||
|
|||||||
@@ -1,206 +1,297 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start">
|
<div v-if="visible" class="pd-mask" @click="handleMaskClick">
|
||||||
<div class="code-fun-flex-col code-fun-justify-start group_1">
|
<div class="pd-confirm" @click.stop>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-end group">
|
<h2 class="pd-title">{{ title }}</h2>
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-start code-fun-relative group_2">
|
<div class="pd-input-container" v-if="showInput">
|
||||||
<div class="code-fun-flex-col section_7">
|
<input v-model="inputModel" class="pd-input" :placeholder="inputPlaceholder" @keyup.enter="handleConfirm" />
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_1">
|
|
||||||
<div class="code-fun-flex-row section_11">
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_12" @click="handleCancel">
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-center text-wrapper_2">
|
|
||||||
<span class="text_4">{{ cancelText }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_13 code-fun-ml-8" @click="handleConfirm">
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-center text-wrapper_3">
|
|
||||||
<span class="text_5">{{ confirmText }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-relative section_9">
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start section_10">
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-start text-wrapper">
|
|
||||||
<input
|
|
||||||
v-model="inputValue"
|
|
||||||
class="text_3"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
@keyup.enter="handleConfirm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="code-fun-flex-col section_8 pos">
|
|
||||||
<div class="code-fun-flex-col code-fun-justify-start code-fun-items-start code-fun-self-stretch group_3">
|
|
||||||
<span class="text_2">{{ title }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="code-fun-self-start divider"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="pd-message" v-else>{{ message }}</p>
|
||||||
|
<div class="pd-plan"></div>
|
||||||
|
<div class="pd-buttons">
|
||||||
|
<button v-if="showConfirm" class="pd-button pd-confirm-btn" @click="handleConfirm">
|
||||||
|
{{ confirmText }}
|
||||||
|
</button>
|
||||||
|
<button v-if="showCancel" class="pd-button pd-cancel" @click="handleCancel">
|
||||||
|
{{ cancelText }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { watch, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '新建文件夹'
|
default: '',
|
||||||
},
|
},
|
||||||
placeholder: {
|
message: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '新建文件夹'
|
default: '',
|
||||||
},
|
},
|
||||||
confirmText: {
|
confirmText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '确定'
|
default: '确认',
|
||||||
},
|
},
|
||||||
cancelText: {
|
cancelText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '取消'
|
default: '取消',
|
||||||
|
},
|
||||||
|
showConfirm: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
showCancel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
showInput: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
inputPlaceholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请输入文字',
|
||||||
|
},
|
||||||
|
inputValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['confirm', 'cancel', 'update:visible'])
|
||||||
|
const visible = defineModel('visible', { type: Boolean, default: false })
|
||||||
|
const inputModel = defineModel('inputValue', { type: String, default: '' })
|
||||||
|
|
||||||
|
// Promise 控制变量
|
||||||
|
let resolvePromise, rejectPromise
|
||||||
|
|
||||||
|
// 返回 Promise 的方法,模拟原生 confirm 行为
|
||||||
|
const show = (options = {}) => {
|
||||||
|
// 更新 props 值
|
||||||
|
Object.assign(props, options)
|
||||||
|
|
||||||
|
// 如果提供了 inputValue,则更新输入框的值
|
||||||
|
if (options.inputValue !== undefined) {
|
||||||
|
inputModel.value = options.inputValue
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['confirm', 'cancel']);
|
// 显示对话框
|
||||||
|
visible.value = true
|
||||||
|
emit('update:visible', true)
|
||||||
|
|
||||||
const inputValue = ref('');
|
// 返回 Promise
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resolvePromise = resolve
|
||||||
|
rejectPromise = reject
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
emit('confirm', inputValue.value);
|
emit('confirm')
|
||||||
};
|
visible.value = false
|
||||||
|
emit('update:visible', false)
|
||||||
|
|
||||||
|
// 解决 Promise,传递输入框的值
|
||||||
|
if (resolvePromise) {
|
||||||
|
resolvePromise(inputModel.value)
|
||||||
|
resolvePromise = null
|
||||||
|
rejectPromise = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
emit('cancel');
|
emit('cancel')
|
||||||
};
|
visible.value = false
|
||||||
|
emit('update:visible', false)
|
||||||
|
|
||||||
|
// 拒绝 Promise
|
||||||
|
if (rejectPromise) {
|
||||||
|
rejectPromise()
|
||||||
|
resolvePromise = null
|
||||||
|
rejectPromise = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMaskClick = () => {
|
||||||
|
if (props.maskClosable) {
|
||||||
|
handleCancel() // 点击遮罩相当于取消
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加/移除 body 滚动锁定
|
||||||
|
const lockBodyScroll = () => {
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlockBodyScroll = () => {
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (visible.value) {
|
||||||
|
lockBodyScroll()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
unlockBodyScroll()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听 visible 变化
|
||||||
|
watch(visible, newVal => {
|
||||||
|
if (newVal) {
|
||||||
|
lockBodyScroll()
|
||||||
|
} else {
|
||||||
|
unlockBodyScroll()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露 show 方法给父组件使用
|
||||||
|
defineExpose({ show })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped>
|
||||||
.group_1 {
|
.pd-mask {
|
||||||
padding-bottom: 1rem;
|
position: fixed;
|
||||||
.group {
|
inset: 0;
|
||||||
padding: 0.5rem 0 0.25rem;
|
top: 0;
|
||||||
.group_2 {
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
width: 32.31rem;
|
|
||||||
.section_7 {
|
|
||||||
padding-top: 5.75rem;
|
|
||||||
background-color: #00000000;
|
|
||||||
width: 29.06rem;
|
|
||||||
height: 18.16rem;
|
|
||||||
background-image: url(https://codefun-proj-user-res-1256085488.cos.ap-guangzhou.myqcloud.com/686f20ecd54496f19f54e801/68e862ab9520a30011f388ff/17600601480475170758.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% auto;
|
|
||||||
background-position: 0% 0%;
|
|
||||||
.section_1 {
|
|
||||||
margin-top: 7.88rem;
|
|
||||||
background-color: #00000000;
|
|
||||||
.section_11 {
|
|
||||||
padding: 0.5rem 0.5rem 0.75rem;
|
|
||||||
background-color: #f4f4f7;
|
|
||||||
border-radius: 0rem 0rem 0.75rem 0.75rem;
|
|
||||||
border: solid 0.032rem #edeee8;
|
|
||||||
.section_12 {
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
background-color: #00000000;
|
|
||||||
width: 13.63rem;
|
|
||||||
height: 3.13rem;
|
|
||||||
.text-wrapper_2 {
|
|
||||||
margin: 0 0.25rem;
|
|
||||||
padding: 0.75rem 0;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
width: 13.34rem;
|
|
||||||
border: solid 0.032rem #d7d7d7;
|
|
||||||
.text_4 {
|
|
||||||
color: #757575;
|
|
||||||
font-size: 1.23rem;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.23rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.section_13 {
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
padding-top: 0.25rem;
|
|
||||||
background-color: #00000000;
|
|
||||||
width: 13.59rem;
|
|
||||||
height: 3.06rem;
|
|
||||||
.text-wrapper_3 {
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
padding: 0.75rem 0;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
background-image: url('assets/53062683132af1946e1a4953530af228.png');
|
|
||||||
background-size: 100% 100%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
width: 13.34rem;
|
|
||||||
.text_5 {
|
|
||||||
color: #cddff2;
|
|
||||||
font-size: 1.19rem;
|
|
||||||
line-height: 1.19rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.section_9 {
|
|
||||||
margin-top: -12.5rem;
|
|
||||||
padding: 1.75rem 0 0.25rem;
|
|
||||||
background-color: #00000000;
|
|
||||||
.section_10 {
|
|
||||||
margin: 0 1.5rem;
|
|
||||||
padding-top: 0.25rem;
|
|
||||||
background-color: #00000000;
|
|
||||||
width: 25.88rem;
|
|
||||||
.text-wrapper {
|
|
||||||
margin: 0 0.25rem;
|
|
||||||
padding: 0.75rem 0;
|
|
||||||
background-color: #fefefe;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
width: 25.53rem;
|
|
||||||
border: solid 0.063rem #e1e1e1;
|
|
||||||
.text_3 {
|
|
||||||
margin-left: 1rem;
|
|
||||||
color: #d0d0d0;
|
|
||||||
font-size: 1.38rem;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.38rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.section_8 {
|
|
||||||
padding: 0 0.13rem;
|
|
||||||
background-color: #00000000;
|
|
||||||
width: 32.31rem;
|
|
||||||
.group_3 {
|
|
||||||
padding: 1.5rem 0;
|
|
||||||
width: 32.06rem;
|
|
||||||
.text_2 {
|
|
||||||
margin-left: 10.5rem;
|
|
||||||
color: #7a7a7a;
|
|
||||||
font-size: 1.54rem;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1.54rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.divider {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
width: 29rem;
|
|
||||||
height: 0.094rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pos {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999999999;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: inherit;
|
||||||
|
background: var(--confirmBg, inherit);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-confirm {
|
||||||
|
background: inherit;
|
||||||
|
background: var(--confirmTheme, #fff);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--confirmColor, #636363);
|
||||||
|
font-size: var(--confirmFontSize, 1rem);
|
||||||
|
max-width: 75vw;
|
||||||
|
min-width: 20em;
|
||||||
|
box-shadow: 0px 35px 35px -10px rgba(0, 0, 0, 0.33);
|
||||||
|
border-radius: 10px;
|
||||||
|
position: relative;
|
||||||
|
white-space: break-spaces;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-plan {
|
||||||
|
background: inherit;
|
||||||
|
background: rgba(255, 255, 255, 0.66);
|
||||||
|
filter: blur(10px) saturate(2);
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pd-title {
|
||||||
|
position: relative;
|
||||||
|
font-size: 1.3em;
|
||||||
|
min-height: 1em;
|
||||||
|
background: var(--confirmBtnBg, #fafafa);
|
||||||
|
color: var(--confirmBtnColor, #636363);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pd-message {
|
||||||
|
position: relative;
|
||||||
|
border-top: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
||||||
|
width: 100%;
|
||||||
|
max-height: 70vh;
|
||||||
|
border-bottom: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
||||||
|
margin: 0 auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 2em 10%;
|
||||||
|
line-height: 1.4;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pd-input-container {
|
||||||
|
margin-block: 1.5em;
|
||||||
|
padding: 0 10% 1em;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.8em;
|
||||||
|
font-size: 1em;
|
||||||
|
border: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
|
background: var(--confirmTheme, #fff);
|
||||||
|
color: var(--confirmColor, #636363);
|
||||||
|
box-shadow: 0px 0px 4px inset rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input::placeholder {
|
||||||
|
color: var(--black-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input:focus {
|
||||||
|
border-color: #4d90fe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-input::selection {
|
||||||
|
background-color: #4d90fe; /* 选中时的背景颜色 */
|
||||||
|
color: #ffffff; /* 选中时的文字颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pd-button {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1em;
|
||||||
|
appearance: none;
|
||||||
|
background: var(--confirmBtnBg, #fafafa);
|
||||||
|
color: var(--confirmBtnColor, #636363);
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid var(--confirmBtnBorder, #f1f1f1);
|
||||||
|
padding: 1em 0;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当同时显示确认和取消按钮时,每个按钮占50%宽度 */
|
||||||
|
.pd-button:first-child:nth-last-child(2),
|
||||||
|
.pd-button:first-child:nth-last-child(2) ~ .pd-button {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当只显示一个按钮时,该按钮占100%宽度 */
|
||||||
|
.pd-button:first-child:nth-last-child(1) {
|
||||||
|
width: 100%;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 只有最后一个按钮没有右边框 */
|
||||||
|
.pd-button:last-child {
|
||||||
|
border-right: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ const handleTrashNotesClick = () => {
|
|||||||
setIsFolderExpanded(false)
|
setIsFolderExpanded(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFolderClick = (folderId) => {
|
const handleFolderClick = folderId => {
|
||||||
setCurrentFolder(folderId)
|
setCurrentFolder(folderId)
|
||||||
setIsFolderExpanded(false)
|
setIsFolderExpanded(false)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user