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
b4769b9a
Unverified
Commit
b4769b9a
authored
Dec 24, 2022
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: File Manager improvements + system flags
parent
4cdeba80
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
620 additions
and
205 deletions
+620
-205
data.yml
server/app/data.yml
+3
-2
config.js
server/core/config.js
+1
-1
3.0.0.js
server/db/migrations/3.0.0.js
+10
-1
system.js
server/graph/resolvers/system.js
+6
-7
tree.js
server/graph/resolvers/tree.js
+6
-6
system.graphql
server/graph/schemas/system.graphql
+2
-12
pages.js
server/models/pages.js
+42
-4
ultraviolet-administrative-tools.svg
ux/public/_assets/icons/ultraviolet-administrative-tools.svg
+2
-0
ultraviolet-asciidoc.svg
ux/public/_assets/icons/ultraviolet-asciidoc.svg
+8
-0
App.vue
ux/src/App.vue
+6
-0
FileManager.vue
ux/src/components/FileManager.vue
+145
-47
FolderDeleteDialog.vue
ux/src/components/FolderDeleteDialog.vue
+109
-0
PageNewMenu.vue
ux/src/components/PageNewMenu.vue
+15
-9
PageSaveDialog.vue
ux/src/components/PageSaveDialog.vue
+67
-15
TreeNav.vue
ux/src/components/TreeNav.vue
+19
-1
TreeNode.vue
ux/src/components/TreeNode.vue
+1
-1
en.json
ux/src/i18n/locales/en.json
+15
-8
AdminLayout.vue
ux/src/layouts/AdminLayout.vue
+1
-1
ProfileLayout.vue
ux/src/layouts/ProfileLayout.vue
+15
-13
AdminFlags.vue
ux/src/pages/AdminFlags.vue
+110
-76
AdminScheduler.vue
ux/src/pages/AdminScheduler.vue
+1
-1
flags.js
ux/src/stores/flags.js
+36
-0
No files found.
server/app/data.yml
View file @
b4769b9a
...
@@ -71,8 +71,9 @@ defaults:
...
@@ -71,8 +71,9 @@ defaults:
authJwtRenewablePeriod
:
'
14d'
authJwtRenewablePeriod
:
'
14d'
enforceSameOriginReferrerPolicy
:
true
enforceSameOriginReferrerPolicy
:
true
flags
:
flags
:
ldapdebug
:
false
experimental
:
false
sqllog
:
false
authDebug
:
false
sqlLog
:
false
# System defaults
# System defaults
channel
:
NEXT
channel
:
NEXT
cors
:
cors
:
...
...
server/core/config.js
View file @
b4769b9a
...
@@ -133,7 +133,7 @@ module.exports = {
...
@@ -133,7 +133,7 @@ module.exports = {
* Apply Dev Flags
* Apply Dev Flags
*/
*/
async
applyFlags
()
{
async
applyFlags
()
{
WIKI
.
db
.
knex
.
client
.
config
.
debug
=
WIKI
.
config
.
flags
.
sql
l
og
WIKI
.
db
.
knex
.
client
.
config
.
debug
=
WIKI
.
config
.
flags
.
sql
L
og
},
},
/**
/**
...
...
server/db/migrations/3.0.0.js
View file @
b4769b9a
...
@@ -285,7 +285,7 @@ exports.up = async knex => {
...
@@ -285,7 +285,7 @@ exports.up = async knex => {
table
.
specificType
(
'folderPath'
,
'ltree'
).
index
().
index
(
'tree_folderpath_gist_index'
,
{
indexType
:
'GIST'
})
table
.
specificType
(
'folderPath'
,
'ltree'
).
index
().
index
(
'tree_folderpath_gist_index'
,
{
indexType
:
'GIST'
})
table
.
string
(
'fileName'
).
notNullable
().
index
()
table
.
string
(
'fileName'
).
notNullable
().
index
()
table
.
enu
(
'type'
,
[
'folder'
,
'page'
,
'asset'
]).
notNullable
().
index
()
table
.
enu
(
'type'
,
[
'folder'
,
'page'
,
'asset'
]).
notNullable
().
index
()
table
.
uuid
(
'targetId
'
).
index
()
table
.
string
(
'localeCode'
,
5
).
notNullable
().
defaultTo
(
'en
'
).
index
()
table
.
string
(
'title'
).
notNullable
()
table
.
string
(
'title'
).
notNullable
()
table
.
jsonb
(
'meta'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
jsonb
(
'meta'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
timestamp
(
'createdAt'
).
notNullable
().
defaultTo
(
knex
.
fn
.
now
())
table
.
timestamp
(
'createdAt'
).
notNullable
().
defaultTo
(
knex
.
fn
.
now
())
...
@@ -372,6 +372,7 @@ exports.up = async knex => {
...
@@ -372,6 +372,7 @@ exports.up = async knex => {
table
.
string
(
'localeCode'
,
5
).
references
(
'code'
).
inTable
(
'locales'
).
index
()
table
.
string
(
'localeCode'
,
5
).
references
(
'code'
).
inTable
(
'locales'
).
index
()
table
.
uuid
(
'authorId'
).
notNullable
().
references
(
'id'
).
inTable
(
'users'
).
index
()
table
.
uuid
(
'authorId'
).
notNullable
().
references
(
'id'
).
inTable
(
'users'
).
index
()
table
.
uuid
(
'creatorId'
).
notNullable
().
references
(
'id'
).
inTable
(
'users'
).
index
()
table
.
uuid
(
'creatorId'
).
notNullable
().
references
(
'id'
).
inTable
(
'users'
).
index
()
table
.
uuid
(
'ownerId'
).
notNullable
().
references
(
'id'
).
inTable
(
'users'
).
index
()
table
.
uuid
(
'siteId'
).
notNullable
().
references
(
'id'
).
inTable
(
'sites'
).
index
()
table
.
uuid
(
'siteId'
).
notNullable
().
references
(
'id'
).
inTable
(
'sites'
).
index
()
})
})
.
table
(
'storage'
,
table
=>
{
.
table
(
'storage'
,
table
=>
{
...
@@ -440,6 +441,14 @@ exports.up = async knex => {
...
@@ -440,6 +441,14 @@ exports.up = async knex => {
}
}
},
},
{
{
key
:
'flags'
,
value
:
{
experimental
:
false
,
authDebug
:
false
,
sqlLog
:
false
}
},
{
key
:
'icons'
,
key
:
'icons'
,
value
:
{
value
:
{
fa
:
{
fa
:
{
...
...
server/graph/resolvers/system.js
View file @
b4769b9a
...
@@ -11,9 +11,7 @@ const graphHelper = require('../../helpers/graph')
...
@@ -11,9 +11,7 @@ const graphHelper = require('../../helpers/graph')
module
.
exports
=
{
module
.
exports
=
{
Query
:
{
Query
:
{
systemFlags
()
{
systemFlags
()
{
return
_
.
transform
(
WIKI
.
config
.
flags
,
(
result
,
value
,
key
)
=>
{
return
WIKI
.
config
.
flags
result
.
push
({
key
,
value
})
},
[])
},
},
async
systemInfo
()
{
return
{}
},
async
systemInfo
()
{
return
{}
},
async
systemExtensions
()
{
async
systemExtensions
()
{
...
@@ -150,9 +148,10 @@ module.exports = {
...
@@ -150,9 +148,10 @@ module.exports = {
}
}
},
},
async
updateSystemFlags
(
obj
,
args
,
context
)
{
async
updateSystemFlags
(
obj
,
args
,
context
)
{
WIKI
.
config
.
flags
=
_
.
transform
(
args
.
flags
,
(
result
,
row
)
=>
{
WIKI
.
config
.
flags
=
{
_
.
set
(
result
,
row
.
key
,
row
.
value
)
...
WIKI
.
config
.
flags
,
},
{})
...
args
.
flags
}
await
WIKI
.
configSvc
.
applyFlags
()
await
WIKI
.
configSvc
.
applyFlags
()
await
WIKI
.
configSvc
.
saveToDb
([
'flags'
])
await
WIKI
.
configSvc
.
saveToDb
([
'flags'
])
return
{
return
{
...
@@ -164,7 +163,7 @@ module.exports = {
...
@@ -164,7 +163,7 @@ module.exports = {
// TODO: broadcast config update
// TODO: broadcast config update
await
WIKI
.
configSvc
.
saveToDb
([
'security'
])
await
WIKI
.
configSvc
.
saveToDb
([
'security'
])
return
{
return
{
status
:
graphHelper
.
generateSuccess
(
'System Security configuration applied successfully'
)
operation
:
graphHelper
.
generateSuccess
(
'System Security configuration applied successfully'
)
}
}
}
}
},
},
...
...
server/graph/resolvers/tree.js
View file @
b4769b9a
...
@@ -7,7 +7,7 @@ const typeResolvers = {
...
@@ -7,7 +7,7 @@ const typeResolvers = {
asset
:
'TreeItemAsset'
asset
:
'TreeItemAsset'
}
}
const
rePathName
=
/^
[
a-z0-9
_
]
+$/
const
rePathName
=
/^
[
a-z0-9
-
]
+$/
const
reTitle
=
/^
[^
<>"
]
+$/
const
reTitle
=
/^
[^
<>"
]
+$/
module
.
exports
=
{
module
.
exports
=
{
...
@@ -41,7 +41,7 @@ module.exports = {
...
@@ -41,7 +41,7 @@ module.exports = {
if
(
args
.
parentId
)
{
if
(
args
.
parentId
)
{
const
parent
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
args
.
parentId
).
first
()
const
parent
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
args
.
parentId
).
first
()
if
(
parent
)
{
if
(
parent
)
{
parentPath
=
parent
.
folderPath
?
`
${
parent
.
folderPath
}
.
${
parent
.
fileName
}
`
:
parent
.
fileName
parentPath
=
(
parent
.
folderPath
?
`
${
parent
.
folderPath
}
.
${
parent
.
fileName
}
`
:
parent
.
fileName
).
replaceAll
(
'-'
,
'_'
)
}
}
}
else
if
(
args
.
parentPath
)
{
}
else
if
(
args
.
parentPath
)
{
parentPath
=
args
.
parentPath
.
replaceAll
(
'/'
,
'.'
).
replaceAll
(
'-'
,
'_'
).
toLowerCase
()
parentPath
=
args
.
parentPath
.
replaceAll
(
'/'
,
'.'
).
replaceAll
(
'-'
,
'_'
).
toLowerCase
()
...
@@ -101,11 +101,11 @@ module.exports = {
...
@@ -101,11 +101,11 @@ module.exports = {
if
(
parent
)
{
if
(
parent
)
{
parentPath
=
parent
.
folderPath
?
`
${
parent
.
folderPath
}
.
${
parent
.
fileName
}
`
:
parent
.
fileName
parentPath
=
parent
.
folderPath
?
`
${
parent
.
folderPath
}
.
${
parent
.
fileName
}
`
:
parent
.
fileName
}
}
parentPath
=
parentPath
.
replaceAll
(
'-'
,
'_'
)
}
}
// Validate path name
// Validate path name
const
pathName
=
args
.
pathName
.
replaceAll
(
'-'
,
'_'
)
if
(
!
rePathName
.
test
(
args
.
pathName
))
{
if
(
!
rePathName
.
test
(
pathName
))
{
throw
new
Error
(
'ERR_INVALID_PATH_NAME'
)
throw
new
Error
(
'ERR_INVALID_PATH_NAME'
)
}
}
...
@@ -118,7 +118,7 @@ module.exports = {
...
@@ -118,7 +118,7 @@ module.exports = {
const
existingFolder
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
({
const
existingFolder
=
await
WIKI
.
db
.
knex
(
'tree'
).
where
({
siteId
:
args
.
siteId
,
siteId
:
args
.
siteId
,
folderPath
:
parentPath
,
folderPath
:
parentPath
,
fileName
:
pathName
fileName
:
args
.
pathName
}).
first
()
}).
first
()
if
(
existingFolder
)
{
if
(
existingFolder
)
{
throw
new
Error
(
'ERR_FOLDER_ALREADY_EXISTS'
)
throw
new
Error
(
'ERR_FOLDER_ALREADY_EXISTS'
)
...
@@ -127,7 +127,7 @@ module.exports = {
...
@@ -127,7 +127,7 @@ module.exports = {
// Create folder
// Create folder
await
WIKI
.
db
.
knex
(
'tree'
).
insert
({
await
WIKI
.
db
.
knex
(
'tree'
).
insert
({
folderPath
:
parentPath
,
folderPath
:
parentPath
,
fileName
:
pathName
,
fileName
:
args
.
pathName
,
type
:
'folder'
,
type
:
'folder'
,
title
:
args
.
title
,
title
:
args
.
title
,
siteId
:
args
.
siteId
siteId
:
args
.
siteId
...
...
server/graph/schemas/system.graphql
View file @
b4769b9a
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
extend
type
Query
{
extend
type
Query
{
systemExtensions
:
[
SystemExtension
]
systemExtensions
:
[
SystemExtension
]
systemFlags
:
[
SystemFlag
]
systemFlags
:
JSON
systemInfo
:
SystemInfo
systemInfo
:
SystemInfo
systemInstances
:
[
SystemInstance
]
systemInstances
:
[
SystemInstance
]
systemSecurity
:
SystemSecurity
systemSecurity
:
SystemSecurity
...
@@ -31,7 +31,7 @@ extend type Mutation {
...
@@ -31,7 +31,7 @@ extend type Mutation {
):
DefaultResponse
):
DefaultResponse
updateSystemFlags
(
updateSystemFlags
(
flags
:
[
SystemFlagInput
]
!
flags
:
JSON
!
):
DefaultResponse
):
DefaultResponse
updateSystemSecurity
(
updateSystemSecurity
(
...
@@ -60,16 +60,6 @@ extend type Mutation {
...
@@ -60,16 +60,6 @@ extend type Mutation {
# TYPES
# TYPES
# -----------------------------------------------
# -----------------------------------------------
type
SystemFlag
{
key
:
String
value
:
Boolean
}
input
SystemFlagInput
{
key
:
String
!
value
:
Boolean
!
}
type
SystemInfo
{
type
SystemInfo
{
configFile
:
String
configFile
:
String
cpuCores
:
Int
cpuCores
:
Int
...
...
server/models/pages.js
View file @
b4769b9a
...
@@ -310,12 +310,12 @@ module.exports = class Page extends Model {
...
@@ -310,12 +310,12 @@ module.exports = class Page extends Model {
},
},
contentType
:
WIKI
.
data
.
editors
[
opts
.
editor
]?.
contentType
??
'text'
,
contentType
:
WIKI
.
data
.
editors
[
opts
.
editor
]?.
contentType
??
'text'
,
description
:
opts
.
description
,
description
:
opts
.
description
,
// dotPath: dotPath,
editor
:
opts
.
editor
,
editor
:
opts
.
editor
,
hash
:
pageHelper
.
generateHash
({
path
:
opts
.
path
,
locale
:
opts
.
locale
}),
hash
:
pageHelper
.
generateHash
({
path
:
opts
.
path
,
locale
:
opts
.
locale
}),
icon
:
opts
.
icon
,
icon
:
opts
.
icon
,
isBrowsable
:
opts
.
isBrowsable
??
true
,
isBrowsable
:
opts
.
isBrowsable
??
true
,
localeCode
:
opts
.
locale
,
localeCode
:
opts
.
locale
,
ownerId
:
opts
.
user
.
id
,
path
:
opts
.
path
,
path
:
opts
.
path
,
publishState
:
opts
.
publishState
,
publishState
:
opts
.
publishState
,
publishEndDate
:
opts
.
publishEndDate
?.
toISO
(),
publishEndDate
:
opts
.
publishEndDate
?.
toISO
(),
...
@@ -339,6 +339,29 @@ module.exports = class Page extends Model {
...
@@ -339,6 +339,29 @@ module.exports = class Page extends Model {
// -> Render page to HTML
// -> Render page to HTML
await
WIKI
.
db
.
pages
.
renderPage
(
page
)
await
WIKI
.
db
.
pages
.
renderPage
(
page
)
// -> Add to tree
const
pathParts
=
page
.
path
.
split
(
'/'
)
await
WIKI
.
db
.
knex
(
'tree'
).
insert
({
id
:
page
.
id
,
folderPath
:
_
.
initial
(
pathParts
).
join
(
'/'
),
fileName
:
_
.
last
(
pathParts
),
type
:
'page'
,
localeCode
:
page
.
localeCode
,
title
:
page
.
title
,
meta
:
{
authorId
:
page
.
authorId
,
contentType
:
page
.
contentType
,
creatorId
:
page
.
creatorId
,
description
:
page
.
description
,
isBrowsable
:
page
.
isBrowsable
,
ownerId
:
page
.
ownerId
,
publishState
:
page
.
publishState
,
publishEndDate
:
page
.
publishEndDate
,
publishStartDate
:
page
.
publishStartDate
},
siteId
:
page
.
siteId
})
return
page
return
page
// TODO: Handle remaining flow
// TODO: Handle remaining flow
...
@@ -590,6 +613,23 @@ module.exports = class Page extends Model {
...
@@ -590,6 +613,23 @@ module.exports = class Page extends Model {
}
}
WIKI
.
events
.
outbound
.
emit
(
'deletePageFromCache'
,
page
.
hash
)
WIKI
.
events
.
outbound
.
emit
(
'deletePageFromCache'
,
page
.
hash
)
// -> Update tree
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
page
.
id
).
update
({
title
:
page
.
title
,
meta
:
{
authorId
:
page
.
authorId
,
contentType
:
page
.
contentType
,
creatorId
:
page
.
creatorId
,
description
:
page
.
description
,
isBrowsable
:
page
.
isBrowsable
,
ownerId
:
page
.
ownerId
,
publishState
:
page
.
publishState
,
publishEndDate
:
page
.
publishEndDate
,
publishStartDate
:
page
.
publishStartDate
},
updatedAt
:
page
.
updatedAt
})
// // -> Update Search Index
// // -> Update Search Index
// const pageContents = await WIKI.db.pages.query().findById(page.id).select('render')
// const pageContents = await WIKI.db.pages.query().findById(page.id).select('render')
// page.safeContent = WIKI.db.pages.cleanHTML(pageContents.render)
// page.safeContent = WIKI.db.pages.cleanHTML(pageContents.render)
...
@@ -948,12 +988,10 @@ module.exports = class Page extends Model {
...
@@ -948,12 +988,10 @@ module.exports = class Page extends Model {
// -> Delete page
// -> Delete page
await
WIKI
.
db
.
pages
.
query
().
delete
().
where
(
'id'
,
page
.
id
)
await
WIKI
.
db
.
pages
.
query
().
delete
().
where
(
'id'
,
page
.
id
)
await
WIKI
.
db
.
knex
(
'tree'
).
where
(
'id'
,
page
.
id
).
del
()
await
WIKI
.
db
.
pages
.
deletePageFromCache
(
page
.
hash
)
await
WIKI
.
db
.
pages
.
deletePageFromCache
(
page
.
hash
)
WIKI
.
events
.
outbound
.
emit
(
'deletePageFromCache'
,
page
.
hash
)
WIKI
.
events
.
outbound
.
emit
(
'deletePageFromCache'
,
page
.
hash
)
// -> Rebuild page tree
await
WIKI
.
db
.
pages
.
rebuildTree
()
// -> Delete from Search Index
// -> Delete from Search Index
await
WIKI
.
data
.
searchEngine
.
deleted
(
page
)
await
WIKI
.
data
.
searchEngine
.
deleted
(
page
)
...
...
ux/public/_assets/icons/ultraviolet-administrative-tools.svg
0 → 100644
View file @
b4769b9a
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 40 40"
width=
"80px"
height=
"80px"
><path
fill=
"#b6dcfe"
d=
"M17.676,38.5l-1.125-4.501l-0.284-0.076c-2.458-0.658-4.696-1.946-6.473-3.724l-0.208-0.208 l-4.445,1.271l-2.324-4.025l3.335-3.225l-0.076-0.284C5.741,22.475,5.571,21.22,5.571,20s0.17-2.475,0.504-3.729l0.076-0.284 l-3.335-3.225l2.324-4.025l4.445,1.271l0.208-0.208c1.778-1.779,4.016-3.066,6.473-3.724l0.284-0.076L17.676,1.5h4.647 l1.125,4.501l0.284,0.076c2.457,0.657,4.695,1.945,6.473,3.724l0.208,0.208l4.445-1.271l2.324,4.025l-3.335,3.225l0.076,0.284 c0.335,1.253,0.505,2.507,0.505,3.728c0,1.22-0.17,2.475-0.504,3.729l-0.076,0.284l3.335,3.224l-2.324,4.026l-4.445-1.272 l-0.208,0.208c-1.777,1.779-4.016,3.066-6.473,3.724l-0.284,0.076L22.324,38.5H17.676z M20,12.036 c-4.392,0-7.964,3.573-7.964,7.964s3.573,7.964,7.964,7.964s7.964-3.573,7.964-7.964S24.392,12.036,20,12.036z"
/><path
fill=
"#4788c7"
d=
"M21.933,2l0.959,3.837l0.143,0.571l0.569,0.152c2.372,0.635,4.532,1.878,6.248,3.594l0.416,0.417 l0.566-0.162l3.787-1.083l1.934,3.349l-2.843,2.749l-0.423,0.409l0.152,0.568c0.324,1.211,0.488,2.422,0.488,3.599 c0,1.177-0.164,2.388-0.488,3.599l-0.152,0.568l0.423,0.409l2.843,2.749l-1.934,3.349l-3.787-1.083l-0.566-0.162l-0.416,0.417 c-1.715,1.716-3.876,2.959-6.248,3.594l-0.569,0.152l-0.143,0.571L21.934,38h-3.867l-0.959-3.837l-0.143-0.571l-0.569-0.152 c-2.372-0.635-4.533-1.878-6.248-3.594l-0.416-0.417l-0.566,0.162l-3.787,1.083l-1.934-3.349l2.843-2.749l0.423-0.409 l-0.152-0.568C6.235,22.388,6.071,21.177,6.071,20s0.164-2.388,0.488-3.599l0.152-0.568l-0.423-0.409l-2.843-2.749l1.934-3.349 l3.787,1.083l0.566,0.162l0.416-0.417c1.715-1.716,3.876-2.959,6.248-3.594l0.569-0.152l0.143-0.571L18.066,2H21.933 M20,28.464 c4.667,0,8.464-3.797,8.464-8.464c0-4.667-3.797-8.464-8.464-8.464c-4.667,0-8.464,3.797-8.464,8.464 C11.536,24.667,15.333,28.464,20,28.464 M22.714,1h-5.429l-1.149,4.594c-2.569,0.688-4.871,2.027-6.696,3.853L4.903,8.149 l-2.714,4.701l3.405,3.292C5.264,17.375,5.071,18.664,5.071,20s0.192,2.625,0.522,3.857l-3.405,3.292l2.714,4.701l4.538-1.298 c1.825,1.826,4.128,3.165,6.697,3.853L17.286,39h5.429l1.148-4.594c2.569-0.688,4.872-2.027,6.697-3.853l4.538,1.298l2.714-4.701 l-3.405-3.292c0.329-1.232,0.522-2.521,0.522-3.857s-0.192-2.625-0.522-3.857l3.405-3.292l-2.714-4.701l-4.538,1.298 c-1.825-1.826-4.127-3.165-6.696-3.853L22.714,1L22.714,1z M20,27.464c-4.122,0-7.464-3.342-7.464-7.464 c0-4.122,3.342-7.464,7.464-7.464c4.122,0,7.464,3.342,7.464,7.464C27.464,24.122,24.122,27.464,20,27.464L20,27.464z"
/><path
fill=
"#dff0fe"
d=
"M20,9C13.925,9,9,13.925,9,20c0,6.075,4.925,11,11,11s11-4.925,11-11C31,13.925,26.075,9,20,9z M20,24c-2.209,0-4-1.791-4-4c0-2.209,1.791-4,4-4s4,1.791,4,4C24,22.209,22.209,24,20,24z"
/><path
fill=
"#4788c7"
d=
"M20,16c2.209,0,4,1.791,4,4c0,2.209-1.791,4-4,4s-4-1.791-4-4C16,17.791,17.791,16,20,16 M20,15 c-2.757,0-5,2.243-5,5s2.243,5,5,5s5-2.243,5-5S22.757,15,20,15L20,15z"
/><path
fill=
"#fff"
d=
"M21.5 23.5H39.5V39.5H21.5z"
/><path
fill=
"#4788c7"
d=
"M39,24v15H22V24H39 M40,23H21v17h19V23L40,23z"
/><g><path
fill=
"#4788c7"
d=
"M21 23H40V27H21z"
/></g><path
fill=
"none"
stroke=
"#4788c7"
stroke-linecap=
"round"
stroke-miterlimit=
"10"
stroke-width=
"2"
d=
"M27 32L29.5 34.375 34 30"
/></svg>
\ No newline at end of file
ux/public/_assets/icons/ultraviolet-asciidoc.svg
0 → 100644
View file @
b4769b9a
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
width=
"100%"
height=
"100%"
viewBox=
"0 0 80 80"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
xml:space=
"preserve"
xmlns:serif=
"http://www.serif.com/"
style=
"fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
>
<path
d=
"M13.687,7.865L34.903,44.827L71.741,44.827C71.99,43.253 72.114,41.595 72.114,39.938C72.114,22.244 57.735,7.865 40,7.865L13.687,7.865Z"
style=
"fill:rgb(152,204,253);fill-rule:nonzero;stroke:rgb(71,136,199);stroke-width:2px;"
/>
<path
d=
"M7.886,19.716L22.306,44.869L7.886,44.869L7.886,19.716Z"
style=
"fill:rgb(152,204,253);fill-rule:nonzero;stroke:rgb(71,136,199);stroke-width:2px;"
/>
<path
d=
"M7.886,55.808L7.886,65.878C7.886,69.317 10.662,72.135 14.143,72.135L37.97,72.135L28.605,55.808C28.605,55.85 7.886,55.85 7.886,55.808Z"
style=
"fill:rgb(152,204,253);fill-rule:nonzero;stroke:rgb(71,136,199);stroke-width:2px;"
/>
<path
d=
"M49.696,70.602C57.487,68.157 63.992,62.77 67.97,55.767L41.16,55.767L49.696,70.602Z"
style=
"fill:rgb(152,204,253);fill-rule:nonzero;stroke:rgb(71,136,199);stroke-width:2px;"
/>
</svg>
ux/src/App.vue
View file @
b4769b9a
...
@@ -5,6 +5,7 @@ router-view
...
@@ -5,6 +5,7 @@ router-view
<
script
setup
>
<
script
setup
>
import
{
nextTick
,
onMounted
,
reactive
,
watch
}
from
'vue'
import
{
nextTick
,
onMounted
,
reactive
,
watch
}
from
'vue'
import
{
useRouter
,
useRoute
}
from
'vue-router'
import
{
useRouter
,
useRoute
}
from
'vue-router'
import
{
useFlagsStore
}
from
'src/stores/flags'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useUserStore
}
from
'src/stores/user'
import
{
useUserStore
}
from
'src/stores/user'
import
{
setCssVar
,
useQuasar
}
from
'quasar'
import
{
setCssVar
,
useQuasar
}
from
'quasar'
...
@@ -17,6 +18,7 @@ const $q = useQuasar()
...
@@ -17,6 +18,7 @@ const $q = useQuasar()
// STORES
// STORES
const
flagsStore
=
useFlagsStore
()
const
siteStore
=
useSiteStore
()
const
siteStore
=
useSiteStore
()
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
...
@@ -67,6 +69,10 @@ if (typeof siteConfig !== 'undefined') {
...
@@ -67,6 +69,10 @@ if (typeof siteConfig !== 'undefined') {
router
.
beforeEach
(
async
(
to
,
from
)
=>
{
router
.
beforeEach
(
async
(
to
,
from
)
=>
{
siteStore
.
routerLoading
=
true
siteStore
.
routerLoading
=
true
// System Flags
if
(
!
flagsStore
.
loaded
)
{
flagsStore
.
load
()
}
// Site Info
// Site Info
if
(
!
siteStore
.
id
)
{
if
(
!
siteStore
.
id
)
{
console
.
info
(
'No pre-cached site config. Loading site info...'
)
console
.
info
(
'No pre-cached site config. Loading site info...'
)
...
...
ux/src/components/FileManager.vue
View file @
b4769b9a
...
@@ -38,6 +38,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -38,6 +38,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
q-drawer.fileman-left(:model-value='true', :width='350')
q-drawer.fileman-left(:model-value='true', :width='350')
.q-px-md.q-pb-sm
.q-px-md.q-pb-sm
tree(
tree(
ref='treeComp'
:nodes='state.treeNodes'
:nodes='state.treeNodes'
:roots='state.treeRoots'
:roots='state.treeRoots'
v-model:selected='state.currentFolderId'
v-model:selected='state.currentFolderId'
...
@@ -46,7 +47,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -46,7 +47,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
@context-action='treeContextAction'
@context-action='treeContextAction'
:display-mode='state.displayMode'
:display-mode='state.displayMode'
)
)
q-drawer.fileman-right(:model-value='
true
', :width='350', side='right')
q-drawer.fileman-right(:model-value='
$q.screen.gt.md
', :width='350', side='right')
.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(
...
@@ -143,7 +144,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -143,7 +144,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
color='grey'
color='grey'
:aria-label='t(`common.actions.refresh`)'
:aria-label='t(`common.actions.refresh`)'
icon='las la-redo-alt'
icon='las la-redo-alt'
@click=''
@click='
reloadFolder(state.currentFolderId)
'
)
)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.refresh`
)
}}
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.refresh`
)
}}
q-separator.q-mr-sm(inset, vertical)
q-separator.q-mr-sm(inset, vertical)
...
@@ -172,14 +173,21 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -172,14 +173,21 @@ q-layout.fileman(view='hHh lpR lFr', container)
icon='las la-cloud-upload-alt'
icon='las la-cloud-upload-alt'
@click='uploadFile'
@click='uploadFile'
)
)
q-list.fileman-filelist
.fileman-emptylist(v-if='files.length < 1')
template(v-if='state.fileListLoading')
q-spinner.q-mr-sm(color='primary', size='xs', :thickness='3')
span.text-primary Loading...
template(v-else)
q-icon.q-mr-sm(name='las la-exclamation-triangle', size='sm')
span This folder is empty.
q-list.fileman-filelist(v-else)
q-item(
q-item(
v-for='item of files'
v-for='item of files'
:key='item.id'
:key='item.id'
clickable
clickable
active-class='active'
active-class='active'
:active='item.id === state.currentFileId'
:active='item.id === state.currentFileId'
@click.native='s
tate.currentFileId = item.id
'
@click.native='s
electItem(item)
'
@dblclick.native='openItem(item)'
@dblclick.native='openItem(item)'
)
)
q-item-section.fileman-filelist-icon(avatar)
q-item-section.fileman-filelist-icon(avatar)
...
@@ -229,7 +237,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
...
@@ -229,7 +237,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
q-item-section.text-negative Delete
q-item-section.text-negative Delete
q-footer
q-footer
q-bar.fileman-path
q-bar.fileman-path
small.text-caption.text-grey-7
/ foo / bar
small.text-caption.text-grey-7
{{
folderPath
}}
input(
input(
type='file'
type='file'
...
@@ -258,6 +266,7 @@ import { usePageStore } from 'src/stores/page'
...
@@ -258,6 +266,7 @@ import { usePageStore } from 'src/stores/page'
import
{
useSiteStore
}
from
'src/stores/site'
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'
// QUASAR
// QUASAR
...
@@ -277,50 +286,34 @@ const { t } = useI18n()
...
@@ -277,50 +286,34 @@ const { t } = useI18n()
const
state
=
reactive
({
const
state
=
reactive
({
loading
:
0
,
loading
:
0
,
search
:
''
,
search
:
''
,
currentFolderId
:
''
,
currentFolderId
:
null
,
currentFileId
:
''
,
currentFileId
:
null
,
treeNodes
:
{},
treeNodes
:
{},
treeRoots
:
[],
treeRoots
:
[],
displayMode
:
'title'
,
displayMode
:
'title'
,
isUploading
:
false
,
isUploading
:
false
,
shouldCancelUpload
:
false
,
shouldCancelUpload
:
false
,
uploadPercentage
:
0
,
uploadPercentage
:
0
,
fileList
:
[
fileList
:
[],
{
fileListLoading
:
false
id
:
'1'
,
type
:
'folder'
,
title
:
'Beep Boop'
,
children
:
19
},
{
id
:
'2'
,
type
:
'folder'
,
title
:
'Second Folder'
,
children
:
0
},
{
id
:
'3'
,
type
:
'page'
,
title
:
'Some Page'
,
pageType
:
'markdown'
,
updatedAt
:
'2022-11-24T18:27:00Z'
},
{
id
:
'4'
,
type
:
'file'
,
title
:
'Important Document'
,
fileType
:
'pdf'
,
fileSize
:
19000
}
]
})
})
// REFS
// REFS
const
fileIpt
=
ref
(
null
)
const
fileIpt
=
ref
(
null
)
const
treeComp
=
ref
(
null
)
// COMPUTED
// COMPUTED
const
folderPath
=
computed
(()
=>
{
if
(
!
state
.
currentFolderId
)
{
return
'/'
}
else
{
const
folderNode
=
state
.
treeNodes
[
state
.
currentFolderId
]
??
{}
return
folderNode
.
folderPath
?
`/
${
folderNode
.
folderPath
}
/
${
folderNode
.
fileName
}
/`
:
`/
${
folderNode
.
fileName
}
/`
}
})
const
files
=
computed
(()
=>
{
const
files
=
computed
(()
=>
{
return
state
.
fileList
.
map
(
f
=>
{
return
state
.
fileList
.
map
(
f
=>
{
switch
(
f
.
type
)
{
switch
(
f
.
type
)
{
...
@@ -334,7 +327,7 @@ const files = computed(() => {
...
@@ -334,7 +327,7 @@ const files = computed(() => {
f
.
caption
=
t
(
`fileman.
${
f
.
pageType
}
PageType`
)
f
.
caption
=
t
(
`fileman.
${
f
.
pageType
}
PageType`
)
break
break
}
}
case
'
file
'
:
{
case
'
asset
'
:
{
f
.
icon
=
fileTypes
[
f
.
fileType
]?.
icon
??
''
f
.
icon
=
fileTypes
[
f
.
fileType
]?.
icon
??
''
f
.
side
=
filesize
(
f
.
fileSize
)
f
.
side
=
filesize
(
f
.
fileSize
)
if
(
fileTypes
[
f
.
fileType
])
{
if
(
fileTypes
[
f
.
fileType
])
{
...
@@ -382,7 +375,7 @@ const currentFileDetails = computed(() => {
...
@@ -382,7 +375,7 @@ const currentFileDetails = computed(() => {
})
})
break
break
}
}
case
'
file
'
:
{
case
'
asset
'
:
{
items
.
push
({
items
.
push
({
label
:
t
(
'fileman.detailsAssetType'
),
label
:
t
(
'fileman.detailsAssetType'
),
value
:
fileTypes
[
item
.
fileType
]
?
t
(
`fileman.
${
item
.
fileType
}
FileType`
)
:
t
(
'fileman.unknownFileType'
,
{
type
:
item
.
fileType
.
toUpperCase
()
})
value
:
fileTypes
[
item
.
fileType
]
?
t
(
`fileman.
${
item
.
fileType
}
FileType`
)
:
t
(
'fileman.unknownFileType'
,
{
type
:
item
.
fileType
.
toUpperCase
()
})
...
@@ -405,8 +398,8 @@ const currentFileDetails = computed(() => {
...
@@ -405,8 +398,8 @@ const currentFileDetails = computed(() => {
// WATCHERS
// WATCHERS
watch
(()
=>
state
.
currentFolderId
,
(
newValue
)
=>
{
watch
(()
=>
state
.
currentFolderId
,
async
(
newValue
)
=>
{
state
.
currentFileId
=
null
await
loadTree
(
newValue
)
})
})
// METHODS
// METHODS
...
@@ -420,7 +413,16 @@ async function treeLazyLoad (nodeId, { done, fail }) {
...
@@ -420,7 +413,16 @@ async function treeLazyLoad (nodeId, { done, fail }) {
done
()
done
()
}
}
async
function
loadTree
(
parentId
,
types
)
{
async
function
loadTree
(
parentId
,
types
,
noCache
=
false
)
{
if
(
!
parentId
)
{
parentId
=
null
state
.
treeRoots
=
[]
}
if
(
parentId
===
state
.
currentFolderId
)
{
state
.
fileListLoading
=
true
state
.
currentFileId
=
null
state
.
fileList
=
[]
}
try
{
try
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query
:
gql
`
...
@@ -476,15 +478,58 @@ async function loadTree (parentId, types) {
...
@@ -476,15 +478,58 @@ async function loadTree (parentId, types) {
for
(
const
item
of
items
)
{
for
(
const
item
of
items
)
{
switch
(
item
.
__typename
)
{
switch
(
item
.
__typename
)
{
case
'TreeItemFolder'
:
{
case
'TreeItemFolder'
:
{
state
.
treeNodes
[
item
.
id
]
=
{
// -> Tree Nodes
text
:
item
.
title
,
if
(
!
state
.
treeNodes
[
item
.
id
]
||
(
parentId
&&
!
treeComp
.
value
.
isLoaded
(
item
.
id
)))
{
fileName
:
item
.
fileName
,
state
.
treeNodes
[
item
.
id
]
=
{
children
:
[]
folderPath
:
item
.
folderPath
,
fileName
:
item
.
fileName
,
title
:
item
.
title
,
children
:
[]
}
if
(
item
.
folderPath
)
{
if
(
!
state
.
treeNodes
[
parentId
].
children
.
includes
(
item
.
id
))
{
state
.
treeNodes
[
parentId
].
children
.
push
(
item
.
id
)
}
}
}
}
// -> Set Tree Roots
if
(
!
item
.
folderPath
)
{
if
(
!
item
.
folderPath
)
{
newTreeRoots
.
push
(
item
.
id
)
newTreeRoots
.
push
(
item
.
id
)
}
else
{
}
state
.
treeNodes
[
parentId
].
children
.
push
(
item
.
id
)
// -> File List
if
(
parentId
===
state
.
currentFolderId
)
{
state
.
fileList
.
push
({
id
:
item
.
id
,
type
:
'folder'
,
title
:
item
.
title
,
children
:
0
})
}
break
}
case
'TreeItemAsset'
:
{
if
(
parentId
===
state
.
currentFolderId
)
{
state
.
fileList
.
push
({
id
:
item
.
id
,
type
:
'asset'
,
title
:
item
.
title
,
fileType
:
'pdf'
,
fileSize
:
19000
})
}
break
}
case
'TreeItemPage'
:
{
if
(
parentId
===
state
.
currentFolderId
)
{
state
.
fileList
.
push
({
id
:
item
.
id
,
type
:
'page'
,
title
:
item
.
title
,
pageType
:
'markdown'
,
updatedAt
:
'2022-11-24T18:27:00Z'
})
}
}
break
break
}
}
...
@@ -501,15 +546,23 @@ async function loadTree (parentId, types) {
...
@@ -501,15 +546,23 @@ async function loadTree (parentId, types) {
caption
:
err
.
message
caption
:
err
.
message
})
})
}
}
if
(
parentId
===
state
.
currentFolderId
)
{
nextTick
(()
=>
{
state
.
fileListLoading
=
false
})
}
}
}
function
treeContextAction
(
nodeId
,
action
)
{
function
treeContextAction
(
nodeId
,
action
)
{
console
.
info
(
nodeId
,
action
)
switch
(
action
)
{
switch
(
action
)
{
case
'newFolder'
:
{
case
'newFolder'
:
{
newFolder
(
nodeId
)
newFolder
(
nodeId
)
break
break
}
}
case
'del'
:
{
delFolder
(
nodeId
)
break
}
}
}
}
}
...
@@ -524,6 +577,28 @@ function newFolder (parentId) {
...
@@ -524,6 +577,28 @@ function newFolder (parentId) {
})
})
}
}
function
delFolder
(
folderId
)
{
$q
.
dialog
({
component
:
FolderDeleteDialog
,
componentProps
:
{
folderId
,
folderName
:
state
.
treeNodes
[
folderId
].
title
}
}).
onOk
(()
=>
{
for
(
const
nodeId
in
state
.
treeNodes
)
{
if
(
state
.
treeNodes
[
nodeId
].
children
.
includes
(
folderId
))
{
state
.
treeNodes
[
nodeId
].
children
=
state
.
treeNodes
[
nodeId
].
children
.
filter
(
c
=>
c
!==
folderId
)
}
}
delete
state
.
treeNodes
[
folderId
]
})
}
function
reloadFolder
(
folderId
)
{
loadTree
(
folderId
,
null
,
true
)
treeComp
.
value
.
resetLoaded
()
}
// -> Upload Methods
// -> Upload Methods
function
uploadFile
()
{
function
uploadFile
()
{
...
@@ -603,6 +678,15 @@ function uploadCancel () {
...
@@ -603,6 +678,15 @@ function uploadCancel () {
state
.
uploadPercentage
=
0
state
.
uploadPercentage
=
0
}
}
function
selectItem
(
item
)
{
if
(
item
.
type
===
'folder'
)
{
state
.
currentFolderId
=
item
.
id
treeComp
.
value
.
setOpened
(
item
.
id
)
}
else
{
state
.
currentFileId
=
item
.
id
}
}
function
openItem
(
item
)
{
function
openItem
(
item
)
{
console
.
info
(
item
.
id
)
console
.
info
(
item
.
id
)
}
}
...
@@ -662,6 +746,20 @@ onMounted(() => {
...
@@ -662,6 +746,20 @@ onMounted(() => {
}
}
}
}
&
-emptylist
{
padding
:
16px
;
font-style
:
italic
;
display
:
flex
;
align-items
:
center
;
@at-root
.body--light
&
{
color
:
$grey-6
;
}
@at-root
.body--dark
&
{
color
:
$dark-4
;
}
}
&
-filelist
{
&
-filelist
{
padding
:
8px
12px
;
padding
:
8px
12px
;
...
...
ux/src/components/FolderDeleteDialog.vue
0 → 100644
View file @
b4769b9a
<
template
lang=
"pug"
>
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 550px; max-width: 850px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-delete-bin.svg', left, size='sm')
span
{{
t
(
`folderDeleteDialog.title`
)
}}
q-card-section
.text-body2
i18n-t(keypath='folderDeleteDialog.confirm')
template(v-slot:name)
strong
{{
folderName
}}
.text-caption.text-grey.q-mt-sm
{{
t
(
'folderDeleteDialog.folderId'
,
{
id
:
folderId
}
)
}}
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.delete`)'
color
=
'negative'
padding
=
'xs md'
@
click
=
'confirm'
:
loading
=
'state.isLoading'
)
<
/template
>
<
script
setup
>
import
gql
from
'graphql-tag'
import
{
useI18n
}
from
'vue-i18n'
import
{
useDialogPluginComponent
,
useQuasar
}
from
'quasar'
import
{
reactive
}
from
'vue'
// PROPS
const
props
=
defineProps
({
folderId
:
{
type
:
String
,
required
:
true
}
,
folderName
:
{
type
:
String
,
required
:
true
}
}
)
// EMITS
defineEmits
([
...
useDialogPluginComponent
.
emits
])
// QUASAR
const
{
dialogRef
,
onDialogHide
,
onDialogOK
,
onDialogCancel
}
=
useDialogPluginComponent
()
const
$q
=
useQuasar
()
// I18N
const
{
t
}
=
useI18n
()
// DATA
const
state
=
reactive
({
isLoading
:
false
}
)
// METHODS
async
function
confirm
()
{
state
.
isLoading
=
true
try
{
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation deleteFolder ($id: UUID!) {
deleteFolder(folderId: $id) {
operation {
succeeded
message
}
}
}
`
,
variables
:
{
id
:
props
.
folderId
}
}
)
if
(
resp
?.
data
?.
deleteFolder
?.
operation
?.
succeeded
)
{
$q
.
notify
({
type
:
'positive'
,
message
:
t
(
'folderDeleteDialog.deleteSuccess'
)
}
)
onDialogOK
()
}
else
{
throw
new
Error
(
resp
?.
data
?.
deleteFolder
?.
operation
?.
message
||
'An unexpected error occured.'
)
}
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
}
)
}
state
.
isLoading
=
false
}
<
/script
>
ux/src/components/PageNewMenu.vue
View file @
b4769b9a
...
@@ -11,15 +11,19 @@ q-menu.translucent-menu(
...
@@ -11,15 +11,19 @@ q-menu.translucent-menu(
q-item(clickable, @click='create(`markdown`)')
q-item(clickable, @click='create(`markdown`)')
blueprint-icon(icon='markdown')
blueprint-icon(icon='markdown')
q-item-section.q-pr-sm New Markdown Page
q-item-section.q-pr-sm New Markdown Page
q-item(clickable, @click='create(`channel`)')
q-item(clickable, @click='create(`asciidoc`)')
blueprint-icon(icon='chat')
blueprint-icon(icon='asciidoc')
q-item-section.q-pr-sm New Discussion Space
q-item-section.q-pr-sm New AsciiDoc Page
q-item(clickable, @click='create(`blog`)')
template(v-if='flagsStore.experimental')
blueprint-icon(icon='typewriter-with-paper')
q-item(clickable, @click='create(`channel`)')
q-item-section.q-pr-sm New Blog Page
blueprint-icon(icon='chat')
q-item(clickable, @click='create(`api`)')
q-item-section.q-pr-sm New Discussion Space
blueprint-icon(icon='api')
q-item(clickable, @click='create(`blog`)')
q-item-section.q-pr-sm New API Documentation
blueprint-icon(icon='typewriter-with-paper')
q-item-section.q-pr-sm New Blog Page
q-item(clickable, @click='create(`api`)')
blueprint-icon(icon='api')
q-item-section.q-pr-sm New API Documentation
q-item(clickable, @click='create(`redirect`)')
q-item(clickable, @click='create(`redirect`)')
blueprint-icon(icon='advance')
blueprint-icon(icon='advance')
q-item-section.q-pr-sm New Redirection
q-item-section.q-pr-sm New Redirection
...
@@ -41,6 +45,7 @@ import { useQuasar } from 'quasar'
...
@@ -41,6 +45,7 @@ import { useQuasar } from 'quasar'
import
{
usePageStore
}
from
'src/stores/page'
import
{
usePageStore
}
from
'src/stores/page'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useFlagsStore
}
from
'src/stores/flags'
// PROPS
// PROPS
...
@@ -65,6 +70,7 @@ const $q = useQuasar()
...
@@ -65,6 +70,7 @@ const $q = useQuasar()
// STORES
// STORES
const
flagsStore
=
useFlagsStore
()
const
pageStore
=
usePageStore
()
const
pageStore
=
usePageStore
()
const
siteStore
=
useSiteStore
()
const
siteStore
=
useSiteStore
()
...
...
ux/src/components/PageSaveDialog.vue
View file @
b4769b9a
...
@@ -5,17 +5,23 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
...
@@ -5,17 +5,23 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
q-icon(name='img:/_assets/icons/fluent-save-as.svg', left, size='sm')
q-icon(name='img:/_assets/icons/fluent-save-as.svg', left, size='sm')
span
{{
t
(
'pageSaveDialog.title'
)
}}
span
{{
t
(
'pageSaveDialog.title'
)
}}
.row.page-save-dialog-browser
.row.page-save-dialog-browser
.col-4.q-px-sm
.col-4
tree(
q-scroll-area(
:nodes='state.treeNodes'
:thumb-style='thumbStyle'
:roots='state.treeRoots'
:bar-style='barStyle'
v-model:selected='state.currentFolderId'
style='height: 300px'
@lazy-load='treeLazyLoad'
)
:use-lazy-load='true'
.q-px-sm
@context-action='treeContextAction'
tree(
:context-action-list='[`newFolder`]'
:nodes='state.treeNodes'
:display-mode='state.displayMode'
:roots='state.treeRoots'
)
v-model:selected='state.currentFolderId'
@lazy-load='treeLazyLoad'
:use-lazy-load='true'
@context-action='treeContextAction'
:context-action-list='[`newFolder`]'
:display-mode='state.displayMode'
)
.col-8
.col-8
q-list.page-save-dialog-filelist(dense)
q-list.page-save-dialog-filelist(dense)
q-item(
q-item(
...
@@ -31,6 +37,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
...
@@ -31,6 +37,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
q-icon(:name='item.icon', size='sm')
q-icon(:name='item.icon', size='sm')
q-item-section
q-item-section
q-item-label
{{
item
.
title
}}
q-item-label
{{
item
.
title
}}
.page-save-dialog-path.font-robotomono
{{
folderPath
}}
q-list.q-py-sm
q-list.q-py-sm
q-item
q-item
blueprint-icon(icon='new-document')
blueprint-icon(icon='new-document')
...
@@ -197,8 +204,28 @@ const displayModes = [
...
@@ -197,8 +204,28 @@ const displayModes = [
{
value
:
'path'
,
label
:
t
(
'pageSaveDialog.displayModePath'
)
}
{
value
:
'path'
,
label
:
t
(
'pageSaveDialog.displayModePath'
)
}
]
]
const
thumbStyle
=
{
right
:
'1px'
,
borderRadius
:
'5px'
,
backgroundColor
:
'#666'
,
width
:
'5px'
,
opacity
:
0.5
}
const
barStyle
=
{
width
:
'7px'
}
// COMPUTED
// COMPUTED
const
folderPath
=
computed
(()
=>
{
if
(
!
state
.
currentFolderId
)
{
return
'/'
}
else
{
const
folderNode
=
state
.
treeNodes
[
state
.
currentFolderId
]
??
{}
return
folderNode
.
folderPath
?
`/
${
folderNode
.
folderPath
}
/
${
folderNode
.
fileName
}
/`
:
`/
${
folderNode
.
fileName
}
/`
}
})
const
files
=
computed
(()
=>
{
const
files
=
computed
(()
=>
{
return
state
.
fileList
.
map
(
f
=>
{
return
state
.
fileList
.
map
(
f
=>
{
switch
(
f
.
type
)
{
switch
(
f
.
type
)
{
...
@@ -274,8 +301,9 @@ async function loadTree (parentId, types) {
...
@@ -274,8 +301,9 @@ async function loadTree (parentId, types) {
switch
(
item
.
__typename
)
{
switch
(
item
.
__typename
)
{
case
'TreeItemFolder'
:
{
case
'TreeItemFolder'
:
{
state
.
treeNodes
[
item
.
id
]
=
{
state
.
treeNodes
[
item
.
id
]
=
{
text
:
item
.
title
,
folderPath
:
item
.
folderPath
,
fileName
:
item
.
fileName
,
fileName
:
item
.
fileName
,
title
:
item
.
title
,
children
:
[]
children
:
[]
}
}
if
(
!
item
.
folderPath
)
{
if
(
!
item
.
folderPath
)
{
...
@@ -336,16 +364,23 @@ onMounted(() => {
...
@@ -336,16 +364,23 @@ onMounted(() => {
&
-browser
{
&
-browser
{
height
:
300px
;
height
:
300px
;
max-height
:
90vh
;
max-height
:
90vh
;
border-bottom
:
1px
solid
$blue-grey-1
;
border-bottom
:
1px
solid
#FFF
;
@at-root
.body--light
&
{
border-bottom-color
:
$blue-grey-1
;
}
@at-root
.body--dark
&
{
border-bottom-color
:
$dark-3
;
}
>
.col-4
{
>
.col-4
{
height
:
300px
;
@at-root
.body--light
&
{
@at-root
.body--light
&
{
background-color
:
$blue-grey-1
;
background-color
:
$blue-grey-1
;
border-bottom-color
:
$blue-grey-1
;
}
}
@at-root
.body--dark
&
{
@at-root
.body--dark
&
{
background-color
:
$dark-4
;
background-color
:
$dark-4
;
border-bottom-color
:
$dark-4
;
}
}
}
}
}
}
...
@@ -372,5 +407,22 @@ onMounted(() => {
...
@@ -372,5 +407,22 @@ onMounted(() => {
}
}
}
}
&
-path
{
padding
:
5px
16px
;
font-size
:
12px
;
border-bottom
:
1px
solid
#FFF
;
@at-root
.body--light
&
{
background-color
:
lighten
(
$blue-grey-1
,
4%
);
border-bottom-color
:
$blue-grey-1
;
color
:
$blue-grey-9
;
}
@at-root
.body--dark
&
{
background-color
:
darken
(
$dark-4
,
1%
);
border-bottom-color
:
$dark-1
;
color
:
$blue-grey-3
;
}
}
}
}
</
style
>
</
style
>
ux/src/components/TreeNav.vue
View file @
b4769b9a
...
@@ -95,7 +95,7 @@ const state = reactive({
...
@@ -95,7 +95,7 @@ const state = reactive({
opened
:
{}
opened
:
{}
})
})
// COMP
O
UTED
// COMPUTED
const
selection
=
computed
({
const
selection
=
computed
({
get
()
{
get
()
{
...
@@ -120,6 +120,16 @@ function emitContextAction (nodeId, action) {
...
@@ -120,6 +120,16 @@ function emitContextAction (nodeId, action) {
emit
(
'contextAction'
,
nodeId
,
action
)
emit
(
'contextAction'
,
nodeId
,
action
)
}
}
function
setOpened
(
nodeId
)
{
state
.
opened
[
nodeId
]
=
true
}
function
isLoaded
(
nodeId
)
{
return
state
.
loaded
[
nodeId
]
}
function
resetLoaded
(
nodeId
)
{
state
.
loaded
[
nodeId
]
=
false
}
// PROVIDE
// PROVIDE
provide
(
'roots'
,
toRef
(
props
,
'roots'
))
provide
(
'roots'
,
toRef
(
props
,
'roots'
))
...
@@ -131,6 +141,14 @@ provide('selection', selection)
...
@@ -131,6 +141,14 @@ provide('selection', selection)
provide
(
'emitLazyLoad'
,
emitLazyLoad
)
provide
(
'emitLazyLoad'
,
emitLazyLoad
)
provide
(
'emitContextAction'
,
emitContextAction
)
provide
(
'emitContextAction'
,
emitContextAction
)
// EXPOSE
defineExpose
({
setOpened
,
isLoaded
,
resetLoaded
})
// MOUNTED
// MOUNTED
onMounted
(()
=>
{
onMounted
(()
=>
{
...
...
ux/src/components/TreeNode.vue
View file @
b4769b9a
...
@@ -7,7 +7,7 @@ li.treeview-node
...
@@ -7,7 +7,7 @@ li.treeview-node
size='sm'
size='sm'
@click.stop='hasChildren ? toggleNode() : openNode()'
@click.stop='hasChildren ? toggleNode() : openNode()'
)
)
.treeview-label-text
{{
displayMode
===
'path'
?
node
.
fileName
:
node
.
t
ext
}}
.treeview-label-text
{{
displayMode
===
'path'
?
node
.
fileName
:
node
.
t
itle
}}
q-spinner.q-mr-xs(
q-spinner.q-mr-xs(
color='primary'
color='primary'
v-if='state.isLoading'
v-if='state.isLoading'
...
...
ux/src/i18n/locales/en.json
View file @
b4769b9a
...
@@ -157,15 +157,13 @@
...
@@ -157,15 +157,13 @@
"admin.extensions.requiresSharp"
:
"Requires Sharp extension"
,
"admin.extensions.requiresSharp"
:
"Requires Sharp extension"
,
"admin.extensions.subtitle"
:
"Install extensions for extra functionality"
,
"admin.extensions.subtitle"
:
"Install extensions for extra functionality"
,
"admin.extensions.title"
:
"Extensions"
,
"admin.extensions.title"
:
"Extensions"
,
"admin.flags.hidedonatebtn.hint"
:
"You have already donated to this project (thank you!) and want to hide the button from the administration area."
,
"admin.flags.authDebug.hint"
:
"Log detailed debug info of all login / registration attempts."
,
"admin.flags.hidedonatebtn.label"
:
"Hide Donate Button"
,
"admin.flags.authDebug.label"
:
"Auth Debug"
,
"admin.flags.ldapdebug.hint"
:
"Log detailed debug info on LDAP/AD login attempts."
,
"admin.flags.sqlLog.hint"
:
"Log all queries made to the database to console."
,
"admin.flags.ldapdebug.label"
:
"LDAP Debug"
,
"admin.flags.sqlLog.label"
:
"SQL Query Logging"
,
"admin.flags.sqllog.hint"
:
"Log all queries made to the database to console."
,
"admin.flags.sqllog.label"
:
"SQL Query Logging"
,
"admin.flags.subtitle"
:
"Low-level system flags for debugging or experimental purposes"
,
"admin.flags.subtitle"
:
"Low-level system flags for debugging or experimental purposes"
,
"admin.flags.title"
:
"Flags"
,
"admin.flags.title"
:
"Flags"
,
"admin.flags.warn.hint"
:
"Doing so may result in data loss
or
broken installation!"
,
"admin.flags.warn.hint"
:
"Doing so may result in data loss
, performance issues or a
broken installation!"
,
"admin.flags.warn.label"
:
"Do NOT enable these flags unless you know what you're doing!"
,
"admin.flags.warn.label"
:
"Do NOT enable these flags unless you know what you're doing!"
,
"admin.general.allowComments"
:
"Allow Comments"
,
"admin.general.allowComments"
:
"Allow Comments"
,
"admin.general.allowCommentsHint"
:
"Can users leave comments on pages? Can be restricted using Page Rules."
,
"admin.general.allowCommentsHint"
:
"Can users leave comments on pages? Can be restricted using Page Rules."
,
...
@@ -1603,5 +1601,14 @@
...
@@ -1603,5 +1601,14 @@
"common.actions.duplicate"
:
"Duplicate"
,
"common.actions.duplicate"
:
"Duplicate"
,
"common.actions.moveTo"
:
"Move To"
,
"common.actions.moveTo"
:
"Move To"
,
"pageSaveDialog.displayModeTitle"
:
"Title"
,
"pageSaveDialog.displayModeTitle"
:
"Title"
,
"pageSaveDialog.displayModePath"
:
"Path"
"pageSaveDialog.displayModePath"
:
"Path"
,
"folderDeleteDialog.title"
:
"Confirm Delete Folder"
,
"folderDeleteDialog.confirm"
:
"Are you sure you want to delete folder {name} and all its content?"
,
"folderDeleteDialog.folderId"
:
"Folder ID {id}"
,
"folderDeleteDialog.deleteSuccess"
:
"Folder has been deleted successfully."
,
"admin.flags.experimental.label"
:
"Experimental Features"
,
"admin.flags.experimental.hint"
:
"Enable unstable / unfinished features. DO NOT enable in a production environment!"
,
"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.saveSuccess"
:
"Flags have been updated successfully."
}
}
ux/src/layouts/AdminLayout.vue
View file @
b4769b9a
...
@@ -29,7 +29,7 @@ q-layout.admin(view='hHh Lpr lff')
...
@@ -29,7 +29,7 @@ q-layout.admin(view='hHh Lpr lff')
:thumb-style='thumbStyle'
:thumb-style='thumbStyle'
:bar-style='barStyle'
:bar-style='barStyle'
)
)
q-list.text-white(padding, dense)
q-list.text-white
.q-pb-lg
(padding, dense)
q-item.q-mb-sm
q-item.q-mb-sm
q-item-section
q-item-section
q-btn.acrylic-btn(
q-btn.acrylic-btn(
...
...
ux/src/layouts/ProfileLayout.vue
View file @
b4769b9a
...
@@ -5,19 +5,19 @@ q-layout(view='hHh Lpr lff')
...
@@ -5,19 +5,19 @@ q-layout(view='hHh Lpr lff')
.layout-profile-card
.layout-profile-card
.layout-profile-sd
.layout-profile-sd
q-list
q-list
q-item(
template(v-for='navItem of sidenav' :key='navItem.key')
v-for='navItem of sidenav'
q-item(
:key='navItem.key
'
v-if='!navItem.disabled || flagsStore.experimental
'
clickable
clickable
:to='`/_profile/` + navItem.key'
:to='`/_profile/` + navItem.key'
active-class='is-active'
active-class='is-active'
:disabled='navItem.disabled'
:disabled='navItem.disabled'
v-ripple
v-ripple
)
)
q-item-section(side)
q-item-section(side)
q-icon(:name='navItem.icon')
q-icon(:name='navItem.icon')
q-item-section
q-item-section
q-item-label
{{
navItem
.
label
}}
q-item-label
{{
navItem
.
label
}}
q-separator.q-my-sm(inset)
q-separator.q-my-sm(inset)
q-item(
q-item(
clickable
clickable
...
@@ -48,6 +48,7 @@ import { useI18n } from 'vue-i18n'
...
@@ -48,6 +48,7 @@ import { useI18n } from 'vue-i18n'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
onMounted
,
reactive
,
watch
}
from
'vue'
import
{
onMounted
,
reactive
,
watch
}
from
'vue'
import
{
useFlagsStore
}
from
'src/stores/flags'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useUserStore
}
from
'src/stores/user'
import
{
useUserStore
}
from
'src/stores/user'
...
@@ -61,6 +62,7 @@ const $q = useQuasar()
...
@@ -61,6 +62,7 @@ const $q = useQuasar()
// STORES
// STORES
const
flagsStore
=
useFlagsStore
()
const
siteStore
=
useSiteStore
()
const
siteStore
=
useSiteStore
()
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
...
...
ux/src/pages/AdminFlags.vue
View file @
b4769b9a
...
@@ -15,13 +15,20 @@ q-page.admin-flags
...
@@ -15,13 +15,20 @@ q-page.admin-flags
target='_blank'
target='_blank'
type='a'
type='a'
)
)
q-btn.q-mr-sm.acrylic-btn(
icon='las la-redo-alt'
flat
color='secondary'
:loading='state.loading > 0'
@click='load'
)
q-btn(
q-btn(
unelevated
unelevated
icon='fa-solid fa-check'
icon='fa-solid fa-check'
:label='t(`common.actions.apply`)'
:label='t(`common.actions.apply`)'
color='secondary'
color='secondary'
@click='save'
@click='save'
:loading='
loading
'
:loading='
state.loading > 0
'
)
)
q-separator(inset)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
.row.q-pa-md.q-col-gutter-md
...
@@ -39,44 +46,60 @@ q-page.admin-flags
...
@@ -39,44 +46,60 @@ q-page.admin-flags
q-item(tag='label')
q-item(tag='label')
blueprint-icon(icon='flag-filled')
blueprint-icon(icon='flag-filled')
q-item-section
q-item-section
q-item-label
{{
t
(
`admin.flags.
ldapdebug
.label`
)
}}
q-item-label
{{
t
(
`admin.flags.
experimental
.label`
)
}}
q-item-label(caption)
{{
t
(
`admin.flags.
ldapdebug
.hint`
)
}}
q-item-label(caption)
{{
t
(
`admin.flags.
experimental
.hint`
)
}}
q-item-section(avatar)
q-item-section(avatar)
q-toggle(
q-toggle(
v-model='
flags.ldapdebug
'
v-model='
state.flags.experimental
'
color='
primary
'
color='
negative
'
checked-icon='las la-check'
checked-icon='las la-check'
unchecked-icon='las la-times'
unchecked-icon='las la-times'
:aria-label='t(`admin.flags.
ldapdebug
.label`)'
:aria-label='t(`admin.flags.
experimental
.label`)'
)
)
q-separator.q-my-sm(inset)
q-separator.q-my-sm(inset)
q-item(tag='label')
q-item(tag='label')
blueprint-icon(icon='flag-filled')
blueprint-icon(icon='flag-filled')
q-item-section
q-item-section
q-item-label
{{
t
(
`admin.flags.
sqllo
g.label`
)
}}
q-item-label
{{
t
(
`admin.flags.
authDebu
g.label`
)
}}
q-item-label(caption)
{{
t
(
`admin.flags.
sqllo
g.hint`
)
}}
q-item-label(caption)
{{
t
(
`admin.flags.
authDebu
g.hint`
)
}}
q-item-section(avatar)
q-item-section(avatar)
q-toggle(
q-toggle(
v-model='
flags.sqllo
g'
v-model='
state.flags.authDebu
g'
color='
primary
'
color='
negative
'
checked-icon='las la-check'
checked-icon='las la-check'
unchecked-icon='las la-times'
unchecked-icon='las la-times'
:aria-label='t(`admin.flags.
sqllo
g.label`)'
:aria-label='t(`admin.flags.
authDebu
g.label`)'
)
)
q-card.shadow-1.q-py-sm.q-mt-md
q-separator.q-my-sm(inset)
q-item(tag='label')
q-item(tag='label')
blueprint-icon(icon='
heart-outline
')
blueprint-icon(icon='
flag-filled
')
q-item-section
q-item-section
q-item-label
{{
t
(
`admin.flags.
hidedonatebtn
.label`
)
}}
q-item-label
{{
t
(
`admin.flags.
sqlLog
.label`
)
}}
q-item-label(caption)
{{
t
(
`admin.flags.
hidedonatebtn
.hint`
)
}}
q-item-label(caption)
{{
t
(
`admin.flags.
sqlLog
.hint`
)
}}
q-item-section(avatar)
q-item-section(avatar)
q-toggle(
q-toggle(
v-model='
flags.hidedonatebtn
'
v-model='
state.flags.sqlLog
'
color='
primary
'
color='
negative
'
checked-icon='las la-check'
checked-icon='las la-check'
unchecked-icon='las la-times'
unchecked-icon='las la-times'
:aria-label='t(`admin.flags.
hidedonatebtn
.label`)'
:aria-label='t(`admin.flags.
sqlLog
.label`)'
)
)
q-card.shadow-1.q-py-sm.q-mt-md
q-item
blueprint-icon(icon='administrative-tools')
q-item-section
q-item-label
{{
t
(
`admin.flags.advanced.label`
)
}}
q-item-label(caption)
{{
t
(
`admin.flags.advanced.hint`
)
}}
q-item-section(avatar)
q-btn(
:label='t(`common.actions.edit`)'
unelevated
icon='las la-code'
color='primary'
text-color='white'
@click=''
disabled
)
.col-12.col-lg-5.gt-md
.col-12.col-lg-5.gt-md
.q-pa-md.text-center
.q-pa-md.text-center
...
@@ -85,12 +108,13 @@ q-page.admin-flags
...
@@ -85,12 +108,13 @@ q-page.admin-flags
<
script
setup
>
<
script
setup
>
import
gql
from
'graphql-tag'
import
gql
from
'graphql-tag'
import
{
defineAsyncComponent
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
defineAsyncComponent
,
onMounted
,
reactive
,
ref
}
from
'vue'
import
{
transform
}
from
'lodash-es'
import
{
cloneDeep
,
omit
}
from
'lodash-es'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
useMeta
,
useQuasar
}
from
'quasar'
import
{
useI18n
}
from
'vue-i18n'
import
{
useI18n
}
from
'vue-i18n'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useFlagsStore
}
from
'src/stores/flags'
// QUASAR
// QUASAR
...
@@ -98,6 +122,7 @@ const $q = useQuasar()
...
@@ -98,6 +122,7 @@ const $q = useQuasar()
// STORES
// STORES
const
flagsStore
=
useFlagsStore
()
const
siteStore
=
useSiteStore
()
const
siteStore
=
useSiteStore
()
// I18N
// I18N
...
@@ -110,67 +135,76 @@ useMeta({
...
@@ -110,67 +135,76 @@ useMeta({
title
:
t
(
'admin.flags.title'
)
title
:
t
(
'admin.flags.title'
)
})
})
const
loading
=
ref
(
false
)
// DATA
const
flags
=
reactive
({
ldapdebug
:
false
,
const
state
=
reactive
({
sqllog
:
false
,
loading
:
0
,
hidedonatebtn
:
false
flags
:
{
experimental
:
false
,
authDebug
:
false
,
sqlLog
:
false
}
})
})
const
save
=
async
()
=>
{
// METHODS
async
function
load
()
{
state
.
loading
++
$q
.
loading
.
show
()
await
flagsStore
.
load
()
state
.
flags
=
omit
(
cloneDeep
(
flagsStore
.
$state
),
[
'loaded'
])
$q
.
loading
.
hide
()
state
.
loading
--
}
}
// methods: {
async
function
save
()
{
// async save () {
if
(
state
.
loading
>
0
)
{
return
}
// try {
// await this.$apollo.mutate({
state
.
loading
++
// mutation: gql`
try
{
// mutation updateFlags (
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
// $flags: [SystemFlagInput]!
mutation
:
gql
`
// ) {
mutation updateFlags (
// updateSystemFlags(
$flags: JSON!
// flags: $flags
) {
// ) {
updateSystemFlags(
// status {
flags: $flags
// succeeded
) {
// slug
operation {
// message
succeeded
// }
message
// }
}
// }
}
// `,
}
// variables: {
`
,
// flags: _transform(this.flags, (result, value, key) => {
variables
:
{
// result.push({ key, value })
flags
:
state
.
flags
// }, [])
}
// },
})
// watchLoading (isLoading) {
if
(
resp
?.
data
?.
updateSystemFlags
?.
operation
?.
succeeded
)
{
// this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-flags-update')
load
()
// }
$q
.
notify
({
// })
type
:
'positive'
,
// this.$store.commit('showNotification', {
message
:
t
(
'admin.flags.saveSuccess'
)
// style: 'success',
})
// message: 'Flags applied successfully.',
}
else
{
// icon: 'check'
throw
new
Error
(
resp
?.
data
?.
updateSystemFlags
?.
operation
?.
message
||
'An unexpected error occured.'
)
// })
}
// } catch (err) {
}
catch
(
err
)
{
// this.$store.commit('pushGraphError', err)
$q
.
notify
({
// }
type
:
'negative'
,
// }
message
:
err
.
message
// }
})
// apollo: {
}
// flags: {
state
.
loading
--
// query: gql``,
}
// fetchPolicy: 'network-only',
// update: (data) => _transform(data.system.flags, (result, row) => {
// MOUNTED
// _set(result, row.key, row.value)
// }, {}),
onMounted
(
async
()
=>
{
// watchLoading (isLoading) {
load
()
// this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-flags-refresh')
})
// }
// }
// }
</
script
>
</
script
>
<
style
lang=
'scss'
>
<
style
lang=
'scss'
>
...
...
ux/src/pages/AdminScheduler.vue
View file @
b4769b9a
...
@@ -577,7 +577,7 @@ async function cancelJob (jobId) {
...
@@ -577,7 +577,7 @@ async function cancelJob (jobId) {
}
}
})
})
if
(
resp
?.
data
?.
cancelJob
?.
operation
?.
succeeded
)
{
if
(
resp
?.
data
?.
cancelJob
?.
operation
?.
succeeded
)
{
this
.
load
()
load
()
$q
.
notify
({
$q
.
notify
({
type
:
'positive'
,
type
:
'positive'
,
message
:
t
(
'admin.scheduler.cancelJobSuccess'
)
message
:
t
(
'admin.scheduler.cancelJobSuccess'
)
...
...
ux/src/stores/flags.js
0 → 100644
View file @
b4769b9a
import
{
defineStore
}
from
'pinia'
import
gql
from
'graphql-tag'
export
const
useFlagsStore
=
defineStore
(
'flags'
,
{
state
:
()
=>
({
loaded
:
false
,
experimental
:
false
}),
getters
:
{},
actions
:
{
async
load
()
{
try
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query getFlag {
systemFlags
}
`
,
fetchPolicy
:
'network-only'
})
const
systemFlags
=
resp
.
data
.
systemFlags
if
(
systemFlags
)
{
this
.
$patch
({
...
systemFlags
,
loaded
:
true
})
}
else
{
throw
new
Error
(
'Could not fetch system flags.'
)
}
}
catch
(
err
)
{
console
.
warn
(
err
.
networkError
?.
result
??
err
.
message
)
throw
err
}
}
}
})
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