mirror of https://github.com/requarks/wiki
parent
fdc45f6b49
commit
46fb25c1e9
@ -1,2 +1,3 @@
|
|||||||
|
compiled
|
||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
|
@ -1,70 +1,109 @@
|
|||||||
import { LitElement, html, css } from 'lit'
|
import { LitElement, html, css } from 'lit'
|
||||||
import folderByPath from './folderByPath.graphql'
|
import treeQuery from './folderByPath.graphql'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An example element.
|
* Block Index
|
||||||
*
|
|
||||||
* @fires count-changed - Indicates when the count changes
|
|
||||||
* @slot - This element has a slot
|
|
||||||
* @csspart button - The button
|
|
||||||
*/
|
*/
|
||||||
export class BlockIndexElement extends LitElement {
|
export class BlockIndexElement extends LitElement {
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
border: solid 1px gray;
|
margin-bottom: 16px;
|
||||||
padding: 16px;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
}
|
||||||
`;
|
:host-context(body.body--dark) {
|
||||||
|
background-color: #F00;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
background-color: #fafafa;
|
||||||
|
background-image: linear-gradient(180deg,#fff,#fafafa);
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
border-left: 5px solid #e0e0e0;
|
||||||
|
box-shadow: 0 3px 8px 0 rgba(116,129,141,.1);
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
li:hover {
|
||||||
|
background-image: linear-gradient(180deg,#fff,#f6fbfe);
|
||||||
|
border-left-color: #2196f3;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
li + li {
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
li a {
|
||||||
|
display: block;
|
||||||
|
color: #1976d2;
|
||||||
|
padding: 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* The name to say "Hello" to.
|
* The base path to fetch pages from
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
path: {type: String},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A comma-separated list of tags to filter with
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
name: {type: String},
|
tags: {type: String},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of times the button has been clicked.
|
* The maximum number of items to fetch
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
count: {type: Number},
|
limit: {type: Number}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super()
|
||||||
this.name = 'World';
|
this.pages = []
|
||||||
this.count = 0;
|
}
|
||||||
|
|
||||||
|
async connectedCallback() {
|
||||||
|
super.connectedCallback()
|
||||||
|
const resp = await APOLLO_CLIENT.query({
|
||||||
|
query: treeQuery,
|
||||||
|
variables: {
|
||||||
|
siteId: WIKI_STORES.site.id,
|
||||||
|
locale: 'en',
|
||||||
|
parentPath: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.pages = resp.data.tree
|
||||||
|
this.requestUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<h1>${this.sayHello(this.name)}!</h1>
|
<ul>
|
||||||
<button @click=${this._onClick} part="button">
|
${this.pages.map(p =>
|
||||||
Click Count: ${this.count}
|
html`<li><a href="#">${p.title}</a></li>`
|
||||||
</button>
|
)}
|
||||||
|
</ul>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
`;
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClick() {
|
// createRenderRoot() {
|
||||||
this.count++;
|
// return this;
|
||||||
this.dispatchEvent(new CustomEvent('count-changed'));
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats a greeting
|
|
||||||
* @param name {string} The name to say "Hello" to
|
|
||||||
* @returns {string} A greeting directed at `name`
|
|
||||||
*/
|
|
||||||
sayHello(name) {
|
|
||||||
return `Hello, ${name}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.customElements.define('block-index', BlockIndexElement);
|
window.customElements.define('block-index', BlockIndexElement)
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Query: {
|
||||||
|
async blocks (obj, args, context) {
|
||||||
|
return WIKI.db.blocks.query().where({
|
||||||
|
siteId: args.siteId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Mutation: {
|
||||||
|
async setBlocksState(obj, args, context) {
|
||||||
|
try {
|
||||||
|
// TODO: update blocks state
|
||||||
|
return {
|
||||||
|
operation: generateSuccess('Blocks state updated successfully')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return generateError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
# ===============================================
|
||||||
|
# BLOCKS
|
||||||
|
# ===============================================
|
||||||
|
|
||||||
|
extend type Query {
|
||||||
|
blocks(
|
||||||
|
siteId: UUID!
|
||||||
|
): [Block]
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
setBlocksState(
|
||||||
|
siteId: UUID!
|
||||||
|
states: [BlockStateInput]!
|
||||||
|
): DefaultResponse
|
||||||
|
|
||||||
|
deleteBlock(
|
||||||
|
id: UUID!
|
||||||
|
): DefaultResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------
|
||||||
|
# TYPES
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
type Block {
|
||||||
|
id: UUID
|
||||||
|
block: String
|
||||||
|
name: String
|
||||||
|
description: String
|
||||||
|
icon: String
|
||||||
|
isEnabled: Boolean
|
||||||
|
isCustom: Boolean
|
||||||
|
config: JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
input BlockStateInput {
|
||||||
|
id: UUID!
|
||||||
|
isEnabled: Boolean!
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
import { Model } from 'objection'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block model
|
||||||
|
*/
|
||||||
|
export class Block extends Model {
|
||||||
|
static get tableName () { return 'blocks' }
|
||||||
|
|
||||||
|
static get jsonAttributes () {
|
||||||
|
return ['config']
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addBlock (data) {
|
||||||
|
return WIKI.db.blocks.query().insertAndFetch({
|
||||||
|
block: data.block,
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
icon: data.icon,
|
||||||
|
isEnabled: true,
|
||||||
|
isCustom: true,
|
||||||
|
config: {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deleteBlock (id) {
|
||||||
|
return WIKI.db.blocks.query().deleteById(id)
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 449 B |
After Width: | Height: | Size: 996 B |
@ -0,0 +1,17 @@
|
|||||||
|
import { boot } from 'quasar/wrappers'
|
||||||
|
import { useSiteStore } from 'src/stores/site'
|
||||||
|
import { useUserStore } from 'src/stores/user'
|
||||||
|
|
||||||
|
export default boot(() => {
|
||||||
|
if (import.meta.env.SSR) {
|
||||||
|
global.WIKI_STORES = {
|
||||||
|
site: useSiteStore(),
|
||||||
|
user: useUserStore()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.WIKI_STORES = {
|
||||||
|
site: useSiteStore(),
|
||||||
|
user: useUserStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
@ -0,0 +1,235 @@
|
|||||||
|
<template lang='pug'>
|
||||||
|
q-page.admin-flags
|
||||||
|
.row.q-pa-md.items-center
|
||||||
|
.col-auto
|
||||||
|
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-rfid-tag.svg')
|
||||||
|
.col.q-pl-md
|
||||||
|
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.blocks.title') }}
|
||||||
|
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.blocks.subtitle') }}
|
||||||
|
.col-auto.flex
|
||||||
|
template(v-if='flagsStore.experimental')
|
||||||
|
q-btn.q-mr-sm.acrylic-btn(
|
||||||
|
unelevated
|
||||||
|
icon='las la-plus'
|
||||||
|
:label='t(`admin.blocks.add`)'
|
||||||
|
color='primary'
|
||||||
|
@click='addBlock'
|
||||||
|
)
|
||||||
|
q-separator.q-mr-sm(vertical)
|
||||||
|
q-btn.q-mr-sm.acrylic-btn(
|
||||||
|
icon='las la-question-circle'
|
||||||
|
flat
|
||||||
|
color='grey'
|
||||||
|
:aria-label='t(`common.actions.viewDocs`)'
|
||||||
|
:href='siteStore.docsBase + `/admin/editors`'
|
||||||
|
target='_blank'
|
||||||
|
type='a'
|
||||||
|
)
|
||||||
|
q-tooltip {{ t(`common.actions.viewDocs`) }}
|
||||||
|
q-btn.q-mr-sm.acrylic-btn(
|
||||||
|
icon='las la-redo-alt'
|
||||||
|
flat
|
||||||
|
color='secondary'
|
||||||
|
:loading='state.loading > 0'
|
||||||
|
:aria-label='t(`common.actions.refresh`)'
|
||||||
|
@click='refresh'
|
||||||
|
)
|
||||||
|
q-tooltip {{ t(`common.actions.refresh`) }}
|
||||||
|
q-btn(
|
||||||
|
unelevated
|
||||||
|
icon='mdi-check'
|
||||||
|
:label='t(`common.actions.apply`)'
|
||||||
|
color='secondary'
|
||||||
|
@click='save'
|
||||||
|
:disabled='state.loading > 0'
|
||||||
|
)
|
||||||
|
q-separator(inset)
|
||||||
|
.q-pa-md.q-gutter-md
|
||||||
|
q-card
|
||||||
|
q-list(separator)
|
||||||
|
q-item(v-for='block of state.blocks', :key='block.id')
|
||||||
|
blueprint-icon(:icon='block.isCustom ? `plugin` : block.icon')
|
||||||
|
q-item-section
|
||||||
|
q-item-label: strong {{block.name}}
|
||||||
|
q-item-label(caption) {{ block.description}}
|
||||||
|
q-item-label.flex.items-center(caption)
|
||||||
|
q-chip.q-ma-none(square, dense, :color='$q.dark.isActive ? `pink-8` : `pink-1`', :text-color='$q.dark.isActive ? `white` : `pink-9`'): span.text-caption <block-{{ block.block }}>
|
||||||
|
q-separator.q-mx-sm.q-my-xs(vertical)
|
||||||
|
em.text-purple(v-if='block.isCustom') {{ t('admin.blocks.custom') }}
|
||||||
|
em.text-teal-7(v-else) {{ t('admin.blocks.builtin') }}
|
||||||
|
template(v-if='block.isCustom')
|
||||||
|
q-item-section(
|
||||||
|
side
|
||||||
|
)
|
||||||
|
q-btn(
|
||||||
|
icon='las la-trash'
|
||||||
|
:aria-label='t(`common.actions.delete`)'
|
||||||
|
color='negative'
|
||||||
|
outline
|
||||||
|
no-caps
|
||||||
|
padding='xs sm'
|
||||||
|
@click='deleteBlock(block.id)'
|
||||||
|
)
|
||||||
|
q-separator.q-ml-lg(vertical)
|
||||||
|
q-item-section(side)
|
||||||
|
q-toggle.q-pr-sm(
|
||||||
|
v-model='block.isEnabled'
|
||||||
|
color='primary'
|
||||||
|
checked-icon='las la-check'
|
||||||
|
unchecked-icon='las la-times'
|
||||||
|
:label='t(`admin.blocks.isEnabled`)'
|
||||||
|
:aria-label='t(`admin.blocks.isEnabled`)'
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useMeta, useQuasar } from 'quasar'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { defineAsyncComponent, onMounted, reactive, watch } from 'vue'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import { cloneDeep, pick } from 'lodash-es'
|
||||||
|
|
||||||
|
import { useAdminStore } from 'src/stores/admin'
|
||||||
|
import { useFlagsStore } from 'src/stores/flags'
|
||||||
|
import { useSiteStore } from 'src/stores/site'
|
||||||
|
|
||||||
|
// QUASAR
|
||||||
|
|
||||||
|
const $q = useQuasar()
|
||||||
|
|
||||||
|
// STORES
|
||||||
|
|
||||||
|
const adminStore = useAdminStore()
|
||||||
|
const flagsStore = useFlagsStore()
|
||||||
|
const siteStore = useSiteStore()
|
||||||
|
|
||||||
|
// I18N
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// META
|
||||||
|
|
||||||
|
useMeta({
|
||||||
|
title: t('admin.editors.title')
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
loading: 0,
|
||||||
|
blocks: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// WATCHERS
|
||||||
|
|
||||||
|
watch(() => adminStore.currentSiteId, (newValue) => {
|
||||||
|
$q.loading.show()
|
||||||
|
load()
|
||||||
|
})
|
||||||
|
|
||||||
|
// METHODS
|
||||||
|
|
||||||
|
async function load () {
|
||||||
|
state.loading++
|
||||||
|
try {
|
||||||
|
const resp = await APOLLO_CLIENT.query({
|
||||||
|
query: gql`
|
||||||
|
query getSiteBlocks (
|
||||||
|
$siteId: UUID!
|
||||||
|
) {
|
||||||
|
blocks (
|
||||||
|
siteId: $siteId
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
block
|
||||||
|
name
|
||||||
|
description
|
||||||
|
icon
|
||||||
|
isEnabled
|
||||||
|
isCustom
|
||||||
|
config
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
variables: {
|
||||||
|
siteId: adminStore.currentSiteId
|
||||||
|
},
|
||||||
|
fetchPolicy: 'network-only'
|
||||||
|
})
|
||||||
|
state.blocks = cloneDeep(resp?.data?.blocks)
|
||||||
|
} catch (err) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: 'Failed to fetch blocks state.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$q.loading.hide()
|
||||||
|
state.loading--
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save () {
|
||||||
|
state.loading++
|
||||||
|
try {
|
||||||
|
const respRaw = await APOLLO_CLIENT.mutate({
|
||||||
|
mutation: gql`
|
||||||
|
mutation saveSiteBlocks (
|
||||||
|
$siteId: UUID!
|
||||||
|
$states: [BlockStateInput]!
|
||||||
|
) {
|
||||||
|
setBlocksState (
|
||||||
|
siteId: $siteId,
|
||||||
|
states: $states
|
||||||
|
) {
|
||||||
|
operation {
|
||||||
|
succeeded
|
||||||
|
slug
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
siteId: adminStore.currentSiteId,
|
||||||
|
states: state.blocks.map(bl => pick(bl, ['id', 'isEnabled']))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (respRaw?.data?.setBlocksState?.operation?.succeeded) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: t('admin.blocks.saveSuccess')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error(respRaw?.data?.setBlocksState?.operation?.message || 'An unexpected error occured.')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: 'Failed to save site blocks state',
|
||||||
|
caption: err.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
state.loading--
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refresh () {
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBlock () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteBlock (id) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MOUNTED
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
$q.loading.show()
|
||||||
|
if (adminStore.currentSiteId) {
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang='scss'>
|
||||||
|
|
||||||
|
</style>
|
Loading…
Reference in new issue