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
f05a73dc
Unverified
Commit
f05a73dc
authored
Nov 08, 2022
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: edit page properties + update dependencies
parent
274f3f4a
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
459 additions
and
197 deletions
+459
-197
editor.vue
client/components/editor.vue
+22
-43
3.0.0.js
server/db/migrations/3.0.0.js
+27
-21
page.js
server/graph/resolvers/page.js
+27
-8
page.graphql
server/graph/schemas/page.graphql
+111
-35
site.graphql
server/graph/schemas/site.graphql
+2
-0
pages.js
server/models/pages.js
+0
-0
package.json
ux/package.json
+23
-23
ultraviolet-depth.svg
ux/public/_assets/icons/ultraviolet-depth.svg
+2
-0
HeaderNav.vue
ux/src/components/HeaderNav.vue
+1
-1
PagePropertiesDialog.vue
ux/src/components/PagePropertiesDialog.vue
+50
-22
en.json
ux/src/i18n/locales/en.json
+4
-1
AdminGeneral.vue
ux/src/pages/AdminGeneral.vue
+33
-2
AdminSecurity.vue
ux/src/pages/AdminSecurity.vue
+1
-1
Index.vue
ux/src/pages/Index.vue
+16
-2
page.js
ux/src/stores/page.js
+140
-38
yarn.lock
ux/yarn.lock
+0
-0
No files found.
client/components/editor.vue
View file @
f05a73dc
...
...
@@ -295,28 +295,28 @@ export default {
$content: String!
$description: String!
$editor: String!
$
isPublished: Boolean
!
$
publishState: PagePublishState
!
$locale: String!
$path: String!
$publishEndDate: Date
$publishStartDate: Date
$scriptCss: String
$scriptJs: String
$scriptJs
Load
: String
$siteId: UUID!
$tags: [String
]!
$tags: [String
!]
$title: String!
) {
createPage(
content: $content
description: $description
editor: $editor
isPublished: $isPublished
publishState: $publishState
locale: $locale
path: $path
publishEndDate: $publishEndDate
publishStartDate: $publishStartDate
scriptCss: $scriptCss
scriptJs
: $scriptJs
scriptJs
Load: $scriptJsLoad
siteId: $siteId
tags: $tags
title: $title
...
...
@@ -337,12 +337,12 @@ export default {
description
:
this
.
$store
.
get
(
'page/description'
),
editor
:
this
.
$store
.
get
(
'editor/editorKey'
),
locale
:
this
.
$store
.
get
(
'page/locale'
),
isPublished
:
this
.
$store
.
get
(
'page/isPublished'
)
,
publishState
:
this
.
$store
.
get
(
'page/isPublished'
)
?
'published'
:
'draft'
,
path
:
this
.
$store
.
get
(
'page/path'
),
publishEndDate
:
this
.
$store
.
get
(
'page/publishEndDate'
)
||
''
,
publishStartDate
:
this
.
$store
.
get
(
'page/publishStartDate'
)
||
''
,
scriptCss
:
this
.
$store
.
get
(
'page/scriptCss'
),
scriptJs
:
this
.
$store
.
get
(
'page/scriptJs'
),
scriptJs
Load
:
this
.
$store
.
get
(
'page/scriptJs'
),
siteId
:
this
.
$store
.
get
(
'site/id'
),
tags
:
this
.
$store
.
get
(
'page/tags'
),
title
:
this
.
$store
.
get
(
'page/title'
)
...
...
@@ -392,33 +392,11 @@ export default {
mutation
:
gql
`
mutation (
$id: UUID!
$content: String
$description: String
$editor: String
$isPublished: Boolean
$locale: String
$path: String
$publishEndDate: Date
$publishStartDate: Date
$scriptCss: String
$scriptJs: String
$tags: [String]
$title: String
$patch: PageUpdateInput!
) {
updatePage(
id: $id
content: $content
description: $description
editor: $editor
isPublished: $isPublished
locale: $locale
path: $path
publishEndDate: $publishEndDate
publishStartDate: $publishStartDate
scriptCss: $scriptCss
scriptJs: $scriptJs
tags: $tags
title: $title
patch: $patch
) {
operation {
succeeded
...
...
@@ -432,18 +410,19 @@ export default {
`
,
variables
:
{
id
:
this
.
$store
.
get
(
'page/id'
),
content
:
this
.
$store
.
get
(
'editor/content'
),
description
:
this
.
$store
.
get
(
'page/description'
),
editor
:
this
.
$store
.
get
(
'editor/editorKey'
),
locale
:
this
.
$store
.
get
(
'page/locale'
),
isPublished
:
this
.
$store
.
get
(
'page/isPublished'
),
path
:
this
.
$store
.
get
(
'page/path'
),
publishEndDate
:
this
.
$store
.
get
(
'page/publishEndDate'
)
||
''
,
publishStartDate
:
this
.
$store
.
get
(
'page/publishStartDate'
)
||
''
,
scriptCss
:
this
.
$store
.
get
(
'page/scriptCss'
),
scriptJs
:
this
.
$store
.
get
(
'page/scriptJs'
),
tags
:
this
.
$store
.
get
(
'page/tags'
),
title
:
this
.
$store
.
get
(
'page/title'
)
patch
:
{
content
:
this
.
$store
.
get
(
'editor/content'
),
description
:
this
.
$store
.
get
(
'page/description'
),
locale
:
this
.
$store
.
get
(
'page/locale'
),
publishState
:
this
.
$store
.
get
(
'page/isPublished'
)
?
'published'
:
'draft'
,
path
:
this
.
$store
.
get
(
'page/path'
),
publishEndDate
:
this
.
$store
.
get
(
'page/publishEndDate'
)
||
''
,
publishStartDate
:
this
.
$store
.
get
(
'page/publishStartDate'
)
||
''
,
scriptCss
:
this
.
$store
.
get
(
'page/scriptCss'
),
scriptJsLoad
:
this
.
$store
.
get
(
'page/scriptJs'
),
tags
:
this
.
$store
.
get
(
'page/tags'
),
title
:
this
.
$store
.
get
(
'page/title'
)
}
}
})
resp
=
_
.
get
(
resp
,
'data.updatePage'
,
{})
...
...
server/db/migrations/3.0.0.js
View file @
f05a73dc
...
...
@@ -10,6 +10,7 @@ exports.up = async knex => {
// =====================================
// PG EXTENSIONS
// =====================================
await
knex
.
raw
(
'CREATE EXTENSION IF NOT EXISTS ltree;'
)
await
knex
.
raw
(
'CREATE EXTENSION IF NOT EXISTS pgcrypto;'
)
await
knex
.
schema
...
...
@@ -187,21 +188,27 @@ exports.up = async knex => {
.
createTable
(
'pageHistory'
,
table
=>
{
table
.
uuid
(
'id'
).
notNullable
().
primary
().
defaultTo
(
knex
.
raw
(
'gen_random_uuid()'
))
table
.
uuid
(
'pageId'
).
notNullable
().
index
()
table
.
string
(
'action'
).
defaultTo
(
'updated'
)
table
.
jsonb
(
'affectedFields'
).
notNullable
().
defaultTo
(
'[]'
)
table
.
string
(
'path'
).
notNullable
()
table
.
string
(
'hash'
).
notNullable
()
table
.
string
(
'alias'
)
table
.
string
(
'title'
).
notNullable
()
table
.
string
(
'description'
)
table
.
string
(
'icon'
)
table
.
enu
(
'publishState'
,
[
'draft'
,
'published'
,
'scheduled'
]).
notNullable
().
defaultTo
(
'draft'
)
table
.
timestamp
(
'publishStartDate'
)
table
.
timestamp
(
'publishEndDate'
)
table
.
string
(
'action'
).
defaultTo
(
'updated'
)
table
.
jsonb
(
'config'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
jsonb
(
'relations'
).
notNullable
().
defaultTo
(
'[]'
)
table
.
text
(
'content'
)
table
.
text
(
'render'
)
table
.
jsonb
(
'toc'
)
table
.
string
(
'editor'
).
notNullable
()
table
.
string
(
'contentType'
).
notNullable
()
table
.
jsonb
(
'extra'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
jsonb
(
'tags'
).
defaultTo
(
'[]'
)
table
.
timestamp
(
'versionDate'
).
notNullable
().
defaultTo
(
knex
.
fn
.
now
())
table
.
jsonb
(
'scripts'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
timestamp
(
'createdAt'
).
notNullable
().
defaultTo
(
knex
.
fn
.
now
())
table
.
timestamp
(
'versionDate'
).
notNullable
().
defaultTo
(
knex
.
fn
.
now
())
})
// PAGE LINKS --------------------------
.
createTable
(
'pageLinks'
,
table
=>
{
...
...
@@ -212,32 +219,32 @@ exports.up = async knex => {
// PAGES -------------------------------
.
createTable
(
'pages'
,
table
=>
{
table
.
uuid
(
'id'
).
notNullable
().
primary
().
defaultTo
(
knex
.
raw
(
'gen_random_uuid()'
))
table
.
string
(
'slug'
)
table
.
string
(
'path'
).
notNullable
()
table
.
specificType
(
'dotPath'
,
'ltree'
).
notNullable
().
index
()
table
.
string
(
'hash'
).
notNullable
()
table
.
string
(
'alias'
)
table
.
string
(
'title'
).
notNullable
()
table
.
string
(
'description'
)
table
.
string
(
'icon'
)
table
.
enu
(
'publishState'
,
[
'draft'
,
'published'
,
'scheduled'
]).
notNullable
().
defaultTo
(
'draft'
)
table
.
timestamp
(
'publishStartDate'
)
table
.
timestamp
(
'publishEndDate'
)
table
.
jsonb
(
'config'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
jsonb
(
'relations'
).
notNullable
().
defaultTo
(
'[]'
)
table
.
text
(
'content'
)
table
.
text
(
'render'
)
table
.
jsonb
(
'toc'
)
table
.
string
(
'editor'
).
notNullable
()
table
.
string
(
'contentType'
).
notNullable
()
table
.
jsonb
(
'extra'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
boolean
(
'isBrowsable'
).
notNullable
().
defaultTo
(
true
)
table
.
string
(
'password'
)
table
.
integer
(
'ratingScore'
).
notNullable
().
defaultTo
(
0
)
table
.
integer
(
'ratingCount'
).
notNullable
().
defaultTo
(
0
)
table
.
jsonb
(
'scripts'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
jsonb
(
'historyData'
).
notNullable
().
defaultTo
(
'{}'
)
table
.
timestamp
(
'createdAt'
).
notNullable
().
defaultTo
(
knex
.
fn
.
now
())
table
.
timestamp
(
'updatedAt'
).
notNullable
().
defaultTo
(
knex
.
fn
.
now
())
})
// PAGE TREE ---------------------------
.
createTable
(
'pageTree'
,
table
=>
{
table
.
integer
(
'id'
).
unsigned
().
primary
()
table
.
string
(
'path'
).
notNullable
()
table
.
integer
(
'depth'
).
unsigned
().
notNullable
()
table
.
string
(
'title'
).
notNullable
()
table
.
boolean
(
'isFolder'
).
notNullable
().
defaultTo
(
false
)
table
.
jsonb
(
'ancestors'
)
})
// RENDERERS ---------------------------
.
createTable
(
'renderers'
,
table
=>
{
table
.
uuid
(
'id'
).
notNullable
().
primary
().
defaultTo
(
knex
.
raw
(
'gen_random_uuid()'
))
...
...
@@ -365,11 +372,6 @@ exports.up = async knex => {
table
.
uuid
(
'creatorId'
).
notNullable
().
references
(
'id'
).
inTable
(
'users'
).
index
()
table
.
uuid
(
'siteId'
).
notNullable
().
references
(
'id'
).
inTable
(
'sites'
).
index
()
})
.
table
(
'pageTree'
,
table
=>
{
table
.
integer
(
'parent'
).
unsigned
().
references
(
'id'
).
inTable
(
'pageTree'
).
onDelete
(
'CASCADE'
)
table
.
uuid
(
'pageId'
).
notNullable
().
references
(
'id'
).
inTable
(
'pages'
).
onDelete
(
'CASCADE'
)
table
.
string
(
'localeCode'
,
5
).
references
(
'code'
).
inTable
(
'locales'
)
})
.
table
(
'storage'
,
table
=>
{
table
.
uuid
(
'siteId'
).
notNullable
().
references
(
'id'
).
inTable
(
'sites'
)
})
...
...
@@ -507,7 +509,11 @@ exports.up = async knex => {
defaults
:
{
timezone
:
'America/New_York'
,
dateFormat
:
'YYYY-MM-DD'
,
timeFormat
:
'12h'
timeFormat
:
'12h'
,
tocDepth
:
{
min
:
1
,
max
:
2
}
},
features
:
{
ratings
:
false
,
...
...
server/graph/resolvers/page.js
View file @
f05a73dc
...
...
@@ -143,7 +143,7 @@ module.exports = {
* FETCH SINGLE PAGE BY ID
*/
async
pageById
(
obj
,
args
,
context
,
info
)
{
le
t
page
=
await
WIKI
.
db
.
pages
.
getPageFromDb
(
args
.
id
)
cons
t
page
=
await
WIKI
.
db
.
pages
.
getPageFromDb
(
args
.
id
)
if
(
page
)
{
if
(
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'read:pages'
],
{
path
:
page
.
path
,
...
...
@@ -151,30 +151,37 @@ module.exports = {
}))
{
return
{
...
page
,
locale
:
page
.
localeCode
,
editor
:
page
.
editorKey
...
page
.
config
,
scriptCss
:
page
.
scripts
?.
css
,
scriptJsLoad
:
page
.
scripts
?.
jsLoad
,
scriptJsUnload
:
page
.
scripts
?.
jsUnload
,
locale
:
page
.
localeCode
}
}
else
{
throw
new
WIKI
.
Error
.
PageViewForbidden
(
)
throw
new
Error
(
'ERR_FORBIDDEN'
)
}
}
else
{
throw
new
WIKI
.
Error
.
PageNotFound
(
)
throw
new
Error
(
'ERR_PAGE_NOT_FOUND'
)
}
},
/**
* FETCH SINGLE PAGE BY PATH
*/
async
pageByPath
(
obj
,
args
,
context
,
info
)
{
// console.info(info)
const
pageArgs
=
pageHelper
.
parsePath
(
args
.
path
)
le
t
page
=
await
WIKI
.
db
.
pages
.
getPageFromDb
({
cons
t
page
=
await
WIKI
.
db
.
pages
.
getPageFromDb
({
...
pageArgs
,
siteId
:
args
.
siteId
})
if
(
page
)
{
return
{
...
page
,
locale
:
page
.
localeCode
,
editor
:
page
.
editorKey
...
page
.
config
,
scriptCss
:
page
.
scripts
?.
css
,
scriptJsLoad
:
page
.
scripts
?.
jsLoad
,
scriptJsUnload
:
page
.
scripts
?.
jsUnload
,
locale
:
page
.
localeCode
}
}
else
{
throw
new
Error
(
'ERR_PAGE_NOT_FOUND'
)
...
...
@@ -607,8 +614,20 @@ module.exports = {
}
},
Page
:
{
icon
(
obj
)
{
return
obj
.
icon
||
'las la-file-alt'
},
password
(
obj
)
{
return
obj
?
'********'
:
''
},
async
tags
(
obj
)
{
return
WIKI
.
db
.
pages
.
relatedQuery
(
'tags'
).
for
(
obj
.
id
)
},
tocDepth
(
obj
)
{
return
{
min
:
obj
.
extra
?.
tocDepth
?.
min
??
1
,
max
:
obj
.
extra
?.
tocDepth
?.
max
??
2
}
}
// comments(pg) {
// return pg.$relatedQuery('comments')
...
...
server/graph/schemas/page.graphql
View file @
f05a73dc
...
...
@@ -32,11 +32,13 @@ extend type Query {
pageById
(
id
:
UUID
!
password
:
String
):
Page
pageByPath
(
siteId
:
UUID
!
path
:
String
!
password
:
String
):
Page
tags
:
[
PageTag
]!
...
...
@@ -69,35 +71,35 @@ extend type Query {
extend
type
Mutation
{
createPage
(
siteId
:
UUID
!
allowComments
:
Boolean
allowContributions
:
Boolean
allowRatings
:
Boolean
content
:
String
!
description
:
String
!
editor
:
String
!
isPublished
:
Boolean
!
icon
:
String
isBrowsable
:
Boolean
locale
:
String
!
path
:
String
!
publishState
:
PagePublishState
!
publishEndDate
:
Date
publishStartDate
:
Date
relations
:
[
PageRelationInput
!]
scriptCss
:
String
scriptJs
:
String
tags
:
[
String
]!
scriptJsLoad
:
String
scriptJsUnload
:
String
showSidebar
:
Boolean
showTags
:
Boolean
showToc
:
Boolean
siteId
:
UUID
!
tags
:
[
String
!]
title
:
String
!
tocDepth
:
PageTocDepthInput
):
PageResponse
updatePage
(
id
:
UUID
!
content
:
String
description
:
String
editor
:
String
isPublished
:
Boolean
locale
:
String
path
:
String
publishEndDate
:
Date
publishStartDate
:
Date
scriptCss
:
String
scriptJs
:
String
tags
:
[
String
]
title
:
String
patch
:
PageUpdateInput
!
):
PageResponse
convertPage
(
...
...
@@ -163,31 +165,40 @@ type PageMigrationResponse {
}
type
Page
{
id
:
UUID
path
:
String
hash
:
String
title
:
String
description
:
String
isPublished
:
Boolean
publishStartDate
:
Date
publishEndDate
:
Date
tags
:
[
PageTag
]
allowComments
:
Boolean
allowContributions
:
Boolean
allowRatings
:
Boolean
author
:
User
content
:
String
render
:
String
toc
:
[
JSON
]
contentType
:
String
createdAt
:
Date
updatedAt
:
Date
creator
:
User
description
:
String
editor
:
String
hash
:
String
icon
:
String
id
:
UUID
isBrowsable
:
Boolean
locale
:
String
password
:
String
path
:
String
publishEndDate
:
Date
publishStartDate
:
Date
publishState
:
PagePublishState
relations
:
[
PageRelation
]
render
:
String
scriptJsLoad
:
String
scriptJsUnload
:
String
scriptCss
:
String
scriptJs
:
String
authorId
:
Int
authorName
:
String
authorEmail
:
String
creatorId
:
Int
creatorName
:
String
creatorEmail
:
String
showSidebar
:
Boolean
showTags
:
Boolean
showToc
:
Boolean
siteId
:
UUID
tags
:
[
PageTag
]
title
:
String
toc
:
[
JSON
]
tocDepth
:
PageTocDepth
updatedAt
:
Date
}
type
PageTag
{
...
...
@@ -299,6 +310,59 @@ type PageConflictLatest {
updatedAt
:
Date
}
type
PageRelation
{
id
:
UUID
position
:
PageRelationPosition
label
:
String
caption
:
String
icon
:
String
target
:
String
}
input
PageRelationInput
{
id
:
UUID
!
position
:
PageRelationPosition
!
label
:
String
!
caption
:
String
icon
:
String
target
:
String
!
}
input
PageUpdateInput
{
allowComments
:
Boolean
allowContributions
:
Boolean
allowRatings
:
Boolean
content
:
String
description
:
String
icon
:
String
isBrowsable
:
Boolean
locale
:
String
password
:
String
path
:
String
publishEndDate
:
Date
publishStartDate
:
Date
publishState
:
PagePublishState
relations
:
[
PageRelationInput
!]
scriptJsLoad
:
String
scriptJsUnload
:
String
scriptCss
:
String
showSidebar
:
Boolean
showTags
:
Boolean
showToc
:
Boolean
tags
:
[
String
!]
title
:
String
tocDepth
:
PageTocDepthInput
}
type
PageTocDepth
{
min
:
Int
max
:
Int
}
input
PageTocDepthInput
{
min
:
Int
!
max
:
Int
!
}
enum
PageOrderBy
{
CREATED
ID
...
...
@@ -317,3 +381,15 @@ enum PageTreeMode {
PAGES
ALL
}
enum
PagePublishState
{
draft
published
scheduled
}
enum
PageRelationPosition
{
left
center
right
}
server/graph/schemas/site.graphql
View file @
f05a73dc
...
...
@@ -89,6 +89,7 @@ type SiteDefaults {
timezone
:
String
dateFormat
:
String
timeFormat
:
String
tocDepth
:
PageTocDepth
}
type
SiteLocale
{
...
...
@@ -174,6 +175,7 @@ input SiteDefaultsInput {
timezone
:
String
dateFormat
:
String
timeFormat
:
String
tocDepth
:
PageTocDepthInput
}
input
SiteThemeInput
{
...
...
server/models/pages.js
View file @
f05a73dc
This diff is collapsed.
Click to expand it.
ux/package.json
View file @
f05a73dc
...
...
@@ -11,7 +11,7 @@
"lint"
:
"eslint --ext .js,.vue ./"
},
"dependencies"
:
{
"@apollo/client"
:
"3.
6.9
"
,
"@apollo/client"
:
"3.
7.1
"
,
"@codemirror/autocomplete"
:
"6.0.2"
,
"@codemirror/basic-setup"
:
"0.20.0"
,
"@codemirror/closebrackets"
:
"0.19.2"
,
...
...
@@ -31,8 +31,8 @@
"@codemirror/state"
:
"6.0.1"
,
"@codemirror/tooltip"
:
"0.19.16"
,
"@codemirror/view"
:
"6.0.2"
,
"@lezer/common"
:
"1.0.
0
"
,
"@quasar/extras"
:
"1.15.
1
"
,
"@lezer/common"
:
"1.0.
1
"
,
"@quasar/extras"
:
"1.15.
5
"
,
"@tiptap/core"
:
"2.0.0-beta.176"
,
"@tiptap/extension-code-block"
:
"2.0.0-beta.37"
,
"@tiptap/extension-code-block-lowlight"
:
"2.0.0-beta.68"
,
...
...
@@ -58,45 +58,45 @@
"@tiptap/starter-kit"
:
"2.0.0-beta.185"
,
"@tiptap/vue-3"
:
"2.0.0-beta.91"
,
"apollo-upload-client"
:
"17.0.0"
,
"browser-fs-access"
:
"0.31.
0
"
,
"browser-fs-access"
:
"0.31.
1
"
,
"clipboard"
:
"2.0.11"
,
"codemirror"
:
"6.0.1"
,
"filesize"
:
"
9.0.11
"
,
"filesize"
:
"
10.0.5
"
,
"filesize-parser"
:
"1.5.0"
,
"graphql"
:
"16.6.0"
,
"graphql-tag"
:
"2.12.6"
,
"js-cookie"
:
"3.0.1"
,
"jwt-decode"
:
"3.1.2"
,
"lodash-es"
:
"4.17.21"
,
"luxon"
:
"3.
0.1
"
,
"pinia"
:
"2.0.2
0
"
,
"luxon"
:
"3.
1.0
"
,
"pinia"
:
"2.0.2
3
"
,
"pug"
:
"3.0.2"
,
"quasar"
:
"2.
7.7
"
,
"socket.io-client"
:
"4.5.
2
"
,
"quasar"
:
"2.
10.1
"
,
"socket.io-client"
:
"4.5.
3
"
,
"tippy.js"
:
"6.3.7"
,
"uuid"
:
"
8.3.2
"
,
"v-network-graph"
:
"0.6.
6
"
,
"vue"
:
"3.2.
37
"
,
"vue-codemirror"
:
"6.
0.2
"
,
"uuid"
:
"
9.0.0
"
,
"v-network-graph"
:
"0.6.
10
"
,
"vue"
:
"3.2.
41
"
,
"vue-codemirror"
:
"6.
1.1
"
,
"vue-i18n"
:
"9.2.2"
,
"vue-router"
:
"4.1.
3
"
,
"vue-router"
:
"4.1.
6
"
,
"vue3-otp-input"
:
"0.3.6"
,
"vuedraggable"
:
"4.1.0"
,
"xterm"
:
"
4.19
.0"
,
"xterm"
:
"
5.0
.0"
,
"zxcvbn"
:
"4.4.2"
},
"devDependencies"
:
{
"@intlify/vite-plugin-vue-i18n"
:
"6.0.
1
"
,
"@quasar/app-vite"
:
"1.
0.6
"
,
"@types/lodash"
:
"4.14.18
4
"
,
"@volar/vue-language-plugin-pug"
:
"1.0.
1
"
,
"@intlify/vite-plugin-vue-i18n"
:
"6.0.
3
"
,
"@quasar/app-vite"
:
"1.
1.3
"
,
"@types/lodash"
:
"4.14.18
8
"
,
"@volar/vue-language-plugin-pug"
:
"1.0.
9
"
,
"browserlist"
:
"latest"
,
"eslint"
:
"8.2
2
.0"
,
"eslint"
:
"8.2
7
.0"
,
"eslint-config-standard"
:
"17.0.0"
,
"eslint-plugin-import"
:
"2.26.0"
,
"eslint-plugin-n"
:
"15.
2.4
"
,
"eslint-plugin-promise"
:
"6.
0.0
"
,
"eslint-plugin-vue"
:
"9.
3
.0"
"eslint-plugin-n"
:
"15.
5.0
"
,
"eslint-plugin-promise"
:
"6.
1.1
"
,
"eslint-plugin-vue"
:
"9.
7
.0"
},
"engines"
:
{
"node"
:
"^18 || ^16"
,
...
...
ux/public/_assets/icons/ultraviolet-depth.svg
0 → 100644
View file @
f05a73dc
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 40 40"
width=
"80px"
height=
"80px"
><path
fill=
"#b6dcfe"
d=
"M10.5 1.5H28.5V4.5H10.5z"
/><path
fill=
"#4788c7"
d=
"M28,2v2H11V2H28 M29,1H10v4h19V1L29,1z"
/><path
fill=
"#b6dcfe"
d=
"M1.5 34.5H37.5V37.5H1.5z"
/><path
fill=
"#4788c7"
d=
"M37 35v2H2v-2H37M38 34H1v4h37V34L38 34zM37.5 32A.5.5 0 1 0 37.5 33 .5.5 0 1 0 37.5 32zM36.808 30A.5.5 0 1 0 36.808 31 .5.5 0 1 0 36.808 30zM36.115 28A.5.5 0 1 0 36.115 29 .5.5 0 1 0 36.115 28zM35.423 26A.5.5 0 1 0 35.423 27 .5.5 0 1 0 35.423 26zM34.731 24A.5.5 0 1 0 34.731 25 .5.5 0 1 0 34.731 24zM34.038 22A.5.5 0 1 0 34.038 23 .5.5 0 1 0 34.038 22zM33.346 20A.5.5 0 1 0 33.346 21 .5.5 0 1 0 33.346 20zM32.654 18A.5.5 0 1 0 32.654 19 .5.5 0 1 0 32.654 18zM31.962 16A.5.5 0 1 0 31.962 17 .5.5 0 1 0 31.962 16zM31.269 14A.5.5 0 1 0 31.269 15 .5.5 0 1 0 31.269 14zM30.577 12A.5.5 0 1 0 30.577 13 .5.5 0 1 0 30.577 12zM29.885 10A.5.5 0 1 0 29.885 11 .5.5 0 1 0 29.885 10zM29.192 8A.5.5 0 1 0 29.192 9 .5.5 0 1 0 29.192 8zM28.5 6A.5.5 0 1 0 28.5 7 .5.5 0 1 0 28.5 6z"
/><g><path
fill=
"#4788c7"
d=
"M1.5 32A.5.5 0 1 0 1.5 33 .5.5 0 1 0 1.5 32zM2.192 30A.5.5 0 1 0 2.192 31 .5.5 0 1 0 2.192 30zM2.885 28A.5.5 0 1 0 2.885 29 .5.5 0 1 0 2.885 28zM3.577 26A.5.5 0 1 0 3.577 27 .5.5 0 1 0 3.577 26zM4.269 24A.5.5 0 1 0 4.269 25 .5.5 0 1 0 4.269 24zM4.962 22A.5.5 0 1 0 4.962 23 .5.5 0 1 0 4.962 22zM5.654 20A.5.5 0 1 0 5.654 21 .5.5 0 1 0 5.654 20zM6.346 18A.5.5 0 1 0 6.346 19 .5.5 0 1 0 6.346 18zM7.038 16A.5.5 0 1 0 7.038 17 .5.5 0 1 0 7.038 16zM7.731 14A.5.5 0 1 0 7.731 15 .5.5 0 1 0 7.731 14zM8.423 12A.5.5 0 1 0 8.423 13 .5.5 0 1 0 8.423 12zM9.115 10A.5.5 0 1 0 9.115 11 .5.5 0 1 0 9.115 10zM9.808 8A.5.5 0 1 0 9.808 9 .5.5 0 1 0 9.808 8zM10.5 6A.5.5 0 1 0 10.5 7 .5.5 0 1 0 10.5 6z"
/></g><g><path
fill=
"#4788c7"
d=
"M21 27L21 19 18 19 18 27 13 27 19.5 34 26 27z"
/></g><g><path
fill=
"#4788c7"
d=
"M18 12L18 20 21 20 21 12 26 12 19.5 5 13 12z"
/></g></svg>
\ No newline at end of file
ux/src/components/HeaderNav.vue
View file @
f05a73dc
...
...
@@ -81,7 +81,7 @@ q-header.bg-header.text-white.site-header(
round
dense
icon='las la-tools'
color='
secondary
'
color='
positive
'
to='/_admin'
aria-label='Administration'
)
...
...
ux/src/components/PagePropertiesDialog.vue
View file @
f05a73dc
...
...
@@ -41,28 +41,39 @@ q-card.page-properties-dialog
outlined
dense
)
q-input(
v-model='pageStore.icon'
:label='t(`editor.props.icon`)'
outlined
dense
)
template(#append)
q-icon.cursor-pointer(
name='las la-icons'
color='primary'
)
q-card-section.alt-card(id='refCardPublishState')
.text-overline.q-pb-xs.items-center.flex #[q-icon.q-mr-sm(name='las la-power-off', size='xs')]
{{
t
(
'editor.props.publishState'
)
}}
q-form.q-gutter-md
div
q-btn-toggle(
v-model='pageStore.
isPublished
'
v-model='pageStore.
publishState
'
push
glossy
no-caps
toggle-color='primary'
:options=`[
{ label: t('editor.props.draft'), value:
false
},
{ label: t('editor.props.published'), value:
true
},
{ label: t('editor.props.dateRange'), value:
null
}
{ label: t('editor.props.draft'), value:
'draft'
},
{ label: t('editor.props.published'), value:
'published'
},
{ label: t('editor.props.dateRange'), value:
'scheduled'
}
]`
)
.text-caption(v-if='pageStore.
isPublished
'): em
{{
t
(
'editor.props.publishedHint'
)
}}
.text-caption(v-else-if='pageStore.
isPublished === false
'): em
{{
t
(
'editor.props.draftHint'
)
}}
template(v-else-if='pageStore.
isPublished === null
')
.text-caption(v-if='pageStore.
publishState === `published`
'): em
{{
t
(
'editor.props.publishedHint'
)
}}
.text-caption(v-else-if='pageStore.
publishState === `draft`
'): em
{{
t
(
'editor.props.draftHint'
)
}}
template(v-else-if='pageStore.
publishState === `scheduled`
')
.text-caption: em
{{
t
(
'editor.props.dateRangeHint'
)
}}
q-date(
v-model='p
ageStore.p
ublishingRange'
v-model='publishingRange'
range
flat
bordered
...
...
@@ -230,7 +241,7 @@ q-card.page-properties-dialog
q-form.q-gutter-md.q-pt-sm
div
q-toggle(
v-model='pageStore.
showInTre
e'
v-model='pageStore.
isBrowsabl
e'
dense
:label='$t(`editor.props.showInTree`)'
color='primary'
...
...
@@ -240,6 +251,7 @@ q-card.page-properties-dialog
div
q-toggle(
v-model='state.requirePassword'
@update:model-value='toggleRequirePassword'
dense
:label='$t(`editor.props.requirePassword`)'
color='primary'
...
...
@@ -252,7 +264,7 @@ q-card.page-properties-dialog
)
q-input(
ref='iptPagePassword'
v-model='
stat
e.password'
v-model='
pageStor
e.password'
:label='t(`editor.props.password`)'
:hint='t(`editor.props.passwordHint`)'
outlined
...
...
@@ -272,7 +284,7 @@ q-card.page-properties-dialog
<
script
setup
>
import
{
useI18n
}
from
'vue-i18n'
import
{
useQuasar
}
from
'quasar'
import
{
nextTick
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
computed
,
nextTick
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
DateTime
}
from
'luxon'
import
PageRelationDialog
from
'./PageRelationDialog.vue'
...
...
@@ -302,9 +314,7 @@ const { t } = useI18n()
const
state
=
reactive
({
showRelationDialog
:
false
,
showScriptsDialog
:
false
,
publishingRange
:
{},
requirePassword
:
false
,
password
:
''
,
editRelationId
:
null
,
pageScriptsMode
:
'jsLoad'
,
showQuickAccess
:
true
...
...
@@ -325,19 +335,23 @@ const quickaccess = [
const
iptPagePassword
=
ref
(
null
)
//
WATCHERS
//
COMPUTED
watch
(()
=>
state
.
requirePassword
,
(
newValue
)
=>
{
if
(
newValue
)
{
nextTick
(()
=>
{
iptPagePassword
.
value
.
focus
()
iptPagePassword
.
value
.
$el
.
scrollIntoView
({
behavior
:
'smooth'
})
})
const
publishingRange
=
computed
({
get
()
{
return
{
from
:
pageStore
.
publishStartDate
,
to
:
pageStore
.
publishEndDate
}
},
set
(
newValue
)
{
pageStore
.
publishStartDate
=
newValue
?.
from
pageStore
.
publishEndDate
=
newValue
?.
to
}
})
// WATCHERS
pageStore
.
$subscribe
(()
=>
{
editorStore
.
$patch
({
lastChangeTimestamp
:
DateTime
.
utc
()
...
...
@@ -366,10 +380,24 @@ function jumpToSection (id) {
behavior
:
'smooth'
})
}
function
toggleRequirePassword
(
newValue
)
{
if
(
newValue
)
{
nextTick
(()
=>
{
iptPagePassword
.
value
.
focus
()
iptPagePassword
.
value
.
$el
.
scrollIntoView
({
behavior
:
'smooth'
})
})
}
else
{
pageStore
.
password
=
''
}
}
// MOUNTED
onMounted
(()
=>
{
state
.
requirePassword
=
pageStore
.
password
?.
length
>
0
setTimeout
(()
=>
{
state
.
showQuickAccess
=
true
},
300
)
...
...
ux/src/i18n/locales/en.json
View file @
f05a73dc
...
...
@@ -1552,5 +1552,8 @@
"profile.avatarUploadSuccess"
:
"Profile picture uploaded successfully."
,
"profile.avatarUploadFailed"
:
"Failed to upload user profile picture."
,
"profile.avatarClearSuccess"
:
"Profile picture cleared successfully."
,
"profile.avatarClearFailed"
:
"Failed to clear profile picture."
"profile.avatarClearFailed"
:
"Failed to clear profile picture."
,
"admin.general.defaultTocDepth"
:
"Default ToC Depth"
,
"admin.general.defaultTocDepthHint"
:
"The default minimum and maximum header levels to show in the table of contents."
,
"editor.props.icon"
:
"Icon"
}
ux/src/pages/AdminGeneral.vue
View file @
f05a73dc
...
...
@@ -371,6 +371,25 @@ q-page.admin-general
toggle-color='primary'
:options='timeFormats'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='depth')
q-item-section
q-item-label
{{
t
(
`admin.general.defaultTocDepth`
)
}}
q-item-label(caption)
{{
t
(
`admin.general.defaultTocDepthHint`
)
}}
q-item-section.col-auto.q-pl-sm(style='min-width: 180px;')
.text-caption
{{
t
(
'editor.props.tocMinMaxDepth'
)
}}
#[strong (H
{{
state
.
config
.
defaults
.
tocDepth
.
min
}}
→ H
{{
state
.
config
.
defaults
.
tocDepth
.
max
}}
)]
q-range(
v-model='state.config.defaults.tocDepth'
:min='1'
:max='6'
color='primary'
:left-label-value='`H` + state.config.defaults.tocDepth.min'
:right-label-value='`H` + state.config.defaults.tocDepth.max'
snap
label
markers
)
//- -----------------------
//- SEO
...
...
@@ -479,7 +498,11 @@ const state = reactive({
defaults
:
{
timezone
:
''
,
dateFormat
:
''
,
timeFormat
:
''
timeFormat
:
''
,
tocDepth
:
{
min
:
1
,
max
:
2
}
},
robots
:
{
index
:
false
,
...
...
@@ -573,6 +596,10 @@ async function load () {
timezone
dateFormat
timeFormat
tocDepth {
min
max
}
}
}
}
...
...
@@ -635,7 +662,11 @@ async function save () {
defaults
:
{
timezone
:
state
.
config
.
defaults
?.
timezone
??
'America/New_York'
,
dateFormat
:
state
.
config
.
defaults
?.
dateFormat
??
'YYYY-MM-DD'
,
timeFormat
:
state
.
config
.
defaults
?.
timeFormat
??
'12h'
timeFormat
:
state
.
config
.
defaults
?.
timeFormat
??
'12h'
,
tocDepth
:
{
min
:
state
.
config
.
defaults
?.
tocDepth
?.
min
??
1
,
max
:
state
.
config
.
defaults
?.
tocDepth
?.
max
??
2
}
}
}
}
...
...
ux/src/pages/AdminSecurity.vue
View file @
f05a73dc
...
...
@@ -325,7 +325,7 @@ q-page.admin-mail
<
script
setup
>
import
{
cloneDeep
}
from
'lodash-es'
import
gql
from
'graphql-tag'
import
filesize
from
'filesize'
import
{
filesize
}
from
'filesize'
import
filesizeParser
from
'filesize-parser'
import
{
useI18n
}
from
'vue-i18n'
...
...
ux/src/pages/Index.vue
View file @
f05a73dc
...
...
@@ -21,7 +21,7 @@ q-page.column
:to='brd.path'
)
.col-auto.flex.items-center.justify-end
template(v-if='!pageStore.
isPublished
')
template(v-if='!pageStore.
publishState === `draft`
')
.text-caption.text-accent: strong Unpublished
q-separator.q-mx-sm(vertical)
.text-caption.text-grey-6 Last modified on #[strong
{{
lastModified
}}
]
...
...
@@ -233,6 +233,7 @@ q-page.column
color='deep-orange-9'
aria-label='Page Data'
@click='togglePageData'
disable
)
q-tooltip(anchor='center left' self='center right') Page Data
q-separator.q-my-sm(inset)
...
...
@@ -519,7 +520,20 @@ async function discardChanges () {
}
async
function
saveChanges
()
{
$q
.
loading
.
show
()
try
{
await
pageStore
.
pageSave
()
$q
.
notify
({
type
:
'positive'
,
message
:
'Page saved successfully.'
})
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
'Failed to save page changes.'
})
}
$q
.
loading
.
hide
()
}
</
script
>
...
...
ux/src/stores/page.js
View file @
f05a73dc
import
{
defineStore
}
from
'pinia'
import
gql
from
'graphql-tag'
import
{
cloneDeep
,
last
,
transform
}
from
'lodash-es'
import
{
cloneDeep
,
last
,
pick
,
transform
}
from
'lodash-es'
import
{
DateTime
}
from
'luxon'
import
{
useSiteStore
}
from
'./site'
import
{
useEditorStore
}
from
'./editor'
const
pagePropsFragment
=
gql
`
fragment PageRead on Page {
allowComments
allowContributions
allowRatings
contentType
createdAt
description
editor
icon
id
isBrowsable
locale
password
path
publishEndDate
publishStartDate
publishState
relations {
id
position
label
caption
icon
target
}
render
scriptJsLoad
scriptJsUnload
scriptCss
showSidebar
showTags
showToc
tags {
tag
title
}
title
toc
tocDepth {
min
max
}
updatedAt
}
`
const
gqlQueries
=
{
pageById
:
gql
`
query loadPage (
...
...
@@ -14,16 +60,10 @@ const gqlQueries = {
pageById(
id: $id
) {
id
title
description
path
locale
updatedAt
render
toc
...PageRead
}
}
${
pagePropsFragment
}
`
,
pageByPath
:
gql
`
query loadPage (
...
...
@@ -34,58 +74,49 @@ const gqlQueries = {
siteId: $siteId
path: $path
) {
id
title
description
path
locale
updatedAt
render
toc
...PageRead
}
}
${
pagePropsFragment
}
`
}
export
const
usePageStore
=
defineStore
(
'page'
,
{
state
:
()
=>
({
isLoading
:
true
,
mode
:
'view'
,
editor
:
'wysiwyg'
,
editorMode
:
'edit'
,
id
:
0
,
allowComments
:
false
,
allowContributions
:
true
,
allowRatings
:
true
,
authorId
:
0
,
authorName
:
''
,
commentsCount
:
0
,
content
:
''
,
createdAt
:
''
,
description
:
''
,
isPublished
:
true
,
showInTree
:
true
,
icon
:
'las la-file-alt'
,
id
:
''
,
isBrowsable
:
true
,
locale
:
'en'
,
password
:
''
,
path
:
''
,
publishEndDate
:
''
,
publishStartDate
:
''
,
tags
:
[],
title
:
''
,
icon
:
'las la-file-alt'
,
updatedAt
:
''
,
publishState
:
''
,
relations
:
[],
render
:
''
,
scriptJsLoad
:
''
,
scriptJsUnload
:
''
,
scriptStyles
:
''
,
allowComments
:
false
,
allowContributions
:
true
,
allowRatings
:
true
,
scriptCss
:
''
,
showSidebar
:
true
,
showToc
:
true
,
showTags
:
true
,
showToc
:
true
,
tags
:
[],
title
:
''
,
toc
:
[],
tocDepth
:
{
min
:
1
,
max
:
2
},
commentsCount
:
0
,
content
:
''
,
render
:
''
,
toc
:
[]
updatedAt
:
''
}),
getters
:
{
breadcrumbs
:
(
state
)
=>
{
...
...
@@ -120,7 +151,11 @@ export const usePageStore = defineStore('page', {
throw
new
Error
(
'ERR_PAGE_NOT_FOUND'
)
}
// Update page store
this
.
$patch
(
pageData
)
this
.
$patch
({
...
pageData
,
relations
:
pageData
.
relations
.
map
(
r
=>
pick
(
r
,
[
'id'
,
'position'
,
'label'
,
'caption'
,
'icon'
,
'target'
])),
tocDepth
:
pick
(
pageData
.
tocDepth
,
[
'min'
,
'max'
])
})
// Update editor state timestamps
const
curDate
=
DateTime
.
utc
()
editorStore
.
$patch
({
...
...
@@ -174,6 +209,73 @@ export const usePageStore = defineStore('page', {
// -> View Mode
this
.
mode
=
'edit'
},
/**
* PAGE SAVE
*/
async
pageSave
()
{
const
editorStore
=
useEditorStore
()
try
{
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation savePage (
$id: UUID!
$patch: PageUpdateInput!
) {
updatePage (
id: $id
patch: $patch
) {
operation {
succeeded
message
}
}
}
`
,
variables
:
{
id
:
this
.
id
,
patch
:
pick
(
this
,
[
'allowComments'
,
'allowContributions'
,
'allowRatings'
,
// 'content',
'description'
,
'icon'
,
'isBrowsable'
,
'locale'
,
'password'
,
'path'
,
'publishEndDate'
,
'publishStartDate'
,
'publishState'
,
'relations'
,
'scriptJsLoad'
,
'scriptJsUnload'
,
'scriptCss'
,
'showSidebar'
,
'showTags'
,
'showToc'
,
'tags'
,
'title'
,
'tocDepth'
])
}
})
const
result
=
resp
?.
data
?.
updatePage
?.
operation
??
{}
if
(
!
result
.
succeeded
)
{
throw
new
Error
(
result
.
message
)
}
// Update editor state timestamps
const
curDate
=
DateTime
.
utc
()
editorStore
.
$patch
({
lastChangeTimestamp
:
curDate
,
lastSaveTimestamp
:
curDate
})
}
catch
(
err
)
{
console
.
warn
(
err
)
throw
err
}
},
generateToc
()
{
}
...
...
ux/yarn.lock
View file @
f05a73dc
This diff was suppressed by a .gitattributes entry.
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