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
1fb13098
Unverified
Commit
1fb13098
authored
Aug 01, 2023
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: upload pending assets
parent
dd3335c1
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
210 additions
and
9 deletions
+210
-9
en.json
server/locales/en.json
+1
-0
fluent-upload.svg
ux/public/_assets/icons/fluent-upload.svg
+2
-0
undraw_upload.svg
ux/public/_assets/illustrations/undraw_upload.svg
+0
-0
EditorMarkdown.vue
ux/src/components/EditorMarkdown.vue
+43
-8
PageActionsCol.vue
ux/src/components/PageActionsCol.vue
+1
-1
PageHeader.vue
ux/src/components/PageHeader.vue
+15
-0
UploadPendingAssetsDialog.vue
ux/src/components/UploadPendingAssetsDialog.vue
+115
-0
editor.js
ux/src/stores/editor.js
+33
-0
No files found.
server/locales/en.json
View file @
1fb13098
...
@@ -1530,6 +1530,7 @@
...
@@ -1530,6 +1530,7 @@
"editor.pageRel.title"
:
"Add Page Relation"
,
"editor.pageRel.title"
:
"Add Page Relation"
,
"editor.pageRel.titleEdit"
:
"Edit Page Relation"
,
"editor.pageRel.titleEdit"
:
"Edit Page Relation"
,
"editor.pageScripts.title"
:
"Page Scripts"
,
"editor.pageScripts.title"
:
"Page Scripts"
,
"editor.pendingAssetsUploading"
:
"Uploading assets..."
,
"editor.props.alias"
:
"Alias"
,
"editor.props.alias"
:
"Alias"
,
"editor.props.allowComments"
:
"Allow Comments"
,
"editor.props.allowComments"
:
"Allow Comments"
,
"editor.props.allowCommentsHint"
:
"Enable commenting abilities on this page."
,
"editor.props.allowCommentsHint"
:
"Enable commenting abilities on this page."
,
...
...
ux/public/_assets/icons/fluent-upload.svg
0 → 100644
View file @
1fb13098
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 48 48"
width=
"96px"
height=
"96px"
><linearGradient
id=
"wZGpzcUawlversdxtSVKMa"
x1=
"22.255"
x2=
"28.545"
y1=
"18.269"
y2=
"43.563"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#32bdef"
/><stop
offset=
"1"
stop-color=
"#1ea2e4"
/></linearGradient><path
fill=
"url(#wZGpzcUawlversdxtSVKMa)"
d=
"M31.789,24.789l-6.728-6.728c-0.586-0.586-1.536-0.586-2.121,0l-6.728,6.728 C15.764,25.236,16.081,26,16.713,26H21v16c0,0.552,0.448,1,1,1h4c0.552,0,1-0.448,1-1V26h4.287 C31.919,26,32.236,25.236,31.789,24.789z"
/><linearGradient
id=
"wZGpzcUawlversdxtSVKMb"
x1=
"42"
x2=
"42"
y1=
"4.513"
y2=
"16.282"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#32bdef"
/><stop
offset=
"1"
stop-color=
"#1ea2e4"
/></linearGradient><path
fill=
"url(#wZGpzcUawlversdxtSVKMb)"
d=
"M43.828,9.828L39,5v10c0,0.552,0.448,1,1,1h4c0.552,0,1-0.448,1-1v-2.343 C45,11.596,44.579,10.579,43.828,9.828z"
/><linearGradient
id=
"wZGpzcUawlversdxtSVKMc"
x1=
"6"
x2=
"6"
y1=
"4.513"
y2=
"16.282"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#32bdef"
/><stop
offset=
"1"
stop-color=
"#1ea2e4"
/></linearGradient><path
fill=
"url(#wZGpzcUawlversdxtSVKMc)"
d=
"M9,5L4.172,9.828C3.421,10.579,3,11.596,3,12.657V15c0,0.552,0.448,1,1,1h4 c0.552,0,1-0.448,1-1V5z"
/><linearGradient
id=
"wZGpzcUawlversdxtSVKMd"
x1=
"9"
x2=
"39"
y1=
"8"
y2=
"8"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#0362b0"
/><stop
offset=
".112"
stop-color=
"#036abd"
/><stop
offset=
".258"
stop-color=
"#036fc5"
/><stop
offset=
".5"
stop-color=
"#0370c8"
/><stop
offset=
".742"
stop-color=
"#036fc5"
/><stop
offset=
".888"
stop-color=
"#036abd"
/><stop
offset=
"1"
stop-color=
"#0362b0"
/></linearGradient><rect
width=
"30"
height=
"6"
x=
"9"
y=
"5"
fill=
"url(#wZGpzcUawlversdxtSVKMd)"
/></svg>
\ No newline at end of file
ux/public/_assets/illustrations/undraw_upload.svg
0 → 100644
View file @
1fb13098
This diff is collapsed.
Click to expand it.
ux/src/components/EditorMarkdown.vue
View file @
1fb13098
...
@@ -28,6 +28,8 @@
...
@@ -28,6 +28,8 @@
q-item-label From File Manager...
q-item-label From File Manager...
q-item(
q-item(
clickable
clickable
@click='getAssetFromClipboard'
v-close-popup
)
)
q-item-section(side)
q-item-section(side)
q-icon(name='las la-clipboard', color='brown')
q-icon(name='las la-clipboard', color='brown')
...
@@ -251,11 +253,10 @@
...
@@ -251,11 +253,10 @@
import
{
reactive
,
ref
,
shallowRef
,
nextTick
,
onMounted
,
watch
,
onBeforeUnmount
}
from
'vue'
import
{
reactive
,
ref
,
shallowRef
,
nextTick
,
onMounted
,
watch
,
onBeforeUnmount
}
from
'vue'
import
{
useMeta
,
useQuasar
,
setCssVar
}
from
'quasar'
import
{
useMeta
,
useQuasar
,
setCssVar
}
from
'quasar'
import
{
useI18n
}
from
'vue-i18n'
import
{
useI18n
}
from
'vue-i18n'
import
{
get
,
flatten
,
last
,
times
,
startsWith
,
debounce
}
from
'lodash-es'
import
{
find
,
get
,
last
,
times
,
startsWith
,
debounce
}
from
'lodash-es'
import
{
DateTime
}
from
'luxon'
import
{
DateTime
}
from
'luxon'
import
*
as
monaco
from
'monaco-editor'
import
*
as
monaco
from
'monaco-editor'
import
{
Position
,
Range
}
from
'monaco-editor'
import
{
Position
,
Range
}
from
'monaco-editor'
import
{
v4
as
uuid
}
from
'uuid'
import
{
useEditorStore
}
from
'src/stores/editor'
import
{
useEditorStore
}
from
'src/stores/editor'
import
{
usePageStore
}
from
'src/stores/page'
import
{
usePageStore
}
from
'src/stores/page'
...
@@ -477,6 +478,43 @@ function openEditorSettings () {
...
@@ -477,6 +478,43 @@ function openEditorSettings () {
siteStore
.
$patch
({
overlay
:
'EditorMarkdownConfig'
}
)
siteStore
.
$patch
({
overlay
:
'EditorMarkdownConfig'
}
)
}
}
async
function
getAssetFromClipboard
()
{
try
{
const
permission
=
await
navigator
.
permissions
.
query
({
name
:
'clipboard-read'
}
)
if
(
permission
.
state
===
'denied'
)
{
throw
new
Error
(
'Not allowed to read clipboard.'
)
}
const
clipboardContents
=
await
navigator
.
clipboard
.
read
()
let
hasValidItem
=
false
for
(
const
item
of
clipboardContents
)
{
const
imageType
=
find
(
item
.
types
,
t
=>
t
.
startsWith
(
'image/'
))
if
(
imageType
)
{
hasValidItem
=
true
const
blob
=
await
item
.
getType
(
imageType
)
const
blobUrl
=
editorStore
.
addPendingAsset
(
blob
)
insertAtCursor
({
content
:
``
}
)
}
}
if
(
!
hasValidItem
)
{
throw
new
Error
(
'No supported content found in the Clipboard.'
)
}
}
catch
(
err
)
{
return
$q
.
notify
({
type
:
'negative'
,
message
:
'Unable to copy from Clipboard'
,
caption
:
err
.
message
}
)
}
}
function
reloadEditorContent
()
{
editor
.
getModel
().
setValue
(
pageStore
.
content
)
}
// MOUNTED
// MOUNTED
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
...
@@ -624,12 +662,7 @@ onMounted(async () => {
...
@@ -624,12 +662,7 @@ onMounted(async () => {
editor
.
getContainerDomNode
().
addEventListener
(
'drop'
,
ev
=>
{
editor
.
getContainerDomNode
().
addEventListener
(
'drop'
,
ev
=>
{
ev
.
preventDefault
()
ev
.
preventDefault
()
for
(
const
file
of
ev
.
dataTransfer
.
files
)
{
for
(
const
file
of
ev
.
dataTransfer
.
files
)
{
const
blobUrl
=
URL
.
createObjectURL
(
file
)
const
blobUrl
=
editorStore
.
addPendingAsset
(
file
)
editorStore
.
pendingAssets
.
push
({
id
:
uuid
(),
file
,
blobUrl
}
)
if
(
file
.
type
.
startsWith
(
'image'
))
{
if
(
file
.
type
.
startsWith
(
'image'
))
{
insertAtCursor
({
insertAtCursor
({
content
:
``
content
:
``
...
@@ -652,6 +685,7 @@ onMounted(async () => {
...
@@ -652,6 +685,7 @@ onMounted(async () => {
EVENT_BUS
.
on
(
'insertAsset'
,
insertAssetClb
)
EVENT_BUS
.
on
(
'insertAsset'
,
insertAssetClb
)
EVENT_BUS
.
on
(
'openEditorSettings'
,
openEditorSettings
)
EVENT_BUS
.
on
(
'openEditorSettings'
,
openEditorSettings
)
EVENT_BUS
.
on
(
'reloadEditorContent'
,
reloadEditorContent
)
// this.$root.$on('editorInsert', opts =>
{
// this.$root.$on('editorInsert', opts =>
{
// switch (opts.kind)
{
// switch (opts.kind)
{
...
@@ -689,6 +723,7 @@ onMounted(async () => {
...
@@ -689,6 +723,7 @@ onMounted(async () => {
onBeforeUnmount(() => {
onBeforeUnmount(() => {
EVENT_BUS.off('insertAsset', insertAssetClb)
EVENT_BUS.off('insertAsset', insertAssetClb)
EVENT_BUS.off('openEditorSettings', openEditorSettings)
EVENT_BUS.off('openEditorSettings', openEditorSettings)
EVENT_BUS.off('reloadEditorContent', reloadEditorContent)
if (editor) {
if (editor) {
editor.dispose()
editor.dispose()
}
}
...
...
ux/src/components/PageActionsCol.vue
View file @
1fb13098
...
@@ -51,7 +51,7 @@
...
@@ -51,7 +51,7 @@
q-item(v-for='item of editorStore.pendingAssets')
q-item(v-for='item of editorStore.pendingAssets')
q-item-section(side)
q-item-section(side)
q-icon(name='las la-file-image')
q-icon(name='las la-file-image')
q-item-section
{{
item
.
file
.
n
ame
}}
q-item-section
{{
item
.
file
N
ame
}}
q-item-section(side)
q-item-section(side)
q-btn.acrylic-btn(
q-btn.acrylic-btn(
color='negative'
color='negative'
...
...
ux/src/components/PageHeader.vue
View file @
1fb13098
...
@@ -289,6 +289,7 @@ async function saveChanges (closeAfter = false) {
...
@@ -289,6 +289,7 @@ async function saveChanges (closeAfter = false) {
}
}
async
function
saveChangesCommit
(
closeAfter
=
false
)
{
async
function
saveChangesCommit
(
closeAfter
=
false
)
{
await
processPendingAssets
()
$q
.
loading
.
show
()
$q
.
loading
.
show
()
try
{
try
{
await
pageStore
.
pageSave
()
await
pageStore
.
pageSave
()
...
@@ -315,6 +316,7 @@ async function saveChangesCommit (closeAfter = false) {
...
@@ -315,6 +316,7 @@ async function saveChangesCommit (closeAfter = false) {
async
function
createPage
()
{
async
function
createPage
()
{
// Handle home page creation flow
// Handle home page creation flow
if
(
pageStore
.
path
===
'home'
)
{
if
(
pageStore
.
path
===
'home'
)
{
await
processPendingAssets
()
$q
.
loading
.
show
()
$q
.
loading
.
show
()
try
{
try
{
await
pageStore
.
pageSave
()
await
pageStore
.
pageSave
()
...
@@ -347,6 +349,8 @@ async function createPage () {
...
@@ -347,6 +349,8 @@ async function createPage () {
itemFileName
:
pageStore
.
path
itemFileName
:
pageStore
.
path
}
}
}).
onOk
(
async
({
path
,
title
})
=>
{
}).
onOk
(
async
({
path
,
title
})
=>
{
await
processPendingAssets
()
$q
.
loading
.
show
()
$q
.
loading
.
show
()
try
{
try
{
pageStore
.
$patch
({
pageStore
.
$patch
({
...
@@ -372,6 +376,17 @@ async function createPage () {
...
@@ -372,6 +376,17 @@ async function createPage () {
})
})
}
}
async
function
processPendingAssets
()
{
if
(
editorStore
.
pendingAssets
?.
length
>
0
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
$q
.
dialog
({
component
:
defineAsyncComponent
(()
=>
import
(
'../components/UploadPendingAssetsDialog.vue'
)),
persistent
:
true
}).
onOk
(
resolve
).
onCancel
(
reject
)
})
}
}
async
function
editPage
()
{
async
function
editPage
()
{
$q
.
loading
.
show
()
$q
.
loading
.
show
()
await
pageStore
.
pageEdit
()
await
pageStore
.
pageEdit
()
...
...
ux/src/components/UploadPendingAssetsDialog.vue
0 → 100644
View file @
1fb13098
<
template
lang=
"pug"
>
q-dialog(ref='dialogRef', @hide='onDialogHide', persistent)
q-card(style='min-width: 350px; max-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-upload.svg', left, size='sm')
span
{{
t
(
`editor.pendingAssetsUploading`
)
}}
q-card-section
.q-pa-md.text-center
img(src='/_assets/illustrations/undraw_upload.svg', style='width: 150px;')
q-linear-progress(
indeterminate
size='lg'
rounded
)
.q-mt-sm.text-center.text-caption
{{
state
.
current
}}
/
{{
state
.
total
}}
</
template
>
<
script
setup
>
import
gql
from
'graphql-tag'
import
{
useI18n
}
from
'vue-i18n'
import
{
useDialogPluginComponent
,
useQuasar
}
from
'quasar'
import
{
computed
,
onMounted
,
reactive
}
from
'vue'
import
{
useEditorStore
}
from
'src/stores/editor'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
usePageStore
}
from
'src/stores/page'
// EMITS
defineEmits
([
...
useDialogPluginComponent
.
emits
])
// QUASAR
const
{
dialogRef
,
onDialogHide
,
onDialogOK
,
onDialogCancel
}
=
useDialogPluginComponent
()
const
$q
=
useQuasar
()
// STORES
const
editorStore
=
useEditorStore
()
const
pageStore
=
usePageStore
()
const
siteStore
=
useSiteStore
()
// I18N
const
{
t
}
=
useI18n
()
// DATA
const
state
=
reactive
({
current
:
1
,
total
:
1
})
// MOUNTED
onMounted
(
async
()
=>
{
state
.
total
=
editorStore
.
pendingAssets
.
length
??
0
state
.
current
=
0
await
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
500
))
try
{
for
(
const
item
of
editorStore
.
pendingAssets
)
{
state
.
current
++
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
context
:
{
uploadMode
:
true
},
mutation
:
gql
`
mutation uploadAssets (
$folderId: UUID
$locale: String
$siteId: UUID
$files: [Upload!]!
) {
uploadAssets (
folderId: $folderId
locale: $locale
siteId: $siteId
files: $files
) {
operation {
succeeded
message
}
}
}
`
,
variables
:
{
folderId
:
null
,
// TODO: Upload to page specific folder
siteId
:
siteStore
.
id
,
locale
:
'en'
,
// TODO: use current locale
files
:
[
item
.
file
]
}
})
if
(
!
resp
?.
data
?.
uploadAssets
?.
operation
?.
succeeded
)
{
throw
new
Error
(
resp
?.
data
?.
uploadAssets
?.
operation
?.
message
||
'An unexpected error occured.'
)
}
pageStore
.
content
=
pageStore
.
content
.
replaceAll
(
item
.
blobUrl
,
`/
${
item
.
fileName
}
`
)
URL
.
revokeObjectURL
(
item
.
blobUrl
)
}
editorStore
.
pendingAssets
=
[]
EVENT_BUS
.
emit
(
'reloadEditorContent'
)
onDialogOK
()
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
onDialogCancel
()
}
})
</
script
>
ux/src/stores/editor.js
View file @
1fb13098
import
{
defineStore
}
from
'pinia'
import
{
defineStore
}
from
'pinia'
import
gql
from
'graphql-tag'
import
gql
from
'graphql-tag'
import
{
clone
}
from
'lodash-es'
import
{
clone
}
from
'lodash-es'
import
{
v4
as
uuid
}
from
'uuid'
import
{
useSiteStore
}
from
'./site'
import
{
useSiteStore
}
from
'./site'
const
imgMimeExt
=
{
'image/jpeg'
:
'jpg'
,
'image/gif'
:
'gif'
,
'image/png'
:
'png'
,
'image/webp'
:
'webp'
,
'image/svg+xml'
:
'svg'
,
'image/tiff'
:
'tif'
}
export
const
useEditorStore
=
defineStore
(
'editor'
,
{
export
const
useEditorStore
=
defineStore
(
'editor'
,
{
state
:
()
=>
({
state
:
()
=>
({
isActive
:
false
,
isActive
:
false
,
...
@@ -33,6 +43,29 @@ export const useEditorStore = defineStore('editor', {
...
@@ -33,6 +43,29 @@ export const useEditorStore = defineStore('editor', {
}
}
},
},
actions
:
{
actions
:
{
addPendingAsset
(
data
)
{
const
blobUrl
=
URL
.
createObjectURL
(
data
)
if
(
data
instanceof
File
)
{
this
.
pendingAssets
.
push
({
id
:
uuid
(),
kind
:
'file'
,
file
:
data
,
fileName
:
data
.
name
,
blobUrl
})
}
else
{
const
fileId
=
uuid
()
const
fileName
=
`
${
fileId
}
.
${
imgMimeExt
[
data
.
type
]
||
'dat'
}
`
this
.
pendingAssets
.
push
({
id
:
fileId
,
kind
:
'blob'
,
file
:
new
File
(
data
,
fileName
,
{
type
:
data
.
type
}),
fileName
,
blobUrl
})
}
return
blobUrl
},
async
fetchConfigs
()
{
async
fetchConfigs
()
{
const
siteStore
=
useSiteStore
()
const
siteStore
=
useSiteStore
()
try
{
try
{
...
...
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