mirror of
https://github.com/requarks/wiki.git
synced 2026-08-26 13:17:10 -05:00
feat: page browser dialog + various improvements
This commit is contained in:
@@ -241,12 +241,6 @@
|
||||
page-selector(mode='create', v-model='duplicateOpts.modal', :open-handler='pageDuplicateHandle', :path='duplicateOpts.path', :locale='duplicateOpts.locale')
|
||||
page-delete(v-model='deletePageModal', v-if='path && path.length')
|
||||
page-convert(v-model='convertPageModal', v-if='path && path.length')
|
||||
|
||||
.nav-header-dev(v-if='isDevMode')
|
||||
v-icon mdi-alert
|
||||
div
|
||||
.overline DEVELOPMENT VERSION
|
||||
.overline This code base is NOT for production use!
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -36,26 +36,6 @@
|
||||
img(src='/_assets-legacy/svg/editor-icon-ckeditor.svg', alt='Visual Editor', style='width: 36px;')
|
||||
.body-2.mt-2.primary--text Visual Editor
|
||||
.caption.grey--text Rich-text WYSIWYG
|
||||
|
||||
v-card.radius-7.mt-2(color='teal darken-3', dark)
|
||||
v-card-text.text-center.py-4
|
||||
.subtitle-1.white--text {{$t('editor:select.customView')}}
|
||||
v-container(grid-list-lg, fluid)
|
||||
v-layout(row, wrap, justify-center)
|
||||
v-flex(xs4)
|
||||
v-hover
|
||||
template(v-slot:default='{ hover }')
|
||||
v-card.radius-7.animated.fadeInUp(
|
||||
hover
|
||||
light
|
||||
ripple
|
||||
)
|
||||
v-card-text.text-center(@click='fromTemplate')
|
||||
img(src='/_assets-legacy/svg/icon-cube.svg', alt='From Template', style='width: 42px; opacity: .5;')
|
||||
.body-2.mt-1.teal--text From Template
|
||||
.caption.grey--text Use an existing page...
|
||||
|
||||
page-selector(mode='select', v-model='templateDialogIsShown', :open-handler='fromTemplateHandle', :path='path', :locale='locale', must-exist)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -58,12 +58,7 @@ exports.up = async knex => {
|
||||
.createTable('assetData', table => {
|
||||
table.uuid('id').notNullable().primary()
|
||||
table.binary('data').notNullable()
|
||||
})
|
||||
// ASSET FOLDERS -----------------------
|
||||
.createTable('assetFolders', table => {
|
||||
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
|
||||
table.string('name').notNullable()
|
||||
table.string('slug').notNullable()
|
||||
table.binary('preview')
|
||||
})
|
||||
// AUTHENTICATION ----------------------
|
||||
.createTable('authentication', table => {
|
||||
@@ -351,13 +346,9 @@ exports.up = async knex => {
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites')
|
||||
})
|
||||
.table('assets', table => {
|
||||
table.uuid('folderId').notNullable().references('id').inTable('assetFolders').index()
|
||||
table.uuid('authorId').notNullable().references('id').inTable('users')
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
|
||||
})
|
||||
.table('assetFolders', table => {
|
||||
table.uuid('parentId').references('id').inTable('assetFolders').index()
|
||||
})
|
||||
.table('commentProviders', table => {
|
||||
table.uuid('siteId').notNullable().references('id').inTable('sites')
|
||||
})
|
||||
@@ -551,6 +542,7 @@ exports.up = async knex => {
|
||||
comments: false,
|
||||
contributions: false,
|
||||
profile: true,
|
||||
reasonForChange: 'required',
|
||||
search: true
|
||||
},
|
||||
logoText: true,
|
||||
|
||||
@@ -2,7 +2,9 @@ const _ = require('lodash')
|
||||
const sanitize = require('sanitize-filename')
|
||||
const graphHelper = require('../../helpers/graph')
|
||||
const assetHelper = require('../../helpers/asset')
|
||||
const { setTimeout } = require('node:timers/promises')
|
||||
const path = require('node:path')
|
||||
const fs = require('fs-extra')
|
||||
const { v4: uuid } = require('uuid')
|
||||
|
||||
module.exports = {
|
||||
Query: {
|
||||
@@ -187,10 +189,54 @@ module.exports = {
|
||||
*/
|
||||
async uploadAssets(obj, args, context) {
|
||||
try {
|
||||
const results = await Promise.allSettled(args.files.map(async fl => {
|
||||
const { filename, mimetype, createReadStream } = await fl
|
||||
WIKI.logger.debug(`Processing asset upload ${filename} of type ${mimetype}...`)
|
||||
|
||||
|
||||
|
||||
if (!WIKI.extensions.ext.sharp.isInstalled) {
|
||||
throw new Error('This feature requires the Sharp extension but it is not installed.')
|
||||
}
|
||||
if (!['.png', '.jpg', 'webp', '.gif'].some(s => filename.endsWith(s))) {
|
||||
throw new Error('Invalid File Extension. Must be svg, png, jpg, webp or gif.')
|
||||
}
|
||||
const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
|
||||
const destFolder = path.resolve(
|
||||
process.cwd(),
|
||||
WIKI.config.dataPath,
|
||||
`assets`
|
||||
)
|
||||
const destPath = path.join(destFolder, `logo-${args.id}.${destFormat}`)
|
||||
await fs.ensureDir(destFolder)
|
||||
// -> Resize
|
||||
await WIKI.extensions.ext.sharp.resize({
|
||||
format: destFormat,
|
||||
inputStream: createReadStream(),
|
||||
outputPath: destPath,
|
||||
height: 72
|
||||
})
|
||||
// -> Save logo meta to DB
|
||||
const site = await WIKI.db.sites.query().findById(args.id)
|
||||
if (!site.config.assets.logo) {
|
||||
site.config.assets.logo = uuid()
|
||||
}
|
||||
site.config.assets.logoExt = destFormat
|
||||
await WIKI.db.sites.query().findById(args.id).patch({ config: site.config })
|
||||
await WIKI.db.sites.reloadCache()
|
||||
// -> Save image data to DB
|
||||
const imgBuffer = await fs.readFile(destPath)
|
||||
await WIKI.db.knex('assetData').insert({
|
||||
id: site.config.assets.logo,
|
||||
data: imgBuffer
|
||||
}).onConflict('id').merge()
|
||||
}))
|
||||
WIKI.logger.debug('Asset(s) uploaded successfully.')
|
||||
return {
|
||||
operation: graphHelper.generateSuccess('Asset(s) uploaded successfully.')
|
||||
operation: graphHelper.generateSuccess('Asset(s) uploaded successfully')
|
||||
}
|
||||
} catch (err) {
|
||||
WIKI.logger.warn(err)
|
||||
return graphHelper.generateError(err)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -103,7 +103,7 @@ extend type Mutation {
|
||||
): PageResponse
|
||||
|
||||
convertPage(
|
||||
id: Int!
|
||||
id: UUID!
|
||||
editor: String!
|
||||
): DefaultResponse
|
||||
|
||||
@@ -114,7 +114,7 @@ extend type Mutation {
|
||||
): DefaultResponse
|
||||
|
||||
deletePage(
|
||||
id: Int!
|
||||
id: UUID!
|
||||
): DefaultResponse
|
||||
|
||||
deleteTag(
|
||||
|
||||
@@ -78,10 +78,11 @@ type SiteRobots {
|
||||
|
||||
type SiteFeatures {
|
||||
ratings: Boolean
|
||||
ratingsMode: SitePageRatingModes
|
||||
ratingsMode: SitePageRatingMode
|
||||
comments: Boolean
|
||||
contributions: Boolean
|
||||
profile: Boolean
|
||||
reasonForChange: SiteReasonForChangeMode
|
||||
search: Boolean
|
||||
}
|
||||
|
||||
@@ -129,12 +130,18 @@ enum SiteThemePosition {
|
||||
right
|
||||
}
|
||||
|
||||
enum SitePageRatingModes {
|
||||
enum SitePageRatingMode {
|
||||
off
|
||||
thumbs
|
||||
stars
|
||||
}
|
||||
|
||||
enum SiteReasonForChangeMode {
|
||||
off
|
||||
optional
|
||||
required
|
||||
}
|
||||
|
||||
type SiteCreateResponse {
|
||||
operation: Operation
|
||||
site: Site
|
||||
@@ -164,10 +171,11 @@ input SiteRobotsInput {
|
||||
|
||||
input SiteFeaturesInput {
|
||||
ratings: Boolean
|
||||
ratingsMode: SitePageRatingModes
|
||||
ratingsMode: SitePageRatingMode
|
||||
comments: Boolean
|
||||
contributions: Boolean
|
||||
profile: Boolean
|
||||
reasonForChange: SiteReasonForChangeMode
|
||||
search: Boolean
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -40,20 +40,28 @@ html(lang=siteConfig.lang)
|
||||
|
||||
//- CSS
|
||||
|
||||
|
||||
link(
|
||||
type='text/css'
|
||||
rel='stylesheet'
|
||||
href='/_assets-legacy/css/app.36b4c9522aa279325701.css'
|
||||
)
|
||||
|
||||
|
||||
|
||||
//- JS
|
||||
|
||||
|
||||
script(
|
||||
type='text/javascript'
|
||||
src='/_assets-legacy/js/runtime.js'
|
||||
src='/_assets-legacy/js/runtime.js?1671237890'
|
||||
)
|
||||
|
||||
|
||||
|
||||
script(
|
||||
type='text/javascript'
|
||||
src='/_assets-legacy/js/app.js'
|
||||
src='/_assets-legacy/js/app.js?1671237890'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="ejEliz0oBcrSlcUFYPkYAa" x1="5.715" x2="40.857" y1="40.37" y2="5.229" gradientTransform="matrix(1 0 0 -1 0 48)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#ejEliz0oBcrSlcUFYPkYAa)" d="M36.2,6H8C6.9,6,6,6.9,6,8v32c0,1.1,0.9,2,2,2h32c1.1,0,2-0.9,2-2V11.8c0-0.5-0.2-1-0.6-1.4l-3.8-3.8 C37.2,6.2,36.7,6,36.2,6z"/><linearGradient id="ejEliz0oBcrSlcUFYPkYAb" x1="38.003" x2="40.027" y1="9.997" y2="7.973" gradientTransform="matrix(1 0 0 -1 0 48)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0d61a9"/><stop offset="1" stop-color="#16528c"/></linearGradient><rect width="2" height="2" x="38" y="38" fill="url(#ejEliz0oBcrSlcUFYPkYAb)"/><linearGradient id="ejEliz0oBcrSlcUFYPkYAc" x1="8.003" x2="10.027" y1="9.997" y2="7.973" gradientTransform="matrix(1 0 0 -1 0 48)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0d61a9"/><stop offset="1" stop-color="#16528c"/></linearGradient><rect width="2" height="2" x="8" y="38" fill="url(#ejEliz0oBcrSlcUFYPkYAc)"/><linearGradient id="ejEliz0oBcrSlcUFYPkYAd" x1="13.83" x2="34.965" y1="23.83" y2="44.965" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff"/><stop offset=".242" stop-color="#f2f2f2"/><stop offset="1" stop-color="#ccc"/></linearGradient><path fill="url(#ejEliz0oBcrSlcUFYPkYAd)" d="M34,42H14c-1.1,0-2-0.9-2-2V28c0-1.1,0.9-2,2-2h20c1.1,0,2,0.9,2,2v12C36,41.1,35.1,42,34,42z"/><path d="M42,15.6L26.1,31.5l-1.2,4.9c-0.1,0.4,0.3,0.8,0.7,0.7l4.9-1.2L42,24.4V15.6z" opacity=".05"/><path d="M42,16.3L26.9,31.4l-0.5,1l0,0h0l-0.9,3.4c-0.1,0.4,0.3,0.7,0.6,0.6l3.4-0.9l0,0l0,0l1-0.5L42,23.7V16.3z" opacity=".07"/><path fill="#c94f60" d="M45.8,18.1l-1.9-1.9c-0.3-0.3-0.8-0.3-1.1,0l-0.9,0.9l3,3l0.9-0.9C46.1,18.9,46.1,18.4,45.8,18.1"/><path fill="#f0f0f0" d="M27,32l-1,4l4-1l0.4-3.5L27,32z"/><path fill="#edbe00" d="M42.3,22.6L30,35l-3-3l12.3-12.3L42.3,22.6z"/><linearGradient id="ejEliz0oBcrSlcUFYPkYAe" x1="42.112" x2="42.112" y1="30.688" y2="25.199" gradientTransform="matrix(1 0 0 -1 0 48)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#dedede"/><stop offset="1" stop-color="#d6d6d6"/></linearGradient><path fill="url(#ejEliz0oBcrSlcUFYPkYAe)" d="M39.3,19.7l2.5-2.5l3,3l-2.5,2.5L39.3,19.7z"/><path fill="#787878" d="M26.5,34L26,36l2-0.5L26.5,34z"/><path fill="#1fa4e6" d="M33,19H13c-1.1,0-2-0.9-2-2V6h24v11C35,18.1,34.1,19,33,19z"/><path d="M33,18.5H17c-1.4,0-2.5-1.1-2.5-2.5V6h21v10C35.5,17.4,34.4,18.5,33,18.5z" opacity=".07"/><radialGradient id="ejEliz0oBcrSlcUFYPkYAf" cx="17.573" cy="45.392" r="23.87" gradientTransform="matrix(1 0 0 -1 0 48)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fafafb"/><stop offset=".523" stop-color="#e2e4e7"/><stop offset="1" stop-color="#c8cdd1"/></radialGradient><path fill="url(#ejEliz0oBcrSlcUFYPkYAf)" d="M15,6v10c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V6H15z"/><rect width="4" height="8" x="27" y="8" fill="#177cad"/></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#fff" d="M13.5,38.5v-5H12c-2.481,0-4.5-2.019-4.5-4.5v-5.141l-2.512-0.932 c-0.197-0.073-0.349-0.22-0.428-0.413c-0.08-0.194-0.074-0.406,0.015-0.596L7.5,15.705v-0.111c0-3.502,0.904-6.11,2.687-7.751 l0.376-0.346l-0.354-0.369C9.469,6.357,8.94,5.583,8.663,5.14C9.93,4.276,14.53,1.5,21.268,1.5C28.982,1.5,35.5,7.867,35.5,15.403 c0,5.646-2.612,8.415-4.917,10.856c-1.352,1.433-2.628,2.785-3.067,4.505L27.5,30.826V38.5H13.5z"/><path fill="#4788c7" d="M21.268,2C28.711,2,35,8.138,35,15.403c0,5.448-2.54,8.139-4.781,10.514 c-1.397,1.481-2.717,2.879-3.188,4.724L27,30.763v0.126V38H14v-4v-1h-1h-1c-2.206,0-4-1.794-4-4v-4.793v-0.696l-0.652-0.242 l-2.185-0.81c-0.082-0.03-0.121-0.09-0.139-0.134s-0.032-0.114,0.005-0.193l2.877-6.112L8,15.817v-0.224 c0-3.356,0.85-5.84,2.526-7.383l0.752-0.692L10.57,6.783c-0.518-0.54-0.928-1.082-1.215-1.499C10.99,4.255,15.259,2,21.268,2 M21.268,1C13.043,1,8,5,8,5s0.657,1.234,1.849,2.475C8.132,9.055,7,11.635,7,15.594l-2.877,6.112 c-0.309,0.657,0.01,1.439,0.691,1.691L7,24.207V29c0,2.761,2.239,5,5,5h1v5h15v-8.111c1.105-4.326,8-6.228,8-15.486 C36,7.449,29.223,1,21.268,1L21.268,1z"/><g><path fill="#98ccfd" d="M18.548,23.841c-0.073-0.203-0.28-0.922-0.28-1.756c0-2.916,4.013-4.213,4.013-6.565 c0-2.044-2.383-2.154-2.757-2.154c-1.212,0-2.366,0.541-3.463,1.622v-2.817C17.393,11.39,18.775,11,20.206,11 c3.955,0,4.817,2.557,4.817,3.988c0,3.792-4.305,4.749-4.305,7.183c0,0.762,0.313,1.476,0.402,1.671H18.548z M19.938,29 c-0.79,0-1.744-0.566-1.744-1.622c0-1.163,1.125-1.634,1.744-1.634c1.168,0,1.732,0.953,1.732,1.634 C21.67,28.424,20.654,29,19.938,29z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#fff" d="M6,36.5c-1.379,0-2.5-1.121-2.5-2.5V6c0-1.379,1.121-2.5,2.5-2.5h28c1.379,0,2.5,1.121,2.5,2.5v28 c0,1.379-1.121,2.5-2.5,2.5H6z"/><path fill="#4788c7" d="M34,4c1.103,0,2,0.897,2,2v28c0,1.103-0.897,2-2,2H6c-1.103,0-2-0.897-2-2V6c0-1.103,0.897-2,2-2 H34 M34,3H6C4.343,3,3,4.343,3,6v28c0,1.657,1.343,3,3,3h28c1.657,0,3-1.343,3-3V6C37,4.343,35.657,3,34,3L34,3z"/><path fill="#4788c7" d="M28.5 13h-17c-.276 0-.5-.224-.5-.5v0c0-.276.224-.5.5-.5h17c.276 0 .5.224.5.5v0C29 12.776 28.776 13 28.5 13zM23.5 17h-12c-.276 0-.5-.224-.5-.5v0c0-.276.224-.5.5-.5h12c.276 0 .5.224.5.5v0C24 16.776 23.776 17 23.5 17zM28.5 21h-17c-.276 0-.5-.224-.5-.5v0c0-.276.224-.5.5-.5h17c.276 0 .5.224.5.5v0C29 20.776 28.776 21 28.5 21zM28.5 29h-17c-.276 0-.5-.224-.5-.5v0c0-.276.224-.5.5-.5h17c.276 0 .5.224.5.5v0C29 28.776 28.776 29 28.5 29zM23.385 25h-12c-.276 0-.5-.224-.5-.5l0 0c0-.276.224-.5.5-.5h12c.276 0 .5.224.5.5v0C23.885 24.776 23.661 25 23.385 25z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -44,6 +44,7 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
@lazy-load='treeLazyLoad'
|
||||
:use-lazy-load='true'
|
||||
@context-action='treeContextAction'
|
||||
:display-mode='state.displayMode'
|
||||
)
|
||||
q-drawer.fileman-right(:model-value='true', :width='350', side='right')
|
||||
.q-pa-md
|
||||
@@ -90,7 +91,6 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
)
|
||||
q-tooltip(anchor='bottom middle', self='top middle') {{t(`fileman.viewOptions`)}}
|
||||
q-menu(
|
||||
auto-close
|
||||
transition-show='jump-down'
|
||||
transition-hide='jump-up'
|
||||
anchor='bottom right'
|
||||
@@ -103,11 +103,38 @@ q-layout.fileman(view='hHh lpR lFr', container)
|
||||
q-separator.q-my-sm
|
||||
q-item(clickable)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-circle', color='grey', size='xs')
|
||||
q-icon(name='las la-list', color='grey', size='xs')
|
||||
q-item-section.q-pr-sm Browse using...
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-angle-right', color='grey', size='xs')
|
||||
q-menu(
|
||||
anchor='top end'
|
||||
self='top start'
|
||||
)
|
||||
q-list.q-pa-sm(dense)
|
||||
q-item(clickable, @click='state.displayMode = `path`')
|
||||
q-item-section(side)
|
||||
q-icon(
|
||||
:name='state.displayMode === `path` ? `las la-check-circle` : `las la-circle`'
|
||||
:color='state.displayMode === `path` ? `positive` : `grey`'
|
||||
size='xs'
|
||||
)
|
||||
q-item-section.q-pr-sm Browse Using Paths
|
||||
q-item(clickable, @click='state.displayMode = `title`')
|
||||
q-item-section(side)
|
||||
q-icon(
|
||||
:name='state.displayMode === `title` ? `las la-check-circle` : `las la-circle`'
|
||||
:color='state.displayMode === `title` ? `positive` : `grey`'
|
||||
size='xs'
|
||||
)
|
||||
q-item-section.q-pr-sm Browse Using Titles
|
||||
q-item(clickable)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-stop', color='grey', size='xs')
|
||||
q-item-section.q-pr-sm Compact List
|
||||
q-item(clickable)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-check-circle', color='positive', size='xs')
|
||||
q-icon(name='las la-check-square', color='positive', size='xs')
|
||||
q-item-section.q-pr-sm Show Folders
|
||||
q-btn.q-mr-sm(
|
||||
flat
|
||||
@@ -254,6 +281,7 @@ const state = reactive({
|
||||
currentFileId: '',
|
||||
treeNodes: {},
|
||||
treeRoots: [],
|
||||
displayMode: 'title',
|
||||
isUploading: false,
|
||||
shouldCancelUpload: false,
|
||||
uploadPercentage: 0,
|
||||
@@ -450,6 +478,7 @@ async function loadTree (parentId, types) {
|
||||
case 'TreeItemFolder': {
|
||||
state.treeNodes[item.id] = {
|
||||
text: item.title,
|
||||
fileName: item.fileName,
|
||||
children: []
|
||||
}
|
||||
if (!item.folderPath) {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
.col-auto.bg-grey-1(style='width: 250px;')
|
||||
q-tree(
|
||||
:nodes='tree'
|
||||
default-expand-all
|
||||
node-key='label'
|
||||
@lazy-load='onLazyLoad'
|
||||
)
|
||||
.col Doude
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
tree: [
|
||||
{
|
||||
label: 'Item 1',
|
||||
icon: 'las la-folder',
|
||||
children: [
|
||||
{ label: 'Item 1.1' },
|
||||
{
|
||||
label: 'Item 1.2',
|
||||
icon: 'las la-folder',
|
||||
children: [
|
||||
{ label: 'Item 1.2.1' },
|
||||
{ label: 'Item 1.2.2' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onLazyLoad ({ node, key, done, fail }) {
|
||||
done([])
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template lang="pug">
|
||||
q-dialog(ref='dialogRef', @hide='onDialogHide')
|
||||
q-card(style='min-width: 550px; max-width: 850px;')
|
||||
q-card-section.card-header
|
||||
q-icon(name='img:/_assets/icons/fluent-delete-bin.svg', left, size='sm')
|
||||
span {{t(`pageDeleteDialog.title`)}}
|
||||
q-card-section
|
||||
.text-body2
|
||||
i18n-t(keypath='pageDeleteDialog.confirm')
|
||||
template(v-slot:name)
|
||||
strong {{pageName}}
|
||||
.text-caption.text-grey.q-mt-sm {{t('pageDeleteDialog.pageId', { id: pageId })}}
|
||||
q-card-actions.card-actions
|
||||
q-space
|
||||
q-btn.acrylic-btn(
|
||||
flat
|
||||
:label='t(`common.actions.cancel`)'
|
||||
color='grey'
|
||||
padding='xs md'
|
||||
@click='onDialogCancel'
|
||||
)
|
||||
q-btn(
|
||||
unelevated
|
||||
:label='t(`common.actions.delete`)'
|
||||
color='negative'
|
||||
padding='xs md'
|
||||
@click='confirm'
|
||||
:loading='state.isLoading'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import gql from 'graphql-tag'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDialogPluginComponent, useQuasar } from 'quasar'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
// PROPS
|
||||
|
||||
const props = defineProps({
|
||||
pageId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
pageName: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// EMITS
|
||||
|
||||
defineEmits([
|
||||
...useDialogPluginComponent.emits
|
||||
])
|
||||
|
||||
// QUASAR
|
||||
|
||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
|
||||
const $q = useQuasar()
|
||||
|
||||
// I18N
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// DATA
|
||||
|
||||
const state = reactive({
|
||||
isLoading: false
|
||||
})
|
||||
|
||||
// METHODS
|
||||
|
||||
async function confirm () {
|
||||
state.isLoading = true
|
||||
try {
|
||||
const resp = await APOLLO_CLIENT.mutate({
|
||||
mutation: gql`
|
||||
mutation deletePage ($id: UUID!) {
|
||||
deletePage(id: $id) {
|
||||
operation {
|
||||
succeeded
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: props.pageId
|
||||
}
|
||||
})
|
||||
if (resp?.data?.deletePage?.operation?.succeeded) {
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
message: t('pageDeleteDialog.deleteSuccess')
|
||||
})
|
||||
onDialogOK()
|
||||
} else {
|
||||
throw new Error(resp?.data?.deletePage?.operation?.message || 'An unexpected error occured.')
|
||||
}
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: err.message
|
||||
})
|
||||
}
|
||||
state.isLoading = false
|
||||
}
|
||||
</script>
|
||||
@@ -1,57 +1,376 @@
|
||||
<template lang="pug">
|
||||
q-card.page-save-dialog(style='width: 860px; max-width: 90vw;')
|
||||
q-toolbar.bg-primary.text-white
|
||||
.text-subtitle2 {{$t('editor.pageSave.title')}}
|
||||
page-browser
|
||||
q-card-section
|
||||
q-input(
|
||||
v-model='reason'
|
||||
label='Reason for change'
|
||||
dense
|
||||
outlined
|
||||
)
|
||||
q-card-actions.card-actions
|
||||
q-space
|
||||
q-btn.acrylic-btn(
|
||||
icon='las la-times'
|
||||
:label='$t(`common.actions.cancel`)'
|
||||
color='grey-7'
|
||||
padding='xs md'
|
||||
v-close-popup
|
||||
flat
|
||||
)
|
||||
q-btn(
|
||||
icon='las la-check'
|
||||
:label='$t(`common.actions.save`)'
|
||||
unelevated
|
||||
color='primary'
|
||||
padding='xs md'
|
||||
@click=''
|
||||
v-close-popup
|
||||
)
|
||||
q-dialog(ref='dialogRef', @hide='onDialogHide')
|
||||
q-card.page-save-dialog(style='width: 860px; max-width: 90vw;')
|
||||
q-card-section.card-header
|
||||
q-icon(name='img:/_assets/icons/fluent-save-as.svg', left, size='sm')
|
||||
span {{t('pageSaveDialog.title')}}
|
||||
.row.page-save-dialog-browser
|
||||
.col-4.q-px-sm
|
||||
tree(
|
||||
:nodes='state.treeNodes'
|
||||
:roots='state.treeRoots'
|
||||
v-model:selected='state.currentFolderId'
|
||||
@lazy-load='treeLazyLoad'
|
||||
:use-lazy-load='true'
|
||||
@context-action='treeContextAction'
|
||||
:context-action-list='[`newFolder`]'
|
||||
:display-mode='state.displayMode'
|
||||
)
|
||||
.col-8
|
||||
q-list.page-save-dialog-filelist(dense)
|
||||
q-item(
|
||||
v-for='item of files'
|
||||
:key='item.id'
|
||||
clickable
|
||||
active-class='active'
|
||||
:active='item.id === state.currentFileId'
|
||||
@click.native='state.currentFileId = item.id'
|
||||
@dblclick.native='openItem(item)'
|
||||
)
|
||||
q-item-section(side)
|
||||
q-icon(:name='item.icon', size='sm')
|
||||
q-item-section
|
||||
q-item-label {{item.title}}
|
||||
q-list.q-py-sm
|
||||
q-item
|
||||
blueprint-icon(icon='new-document')
|
||||
q-item-section
|
||||
q-input(
|
||||
v-model='state.title'
|
||||
label='Page Title'
|
||||
dense
|
||||
outlined
|
||||
)
|
||||
q-item
|
||||
blueprint-icon(icon='file-submodule')
|
||||
q-item-section
|
||||
q-input(
|
||||
v-model='state.path'
|
||||
label='Path Name'
|
||||
dense
|
||||
outlined
|
||||
)
|
||||
q-card-actions.card-actions.q-px-md
|
||||
q-btn.acrylic-btn(
|
||||
icon='las la-ellipsis-h'
|
||||
color='blue-grey'
|
||||
padding='xs sm'
|
||||
flat
|
||||
)
|
||||
q-tooltip(anchor='center right' self='center left') Display Options
|
||||
q-menu(
|
||||
auto-close
|
||||
transition-show='jump-down'
|
||||
transition-hide='jump-up'
|
||||
anchor='top left'
|
||||
self='bottom left'
|
||||
)
|
||||
q-card.q-pa-sm
|
||||
q-list(dense)
|
||||
q-item(clickable, @click='state.displayMode = `path`')
|
||||
q-item-section(side)
|
||||
q-icon(
|
||||
:name='state.displayMode === `path` ? `las la-check-circle` : `las la-circle`'
|
||||
:color='state.displayMode === `path` ? `positive` : `grey`'
|
||||
size='xs'
|
||||
)
|
||||
q-item-section.q-pr-sm Browse Using Paths
|
||||
q-item(clickable, @click='state.displayMode = `title`')
|
||||
q-item-section(side)
|
||||
q-icon(
|
||||
:name='state.displayMode === `title` ? `las la-check-circle` : `las la-circle`'
|
||||
:color='state.displayMode === `title` ? `positive` : `grey`'
|
||||
size='xs'
|
||||
)
|
||||
q-item-section.q-pr-sm Browse Using Titles
|
||||
q-space
|
||||
q-btn.acrylic-btn(
|
||||
icon='las la-times'
|
||||
:label='t(`common.actions.cancel`)'
|
||||
color='grey-7'
|
||||
padding='xs md'
|
||||
@click='onDialogCancel'
|
||||
flat
|
||||
)
|
||||
q-btn(
|
||||
icon='las la-check'
|
||||
:label='t(`common.actions.save`)'
|
||||
unelevated
|
||||
color='primary'
|
||||
padding='xs md'
|
||||
@click='save'
|
||||
v-close-popup
|
||||
)
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageBrowser from './PageBrowser.vue'
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, onMounted, reactive } from 'vue'
|
||||
import { useDialogPluginComponent, useQuasar } from 'quasar'
|
||||
import { cloneDeep, find } from 'lodash-es'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PageBrowser
|
||||
import fileTypes from '../helpers/fileTypes'
|
||||
|
||||
import FolderCreateDialog from 'src/components/FolderCreateDialog.vue'
|
||||
import Tree from 'src/components/TreeNav.vue'
|
||||
|
||||
import { usePageStore } from 'src/stores/page'
|
||||
import { useSiteStore } from 'src/stores/site'
|
||||
|
||||
// PROPS
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'save'
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
reason: ''
|
||||
pageId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
pageName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
pagePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
// EMITS
|
||||
|
||||
defineEmits([
|
||||
...useDialogPluginComponent.emits
|
||||
])
|
||||
|
||||
// QUASAR
|
||||
|
||||
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
|
||||
const $q = useQuasar()
|
||||
|
||||
// STORES
|
||||
|
||||
const pageStore = usePageStore()
|
||||
const siteStore = useSiteStore()
|
||||
|
||||
// I18N
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// DATA
|
||||
|
||||
const state = reactive({
|
||||
displayMode: 'title',
|
||||
currentFolderId: '',
|
||||
currentFileId: '',
|
||||
treeNodes: {},
|
||||
treeRoots: [],
|
||||
fileList: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'folder',
|
||||
title: 'Beep Boop'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'folder',
|
||||
title: 'Second Folder'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'page',
|
||||
title: 'Some Page',
|
||||
pageType: 'markdown'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
methods: {
|
||||
],
|
||||
title: '',
|
||||
path: ''
|
||||
})
|
||||
|
||||
const displayModes = [
|
||||
{ value: 'title', label: t('pageSaveDialog.displayModeTitle') },
|
||||
{ value: 'path', label: t('pageSaveDialog.displayModePath') }
|
||||
]
|
||||
|
||||
// COMPUTED
|
||||
|
||||
const files = computed(() => {
|
||||
return state.fileList.map(f => {
|
||||
switch (f.type) {
|
||||
case 'folder': {
|
||||
f.icon = fileTypes.folder.icon
|
||||
break
|
||||
}
|
||||
case 'page': {
|
||||
f.icon = fileTypes.page.icon
|
||||
break
|
||||
}
|
||||
}
|
||||
return f
|
||||
})
|
||||
})
|
||||
|
||||
// METHODS
|
||||
|
||||
async function save () {
|
||||
onDialogOK()
|
||||
}
|
||||
|
||||
async function treeLazyLoad (nodeId, { done, fail }) {
|
||||
await loadTree(nodeId, ['folder', 'page'])
|
||||
done()
|
||||
}
|
||||
|
||||
async function loadTree (parentId, types) {
|
||||
try {
|
||||
const resp = await APOLLO_CLIENT.query({
|
||||
query: gql`
|
||||
query loadTree (
|
||||
$siteId: UUID!
|
||||
$parentId: UUID
|
||||
$types: [TreeItemType]
|
||||
) {
|
||||
tree (
|
||||
siteId: $siteId
|
||||
parentId: $parentId
|
||||
types: $types
|
||||
) {
|
||||
__typename
|
||||
... on TreeItemFolder {
|
||||
id
|
||||
folderPath
|
||||
fileName
|
||||
title
|
||||
childrenCount
|
||||
}
|
||||
... on TreeItemPage {
|
||||
id
|
||||
folderPath
|
||||
fileName
|
||||
title
|
||||
createdAt
|
||||
updatedAt
|
||||
pageEditor
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
siteId: siteStore.id,
|
||||
parentId,
|
||||
types
|
||||
},
|
||||
fetchPolicy: 'network-only'
|
||||
})
|
||||
const items = cloneDeep(resp?.data?.tree)
|
||||
if (items?.length > 0) {
|
||||
const newTreeRoots = []
|
||||
for (const item of items) {
|
||||
switch (item.__typename) {
|
||||
case 'TreeItemFolder': {
|
||||
state.treeNodes[item.id] = {
|
||||
text: item.title,
|
||||
fileName: item.fileName,
|
||||
children: []
|
||||
}
|
||||
if (!item.folderPath) {
|
||||
newTreeRoots.push(item.id)
|
||||
} else {
|
||||
state.treeNodes[parentId].children.push(item.id)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newTreeRoots.length > 0) {
|
||||
state.treeRoots = newTreeRoots
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: 'Failed to load folder tree.',
|
||||
caption: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function treeContextAction (nodeId, action) {
|
||||
switch (action) {
|
||||
case 'newFolder': {
|
||||
newFolder(nodeId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newFolder (parentId) {
|
||||
$q.dialog({
|
||||
component: FolderCreateDialog,
|
||||
componentProps: {
|
||||
parentId
|
||||
}
|
||||
}).onOk(() => {
|
||||
loadTree(parentId)
|
||||
})
|
||||
}
|
||||
|
||||
// MOUNTED
|
||||
|
||||
onMounted(() => {
|
||||
loadTree()
|
||||
state.title = props.pageName || ''
|
||||
state.path = props.pagePath || ''
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page-save-dialog {
|
||||
|
||||
&-browser {
|
||||
height: 300px;
|
||||
max-height: 90vh;
|
||||
border-bottom: 1px solid $blue-grey-1;
|
||||
|
||||
> .col-4 {
|
||||
@at-root .body--light & {
|
||||
background-color: $blue-grey-1;
|
||||
border-bottom-color: $blue-grey-1;
|
||||
}
|
||||
@at-root .body--dark & {
|
||||
background-color: $dark-4;
|
||||
border-bottom-color: $dark-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-filelist {
|
||||
padding: 8px 12px;
|
||||
|
||||
> .q-item {
|
||||
padding: 4px 6px;
|
||||
border-radius: 4px;
|
||||
|
||||
&.active {
|
||||
background-color: var(--q-primary);
|
||||
color: #FFF;
|
||||
|
||||
.fileman-filelist-label .q-item__label--caption {
|
||||
color: rgba(255,255,255,.7);
|
||||
}
|
||||
|
||||
.fileman-filelist-side .text-caption {
|
||||
color: rgba(255,255,255,.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,6 +6,7 @@ ul.treeview-level
|
||||
q-icon(name='img:/_assets/icons/fluent-ftp.svg', size='sm')
|
||||
.treeview-label-text(:class='$q.dark.isActive ? `text-purple-4` : `text-purple`') root
|
||||
q-menu(
|
||||
v-if='rootContextActionList.length > 0'
|
||||
touch-position
|
||||
context-menu
|
||||
auto-close
|
||||
@@ -14,10 +15,15 @@ ul.treeview-level
|
||||
)
|
||||
q-card.q-pa-sm
|
||||
q-list(dense, style='min-width: 150px;')
|
||||
q-item(clickable, @click='createRootFolder')
|
||||
q-item(
|
||||
v-for='action of rootContextActionList'
|
||||
:key='action.key'
|
||||
clickable
|
||||
@click='action.handler(null)'
|
||||
)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-plus-circle', color='primary')
|
||||
q-item-section New Folder
|
||||
q-icon(:name='action.icon', :color='action.iconColor')
|
||||
q-item-section(:class='action.labelColor && (`text-` + action.labelColor)') {{action.label}}
|
||||
q-icon(
|
||||
v-if='!selection'
|
||||
name='las la-angle-right'
|
||||
@@ -62,9 +68,15 @@ const roots = inject('roots')
|
||||
const nodes = inject('nodes')
|
||||
const selection = inject('selection')
|
||||
const emitContextAction = inject('emitContextAction')
|
||||
const contextActionList = inject('contextActionList')
|
||||
|
||||
// COMPUTED
|
||||
|
||||
const rootContextActionList = computed(() => {
|
||||
if (props.parentId) { return [] }
|
||||
return contextActionList.filter(c => c.key === 'newFolder')
|
||||
})
|
||||
|
||||
const level = computed(() => {
|
||||
const items = []
|
||||
if (!props.parentId) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, onMounted, provide, reactive, toRef } from 'vue'
|
||||
import { findKey } from 'lodash-es'
|
||||
|
||||
@@ -30,6 +31,14 @@ const props = defineProps({
|
||||
useLazyLoad: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
contextActionList: {
|
||||
type: Array,
|
||||
default: () => ['newFolder', 'duplicate', 'rename', 'move', 'del']
|
||||
},
|
||||
displayMode: {
|
||||
type: String,
|
||||
default: 'title'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -37,6 +46,48 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:selected', 'lazyLoad', 'contextAction'])
|
||||
|
||||
// I18N
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// Context Actions
|
||||
|
||||
const contextActions = {
|
||||
newFolder: {
|
||||
icon: 'las la-plus-circle',
|
||||
iconColor: 'blue',
|
||||
label: t('common.actions.newFolder')
|
||||
},
|
||||
duplicate: {
|
||||
icon: 'las la-copy',
|
||||
iconColor: 'teal',
|
||||
label: t('common.actions.duplicate') + '...'
|
||||
},
|
||||
rename: {
|
||||
icon: 'las la-redo',
|
||||
iconColor: 'teal',
|
||||
label: t('common.actions.rename') + '...'
|
||||
},
|
||||
move: {
|
||||
icon: 'las la-arrow-right',
|
||||
iconColor: 'teal',
|
||||
label: t('common.actions.moveTo') + '...'
|
||||
},
|
||||
del: {
|
||||
icon: 'las la-trash-alt',
|
||||
iconColor: 'negative',
|
||||
label: t('common.actions.delete'),
|
||||
labelColor: 'negative'
|
||||
}
|
||||
}
|
||||
provide('contextActionList', props.contextActionList.map(key => ({
|
||||
key,
|
||||
...contextActions[key],
|
||||
handler: (nodeId) => {
|
||||
emit('contextAction', nodeId, key)
|
||||
}
|
||||
})))
|
||||
|
||||
// DATA
|
||||
|
||||
const state = reactive({
|
||||
@@ -75,6 +126,7 @@ provide('roots', toRef(props, 'roots'))
|
||||
provide('nodes', props.nodes)
|
||||
provide('loaded', state.loaded)
|
||||
provide('opened', state.opened)
|
||||
provide('displayMode', toRef(props, 'displayMode'))
|
||||
provide('selection', selection)
|
||||
provide('emitLazyLoad', emitLazyLoad)
|
||||
provide('emitContextAction', emitContextAction)
|
||||
|
||||
@@ -7,7 +7,7 @@ li.treeview-node
|
||||
size='sm'
|
||||
@click.stop='hasChildren ? toggleNode() : openNode()'
|
||||
)
|
||||
.treeview-label-text {{node.text}}
|
||||
.treeview-label-text {{displayMode === 'path' ? node.fileName : node.text}}
|
||||
q-spinner.q-mr-xs(
|
||||
color='primary'
|
||||
v-if='state.isLoading'
|
||||
@@ -19,6 +19,7 @@ li.treeview-node
|
||||
)
|
||||
//- RIGHT-CLICK MENU
|
||||
q-menu(
|
||||
v-if='contextActionList.length > 0'
|
||||
touch-position
|
||||
context-menu
|
||||
auto-close
|
||||
@@ -29,26 +30,15 @@ li.treeview-node
|
||||
)
|
||||
q-card.q-pa-sm
|
||||
q-list(dense, style='min-width: 150px;')
|
||||
q-item(clickable, @click='contextAction(`newFolder`)')
|
||||
q-item(
|
||||
v-for='action of contextActionList'
|
||||
:key='action.key'
|
||||
clickable
|
||||
@click='action.handler(node.id)'
|
||||
)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-plus-circle', color='primary')
|
||||
q-item-section New Folder
|
||||
q-item(clickable)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-copy', color='teal')
|
||||
q-item-section Duplicate...
|
||||
q-item(clickable)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-redo', color='teal')
|
||||
q-item-section Rename...
|
||||
q-item(clickable)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-arrow-right', color='teal')
|
||||
q-item-section Move to...
|
||||
q-item(clickable)
|
||||
q-item-section(side)
|
||||
q-icon(name='las la-trash-alt', color='negative')
|
||||
q-item-section.text-negative Delete
|
||||
q-icon(:name='action.icon', :color='action.iconColor')
|
||||
q-item-section(:class='action.labelColor && (`text-` + action.labelColor)') {{action.label}}
|
||||
//- SUB-LEVEL
|
||||
transition(name='treeview')
|
||||
tree-level(
|
||||
@@ -89,9 +79,11 @@ const $q = useQuasar()
|
||||
|
||||
const loaded = inject('loaded')
|
||||
const opened = inject('opened')
|
||||
const displayMode = inject('displayMode')
|
||||
const selection = inject('selection')
|
||||
const emitLazyLoad = inject('emitLazyLoad')
|
||||
const emitContextAction = inject('emitContextAction')
|
||||
const contextActionList = inject('contextActionList')
|
||||
|
||||
// DATA
|
||||
|
||||
|
||||
@@ -1586,5 +1586,22 @@
|
||||
"welcome.admin": "Administration Area",
|
||||
"welcome.createHome": "Create the homepage",
|
||||
"welcome.subtitle": "Let's get started...",
|
||||
"welcome.title": "Welcome to Wiki.js!"
|
||||
"welcome.title": "Welcome to Wiki.js!",
|
||||
"admin.utilities.scanPageProblems": "Scan for Page Problems",
|
||||
"admin.utilities.scanPageProblemsHint": "Scan all pages for invalid, missing or corrupted data.",
|
||||
"pageSaveDialog.title": "Save As...",
|
||||
"admin.general.reasonForChange": "Reason for Change",
|
||||
"admin.general.reasonForChangeHint": "Should users be prompted the reason for changes made to a page?",
|
||||
"admin.general.reasonForChangeRequired": "Required",
|
||||
"admin.general.reasonForChangeOptional": "Optional",
|
||||
"admin.general.reasonForChangeOff": "Off",
|
||||
"pageDeleteDialog.title": "Confirm Page Deletion",
|
||||
"pageDeleteDialog.confirm": "Are you sure you want to delete the page {name}?",
|
||||
"pageDeleteDialog.pageId": "Page ID {id}",
|
||||
"pageDeleteDialog.deleteSuccess": "Page deleted successfully.",
|
||||
"common.actions.newFolder": "New Folder",
|
||||
"common.actions.duplicate": "Duplicate",
|
||||
"common.actions.moveTo": "Move To",
|
||||
"pageSaveDialog.displayModeTitle": "Title",
|
||||
"pageSaveDialog.displayModePath": "Path"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ q-page.admin-general
|
||||
icon='las la-question-circle'
|
||||
flat
|
||||
color='grey'
|
||||
:href='siteStore.docsBase + `/admin/sites`'
|
||||
:href='siteStore.docsBase + `/admin/sites#general`'
|
||||
target='_blank'
|
||||
type='a'
|
||||
)
|
||||
@@ -208,6 +208,21 @@ q-page.admin-general
|
||||
unchecked-icon='las la-times'
|
||||
:aria-label='t(`admin.general.allowSearch`)'
|
||||
)
|
||||
q-separator.q-my-sm(inset)
|
||||
q-item(tag='label')
|
||||
blueprint-icon(icon='confusion')
|
||||
q-item-section
|
||||
q-item-label {{t(`admin.general.reasonForChange`)}}
|
||||
q-item-label(caption) {{t(`admin.general.reasonForChangeHint`)}}
|
||||
q-item-section(avatar)
|
||||
q-btn-toggle(
|
||||
v-model='state.config.features.reasonForChange'
|
||||
push
|
||||
glossy
|
||||
no-caps
|
||||
toggle-color='primary'
|
||||
:options='reasonForChangeModes'
|
||||
)
|
||||
|
||||
//- -----------------------
|
||||
//- URL Handling
|
||||
@@ -493,6 +508,7 @@ const state = reactive({
|
||||
ratingsMode: 'off',
|
||||
comments: false,
|
||||
contributions: false,
|
||||
reasonForChange: 'off',
|
||||
profile: false
|
||||
},
|
||||
defaults: {
|
||||
@@ -528,6 +544,11 @@ const ratingsModes = [
|
||||
{ value: 'thumbs', label: t('admin.general.ratingsThumbs') },
|
||||
{ value: 'stars', label: t('admin.general.ratingsStars') }
|
||||
]
|
||||
const reasonForChangeModes = [
|
||||
{ value: 'off', label: t('admin.general.reasonForChangeOff') },
|
||||
{ value: 'optional', label: t('admin.general.reasonForChangeOptional') },
|
||||
{ value: 'required', label: t('admin.general.reasonForChangeRequired') }
|
||||
]
|
||||
const dateFormats = [
|
||||
{ value: '', label: t('profile.localeDefault') },
|
||||
{ value: 'DD/MM/YYYY', label: 'DD/MM/YYYY' },
|
||||
@@ -586,10 +607,11 @@ async function load () {
|
||||
}
|
||||
features {
|
||||
comments
|
||||
ratings
|
||||
ratingsMode
|
||||
contributions
|
||||
profile
|
||||
ratings
|
||||
ratingsMode
|
||||
reasonForChange
|
||||
search
|
||||
}
|
||||
defaults {
|
||||
|
||||
@@ -109,7 +109,19 @@ q-page.admin-utilities
|
||||
@click=''
|
||||
:label='t(`common.actions.proceed`)'
|
||||
)
|
||||
|
||||
q-item
|
||||
blueprint-icon(icon='rescan-document', :hue-rotate='45')
|
||||
q-item-section
|
||||
q-item-label {{t(`admin.utilities.scanPageProblems`)}}
|
||||
q-item-label(caption) {{t(`admin.utilities.scanPageProblemsHint`)}}
|
||||
q-item-section(side)
|
||||
q-btn.acrylic-btn(
|
||||
flat
|
||||
icon='las la-arrow-circle-right'
|
||||
color='primary'
|
||||
@click=''
|
||||
:label='t(`common.actions.proceed`)'
|
||||
)
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
+55
-31
@@ -193,7 +193,7 @@ q-page.column
|
||||
@click='state.tagEditMode = !state.tagEditMode'
|
||||
)
|
||||
page-tags.q-mt-sm(:edit='state.tagEditMode')
|
||||
template(v-if='pageStore.allowRatings && pageStore.ratingsMode !== `off`')
|
||||
template(v-if='siteStore.features.ratingsMode !== `off` && pageStore.allowRatings')
|
||||
q-separator(v-if='pageStore.showToc || pageStore.showTags')
|
||||
//- Rating
|
||||
.q-pa-md.flex.items-center
|
||||
@@ -201,13 +201,13 @@ q-page.column
|
||||
.text-caption.text-grey-7 Rate this page
|
||||
.q-px-md
|
||||
q-rating(
|
||||
v-if='pageStore.ratingsMode === `stars`'
|
||||
v-if='siteStore.features.ratingsMode === `stars`'
|
||||
v-model='state.currentRating'
|
||||
icon='las la-star'
|
||||
color='secondary'
|
||||
size='sm'
|
||||
)
|
||||
.flex.items-center(v-else-if='pageStore.ratingsMode === `thumbs`')
|
||||
.flex.items-center(v-else-if='siteStore.features.ratingsMode === `thumbs`')
|
||||
q-btn.acrylic-btn(
|
||||
flat
|
||||
icon='las la-thumbs-down'
|
||||
@@ -237,26 +237,13 @@ q-page.column
|
||||
)
|
||||
q-tooltip(anchor='center left' self='center right') Page Data
|
||||
q-separator.q-my-sm(inset)
|
||||
q-btn.q-py-sm(
|
||||
flat
|
||||
icon='las la-history'
|
||||
color='grey'
|
||||
aria-label='Page History'
|
||||
)
|
||||
q-tooltip(anchor='center left' self='center right') Page History
|
||||
q-btn.q-py-sm(
|
||||
flat
|
||||
icon='las la-code'
|
||||
color='grey'
|
||||
aria-label='Page Source'
|
||||
)
|
||||
q-tooltip(anchor='center left' self='center right') Page Source
|
||||
q-btn.q-py-sm(
|
||||
flat
|
||||
icon='las la-ellipsis-h'
|
||||
color='grey'
|
||||
aria-label='Page Actions'
|
||||
)
|
||||
q-tooltip(anchor='center left' self='center right') Page Actions
|
||||
q-menu(
|
||||
anchor='top left'
|
||||
self='top right'
|
||||
@@ -264,6 +251,16 @@ q-page.column
|
||||
transition-show='jump-left'
|
||||
)
|
||||
q-list(padding, style='min-width: 225px;')
|
||||
q-item(clickable)
|
||||
q-item-section.items-center(avatar)
|
||||
q-icon(color='deep-orange-9', name='las la-history', size='sm')
|
||||
q-item-section
|
||||
q-item-label View History
|
||||
q-item(clickable)
|
||||
q-item-section.items-center(avatar)
|
||||
q-icon(color='deep-orange-9', name='las la-code', size='sm')
|
||||
q-item-section
|
||||
q-item-label View Source
|
||||
q-item(clickable)
|
||||
q-item-section.items-center(avatar)
|
||||
q-icon(color='deep-orange-9', name='las la-atom', size='sm')
|
||||
@@ -285,6 +282,7 @@ q-page.column
|
||||
icon='las la-copy'
|
||||
color='grey'
|
||||
aria-label='Duplicate Page'
|
||||
@click='duplicatePage'
|
||||
)
|
||||
q-tooltip(anchor='center left' self='center right') Duplicate Page
|
||||
q-btn.q-py-sm(
|
||||
@@ -292,6 +290,7 @@ q-page.column
|
||||
icon='las la-share'
|
||||
color='grey'
|
||||
aria-label='Rename / Move Page'
|
||||
@click='renamePage'
|
||||
)
|
||||
q-tooltip(anchor='center left' self='center right') Rename / Move Page
|
||||
q-btn.q-py-sm(
|
||||
@@ -299,7 +298,7 @@ q-page.column
|
||||
icon='las la-trash'
|
||||
color='grey'
|
||||
aria-label='Delete Page'
|
||||
@click='savePage'
|
||||
@click='deletePage'
|
||||
)
|
||||
q-tooltip(anchor='center left' self='center right') Delete Page
|
||||
|
||||
@@ -313,13 +312,6 @@ q-page.column
|
||||
no-shake
|
||||
)
|
||||
component(:is='sideDialogs[state.sideDialogComponent]')
|
||||
|
||||
q-dialog(
|
||||
v-model='state.showGlobalDialog'
|
||||
transition-show='jump-up'
|
||||
transition-hide='jump-down'
|
||||
)
|
||||
component(:is='globalDialogs[state.globalDialogComponent]')
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -349,9 +341,6 @@ const sideDialogs = {
|
||||
loadingComponent: LoadingGeneric
|
||||
})
|
||||
}
|
||||
const globalDialogs = {
|
||||
PageSaveDialog: defineAsyncComponent(() => import('../components/PageSaveDialog.vue'))
|
||||
}
|
||||
|
||||
// QUASAR
|
||||
|
||||
@@ -472,9 +461,44 @@ function togglePageData () {
|
||||
state.showSideDialog = true
|
||||
}
|
||||
|
||||
function savePage () {
|
||||
state.globalDialogComponent = 'PageSaveDialog'
|
||||
state.showGlobalDialog = true
|
||||
function duplicatePage () {
|
||||
$q.dialog({
|
||||
component: defineAsyncComponent(() => import('../components/PageSaveDialog.vue')),
|
||||
componentProps: {
|
||||
mode: 'duplicate',
|
||||
pageId: pageStore.id,
|
||||
pageName: pageStore.title,
|
||||
pagePath: pageStore.path
|
||||
}
|
||||
}).onOk(() => {
|
||||
// TODO: change route to new location
|
||||
})
|
||||
}
|
||||
|
||||
function renamePage () {
|
||||
$q.dialog({
|
||||
component: defineAsyncComponent(() => import('../components/PageSaveDialog.vue')),
|
||||
componentProps: {
|
||||
mode: 'rename',
|
||||
pageId: pageStore.id,
|
||||
pageName: pageStore.title,
|
||||
pagePath: pageStore.path
|
||||
}
|
||||
}).onOk(() => {
|
||||
// TODO: change route to new location
|
||||
})
|
||||
}
|
||||
|
||||
function deletePage () {
|
||||
$q.dialog({
|
||||
component: defineAsyncComponent(() => import('../components/PageDeleteDialog.vue')),
|
||||
componentProps: {
|
||||
pageId: pageStore.id,
|
||||
pageName: pageStore.title
|
||||
}
|
||||
}).onOk(() => {
|
||||
router.replace('/')
|
||||
})
|
||||
}
|
||||
|
||||
function refreshTocExpanded (baseToc, lvl) {
|
||||
|
||||
+10
-1
@@ -21,11 +21,13 @@ export const useSiteStore = defineStore('site', {
|
||||
searchRestrictLocale: false,
|
||||
searchRestrictPath: false,
|
||||
printView: false,
|
||||
ratingsMode: 'thumbs',
|
||||
pageDataTemplates: [],
|
||||
showSideNav: true,
|
||||
showSidebar: true,
|
||||
overlay: null,
|
||||
features: {
|
||||
ratingsMode: 'off'
|
||||
},
|
||||
theme: {
|
||||
dark: false,
|
||||
injectCSS: '',
|
||||
@@ -76,6 +78,9 @@ export const useSiteStore = defineStore('site', {
|
||||
company
|
||||
contentLicense
|
||||
footerExtra
|
||||
features {
|
||||
ratingsMode
|
||||
}
|
||||
theme {
|
||||
dark
|
||||
colorPrimary
|
||||
@@ -107,6 +112,10 @@ export const useSiteStore = defineStore('site', {
|
||||
this.company = clone(siteInfo.company)
|
||||
this.contentLicense = clone(siteInfo.contentLicense)
|
||||
this.footerExtra = clone(siteInfo.footerExtra)
|
||||
this.features = {
|
||||
...this.features,
|
||||
...clone(siteInfo.features)
|
||||
}
|
||||
this.theme = {
|
||||
...this.theme,
|
||||
...clone(siteInfo.theme)
|
||||
|
||||
Reference in New Issue
Block a user