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
5efa0abe
Unverified
Commit
5efa0abe
authored
Jun 25, 2023
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: file manager improvements
parent
7fde587a
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
115 additions
and
210 deletions
+115
-210
docker-compose.yml
.devcontainer/docker-compose.yml
+1
-1
tree.mjs
server/graph/resolvers/tree.mjs
+1
-6
en.json
server/locales/en.json
+2
-0
FileManager.vue
ux/src/components/FileManager.vue
+21
-5
LocaleInstallDialog.vue
ux/src/components/LocaleInstallDialog.vue
+0
-181
LocaleSelectorMenu.vue
ux/src/components/LocaleSelectorMenu.vue
+1
-1
PageNewMenu.vue
ux/src/components/PageNewMenu.vue
+7
-2
TreeBrowserDialog.vue
ux/src/components/TreeBrowserDialog.vue
+11
-3
Index.vue
ux/src/pages/Index.vue
+26
-1
routes.js
ux/src/router/routes.js
+10
-0
editor.js
ux/src/stores/editor.js
+2
-1
page.js
ux/src/stores/page.js
+33
-9
No files found.
.devcontainer/docker-compose.yml
View file @
5efa0abe
...
...
@@ -28,7 +28,7 @@ services:
# (Adding the "ports" property to this file will not forward from a Codespace.)
db
:
image
:
postgres:
latest
image
:
postgres:
16beta1
restart
:
unless-stopped
volumes
:
-
postgres-data:/var/lib/postgresql/data
...
...
server/graph/resolvers/tree.mjs
View file @
5efa0abe
...
...
@@ -66,11 +66,6 @@ export default {
if
(
args
.
includeAncestors
)
{
const
parentPathParts
=
parentPath
.
split
(
'.'
)
for
(
let
i
=
0
;
i
<=
parentPathParts
.
length
;
i
++
)
{
console
.
info
({
folderPath
:
encodeFolderPath
(
_
.
dropRight
(
parentPathParts
,
i
).
join
(
'.'
)),
fileName
:
_
.
nth
(
parentPathParts
,
i
*
-
1
),
type
:
'folder'
})
builder
.
orWhere
({
folderPath
:
encodeFolderPath
(
_
.
dropRight
(
parentPathParts
,
i
).
join
(
'.'
)),
fileName
:
_
.
nth
(
parentPathParts
,
i
*
-
1
),
...
...
@@ -110,7 +105,7 @@ export default {
updatedAt
:
item
.
updatedAt
,
...(
item
.
type
===
'folder'
)
&&
{
childrenCount
:
item
.
meta
?.
children
||
0
,
isAncestor
:
item
.
folderPath
.
length
<
parentPath
.
length
||
(
parentPath
!==
''
&&
item
.
folderPath
===
parentPath
)
isAncestor
:
item
.
folderPath
.
length
<
parentPath
.
length
},
...(
item
.
type
===
'asset'
)
&&
{
fileSize
:
item
.
meta
?.
fileSize
||
0
,
...
...
server/locales/en.json
View file @
5efa0abe
...
...
@@ -1517,6 +1517,7 @@
"editor.pageRel.title"
:
"Add Page Relation"
,
"editor.pageRel.titleEdit"
:
"Edit Page Relation"
,
"editor.pageScripts.title"
:
"Page Scripts"
,
"editor.props.alias"
:
"Alias"
,
"editor.props.allowComments"
:
"Allow Comments"
,
"editor.props.allowCommentsHint"
:
"Enable commenting abilities on this page."
,
"editor.props.allowContributions"
:
"Allow Contributions"
,
...
...
@@ -1642,6 +1643,7 @@
"fileman.rarFileType"
:
"RAR Archive"
,
"fileman.renameFolderInvalidData"
:
"One or more fields are invalid."
,
"fileman.renameFolderSuccess"
:
"Folder renamed successfully."
,
"fileman.searchFolder"
:
"Search folder..."
,
"fileman.svgFileType"
:
"Scalable Vector Graphic"
,
"fileman.tarFileType"
:
"TAR Archive"
,
"fileman.tgzFileType"
:
"Gzipped TAR Archive"
,
...
...
ux/src/components/FileManager.vue
View file @
5efa0abe
...
...
@@ -8,9 +8,11 @@ q-layout.fileman(view='hHh lpR lFr', container)
q-btn.q-mr-sm.acrylic-btn(
flat
color='white'
label='EN'
:label='commonStore.locale'
:aria-label='commonStore.locale'
style='height: 40px;'
)
locale-selector-menu
q-input(
dark
v-model='state.search'
...
...
@@ -18,7 +20,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
dense
ref='searchField'
style='width: 100%;'
label='Search folder...
'
:label='t(`fileman.searchFolder`)
'
:debounce='500'
)
template(v-slot:prepend)
...
...
@@ -197,6 +199,8 @@ q-layout.fileman(view='hHh lpR lFr', container)
:hide-asset-btn='true'
:show-new-folder='true'
@new-folder='() => newFolder(state.currentFolderId)'
@new-page='() => close()'
:base-path='folderPath'
)
q-btn(
flat
...
...
@@ -252,11 +256,11 @@ q-layout.fileman(view='hHh lpR lFr', container)
)
q-card.q-pa-sm
q-list(dense, style='min-width: 150px;')
q-item(clickable, v-if='item.type !== `folder`', @click='insertItem(item)')
q-item(clickable, v-if='i
nsertMode && i
tem.type !== `folder`', @click='insertItem(item)')
q-item-section(side)
q-icon(name='las la-plus-circle', color='primary')
q-item-section
{{
t
(
`common.actions.insert`
)
}}
q-item(clickable, v-if='item.type === `page`')
q-item(clickable, v-if='item.type === `page`'
, @click='editItem(item)'
)
q-item-section(side)
q-icon(name='las la-edit', color='orange')
q-item-section
{{
t
(
`common.actions.edit`
)
}}
...
...
@@ -277,7 +281,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
q-item-section(side)
q-icon(name='las la-clipboard', color='primary')
q-item-section
{{
t
(
`common.actions.copyURL`
)
}}
q-item(clickable, v-if='item.type !== `folder`', @click='')
q-item(clickable, v-if='item.type !== `folder`', @click='
downloadItem(item)
')
q-item-section(side)
q-icon(name='las la-download', color='primary')
q-item-section
{{
t
(
`common.actions.download`
)
}}
...
...
@@ -326,12 +330,14 @@ import Tree from './TreeNav.vue'
import
fileTypes
from
'../helpers/fileTypes'
import
{
useCommonStore
}
from
'src/stores/common'
import
{
usePageStore
}
from
'src/stores/page'
import
{
useSiteStore
}
from
'src/stores/site'
import
FolderCreateDialog
from
'src/components/FolderCreateDialog.vue'
import
FolderDeleteDialog
from
'src/components/FolderDeleteDialog.vue'
import
FolderRenameDialog
from
'src/components/FolderRenameDialog.vue'
import
LocaleSelectorMenu
from
'src/components/LocaleSelectorMenu.vue'
// QUASAR
...
...
@@ -339,6 +345,7 @@ const $q = useQuasar()
// STORES
const
commonStore
=
useCommonStore
()
const
pageStore
=
usePageStore
()
const
siteStore
=
useSiteStore
()
...
...
@@ -941,6 +948,15 @@ async function copyItemURL (item) {
}
}
async
function
editItem
(
item
)
{
router
.
push
(
item
.
folderPath
?
`/_edit/
${
item
.
folderPath
}
/
${
item
.
fileName
}
`
:
`/_edit/
${
item
.
fileName
}
`
)
close
()
}
function
downloadItem
(
item
)
{
}
function
renameItem
(
item
)
{
console
.
info
(
item
)
switch
(
item
.
type
)
{
...
...
ux/src/components/LocaleInstallDialog.vue
deleted
100644 → 0
View file @
7fde587a
<
template
lang=
"pug"
>
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 850px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-down.svg', left, size='sm')
span
{{
t
(
`admin.locale.downloadTitle`
)
}}
q-card-section.q-pa-none
q-table.no-border-radius(
:data='state.locales'
:columns='headers'
row-name='code'
flat
hide-bottom
:rows-per-page-options='[0]'
:loading='state.loading > 0'
)
template(v-slot:body-cell-code='props')
q-td(:props='props')
q-chip(
square
color='teal'
text-color='white'
dense
): span.text-caption
{{
props
.
value
}}
template(v-slot:body-cell-name='props')
q-td(:props='props')
strong
{{
props
.
value
}}
template(v-slot:body-cell-isRTL='props')
q-td(:props='props')
q-icon(
v-if='props.value'
name='las la-check'
color='brown'
size='xs'
)
template(v-slot:body-cell-availability='props')
q-td(:props='props')
q-circular-progress(
size='md'
show-value
:value='props.value'
:thickness='0.1'
:color='props.value <= 33 ? `negative` : (props.value <= 66) ? `warning` : `positive`'
)
{{
props
.
value
}}
%
template(v-slot:body-cell-isInstalled='props')
q-td(:props='props')
q-spinner(
v-if='props.row.isDownloading'
color='primary'
size='20px'
:thickness='2'
)
q-btn(
v-else-if='props.value && props.row.installDate < props.row.updatedAt'
flat
round
dense
@click='download(props.row)'
icon='las la-redo-alt'
color='accent'
)
q-btn(
v-else-if='props.value'
flat
round
dense
@click='download(props.row)'
icon='las la-check-circle'
color='positive'
)
q-btn(
v-else
flat
round
dense
@click='download(props.row)'
icon='las la-cloud-download-alt'
color='primary'
)
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
:label='t(`common.actions.close`)'
color='grey'
padding='xs md'
@click='onDialogCancel'
)
q-inner-loading(:showing='state.loading > 0')
q-spinner(color='accent', size='lg')
</
template
>
<
script
setup
>
import
{
useI18n
}
from
'vue-i18n'
import
{
useDialogPluginComponent
,
useQuasar
}
from
'quasar'
import
{
reactive
,
ref
}
from
'vue'
import
{
useAdminStore
}
from
'../stores/admin'
// EMITS
defineEmits
([
...
useDialogPluginComponent
.
emits
])
// QUASAR
const
{
dialogRef
,
onDialogHide
,
onDialogOK
,
onDialogCancel
}
=
useDialogPluginComponent
()
const
$q
=
useQuasar
()
// STORES
const
adminStore
=
useAdminStore
()
// I18N
const
{
t
}
=
useI18n
()
// DATA
const
state
=
reactive
({
locales
:
[],
loading
:
0
})
const
headers
=
[
{
label
:
t
(
'admin.locale.code'
),
align
:
'left'
,
field
:
'code'
,
name
:
'code'
,
sortable
:
true
,
style
:
'width: 90px'
},
{
label
:
t
(
'admin.locale.name'
),
align
:
'left'
,
field
:
'name'
,
name
:
'name'
,
sortable
:
true
},
{
label
:
t
(
'admin.locale.nativeName'
),
align
:
'left'
,
field
:
'nativeName'
,
name
:
'nativeName'
,
sortable
:
true
},
{
label
:
t
(
'admin.locale.rtl'
),
align
:
'center'
,
field
:
'isRTL'
,
name
:
'isRTL'
,
sortable
:
false
,
style
:
'width: 10px'
},
{
label
:
t
(
'admin.locale.availability'
),
align
:
'center'
,
field
:
'availability'
,
name
:
'availability'
,
sortable
:
false
,
style
:
'width: 120px'
},
{
label
:
t
(
'admin.locale.download'
),
align
:
'center'
,
field
:
'isInstalled'
,
name
:
'isInstalled'
,
sortable
:
false
,
style
:
'width: 100px'
}
]
// METHODS
async
function
download
(
lc
)
{
}
</
script
>
ux/src/components/LocaleSelectorMenu.vue
View file @
5efa0abe
<
template
lang=
"pug"
>
q-menu.translucent-menu(
auto-close
anchor='bottom
middle
'
anchor='bottom
left
'
self='top left'
)
q-list(padding, style='min-width: 200px;')
...
...
ux/src/components/PageNewMenu.vue
View file @
5efa0abe
...
...
@@ -88,12 +88,16 @@ const props = defineProps({
showNewFolder
:
{
type
:
Boolean
,
default
:
false
},
basePath
:
{
type
:
String
,
default
:
null
}
})
// EMITS
const
emit
=
defineEmits
([
'newFolder'
])
const
emit
=
defineEmits
([
'newFolder'
,
'newPage'
])
// QUASAR
...
...
@@ -114,7 +118,8 @@ const { t } = useI18n()
async
function
create
(
editor
)
{
$q
.
loading
.
show
()
await
pageStore
.
pageCreate
({
editor
})
emit
(
'newPage'
)
await
pageStore
.
pageCreate
({
editor
,
basePath
:
props
.
basePath
})
$q
.
loading
.
hide
()
}
...
...
ux/src/components/TreeBrowserDialog.vue
View file @
5efa0abe
...
...
@@ -114,7 +114,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
import
{
useI18n
}
from
'vue-i18n'
import
{
computed
,
onMounted
,
reactive
}
from
'vue'
import
{
useDialogPluginComponent
,
useQuasar
}
from
'quasar'
import
{
cloneDeep
,
find
}
from
'lodash-es'
import
{
cloneDeep
,
find
,
last
}
from
'lodash-es'
import
gql
from
'graphql-tag'
import
fileTypes
from
'../helpers/fileTypes'
...
...
@@ -124,6 +124,7 @@ import Tree from 'src/components/TreeNav.vue'
import
{
usePageStore
}
from
'src/stores/page'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
dropRight
}
from
'lodash'
// PROPS
...
...
@@ -351,6 +352,13 @@ function newFolder (parentId) {
// MOUNTED
onMounted
(()
=>
{
let
fPath
=
props
.
folderPath
let
fName
=
props
.
itemFileName
if
(
props
.
itemFileName
?.
indexOf
(
'/'
)
>=
0
)
{
const
fParts
=
props
.
itemFileName
.
split
(
'/'
)
fPath
=
dropRight
(
fParts
,
1
).
join
(
'/'
)
fName
=
last
(
fParts
)
}
switch
(
props
.
mode
)
{
case
'pageSave'
:
{
state
.
typesToFetch
=
[
'folder'
,
'page'
]
...
...
@@ -358,12 +366,12 @@ onMounted(() => {
}
}
loadTree
({
parentPath
:
props
.
folder
Path
,
parentPath
:
f
Path
,
types
:
state
.
typesToFetch
,
initLoad
:
true
})
state
.
title
=
props
.
itemTitle
||
''
state
.
path
=
props
.
itemFile
Name
||
''
state
.
path
=
f
Name
||
''
})
</
script
>
...
...
ux/src/pages/Index.vue
View file @
5efa0abe
...
...
@@ -262,6 +262,12 @@ const lastModified = computed(() => {
// WATCHERS
watch
(()
=>
route
.
path
,
async
(
newValue
)
=>
{
// -> Ignore route change (e.g. from page create route fix)
if
(
editorStore
.
ignoreRouteChange
)
{
editorStore
.
$patch
({
ignoreRouteChange
:
false
})
return
}
// -> Enter Create Mode?
if
(
newValue
.
startsWith
(
'/_create'
))
{
if
(
!
route
.
params
.
editor
)
{
...
...
@@ -272,8 +278,27 @@ watch(() => route.path, async (newValue) => {
return
router
.
replace
(
'/'
)
}
$q
.
loading
.
show
()
await
pageStore
.
pageCreate
({
editor
:
route
.
params
.
editor
})
const
pageCreateArgs
=
{
editor
:
route
.
params
.
editor
,
fromNavigate
:
true
}
if
(
route
.
query
.
path
)
{
pageCreateArgs
.
path
=
route
.
query
.
path
}
if
(
route
.
query
.
locale
)
{
pageCreateArgs
.
locale
=
route
.
query
.
locale
}
await
pageStore
.
pageCreate
(
pageCreateArgs
)
$q
.
loading
.
hide
()
return
}
// -> Enter Edit Mode?
if
(
newValue
.
startsWith
(
'/_edit'
))
{
if
(
!
route
.
params
.
pagePath
)
{
return
router
.
replace
(
'/'
)
}
$q
.
loading
.
show
()
await
pageStore
.
pageEdit
({
path
:
route
.
params
.
pagePath
,
fromNavigate
:
true
})
$q
.
loading
.
hide
()
return
}
// -> Moving to a non-page path? Ignore
...
...
ux/src/router/routes.js
View file @
5efa0abe
...
...
@@ -86,6 +86,16 @@ const routes = [
{
path
:
''
,
component
:
()
=>
import
(
'../pages/Index.vue'
)
}
]
},
// --------------------------------
// EDIT
// --------------------------------
{
path
:
'/_edit/:pagePath?'
,
component
:
()
=>
import
(
'../layouts/MainLayout.vue'
),
children
:
[
{
path
:
''
,
component
:
()
=>
import
(
'../pages/Index.vue'
)
}
]
},
// -----------------------
// STANDARD PAGE CATCH-ALL
// -----------------------
...
...
ux/src/stores/editor.js
View file @
5efa0abe
...
...
@@ -23,7 +23,8 @@ export const useEditorStore = defineStore('editor', {
lastChangeTimestamp
:
null
,
editors
:
{},
configIsLoaded
:
false
,
reasonForChange
:
''
reasonForChange
:
''
,
ignoreRouteChange
:
false
}),
getters
:
{
hasPendingChanges
:
(
state
)
=>
{
...
...
ux/src/stores/page.js
View file @
5efa0abe
...
...
@@ -313,14 +313,30 @@ export const usePageStore = defineStore('page', {
/**
* PAGE - CREATE
*/
async
pageCreate
({
editor
,
locale
,
path
,
title
=
''
,
description
=
''
,
content
=
''
})
{
async
pageCreate
({
editor
,
locale
,
path
,
basePath
,
title
=
''
,
description
=
''
,
content
=
''
,
fromNavigate
=
false
}
=
{
})
{
const
editorStore
=
useEditorStore
()
// -> Load editor config
if
(
!
editorStore
.
configIsLoaded
)
{
await
editorStore
.
fetchConfigs
()
}
const
noDefaultPath
=
Boolean
(
!
path
&&
path
!==
''
)
// -> Path normalization
if
(
path
?.
startsWith
(
'/'
))
{
path
=
path
.
substring
(
1
)
}
if
(
basePath
?.
startsWith
(
'/'
))
{
basePath
=
basePath
.
substring
(
1
)
}
if
(
basePath
?.
endsWith
(
'/'
))
{
basePath
=
basePath
.
substring
(
0
,
basePath
.
length
-
1
)
}
// -> Redirect if not at /_create path
if
(
!
this
.
router
.
currentRoute
.
value
.
path
.
startsWith
(
'/_create/'
)
&&
!
fromNavigate
)
{
editorStore
.
$patch
({
ignoreRouteChange
:
true
})
this
.
router
.
push
(
`/_create/
${
editor
}
`
)
}
// -> Init editor
editorStore
.
$patch
({
...
...
@@ -333,7 +349,7 @@ export const usePageStore = defineStore('page', {
// -> Default Page Path
let
newPath
=
path
if
(
!
path
&&
path
!==
''
)
{
const
parentPath
=
dropRight
(
this
.
path
.
split
(
'/'
),
1
).
join
(
'/'
)
const
parentPath
=
basePath
||
basePath
===
''
?
basePath
:
dropRight
(
this
.
path
.
split
(
'/'
),
1
).
join
(
'/'
)
newPath
=
parentPath
?
`
${
parentPath
}
/new-page`
:
'new-page'
}
...
...
@@ -353,18 +369,26 @@ export const usePageStore = defineStore('page', {
render
:
''
,
mode
:
'edit'
})
if
(
noDefaultPath
)
{
this
.
router
.
push
(
`/_create/
${
editor
}
`
)
}
},
/**
* PAGE - EDIT
*/
async
pageEdit
()
{
async
pageEdit
(
{
path
,
id
,
fromNavigate
=
false
}
=
{}
)
{
const
editorStore
=
useEditorStore
()
await
this
.
pageLoad
({
id
:
this
.
id
,
withContent
:
true
})
const
loadArgs
=
{
withContent
:
true
}
if
(
id
)
{
loadArgs
.
id
=
id
}
else
if
(
path
)
{
loadArgs
.
path
=
path
}
else
{
loadArgs
.
id
=
this
.
id
}
await
this
.
pageLoad
(
loadArgs
)
if
(
!
editorStore
.
configIsLoaded
)
{
await
editorStore
.
fetchConfigs
()
...
...
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