mirror of
https://github.com/requarks/wiki.git
synced 2026-08-26 13:17:10 -05:00
feat: upload pending assets
This commit is contained in:
@@ -1530,6 +1530,7 @@
|
||||
"editor.pageRel.title": "Add Page Relation",
|
||||
"editor.pageRel.titleEdit": "Edit Page Relation",
|
||||
"editor.pageScripts.title": "Page Scripts",
|
||||
"editor.pendingAssetsUploading": "Uploading assets...",
|
||||
"editor.props.alias": "Alias",
|
||||
"editor.props.allowComments": "Allow Comments",
|
||||
"editor.props.allowCommentsHint": "Enable commenting abilities on this page.",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
@@ -28,6 +28,8 @@
|
||||
q-item-label From File Manager...
|
||||
q-item(
|
||||
clickable
|
||||
@click='getAssetFromClipboard'
|
||||
v-close-popup
|
||||
)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-clipboard', color='brown')
|
||||
@@ -251,11 +253,10 @@
|
||||
import { reactive, ref, shallowRef, nextTick, onMounted, watch, onBeforeUnmount } from 'vue'
|
||||
import { useMeta, useQuasar, setCssVar } from 'quasar'
|
||||
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 * as monaco from 'monaco-editor'
|
||||
import { Position, Range } from 'monaco-editor'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { useEditorStore } from 'src/stores/editor'
|
||||
import { usePageStore } from 'src/stores/page'
|
||||
@@ -477,6 +478,43 @@ function openEditorSettings () {
|
||||
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
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -624,12 +662,7 @@ onMounted(async () => {
|
||||
editor.getContainerDomNode().addEventListener('drop', ev => {
|
||||
ev.preventDefault()
|
||||
for (const file of ev.dataTransfer.files) {
|
||||
const blobUrl = URL.createObjectURL(file)
|
||||
editorStore.pendingAssets.push({
|
||||
id: uuid(),
|
||||
file,
|
||||
blobUrl
|
||||
})
|
||||
const blobUrl = editorStore.addPendingAsset(file)
|
||||
if (file.type.startsWith('image')) {
|
||||
insertAtCursor({
|
||||
content: ``
|
||||
@@ -652,6 +685,7 @@ onMounted(async () => {
|
||||
|
||||
EVENT_BUS.on('insertAsset', insertAssetClb)
|
||||
EVENT_BUS.on('openEditorSettings', openEditorSettings)
|
||||
EVENT_BUS.on('reloadEditorContent', reloadEditorContent)
|
||||
|
||||
// this.$root.$on('editorInsert', opts => {
|
||||
// switch (opts.kind) {
|
||||
@@ -689,6 +723,7 @@ onMounted(async () => {
|
||||
onBeforeUnmount(() => {
|
||||
EVENT_BUS.off('insertAsset', insertAssetClb)
|
||||
EVENT_BUS.off('openEditorSettings', openEditorSettings)
|
||||
EVENT_BUS.off('reloadEditorContent', reloadEditorContent)
|
||||
if (editor) {
|
||||
editor.dispose()
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
q-item(v-for='item of editorStore.pendingAssets')
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-file-image')
|
||||
q-item-section {{ item.file.name }}
|
||||
q-item-section {{ item.fileName }}
|
||||
q-item-section(side)
|
||||
q-btn.acrylic-btn(
|
||||
color='negative'
|
||||
|
||||
@@ -289,6 +289,7 @@ async function saveChanges (closeAfter = false) {
|
||||
}
|
||||
|
||||
async function saveChangesCommit (closeAfter = false) {
|
||||
await processPendingAssets()
|
||||
$q.loading.show()
|
||||
try {
|
||||
await pageStore.pageSave()
|
||||
@@ -315,6 +316,7 @@ async function saveChangesCommit (closeAfter = false) {
|
||||
async function createPage () {
|
||||
// Handle home page creation flow
|
||||
if (pageStore.path === 'home') {
|
||||
await processPendingAssets()
|
||||
$q.loading.show()
|
||||
try {
|
||||
await pageStore.pageSave()
|
||||
@@ -347,6 +349,8 @@ async function createPage () {
|
||||
itemFileName: pageStore.path
|
||||
}
|
||||
}).onOk(async ({ path, title }) => {
|
||||
await processPendingAssets()
|
||||
|
||||
$q.loading.show()
|
||||
try {
|
||||
pageStore.$patch({
|
||||
@@ -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 () {
|
||||
$q.loading.show()
|
||||
await pageStore.pageEdit()
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<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>
|
||||
@@ -1,9 +1,19 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import gql from 'graphql-tag'
|
||||
import { clone } from 'lodash-es'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
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', {
|
||||
state: () => ({
|
||||
isActive: false,
|
||||
@@ -33,6 +43,29 @@ export const useEditorStore = defineStore('editor', {
|
||||
}
|
||||
},
|
||||
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 () {
|
||||
const siteStore = useSiteStore()
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user