Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
c377eca6
Unverified
Commit
c377eca6
authored
Jan 01, 2023
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: rename folder + fileman improvements
parent
bfbb64a7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
442 additions
and
16 deletions
+442
-16
tree.js
server/graph/resolvers/tree.js
+86
-2
tree.graphql
server/graph/schemas/tree.graphql
+5
-2
fluent-rename.svg
ux/public/_assets/icons/fluent-rename.svg
+2
-0
FileManager.vue
ux/src/components/FileManager.vue
+118
-10
FolderRenameDialog.vue
ux/src/components/FolderRenameDialog.vue
+226
-0
PageSaveDialog.vue
ux/src/components/PageSaveDialog.vue
+1
-1
en.json
ux/src/i18n/locales/en.json
+4
-1
No files found.
server/graph/resolvers/tree.js
View file @
c377eca6
...
@@ -88,6 +88,18 @@ module.exports = {
...
@@ -88,6 +88,18 @@ module.exports = {
childrenCount
:
0
childrenCount
:
0
}
}
}))
}))
},
async
folderById
(
obj
,
args
,
context
)
{
const
folder
=
await
WIKI
.
db
.
knex
(
'tree'
)
.
select
(
WIKI
.
db
.
knex
.
raw
(
'tree.*, nlevel(tree."folderPath") AS depth'
))
.
where
(
'id'
,
args
.
id
)
.
first
()
return
{
...
folder
,
folderPath
:
folder
.
folderPath
.
replaceAll
(
'.'
,
'/'
).
replaceAll
(
'_'
,
'-'
),
childrenCount
:
0
}
}
}
},
},
Mutation
:
{
Mutation
:
{
...
@@ -96,6 +108,8 @@ module.exports = {
...
@@ -96,6 +108,8 @@ module.exports = {
*/
*/
async
createFolder
(
obj
,
args
,
context
)
{
async
createFolder
(
obj
,
args
,
context
)
{
try
{
try
{
WIKI
.
logger
.
debug
(
`Creating new folder
${
args
.
pathName
}
...`
)
// Get parent path
// Get parent path
let
parentPath
=
''
let
parentPath
=
''
if
(
args
.
parentId
)
{
if
(
args
.
parentId
)
{
...
@@ -128,6 +142,7 @@ module.exports = {
...
@@ -128,6 +142,7 @@ module.exports = {
}
}
// Create folder
// Create folder
WIKI
.
logger
.
debug
(
`Creating new folder
${
args
.
pathName
}
at path /
${
parentPath
}
...`
)
await
WIKI
.
db
.
knex
(
'tree'
).
insert
({
await
WIKI
.
db
.
knex
(
'tree'
).
insert
({
folderPath
:
parentPath
,
folderPath
:
parentPath
,
fileName
:
args
.
pathName
,
fileName
:
args
.
pathName
,
...
@@ -139,6 +154,74 @@ module.exports = {
...
@@ -139,6 +154,74 @@ module.exports = {
operation
:
graphHelper
.
generateSuccess
(
'Folder created successfully'
)
operation
:
graphHelper
.
generateSuccess
(
'Folder created successfully'
)
}
}
}
catch
(
err
)
{
}
catch
(
err
)
{
WIKI
.
logger
.
debug
(
`Failed to create folder:
${
err
.
message
}
`
)
return
graphHelper
.
generateError
(
err
)
}
},
/**
* RENAME FOLDER
*/
async
renameFolder
(
obj
,
args
,
context
)
{
try
{
// Get folder
const
folder
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
args
.
folderId
).
first
()
WIKI
.
logger
.
debug
(
`Renaming folder
${
folder
.
id
}
path to
${
args
.
pathName
}
...`
)
// Validate path name
if
(
!
rePathName
.
test
(
args
.
pathName
))
{
throw
new
Error
(
'ERR_INVALID_PATH_NAME'
)
}
// Validate title
if
(
!
reTitle
.
test
(
args
.
title
))
{
throw
new
Error
(
'ERR_INVALID_TITLE'
)
}
if
(
args
.
pathName
!==
folder
.
fileName
)
{
// Check for collision
const
existingFolder
=
await
WIKI
.
db
.
knex
(
'tree'
)
.
whereNot
(
'id'
,
folder
.
id
)
.
andWhere
({
siteId
:
folder
.
siteId
,
folderPath
:
folder
.
folderPath
,
fileName
:
args
.
pathName
}).
first
()
if
(
existingFolder
)
{
throw
new
Error
(
'ERR_FOLDER_ALREADY_EXISTS'
)
}
// Build new paths
const
oldFolderPath
=
(
folder
.
folderPath
?
`
${
folder
.
folderPath
}
.
${
folder
.
fileName
}
`
:
folder
.
fileName
).
replaceAll
(
'-'
,
'_'
)
const
newFolderPath
=
(
folder
.
folderPath
?
`
${
folder
.
folderPath
}
.
${
args
.
pathName
}
`
:
args
.
pathName
).
replaceAll
(
'-'
,
'_'
)
// Update children nodes
WIKI
.
logger
.
debug
(
`Updating parent path of children nodes from
${
oldFolderPath
}
to
${
newFolderPath
}
...`
)
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'siteId'
,
folder
.
siteId
).
andWhere
(
'folderPath'
,
oldFolderPath
).
update
({
folderPath
:
newFolderPath
})
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'siteId'
,
folder
.
siteId
).
andWhere
(
'folderPath'
,
'<@'
,
oldFolderPath
).
update
({
folderPath
:
WIKI
.
db
.
knex
.
raw
(
`'
${
newFolderPath
}
' || subpath(tree."folderPath", nlevel('
${
newFolderPath
}
'))`
)
})
// Rename the folder itself
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
folder
.
id
).
update
({
fileName
:
args
.
pathName
,
title
:
args
.
title
})
}
else
{
// Update the folder title only
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
folder
.
id
).
update
({
title
:
args
.
title
})
}
WIKI
.
logger
.
debug
(
`Renamed folder
${
folder
.
id
}
successfully.`
)
return
{
operation
:
graphHelper
.
generateSuccess
(
'Folder renamed successfully'
)
}
}
catch
(
err
)
{
WIKI
.
logger
.
debug
(
`Failed to rename folder
${
args
.
folderId
}
:
${
err
.
message
}
`
)
return
graphHelper
.
generateError
(
err
)
return
graphHelper
.
generateError
(
err
)
}
}
},
},
...
@@ -153,7 +236,7 @@ module.exports = {
...
@@ -153,7 +236,7 @@ module.exports = {
WIKI
.
logger
.
debug
(
`Deleting folder
${
folder
.
id
}
at path
${
folderPath
}
...`
)
WIKI
.
logger
.
debug
(
`Deleting folder
${
folder
.
id
}
at path
${
folderPath
}
...`
)
// Delete all children
// Delete all children
const
deletedNodes
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'folderPath'
,
'
~'
,
`
${
folderPath
}
.*`
).
del
().
returning
([
'id'
,
'type'
])
const
deletedNodes
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'folderPath'
,
'
<@'
,
folderPath
).
del
().
returning
([
'id'
,
'type'
])
// Delete folders
// Delete folders
const
deletedFolders
=
deletedNodes
.
filter
(
n
=>
n
.
type
===
'folder'
).
map
(
n
=>
n
.
id
)
const
deletedFolders
=
deletedNodes
.
filter
(
n
=>
n
.
type
===
'folder'
).
map
(
n
=>
n
.
id
)
...
@@ -179,12 +262,13 @@ module.exports = {
...
@@ -179,12 +262,13 @@ module.exports = {
// Delete the folder itself
// Delete the folder itself
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
folder
.
id
).
del
()
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
folder
.
id
).
del
()
WIKI
.
logger
.
debug
(
`Delet
ing
folder
${
folder
.
id
}
successfully.`
)
WIKI
.
logger
.
debug
(
`Delet
ed
folder
${
folder
.
id
}
successfully.`
)
return
{
return
{
operation
:
graphHelper
.
generateSuccess
(
'Folder deleted successfully'
)
operation
:
graphHelper
.
generateSuccess
(
'Folder deleted successfully'
)
}
}
}
catch
(
err
)
{
}
catch
(
err
)
{
WIKI
.
logger
.
debug
(
`Failed to delete folder
${
args
.
folderId
}
:
${
err
.
message
}
`
)
return
graphHelper
.
generateError
(
err
)
return
graphHelper
.
generateError
(
err
)
}
}
}
}
...
...
server/graph/schemas/tree.graphql
View file @
c377eca6
...
@@ -15,6 +15,9 @@ extend type Query {
...
@@ -15,6 +15,9 @@ extend type Query {
depth
:
Int
depth
:
Int
includeAncestors
:
Boolean
includeAncestors
:
Boolean
):
[
TreeItem
]
):
[
TreeItem
]
folderById
(
id
:
UUID
!
):
TreeItemFolder
}
}
extend
type
Mutation
{
extend
type
Mutation
{
...
@@ -39,8 +42,8 @@ extend type Mutation {
...
@@ -39,8 +42,8 @@ extend type Mutation {
):
DefaultResponse
):
DefaultResponse
renameFolder
(
renameFolder
(
folderId
:
UUID
!
folderId
:
UUID
!
pathName
:
String
pathName
:
String
!
title
:
String
title
:
String
!
):
DefaultResponse
):
DefaultResponse
}
}
...
...
ux/public/_assets/icons/fluent-rename.svg
0 → 100644
View file @
c377eca6
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 48 48"
width=
"96px"
height=
"96px"
><linearGradient
id=
"PuVtuXTbHVUsxZgps56lha"
x1=
"4"
x2=
"44"
y1=
"24"
y2=
"24"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#50e6ff"
/><stop
offset=
".55"
stop-color=
"#50e6ff"
/><stop
offset=
".58"
stop-color=
"#4fe3fc"
/><stop
offset=
".601"
stop-color=
"#4edaf4"
/><stop
offset=
".62"
stop-color=
"#4acae7"
/><stop
offset=
".637"
stop-color=
"#46b4d3"
/><stop
offset=
".64"
stop-color=
"#45b0d0"
/><stop
offset=
".71"
stop-color=
"#45b0d0"
/><stop
offset=
".713"
stop-color=
"#46b4d3"
/><stop
offset=
".73"
stop-color=
"#4acae7"
/><stop
offset=
".749"
stop-color=
"#4edaf4"
/><stop
offset=
".77"
stop-color=
"#4fe3fc"
/><stop
offset=
".8"
stop-color=
"#50e6ff"
/><stop
offset=
"1"
stop-color=
"#50e6ff"
/></linearGradient><path
fill=
"url(#PuVtuXTbHVUsxZgps56lha)"
d=
"M4,16v16c0,1.105,0.895,2,2,2h36c1.105,0,2-0.895,2-2V16c0-1.105-0.895-2-2-2H6 C4.895,14,4,14.895,4,16z"
/><path
fill=
"#057093"
d=
"M38,44h-1c-4.418,0-8-3.582-8-8V12c0-4.418,3.582-8,8-8h1c0.552,0,1,0.448,1,1v2 c0,0.552-0.448,1-1,1h-1c-2.209,0-4,1.791-4,4v24c0,2.209,1.791,4,4,4h1c0.552,0,1,0.448,1,1v2C39,43.552,38.552,44,38,44z"
/><path
fill=
"#057093"
d=
"M24,44h1c4.418,0,8-3.582,8-8V12c0-4.418-3.582-8-8-8h-1c-0.552,0-1,0.448-1,1v2 c0,0.552,0.448,1,1,1h1c2.209,0,4,1.791,4,4v24c0,2.209-1.791,4-4,4h-1c-0.552,0-1,0.448-1,1v2C23,43.552,23.448,44,24,44z"
/></svg>
\ No newline at end of file
ux/src/components/FileManager.vue
View file @
c377eca6
...
@@ -37,6 +37,11 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -37,6 +37,11 @@ q-layout.fileman(view='hHh lpR lFr', container)
)
)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.close`
)
}}
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.close`
)
}}
q-drawer.fileman-left(:model-value='true', :width='350')
q-drawer.fileman-left(:model-value='true', :width='350')
q-scroll-area(
:thumb-style='thumbStyle'
:bar-style='barStyle'
style='height: 100%;'
)
.q-px-md.q-pb-sm
.q-px-md.q-pb-sm
tree(
tree(
ref='treeComp'
ref='treeComp'
...
@@ -49,6 +54,11 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -49,6 +54,11 @@ q-layout.fileman(view='hHh lpR lFr', container)
:display-mode='state.displayMode'
:display-mode='state.displayMode'
)
)
q-drawer.fileman-right(:model-value='$q.screen.gt.md', :width='350', side='right')
q-drawer.fileman-right(:model-value='$q.screen.gt.md', :width='350', side='right')
q-scroll-area(
:thumb-style='thumbStyle'
:bar-style='barStyle'
style='height: 100%;'
)
.q-pa-md
.q-pa-md
template(v-if='currentFileDetails')
template(v-if='currentFileDetails')
q-img.rounded-borders.q-mb-md(
q-img.rounded-borders.q-mb-md(
...
@@ -64,7 +74,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -64,7 +74,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
label
{{
item
.
label
}}
label
{{
item
.
label
}}
span
{{
item
.
value
}}
span
{{
item
.
value
}}
q-page-container
q-page-container
q-page.fileman-center
q-page.fileman-center
.column
//- TOOLBAR -----------------------------------------------------
//- TOOLBAR -----------------------------------------------------
q-toolbar.fileman-toolbar
q-toolbar.fileman-toolbar
template(v-if='state.isUploading')
template(v-if='state.isUploading')
...
@@ -182,14 +192,25 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -182,14 +192,25 @@ q-layout.fileman(view='hHh lpR lFr', container)
icon='las la-cloud-upload-alt'
icon='las la-cloud-upload-alt'
@click='uploadFile'
@click='uploadFile'
)
)
.row(style='flex: 1 1 100%;')
.col
q-scroll-area(
:thumb-style='thumbStyle'
:bar-style='barStyle'
style='height: 100%;'
)
.fileman-emptylist(v-if='files.length < 1')
.fileman-emptylist(v-if='files.length < 1')
template(v-if='state.fileListLoading')
template(v-if='state.fileListLoading')
q-spinner.q-mr-sm(color='primary', size='xs', :thickness='3')
q-spinner.q-mr-sm(color='primary', size='xs', :thickness='3')
span.text-primary Loading...
span.text-primary Loading...
template(v-else)
template(v-else)
q-icon.q-mr-sm(name='las la-exclamation-triangle
', size='sm')
q-icon.q-mr-sm(name='las la-folder-open
', size='sm')
span This folder is empty.
span This folder is empty.
q-list.fileman-filelist(v-else)
q-list.fileman-filelist(
v-else
:class='state.isCompact && `is-compact`'
)
q-item(
q-item(
v-for='item of files'
v-for='item of files'
:key='item.id'
:key='item.id'
...
@@ -202,7 +223,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -202,7 +223,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
q-item-section.fileman-filelist-icon(avatar)
q-item-section.fileman-filelist-icon(avatar)
q-icon(:name='item.icon', :size='state.isCompact ? `md` : `xl`')
q-icon(:name='item.icon', :size='state.isCompact ? `md` : `xl`')
q-item-section.fileman-filelist-label
q-item-section.fileman-filelist-label
q-item-label
{{
item
.
title
}}
q-item-label
{{
usePathTitle
?
item
.
fileName
:
item
.
title
}}
q-item-label(caption, v-if='!state.isCompact')
{{
item
.
caption
}}
q-item-label(caption, v-if='!state.isCompact')
{{
item
.
caption
}}
q-item-section.fileman-filelist-side(side, v-if='item.side')
q-item-section.fileman-filelist-side(side, v-if='item.side')
.text-caption
{{
item
.
side
}}
.text-caption
{{
item
.
side
}}
...
@@ -232,7 +253,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -232,7 +253,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
q-item-section(side)
q-item-section(side)
q-icon(name='las la-copy', color='teal')
q-icon(name='las la-copy', color='teal')
q-item-section Duplicate...
q-item-section Duplicate...
q-item(clickable
)
q-item(clickable, @click='renameItem(item)'
)
q-item-section(side)
q-item-section(side)
q-icon(name='las la-redo', color='teal')
q-icon(name='las la-redo', color='teal')
q-item-section Rename...
q-item-section Rename...
...
@@ -259,7 +280,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -259,7 +280,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
<
script
setup
>
<
script
setup
>
import
{
useI18n
}
from
'vue-i18n'
import
{
useI18n
}
from
'vue-i18n'
import
{
computed
,
nextTick
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
computed
,
defineAsyncComponent
,
nextTick
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
filesize
}
from
'filesize'
import
{
filesize
}
from
'filesize'
import
{
useQuasar
}
from
'quasar'
import
{
useQuasar
}
from
'quasar'
import
{
DateTime
}
from
'luxon'
import
{
DateTime
}
from
'luxon'
...
@@ -278,6 +299,7 @@ import { useSiteStore } from 'src/stores/site'
...
@@ -278,6 +299,7 @@ import { useSiteStore } from 'src/stores/site'
import
FolderCreateDialog
from
'src/components/FolderCreateDialog.vue'
import
FolderCreateDialog
from
'src/components/FolderCreateDialog.vue'
import
FolderDeleteDialog
from
'src/components/FolderDeleteDialog.vue'
import
FolderDeleteDialog
from
'src/components/FolderDeleteDialog.vue'
import
FolderRenameDialog
from
'src/components/FolderRenameDialog.vue'
// QUASAR
// QUASAR
...
@@ -301,6 +323,7 @@ const { t } = useI18n()
...
@@ -301,6 +323,7 @@ const { t } = useI18n()
const
state
=
reactive
({
const
state
=
reactive
({
loading
:
0
,
loading
:
0
,
isFetching
:
false
,
search
:
''
,
search
:
''
,
currentFolderId
:
null
,
currentFolderId
:
null
,
currentFileId
:
null
,
currentFileId
:
null
,
...
@@ -316,6 +339,19 @@ const state = reactive({
...
@@ -316,6 +339,19 @@ const state = reactive({
fileListLoading
:
false
fileListLoading
:
false
})
})
const
thumbStyle
=
{
right
:
'2px'
,
borderRadius
:
'5px'
,
backgroundColor
:
'#000'
,
width
:
'5px'
,
opacity
:
0.15
}
const
barStyle
=
{
backgroundColor
:
'#FAFAFA'
,
width
:
'9px'
,
opacity
:
1
}
// REFS
// REFS
const
fileIpt
=
ref
(
null
)
const
fileIpt
=
ref
(
null
)
...
@@ -332,6 +368,8 @@ const folderPath = computed(() => {
...
@@ -332,6 +368,8 @@ const folderPath = computed(() => {
}
}
})
})
const
usePathTitle
=
computed
(()
=>
state
.
displayMode
===
'path'
)
const
filteredFiles
=
computed
(()
=>
{
const
filteredFiles
=
computed
(()
=>
{
if
(
state
.
search
)
{
if
(
state
.
search
)
{
const
fuse
=
new
Fuse
(
state
.
fileList
,
{
const
fuse
=
new
Fuse
(
state
.
fileList
,
{
...
@@ -348,7 +386,6 @@ const filteredFiles = computed(() => {
...
@@ -348,7 +386,6 @@ const filteredFiles = computed(() => {
const
files
=
computed
(()
=>
{
const
files
=
computed
(()
=>
{
return
filteredFiles
.
value
.
filter
(
f
=>
{
return
filteredFiles
.
value
.
filter
(
f
=>
{
console
.
info
(
f
)
// -> Show Folders Filter
// -> Show Folders Filter
if
(
f
.
type
===
'folder'
&&
!
state
.
shouldShowFolders
)
{
if
(
f
.
type
===
'folder'
&&
!
state
.
shouldShowFolders
)
{
return
false
return
false
...
@@ -453,6 +490,8 @@ async function treeLazyLoad (nodeId, { done, fail }) {
...
@@ -453,6 +490,8 @@ async function treeLazyLoad (nodeId, { done, fail }) {
}
}
async
function
loadTree
(
parentId
,
types
)
{
async
function
loadTree
(
parentId
,
types
)
{
if
(
state
.
isFetching
)
{
return
}
state
.
isFetching
=
true
if
(
!
parentId
)
{
if
(
!
parentId
)
{
parentId
=
null
parentId
=
null
}
}
...
@@ -517,14 +556,12 @@ async function loadTree (parentId, types) {
...
@@ -517,14 +556,12 @@ async function loadTree (parentId, types) {
switch
(
item
.
__typename
)
{
switch
(
item
.
__typename
)
{
case
'TreeItemFolder'
:
{
case
'TreeItemFolder'
:
{
// -> Tree Nodes
// -> Tree Nodes
if
(
!
state
.
treeNodes
[
item
.
id
])
{
state
.
treeNodes
[
item
.
id
]
=
{
state
.
treeNodes
[
item
.
id
]
=
{
folderPath
:
item
.
folderPath
,
folderPath
:
item
.
folderPath
,
fileName
:
item
.
fileName
,
fileName
:
item
.
fileName
,
title
:
item
.
title
,
title
:
item
.
title
,
children
:
state
.
treeNodes
[
item
.
id
]?.
children
??
[]
children
:
state
.
treeNodes
[
item
.
id
]?.
children
??
[]
}
}
}
// -> Set Ancestors / Tree Roots
// -> Set Ancestors / Tree Roots
if
(
item
.
folderPath
)
{
if
(
item
.
folderPath
)
{
...
@@ -596,6 +633,7 @@ async function loadTree (parentId, types) {
...
@@ -596,6 +633,7 @@ async function loadTree (parentId, types) {
if
(
parentId
)
{
if
(
parentId
)
{
treeComp
.
value
.
setLoaded
(
parentId
)
treeComp
.
value
.
setLoaded
(
parentId
)
}
}
state
.
isFetching
=
false
}
}
function
treeContextAction
(
nodeId
,
action
)
{
function
treeContextAction
(
nodeId
,
action
)
{
...
@@ -604,6 +642,10 @@ function treeContextAction (nodeId, action) {
...
@@ -604,6 +642,10 @@ function treeContextAction (nodeId, action) {
newFolder
(
nodeId
)
newFolder
(
nodeId
)
break
break
}
}
case
'rename'
:
{
renameFolder
(
nodeId
)
break
}
case
'del'
:
{
case
'del'
:
{
delFolder
(
nodeId
)
delFolder
(
nodeId
)
break
break
...
@@ -626,6 +668,18 @@ function newFolder (parentId) {
...
@@ -626,6 +668,18 @@ function newFolder (parentId) {
})
})
}
}
function
renameFolder
(
folderId
)
{
$q
.
dialog
({
component
:
FolderRenameDialog
,
componentProps
:
{
folderId
}
}).
onOk
(()
=>
{
treeComp
.
value
.
resetLoaded
()
loadTree
(
folderId
)
})
}
function
delFolder
(
folderId
,
mustReload
=
false
)
{
function
delFolder
(
folderId
,
mustReload
=
false
)
{
$q
.
dialog
({
$q
.
dialog
({
component
:
FolderDeleteDialog
,
component
:
FolderDeleteDialog
,
...
@@ -654,6 +708,21 @@ function reloadFolder (folderId) {
...
@@ -654,6 +708,21 @@ function reloadFolder (folderId) {
treeComp
.
value
.
resetLoaded
()
treeComp
.
value
.
resetLoaded
()
}
}
// PAGE METHODS
// --------------------------------------
function
delPage
(
pageId
,
pageName
)
{
$q
.
dialog
({
component
:
defineAsyncComponent
(()
=>
import
(
'src/components/PageDeleteDialog.vue'
)),
componentProps
:
{
pageId
,
pageName
}
}).
onOk
(()
=>
{
loadTree
(
state
.
currentFolderId
,
null
)
})
}
// --------------------------------------
// --------------------------------------
// UPLOAD METHODS
// UPLOAD METHODS
// --------------------------------------
// --------------------------------------
...
@@ -796,12 +865,34 @@ async function copyItemURL (item) {
...
@@ -796,12 +865,34 @@ async function copyItemURL (item) {
}
}
}
}
function
renameItem
(
item
)
{
console
.
info
(
item
)
switch
(
item
.
type
)
{
case
'folder'
:
{
renameFolder
(
item
.
id
)
break
}
case
'page'
:
{
// TODO: Rename page
break
}
case
'asset'
:
{
// TODO: Rename asset
break
}
}
}
function
delItem
(
item
)
{
function
delItem
(
item
)
{
switch
(
item
.
type
)
{
switch
(
item
.
type
)
{
case
'folder'
:
{
case
'folder'
:
{
delFolder
(
item
.
id
,
true
)
delFolder
(
item
.
id
,
true
)
break
break
}
}
case
'page'
:
{
delPage
(
item
.
id
,
item
.
title
)
break
}
}
}
}
}
...
@@ -825,6 +916,7 @@ onMounted(() => {
...
@@ -825,6 +916,7 @@ onMounted(() => {
}
}
&
-center
{
&
-center
{
@at-root
.body--light
&
{
@at-root
.body--light
&
{
background-color
:
#FFF
;
background-color
:
#FFF
;
}
}
...
@@ -860,6 +952,10 @@ onMounted(() => {
...
@@ -860,6 +952,10 @@ onMounted(() => {
}
}
}
}
&
-main
{
height
:
100%
;
}
&
-emptylist
{
&
-emptylist
{
padding
:
16px
;
padding
:
16px
;
font-style
:
italic
;
font-style
:
italic
;
...
@@ -878,7 +974,7 @@ onMounted(() => {
...
@@ -878,7 +974,7 @@ onMounted(() => {
padding
:
8px
12px
;
padding
:
8px
12px
;
>
.q-item
{
>
.q-item
{
padding
:
8
px
6px
;
padding
:
4
px
6px
;
border-radius
:
8px
;
border-radius
:
8px
;
&
.active
{
&
.active
{
...
@@ -894,6 +990,18 @@ onMounted(() => {
...
@@ -894,6 +990,18 @@ onMounted(() => {
}
}
}
}
}
}
&
.is-compact
{
>
.q-item
{
padding
:
0
6px
;
min-height
:
36px
;
}
.fileman-filelist-icon
{
padding-right
:
6px
;
min-width
:
0
;
}
}
}
}
&
-details-row
{
&
-details-row
{
display
:
flex
;
display
:
flex
;
...
...
ux/src/components/FolderRenameDialog.vue
0 → 100644
View file @
c377eca6
<
template
lang=
"pug"
>
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 650px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-rename.svg', left, size='sm')
span
{{
t
(
`fileman.folderRename`
)
}}
q-form.q-py-sm(ref='renameFolderForm', @submit='rename')
q-item
blueprint-icon(icon='folder')
q-item-section
q-input(
outlined
v-model='state.title'
dense
:rules='titleValidation'
hide-bottom-space
:label='t(`fileman.folderTitle`)'
:aria-label='t(`fileman.folderTitle`)'
lazy-rules='ondemand'
autofocus
ref='iptTitle'
@keyup.enter='rename'
)
q-item
blueprint-icon.self-start(icon='file-submodule')
q-item-section
q-input(
outlined
v-model='state.path'
dense
:rules='pathValidation'
hide-bottom-space
:label='t(`fileman.folderFileName`)'
:aria-label='t(`fileman.folderFileName`)'
:hint='t(`fileman.folderFileNameHint`)'
lazy-rules='ondemand'
@focus='state.pathDirty = true'
@keyup.enter='rename'
)
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
:label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
@click='onDialogCancel'
)
q-btn(
unelevated
:label='t(`common.actions.rename`)'
color='primary'
padding='xs md'
@click='rename'
:loading='state.loading > 0'
)
q-inner-loading(:showing='state.loading > 0')
q-spinner(color='accent', size='lg')
</
template
>
<
script
setup
>
import
gql
from
'graphql-tag'
import
{
useI18n
}
from
'vue-i18n'
import
{
useDialogPluginComponent
,
useQuasar
}
from
'quasar'
import
{
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
slugify
from
'slugify'
import
{
useSiteStore
}
from
'src/stores/site'
// PROPS
const
props
=
defineProps
({
folderId
:
{
type
:
String
,
required
:
true
}
})
// EMITS
defineEmits
([
...
useDialogPluginComponent
.
emits
])
// QUASAR
const
{
dialogRef
,
onDialogHide
,
onDialogOK
,
onDialogCancel
}
=
useDialogPluginComponent
()
const
$q
=
useQuasar
()
// STORES
const
siteStore
=
useSiteStore
()
// I18N
const
{
t
}
=
useI18n
()
// DATA
const
state
=
reactive
({
path
:
''
,
title
:
''
,
pathDirty
:
false
,
loading
:
false
})
// REFS
const
renameFolderForm
=
ref
(
null
)
const
iptTitle
=
ref
(
null
)
// VALIDATION RULES
const
titleValidation
=
[
val
=>
val
.
length
>
0
||
t
(
'fileman.folderTitleMissing'
),
val
=>
/^
[^
<>"
]
+$/
.
test
(
val
)
||
t
(
'fileman.folderTitleInvalidChars'
)
]
const
pathValidation
=
[
val
=>
val
.
length
>
0
||
t
(
'fileman.folderFileNameMissing'
),
val
=>
/^
[
a-z0-9-
]
+$/
.
test
(
val
)
||
t
(
'fileman.folderFileNameInvalid'
)
]
// WATCHERS
watch
(()
=>
state
.
title
,
(
newValue
)
=>
{
if
(
state
.
pathDirty
&&
!
state
.
path
)
{
state
.
pathDirty
=
false
}
if
(
!
state
.
pathDirty
)
{
state
.
path
=
slugify
(
newValue
,
{
lower
:
true
,
strict
:
true
})
}
})
// METHODS
async
function
rename
()
{
state
.
loading
++
try
{
const
isFormValid
=
await
renameFolderForm
.
value
.
validate
(
true
)
if
(
!
isFormValid
)
{
throw
new
Error
(
t
(
'fileman.renameFolderInvalidData'
))
}
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation renameFolder (
$folderId: UUID!
$pathName: String!
$title: String!
) {
renameFolder (
folderId: $folderId
pathName: $pathName
title: $title
) {
operation {
succeeded
message
}
}
}
`
,
variables
:
{
folderId
:
props
.
folderId
,
pathName
:
state
.
path
,
title
:
state
.
title
}
})
if
(
resp
?.
data
?.
renameFolder
?.
operation
?.
succeeded
)
{
$q
.
notify
({
type
:
'positive'
,
message
:
t
(
'fileman.renameFolderSuccess'
)
})
onDialogOK
()
}
else
{
throw
new
Error
(
resp
?.
data
?.
renameFolder
?.
operation
?.
message
||
'An unexpected error occured.'
)
}
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
}
state
.
loading
--
}
// MOUNTED
onMounted
(
async
()
=>
{
state
.
loading
++
try
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query fetchFolderForRename (
$id: UUID!
) {
folderById (
id: $id
) {
id
folderPath
fileName
title
}
}
`
,
variables
:
{
id
:
props
.
folderId
}
})
if
(
resp
?.
data
?.
folderById
?.
id
!==
props
.
folderId
)
{
throw
new
Error
(
'Failed to fetch folder data.'
)
}
state
.
path
=
resp
.
data
.
folderById
.
fileName
state
.
title
=
resp
.
data
.
folderById
.
title
state
.
pathDirty
=
true
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
onDialogCancel
()
}
state
.
loading
--
})
</
script
>
ux/src/components/PageSaveDialog.vue
View file @
c377eca6
...
@@ -282,7 +282,7 @@ async function loadTree (parentId, types) {
...
@@ -282,7 +282,7 @@ async function loadTree (parentId, types) {
title
title
createdAt
createdAt
updatedAt
updatedAt
pageE
ditor
e
ditor
}
}
}
}
}
}
...
...
ux/src/i18n/locales/en.json
View file @
c377eca6
...
@@ -1611,5 +1611,8 @@
...
@@ -1611,5 +1611,8 @@
"admin.flags.advanced.label"
:
"Custom Configuration"
,
"admin.flags.advanced.label"
:
"Custom Configuration"
,
"admin.flags.advanced.hint"
:
"Set custom configuration flags. Note that all values are public to all users! Do not insert senstive data."
,
"admin.flags.advanced.hint"
:
"Set custom configuration flags. Note that all values are public to all users! Do not insert senstive data."
,
"admin.flags.saveSuccess"
:
"Flags have been updated successfully."
,
"admin.flags.saveSuccess"
:
"Flags have been updated successfully."
,
"fileman.copyURLSuccess"
:
"URL has been copied to the clipboard."
"fileman.copyURLSuccess"
:
"URL has been copied to the clipboard."
,
"fileman.folderRename"
:
"Rename Folder"
,
"fileman.renameFolderInvalidData"
:
"One or more fields are invalid."
,
"fileman.renameFolderSuccess"
:
"Folder renamed successfully."
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment