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
274f3f4a
Unverified
Commit
274f3f4a
authored
Nov 05, 2022
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: page changes detection + side overlay component loader
parent
f671d3b1
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
194 additions
and
92 deletions
+194
-92
page.js
server/graph/resolvers/page.js
+1
-1
page.graphql
server/graph/schemas/page.graphql
+1
-1
components.js
ux/src/boot/components.js
+2
-0
FooterNav.vue
ux/src/components/FooterNav.vue
+5
-0
LoadingGeneric.vue
ux/src/components/LoadingGeneric.vue
+39
-0
PagePropertiesDialog.vue
ux/src/components/PagePropertiesDialog.vue
+9
-0
Index.vue
ux/src/pages/Index.vue
+61
-12
editor.js
ux/src/stores/editor.js
+7
-1
page.js
ux/src/stores/page.js
+69
-77
No files found.
server/graph/resolvers/page.js
View file @
274f3f4a
...
...
@@ -145,7 +145,7 @@ module.exports = {
async
pageById
(
obj
,
args
,
context
,
info
)
{
let
page
=
await
WIKI
.
db
.
pages
.
getPageFromDb
(
args
.
id
)
if
(
page
)
{
if
(
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'
manage:pages'
,
'delete
:pages'
],
{
if
(
WIKI
.
auth
.
checkAccess
(
context
.
req
.
user
,
[
'
read
:pages'
],
{
path
:
page
.
path
,
locale
:
page
.
localeCode
}))
{
...
...
server/graph/schemas/page.graphql
View file @
274f3f4a
...
...
@@ -31,7 +31,7 @@ extend type Query {
):
[
PageListItem
!]!
pageById
(
id
:
Int
!
id
:
UUID
!
):
Page
pageByPath
(
...
...
ux/src/boot/components.js
View file @
274f3f4a
...
...
@@ -2,10 +2,12 @@ import { boot } from 'quasar/wrappers'
import
BlueprintIcon
from
'../components/BlueprintIcon.vue'
import
StatusLight
from
'../components/StatusLight.vue'
import
LoadingGeneric
from
'../components/LoadingGeneric.vue'
import
VNetworkGraph
from
'v-network-graph'
export
default
boot
(({
app
})
=>
{
app
.
component
(
'BlueprintIcon'
,
BlueprintIcon
)
app
.
component
(
'LoadingGeneric'
,
LoadingGeneric
)
app
.
component
(
'StatusLight'
,
StatusLight
)
app
.
use
(
VNetworkGraph
)
})
ux/src/components/FooterNav.vue
View file @
274f3f4a
...
...
@@ -66,6 +66,11 @@ const isCopyright = computed(() => {
padding
:
4px
12px
;
font-size
:
11px
;
@at-root
.body--dark
&
{
background-color
:
$dark-4
;
color
:
rgba
(
255
,
255
,
255
,.
4
);
}
&
-line
{
text-align
:
center
;
...
...
ux/src/components/LoadingGeneric.vue
0 → 100644
View file @
274f3f4a
<
template
lang=
"pug"
>
.loader-generic
div
</
template
>
<
style
lang=
"scss"
>
.loader-generic
{
box-shadow
:
none
!
important
;
padding-top
:
64px
;
>
div
{
background-color
:
rgba
(
0
,
0
,
0
,.
75
);
width
:
64px
;
height
:
64px
;
border-radius
:
5px
!
important
;
position
:
relative
;
&
:before
{
content
:
''
;
box-sizing
:
border-box
;
position
:
absolute
;
top
:
50%
;
left
:
50%
;
width
:
24px
;
height
:
24px
;
margin-top
:
-12px
;
margin-left
:
-12px
;
border-radius
:
50%
;
border-top
:
2px
solid
#FFF
;
border-right
:
2px
solid
transparent
;
animation
:
loadergenericspinner
.6s
linear
infinite
;
}
}
}
@keyframes
loadergenericspinner
{
to
{
transform
:
rotate
(
360deg
);
}
}
</
style
>
ux/src/components/PagePropertiesDialog.vue
View file @
274f3f4a
...
...
@@ -273,11 +273,13 @@ q-card.page-properties-dialog
import
{
useI18n
}
from
'vue-i18n'
import
{
useQuasar
}
from
'quasar'
import
{
nextTick
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
DateTime
}
from
'luxon'
import
PageRelationDialog
from
'./PageRelationDialog.vue'
import
PageScriptsDialog
from
'./PageScriptsDialog.vue'
import
PageTags
from
'./PageTags.vue'
import
{
useEditorStore
}
from
'src/stores/editor'
import
{
usePageStore
}
from
'src/stores/page'
import
{
useSiteStore
}
from
'src/stores/site'
...
...
@@ -287,6 +289,7 @@ const $q = useQuasar()
// STORES
const
editorStore
=
useEditorStore
()
const
pageStore
=
usePageStore
()
const
siteStore
=
useSiteStore
()
...
...
@@ -335,6 +338,12 @@ watch(() => state.requirePassword, (newValue) => {
}
})
pageStore
.
$subscribe
(()
=>
{
editorStore
.
$patch
({
lastChangeTimestamp
:
DateTime
.
utc
()
})
})
// METHODS
function
editScripts
(
mode
)
{
...
...
ux/src/pages/Index.vue
View file @
274f3f4a
...
...
@@ -73,15 +73,35 @@ q-page.column
aria-label='Print'
)
q-tooltip Print
q-btn.acrylic-btn(
flat
icon='las la-edit'
color='deep-orange-9'
label='Edit'
aria-label='Edit'
no-caps
:href='editUrl'
)
template(v-if='editorStore.hasPendingChanges')
q-btn.acrylic-btn.q-mr-sm(
flat
icon='las la-times'
color='negative'
label='Discard'
aria-label='Discard'
no-caps
@click='discardChanges'
)
q-btn.acrylic-btn(
flat
icon='las la-check'
color='positive'
label='Save Changes'
aria-label='Save Changes'
no-caps
@click='saveChanges'
)
template(v-else)
q-btn.acrylic-btn(
flat
icon='las la-edit'
color='deep-orange-9'
label='Edit'
aria-label='Edit'
no-caps
:href='editUrl'
)
.page-container.row.no-wrap.items-stretch(style='flex: 1 1 100%;')
.col(style='order: 1;')
q-scroll-area(
...
...
@@ -308,17 +328,25 @@ import { useRouter, useRoute } from 'vue-router'
import
{
useI18n
}
from
'vue-i18n'
import
{
DateTime
}
from
'luxon'
import
{
useEditorStore
}
from
'src/stores/editor'
import
{
usePageStore
}
from
'src/stores/page'
import
{
useSiteStore
}
from
'src/stores/site'
// COMPONENTS
import
SocialSharingMenu
from
'../components/SocialSharingMenu.vue'
import
LoadingGeneric
from
'src/components/LoadingGeneric.vue'
import
PageTags
from
'../components/PageTags.vue'
const
sideDialogs
=
{
PageDataDialog
:
defineAsyncComponent
(()
=>
import
(
'../components/PageDataDialog.vue'
)),
PagePropertiesDialog
:
defineAsyncComponent
(()
=>
import
(
'../components/PagePropertiesDialog.vue'
))
PageDataDialog
:
defineAsyncComponent
({
loader
:
()
=>
import
(
'../components/PageDataDialog.vue'
),
loadingComponent
:
LoadingGeneric
}),
PagePropertiesDialog
:
defineAsyncComponent
({
loader
:
()
=>
import
(
'../components/PagePropertiesDialog.vue'
),
loadingComponent
:
LoadingGeneric
})
}
const
globalDialogs
=
{
PageSaveDialog
:
defineAsyncComponent
(()
=>
import
(
'../components/PageSaveDialog.vue'
))
...
...
@@ -330,6 +358,7 @@ const $q = useQuasar()
// STORES
const
editorStore
=
useEditorStore
()
const
pageStore
=
usePageStore
()
const
siteStore
=
useSiteStore
()
...
...
@@ -448,7 +477,6 @@ function savePage () {
}
function
refreshTocExpanded
(
baseToc
,
lvl
)
{
console
.
info
(
pageStore
.
tocDepth
.
min
,
lvl
,
pageStore
.
tocDepth
.
max
)
const
toExpand
=
[]
let
isRootNode
=
false
if
(
!
baseToc
)
{
...
...
@@ -472,6 +500,27 @@ function refreshTocExpanded (baseToc, lvl) {
return
toExpand
}
}
async
function
discardChanges
()
{
$q
.
loading
.
show
()
try
{
await
pageStore
.
pageLoad
({
id
:
pageStore
.
id
})
$q
.
notify
({
type
:
'positive'
,
message
:
'Page has been reverted to the last saved state.'
})
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
'Failed to reload page state.'
})
}
$q
.
loading
.
hide
()
}
async
function
saveChanges
()
{
}
</
script
>
<
style
lang=
"scss"
>
...
...
ux/src/stores/editor.js
View file @
274f3f4a
...
...
@@ -13,8 +13,14 @@ export const useEditorStore = defineStore('editor', {
currentFileId
:
null
},
checkoutDateActive
:
''
,
lastSaveTimestamp
:
null
,
lastChangeTimestamp
:
null
,
editors
:
{}
}),
getters
:
{},
getters
:
{
hasPendingChanges
:
(
state
)
=>
{
return
state
.
lastSaveTimestamp
&&
state
.
lastSaveTimestamp
!==
state
.
lastChangeTimestamp
}
},
actions
:
{}
})
ux/src/stores/page.js
View file @
274f3f4a
import
{
defineStore
}
from
'pinia'
import
gql
from
'graphql-tag'
import
{
cloneDeep
,
last
,
transform
}
from
'lodash-es'
import
{
DateTime
}
from
'luxon'
import
{
useSiteStore
}
from
'./site'
import
{
useEditorStore
}
from
'./editor'
const
gqlQueries
=
{
pageById
:
gql
`
query loadPage (
$id: UUID!
) {
pageById(
id: $id
) {
id
title
description
path
locale
updatedAt
render
toc
}
}
`
,
pageByPath
:
gql
`
query loadPage (
$siteId: UUID!
$path: String!
) {
pageByPath(
siteId: $siteId
path: $path
) {
id
title
description
path
locale
updatedAt
render
toc
}
}
`
}
export
const
usePageStore
=
defineStore
(
'page'
,
{
state
:
()
=>
({
...
...
@@ -39,101 +82,50 @@ export const usePageStore = defineStore('page', {
min
:
1
,
max
:
2
},
breadcrumbs
:
[
// {
// id: 1,
// title: 'Installation',
// icon: 'las la-file-alt',
// locale: 'en',
// path: 'installation'
// },
// {
// id: 2,
// title: 'Ubuntu',
// icon: 'lab la-ubuntu',
// locale: 'en',
// path: 'installation/ubuntu'
// }
],
effectivePermissions
:
{
comments
:
{
read
:
false
,
write
:
false
,
manage
:
false
},
history
:
{
read
:
false
},
source
:
{
read
:
false
},
pages
:
{
write
:
false
,
manage
:
false
,
delete
:
false
,
script
:
false
,
style
:
false
},
system
:
{
manage
:
false
}
},
commentsCount
:
0
,
content
:
''
,
render
:
''
,
toc
:
[]
}),
getters
:
{},
getters
:
{
breadcrumbs
:
(
state
)
=>
{
const
siteStore
=
useSiteStore
()
const
pathPrefix
=
siteStore
.
useLocales
?
`/
${
state
.
locale
}
`
:
''
return
transform
(
state
.
path
.
split
(
'/'
),
(
result
,
value
,
key
)
=>
{
result
.
push
({
id
:
key
,
title
:
value
,
icon
:
'las la-file-alt'
,
locale
:
'en'
,
path
:
(
last
(
result
)?.
path
||
pathPrefix
)
+
`/
${
value
}
`
})
},
[])
}
},
actions
:
{
/**
* PAGE - LOAD
*/
async
pageLoad
({
path
,
id
})
{
const
editorStore
=
useEditorStore
()
const
siteStore
=
useSiteStore
()
try
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query loadPage (
$siteId: UUID!
$path: String!
) {
pageByPath(
siteId: $siteId
path: $path
) {
id
title
description
path
locale
updatedAt
render
toc
}
}
`
,
variables
:
{
siteId
:
siteStore
.
id
,
path
},
query
:
id
?
gqlQueries
.
pageById
:
gqlQueries
.
pageByPath
,
variables
:
id
?
{
id
}
:
{
siteId
:
siteStore
.
id
,
path
},
fetchPolicy
:
'network-only'
})
const
pageData
=
cloneDeep
(
resp
?.
data
?.
pageByPath
??
{})
const
pageData
=
cloneDeep
(
(
id
?
resp
?.
data
?.
pageById
:
resp
?.
data
?.
pageByPath
)
??
{})
if
(
!
pageData
?.
id
)
{
throw
new
Error
(
'ERR_PAGE_NOT_FOUND'
)
}
const
pathPrefix
=
siteStore
.
useLocales
?
`/
${
pageData
.
locale
}
`
:
''
this
.
$patch
({
...
pageData
,
breadcrumbs
:
transform
(
pageData
.
path
.
split
(
'/'
),
(
result
,
value
,
key
)
=>
{
result
.
push
({
id
:
key
,
title
:
value
,
icon
:
'las la-file-alt'
,
locale
:
'en'
,
path
:
(
last
(
result
)?.
path
||
pathPrefix
)
+
`/
${
value
}
`
})
},
[])
// Update page store
this
.
$patch
(
pageData
)
// Update editor state timestamps
const
curDate
=
DateTime
.
utc
()
editorStore
.
$patch
({
lastChangeTimestamp
:
curDate
,
lastSaveTimestamp
:
curDate
})
}
catch
(
err
)
{
console
.
warn
(
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