feat: view page source + download page

pull/6775/head
NGPixel 1 year ago
parent 5a60fb11b5
commit add3631b22
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -186,6 +186,7 @@ export async function up (knex) {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()')) table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.uuid('pageId').notNullable().index() table.uuid('pageId').notNullable().index()
table.string('action').defaultTo('updated') table.string('action').defaultTo('updated')
table.string('reason')
table.jsonb('affectedFields').notNullable().defaultTo('[]') table.jsonb('affectedFields').notNullable().defaultTo('[]')
table.string('locale', 10).notNullable().defaultTo('en') table.string('locale', 10).notNullable().defaultTo('en')
table.string('path').notNullable() table.string('path').notNullable()

@ -77,21 +77,30 @@ export class PageHistory extends Model {
*/ */
static async addVersion(opts) { static async addVersion(opts) {
await WIKI.db.pageHistory.query().insert({ await WIKI.db.pageHistory.query().insert({
pageId: opts.id, action: opts.historyData?.action ?? 'updated',
siteId: opts.siteId, affectedFields: JSON.stringify(opts.historyData?.affectedFields ?? []),
alias: opts.alias,
config: JSON.stringify(opts.config ?? {}),
authorId: opts.authorId, authorId: opts.authorId,
content: opts.content, content: opts.content,
contentType: opts.contentType, contentType: opts.contentType,
description: opts.description, description: opts.description,
editor: opts.editor, editor: opts.editor,
hash: opts.hash, hash: opts.hash,
publishState: opts.publishState, icon: opts.icon,
locale: opts.locale, locale: opts.locale,
pageId: opts.id,
path: opts.path, path: opts.path,
publishEndDate: opts.publishEndDate?.toISO(), publishEndDate: opts.publishEndDate?.toISO(),
publishStartDate: opts.publishStartDate?.toISO(), publishStartDate: opts.publishStartDate?.toISO(),
publishState: opts.publishState,
reason: opts.historyData?.reason,
relations: JSON.stringify(opts.relations ?? []),
render: opts.render,
scripts: JSON.stringify(opts.scripts ?? {}),
siteId: opts.siteId,
title: opts.title, title: opts.title,
action: opts.action || 'updated', toc: JSON.stringify(opts.toc ?? []),
versionDate: opts.versionDate versionDate: opts.versionDate
}) })
} }

@ -428,6 +428,7 @@ export class Page extends Model {
const patch = {} const patch = {}
const historyData = { const historyData = {
action: 'updated', action: 'updated',
reason: opts.reasonForChange,
affectedFields: [] affectedFields: []
} }
let shouldUpdateSearch = false let shouldUpdateSearch = false

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="AkEixl0fFF2Red42g8M2Fa" x1="12.727" x2="4.186" y1="33.727" y2="25.186" gradientUnits="userSpaceOnUse"><stop offset=".365" stop-color="#199ae0"/><stop offset=".699" stop-color="#1898de"/><stop offset=".819" stop-color="#1691d8"/><stop offset=".905" stop-color="#1186cc"/><stop offset=".974" stop-color="#0a75bc"/><stop offset="1" stop-color="#076cb3"/></linearGradient><path fill="url(#AkEixl0fFF2Red42g8M2Fa)" d="M0.29,24.701l9.509,9.509c0.387,0.387,1.014,0.387,1.401,0l2.009-2.009 c0.387-0.387,0.387-1.014,0-1.401L3.701,21.29c-0.387-0.387-1.014-0.387-1.401,0L0.29,23.299 C-0.097,23.686-0.097,24.314,0.29,24.701z"/><linearGradient id="AkEixl0fFF2Red42g8M2Fb" x1="1.359" x2="12.357" y1="25.641" y2="14.643" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#AkEixl0fFF2Red42g8M2Fb)" d="M3.701,26.71l9.509-9.509c0.387-0.387,0.387-1.014,0-1.401l-2.009-2.009 c-0.387-0.387-1.014-0.387-1.401,0L0.29,23.299c-0.387,0.387-0.387,1.014,0,1.401l2.009,2.009 C2.686,27.097,3.314,27.097,3.701,26.71z"/><linearGradient id="AkEixl0fFF2Red42g8M2Fc" x1="689.727" x2="681.186" y1="33.727" y2="25.186" gradientTransform="matrix(-1 0 0 1 725 0)" gradientUnits="userSpaceOnUse"><stop offset=".365" stop-color="#199ae0"/><stop offset=".699" stop-color="#1898de"/><stop offset=".819" stop-color="#1691d8"/><stop offset=".905" stop-color="#1186cc"/><stop offset=".974" stop-color="#0a75bc"/><stop offset="1" stop-color="#076cb3"/></linearGradient><path fill="url(#AkEixl0fFF2Red42g8M2Fc)" d="M47.71,24.701l-9.509,9.509c-0.387,0.387-1.014,0.387-1.401,0l-2.009-2.009 c-0.387-0.387-0.387-1.014,0-1.401l9.509-9.509c0.387-0.387,1.014-0.387,1.401,0l2.009,2.009 C48.097,23.686,48.097,24.314,47.71,24.701z"/><linearGradient id="AkEixl0fFF2Red42g8M2Fd" x1="678.359" x2="689.357" y1="25.641" y2="14.643" gradientTransform="matrix(-1 0 0 1 725 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#AkEixl0fFF2Red42g8M2Fd)" d="M44.299,26.71l-9.509-9.509c-0.387-0.387-0.387-1.014,0-1.401l2.009-2.009 c0.387-0.387,1.014-0.387,1.401,0l9.509,9.509c0.387,0.387,0.387,1.014,0,1.401l-2.009,2.009 C45.314,27.097,44.686,27.097,44.299,26.71z"/><path fill="#0078d4" d="M30.25,2.5h-3.61c-0.473,0-0.882,0.332-0.979,0.795l-8.644,41.301 c-0.098,0.466,0.258,0.904,0.734,0.904h3.61c0.473,0,0.882-0.332,0.979-0.795l8.644-41.301C31.081,2.938,30.726,2.5,30.25,2.5z"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -196,7 +196,7 @@ onBeforeUnmount(() => {
color: #FFF; color: #FFF;
padding: .5rem 1rem 1rem; padding: .5rem 1rem 1rem;
width: 100%; width: 100%;
backdrop-filter: blur(7px); backdrop-filter: blur(7px) saturate(180%);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 1px 1px rgba(0, 0, 0, 0.14), 0 2px 1px -1px rgba(0, 0, 0, 0.12); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 1px 1px rgba(0, 0, 0, 0.14), 0 2px 1px -1px rgba(0, 0, 0, 0.12);
&-header { &-header {

@ -31,6 +31,10 @@ const overlays = {
loader: () => import('./NavEditOverlay.vue'), loader: () => import('./NavEditOverlay.vue'),
loadingComponent: LoadingGeneric loadingComponent: LoadingGeneric
}), }),
PageSource: defineAsyncComponent({
loader: () => import('./PageSourceOverlay.vue'),
loadingComponent: LoadingGeneric
}),
TableEditor: defineAsyncComponent({ TableEditor: defineAsyncComponent({
loader: () => import('./TableEditorOverlay.vue'), loader: () => import('./TableEditorOverlay.vue'),
loadingComponent: LoadingGeneric loadingComponent: LoadingGeneric

@ -76,6 +76,7 @@
icon='las la-code' icon='las la-code'
:color='editorStore.isActive ? `white` : `grey`' :color='editorStore.isActive ? `white` : `grey`'
aria-label='Page Source' aria-label='Page Source'
@click='viewPageSource'
) )
q-tooltip(anchor='center left' self='center right') Page Source q-tooltip(anchor='center left' self='center right') Page Source
template(v-if='!(editorStore.isActive && editorStore.mode === `create`)') template(v-if='!(editorStore.isActive && editorStore.mode === `create`)')
@ -87,7 +88,7 @@
aria-label='Page Actions' aria-label='Page Actions'
) )
q-tooltip(anchor='center left' self='center right') Page Actions q-tooltip(anchor='center left' self='center right') Page Actions
q-menu( q-menu.translucent-menu(
anchor='top left' anchor='top left'
self='top right' self='top right'
auto-close auto-close
@ -199,6 +200,10 @@ function togglePageData () {
}) })
} }
function viewPageSource () {
siteStore.$patch({ overlay: 'PageSource', overlayOpts: { } })
}
function duplicatePage () { function duplicatePage () {
$q.dialog({ $q.dialog({
component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')), component: defineAsyncComponent(() => import('../components/TreeBrowserDialog.vue')),

@ -0,0 +1,161 @@
<template lang="pug">
q-layout(view='hHh lpR fFf', container)
q-header.card-header.q-px-md.q-py-sm
q-icon(name='img:/_assets/icons/fluent-code.svg', left, size='md')
span Page Source
q-space
transition(name='syncing')
q-spinner-tail.q-mr-sm(
v-show='state.loading > 0'
color='accent'
size='24px'
)
q-btn.q-mr-md(
icon='las la-download'
color='teal-3'
dense
flat
@click='download'
)
q-tooltip(anchor='bottom middle', self='top middle') {{t(`common.actions.download`)}}
q-btn(
icon='las la-times'
color='pink-2'
dense
flat
@click='close'
)
q-tooltip(anchor='bottom middle', self='top middle') {{t(`common.actions.close`)}}
q-page-container
q-page.bg-dark-6.text-white.font-robotomono.pagesource
q-scroll-area(
:thumb-style='thumb'
:bar-style='bar'
:horizontal-thumb-style='{ height: `5px` }'
style="width: 100%; height: calc(100vh - 100px);"
)
pre.q-px-md(v-text='state.content')
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { exportFile, useQuasar } from 'quasar'
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import gql from 'graphql-tag'
import { cloneDeep } from 'lodash-es'
import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site'
// QUASAR
const $q = useQuasar()
// STORES
const pageStore = usePageStore()
const siteStore = useSiteStore()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
loading: 0,
content: ''
})
const thumb = {
right: '2px',
borderRadius: '5px',
backgroundColor: '#FFF',
width: '5px',
opacity: 0.25
}
const bar = {
backgroundColor: '#000',
width: '9px',
opacity: 0.25
}
const contentTypes = {
markdown: {
ext: 'md',
mime: 'text/markdown'
},
html: {
ext: 'html',
mime: 'text/html'
}
}
// METHODS
function download () {
const fileType = contentTypes[state.contentType] ?? { ext: 'txt', mime: 'text/plain' }
exportFile(`page.${fileType.ext}`, state.content, { mimeType: `${fileType.mime};charset=UTF-8` })
}
function close () {
siteStore.$patch({ overlay: '' })
}
async function load () {
state.loading++
$q.loading.show()
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query loadPageSource (
$id: UUID!
) {
pageById(
id: $id
) {
id
content
contentType
}
}
`,
variables: {
id: pageStore.id
},
fetchPolicy: 'network-only'
})
const pageData = cloneDeep(resp?.data?.pageById ?? {})
if (!pageData?.id) {
throw new Error('ERR_PAGE_NOT_FOUND')
}
state.content = pageData.content
state.contentType = pageData.contentType
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
}
$q.loading.hide()
state.loading--
}
onMounted(() => {
load()
})
onBeforeUnmount(() => {
siteStore.overlayOpts = {}
})
</script>
<style lang="scss" scoped>
.pagesource {
> pre {
margin: 0;
overflow-x: auto;
}
}
</style>

@ -228,7 +228,7 @@ body::-webkit-scrollbar-thumb {
background-color: rgba($dark,.7); background-color: rgba($dark,.7);
} }
backdrop-filter: blur(10px); backdrop-filter: blur(10px) saturate(180%);
> .q-card { > .q-card {
background-color: transparent !important; background-color: transparent !important;

@ -394,7 +394,7 @@ onMounted(async () => {
.admin-overlay { .admin-overlay {
> .q-dialog__backdrop { > .q-dialog__backdrop {
background-color: rgba(0,0,0,.6); background-color: rgba(0,0,0,.6);
backdrop-filter: blur(5px); backdrop-filter: blur(5px) saturate(180%);
} }
> .q-dialog__inner { > .q-dialog__inner {
padding: 24px 64px; padding: 24px 64px;

@ -199,7 +199,7 @@ body.body--dark {
.main-overlay { .main-overlay {
> .q-dialog__backdrop { > .q-dialog__backdrop {
background-color: rgba(0,0,0,.6); background-color: rgba(0,0,0,.6);
backdrop-filter: blur(5px); backdrop-filter: blur(5px) saturate(180%);
} }
> .q-dialog__inner { > .q-dialog__inner {
padding: 24px 64px; padding: 24px 64px;
@ -216,7 +216,7 @@ body.body--dark {
background-image: linear-gradient(to bottom, $dark-4 10px, $dark-4 11px, $dark-3); background-image: linear-gradient(to bottom, $dark-4 10px, $dark-4 11px, $dark-3);
} }
border-radius: 6px; border-radius: 6px;
box-shadow: 0 0 0 1px rgba(0,0,0,.5); box-shadow: 0 0 30px 0 rgba(0,0,0,.3);
} }
} }
} }

Loading…
Cancel
Save